• News
  • Games
  • About
  • Cover image

    Toki - Post Mortem

    Toki is a game where you control time. Stop objects with your stasis gun and collect time capsules in this 2D platformer. I used Phaser and Spine2D to create a minimalistic, smooth looking game with vector graphics. The game won the decentralized challenge, featured in the GitHub blog post, and placed 33 overall out of 200 entries during the Gamedev.js gamejam in 2023.

    Toki

    Toki, which means "time" in Japanese, is the title of the game I made for the Gamedev.js gamejam in 2023. The theme was, surprise surprise, "Time". The first thing that came to mind when the theme was announced was the stasis ability from Zelda: Breath of the Wild.

    Stasis Mechanic

    When the theme was announced, I knew right away what I wanted to create. I also wanted to focus on only 1 mechanic in the game, keeping the scope as small as possible. The mechanic is fairly simple. You can stop time of objects by shooting a "stasis ray" at them. Shoot the same object again to unlock the stasis.

    blog post image

    Instead of creating lots of mechanics, I wanted to see how much I was able to create with the "stasis gun." I created some moving platforms, rotating platforms (with and without spikes,) and a hook you can grab onto. I also created some boxes, which doesn't serve a purpose in the game besides being a debug object in production. I used it to test the stasis gun. Once I was satisfied with the functionality of the stasis ability, it was a matter of adding the same behavior on other game objects.

    When we shoot a "stasis ray", we create a line, and find the closest object in the world/scene that intersects with that object.

    getClosestObject.ts

    export function getClosestBody(scene, startPos, endPos) {
      const line = new Phaser.Geom.Line(
        startPos.x, startPos.y, endPos.x, endPos.y
      ); // stasis ray
      const bodies = scene.matter.world
        .getAllBodies()
         // TODO ignore certain type of bodies
        .filter((b) => true) 
         // TODO sort based on proximity to startPos
        .sort((a, b) => 1);
    
      for (var i = 0; i < bodies.length; i++) {
        const body = bodies[i];
        const vertices = body.vertices;
    
        // loop through all edges of the body 
        // check if the line intersects with any edge
        for (var j = 0; j < vertices.length; j++) {
          const v1 = vertices[j];
          const v2 = vertices[(j + 1) % vertices.length];
          const edge = new Phaser.Geom.Line(v1.x, v1.y, v2.x, v2.y)
          const intersection = Phaser.Geom.Intersects.LineToLine(
            line, edge
          );
    
          // return the first body that intersects with the ray
          if (intersection) {
            return body;
          }
        }
      }
      return null;
    };

    When we have such a body, we emit an event, timeLock, with the object we found (e.g. emit(GameEvent.timeLock, { body: closestBody });)

    With this technique, we can decide to listen to the event and react to it, or ignore it entirely. I don't know if this pattern has a name, but it's similar to the composite pattern.

    Our infamous box can then listen for the timeLock event like this

    box.ts

    class Box {
      constructor() {
         on(GameEvent.timeLock, this.onTimeLock);
         // Remember to also remove the event listener 
         // when the object is removed from the game
      }
      
      // Check if the box was hit, and then do something
      onTimeLock({ body }) {
        if (body && body === this.body) {
          // Util function available for all objects in the game
          commonTimeLock(this.scene, this.body);
        }
      }
    }