TipTap Games
← Real-time simulation

Your first simulation game

A top-down arena, built on the arena2d module. Declare it, define the geometry, and run one loop of input and view — the platform owns every hard networking problem underneath.

  • arena2d, 60 Hz
  • You write no netcode
  • Input + view, that's the loop

The simulation tier is a different way to build a multiplayer game: instead of a message pipe you get a physics world that is already networked. If a fixed menu of four genres fits what you want, this is far less code than doing it by hand. If it doesn't, that ceiling is what creator logic (still a preview) is for.

  1. Turn on a module

    The simulation is off until your game declares a module. Do that in Creator Studio, then the game finds TipTap.sim alongside TipTap.net.

    Declare arena2d on the game; sim rides the connection

    // First, declare a module ON your game (not in code):
    //   Creator Studio → Advanced → Multiplayer SDK → arena2d
    // Then, in the game, the simulation rides the multiplayer connection:
    if (!TipTap.net || !TipTap.net.isAvailable()) return; // not multiplayer, or no platform
    
    // TipTap.sim is now a physics world that already knows how to be multiplayer.
    // You will not write a tick, a snapshot, a sequence number or a line of reconciliation.

    TipTap.sim is available. From here you write no networking.

  2. Define the arena

    define() fixes the geometry and constants for the match, once. It's validated by the same code that validates it on the relay, so a config that's accepted here works everywhere. It resolves with your seat and the room — the relay decides those, not your game.

    define() the arena, then start() the loop

    // define() fixes the arena for the match — once, then immutable. It does NOT take
    // the seat, room size or who hosts: the relay decides those and hands them back.
    const me = await TipTap.sim.define("arena2d", {
      bounds: { min: { x: 0, y: 0 }, max: { x: 24, y: 36 } },
      bodyRadius: 1, accel: 46, friction: 0.86, maxSpeed: 9,
      projectileSpeed: 22, projectileRadius: 0.4, projectileTtlTicks: 110,
      projectileDamage: 10, fireCooldownTicks: 18, respawnTicks: 110, maxProjectiles: 24,
      resources: [{ max: 100, initial: 100 }],          // slot 0 is health by convention
      boxes: [{ min: { x: 3, y: 8 }, max: { x: 9, y: 10 } }], // cover
    });
    // me = { seat, players, isHost } — all decided by the relay, not by you.
    await TipTap.sim.start(); // the 60 Hz loop begins

    ✓ The world exists and is stepping at 60 Hz on every player's device.

  3. Input and view — the whole loop

    This is the entire per-frame contract: tell the simulation what this player wants, and draw what it hands back. getView() is already predicted for you and interpolated for everyone else — draw it and nothing else. No positions to compute, no corrections to apply.

    input() intent, getView() to render

    function frame() {
      // 1. Record this player's INTENT. The SDK turns it into exactly 60 inputs a
      //    second and owns the prediction. Diagonals are normalised for you.
      TipTap.sim.input({
        moveX: stick.x, moveY: stick.y, facing: aimAngle,
        buttons: firing ? TipTap.sim.BUTTON.FIRE : 0,
      });
    
      // 2. Draw the view — already predicted for you, already interpolated for others.
      const v = TipTap.sim.getView();
      drawPlayer(v.self.pos, v.self.facing);
      for (const p of v.others) drawPlayer(p.pos, p.facing);
      for (const shot of v.projectiles) drawShot(shot.pos);
    
      requestAnimationFrame(frame);
    }
    frame();

    ✓ A playable, in-sync arena. Design it gently — slow time-to-kill, generous hitboxes, a small map — or the netcode gets blamed for the design.

  4. React to events

    Use events for feel — a shot sound, a hit flash. They arrive about a round-trip behind state and re-fire on re-simulation, so read state for anything visual and keep events for audio.

    onEvent for sound, not for state

    // Events are for FEEL — a sound, a screen shake — and arrive ~1 RTT behind state.
    // Read state (getView) for anything visual; use events for audio.
    TipTap.sim.onEvent((e) => {
      if (e.type === "fire") { if (TipTap.canPlayAudio()) playSound("shot"); }
    });

    ✓ That's a real-time multiplayer game with none of the netcode written by you.

Where to go next