Castaway

The next thing I wanted to handle after getting grid movement and “collisions” working was how to have the character interact with the world. When I walk up to an object, I want to be able to press a button and, if I am facing that object and it is interactive, be able to learn something about that item, or trigger some event.

The issue comes with how to know that I am collided on the correct face with an object. Our objects and players and NPCs fit neatly inside of their grid, and at no point do they ever really come into contact with each other, as far as the collision-detection engine is concerned. It’s like all elements are playing this incredibly strict game of I’m-Not-Touching-You-I’m-Not-Touching-You-I’m-Not-Touching-You!

I looked online to see how other people might be solving this problem, but came away with some pretty strange suggestions. One suggestion was to make the object’s hit-boxes ‘bleed’ out of their grid, so that the player can actually collide or overlap with it in a way the collision engine understands. Another suggested that the player’s hitbox be elongated on its forward facing side, so that it can ‘probe’ the grid in front of it for a collidable object. To translate this into metaphor, it’d be like a guy holding a cane out and spinning around the room until he hits something. Works for blind people. But not for our game, I don’t think.

I then remembered during a game jam at Warpzone, a coworking space for game and web developers, people were mentioning this idea of Raycasts being a good way of querying objects in a 3D environment. Essentially what happens is a ray is sent out in the direction you specify, and if it hits something, you can get data on that item it hits, effectively being able to perform events in response to the collision. It’s like echolocation. Exactly like echolocation. How is it not like echolocation?

I figured, why not try that in Phaser. So I made a Cast class:

export class Cast extends Phaser.GameObjects.Sprite {
  private currentScene: Phaser.Scene;
  constructor({ scene, x, y }) {
    super(scene, x, y, null);
    this.currentScene = scene;
    this.currentScene.add.existing(this);
    this.initSprite();
    this.displayWidth = 8;
    this.displayHeight = 8;
    this.visible = false;
  }
  private initSprite() {
    this.setOrigin(0.5, 0.5);
    setTimeout(()=>{
      this.destroy();
    },200)
    this.currentScene.physics.world.enable(this);
  }
}

So the idea is it’s this ephemeral sprite (it kills itself after 200ms) that gets placed somewhere and checks if what it collides with is an interactive object. Inside of my Movement class, I added two methods:

class Moveable extends Entity{
......
  public queryObject = createThrottle(300, () => {
    const coords = this.getTileInFront();
    this.casts.add(
      new Cast({
        scene: this.currentScene,
        x: coords.x,
        y: coords.y
      })
    );
  });

  public queryUnderfoot = createThrottle(100, () => {
    this.casts.add(
      new Cast({
        scene: this.currentScene,
        x: this.x,
        y: this.y,
      })
    );
  });
......
}

Basically, place a Cast down on the tile in front for queryObject, and place a Cast on the current tile for queryUnderfoot.

The throttle is a function that makes it so I can’t do this more than 1 time every 300ms.

export const createThrottle = (limit, func)=>{
  let lastCalled: Date;
  return ()=>{
    if(!lastCalled ||  Date.now() - lastCalled.getTime() >= limit){
      func();
      lastCalled = new Date();
    }
  }
};

Keep that little number handy for interviews~

And then in my update loop in my scene, I just check the collision between a cast and any other kind of object.

The result is a Player with the ability to query objects in front of it or underfoot. If I were to make the casts visible to the naked eye, it would look something like this:

Lo is able to query everything in the room, including things she’s never interacted with over the course of our marriage.

See? Echolocation.

Next I’ll go over how I decided to handle dialog and item acquisition! That chest up there is looking mighty tempting. I wonder what’s inside!

Leave a comment