How do I get a character to enter a building in Phaser 3?


#1

I am making a top down RPG styled game using Tiled and Phaser 3. I have already made a map in Tiled for the city and store. Also, I have already set the game objects for the location of the store where the player is suppose to go in order to enter the store and the location where the player is supposed to appear. The thing I am having trouble figuring out the code I am suppose to use in order to facilitate this function, as well as going back to city map. One more thing, If there are any other ideas on how to go about this I am all ears.

Update: I got the character to load on the map with the function I already have but problem is that I can’t move my character.

Here is the function and I have so far:
// When the player walks into the store.
player.onCollide = new Phaser.signal();
Store.onCollide.add(enterstore,this);

function enterstore(player){
const map2 = this.make.tilemap({ key: “map2” });
const tileset2 = map.addTilesetImage(“store”, “tiles2”);

const storeLayer = map.createStaticLayer("Tile Layer 1", tileset, 0, 0);

storeLayer.setCollisionByProperty({ collides: true });

this.physics.add.collider(player, storeLayer);

storeLayer.setDepth(10);

const SpawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");
const Store = map.findObject("Objects", obj => obj.name === "store");

player = this.physics.add
.sprite(SpawnPoint.x, SpawnPoint.y, "atlas", "misa-front")
.setSize(30, 40)
.setOffset(282, 202);

#2

I’m not sure if phaser 3 supports the .signal() call you have here.

I think you are to use events listener instead.


#3

Ok, which event listener should I use? There seems to be a lot of them. Remember I just need a character to walk into an object and fire off an event that causes a character to load into another scene. Here is my code:
class SceneA extends Phaser.Scene {

 constructor() {
	super({key: 'sceneA'});
}


 preload() {
  this.load.image("tiles", "../assets/tilesets/RunItUpCity.png");
  this.load.tilemapTiledJSON("map", "../assets/tilemaps/GetawayCity.json");

  // An atlas is a way to pack multiple images together into one texture. I'm using it to load all
  // the player animations (walking left, walking right, etc.) in one image. For more info see:
  //  https://labs.phaser.io/view.html?src=src/animation/texture%20atlas%20animation.js
  // If you don't use an atlas, you can do the same thing with a spritesheet, see:
  //  https://labs.phaser.io/view.html?src=src/animation/single%20sprite%20sheet.js
  this.load.atlas("atlas", "../assets/atlas/atlas.png", "../assets/atlas/atlas.json");
}

 update() {

        if (this.store.contains(this.player.x + this.player.width/2,this.player.y + this.player.height/2))
           this.scene.start('sceneB');

    }

 create() {
  const map = this.make.tilemap({ key: "map" });

  // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in
  // Phaser's cache (i.e. the name you used in preload)
  const tileset = map.addTilesetImage("RunitUpCity", "tiles");

  // Parameters: layer name (or index) from Tiled, tileset, x, y
  const belowLayer = map.createStaticLayer("Bottom Layer", tileset, 0, 0);
  const worldLayer = map.createStaticLayer("Top Layer", tileset, 0, 0);
  const aboveLayer = map.createStaticLayer("Collision Layer", tileset, 0, 0);

  worldLayer.setCollisionByProperty({ collides: true });

  this.store = new Phaser.Rectangle(end.x, end.y, end.width, end.height);

  // By default, everything gets depth sorted on the screen in the order we created things. Here, we
  // want the "Above Player" layer to sit on top of the player, so we explicitly give it a depth.
  // Higher depths will sit on top of lower depth objects.
  aboveLayer.setDepth(10);

  // Object layers in Tiled let you embed extra info into a map - like a spawn point or custom
  // collision shapes. In the tmx file, there's an object layer with a point named "Spawn Point"
  const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");

  // Create a sprite with physics enabled via the physics system. The image used for the sprite has
  // a bit of whitespace, so I'm using setSize & setOffset to control the size of the player's body.
  player = this.physics.add
    .sprite(spawnPoint.x, spawnPoint.y, "atlas", "misa-front")
    .setSize(30, 40)
    .setOffset(0, 24);


  // Watch the player and worldLayer for collisions, for the duration of the scene:
  this.physics.add.collider(player, worldLayer, Store1);



  // Create the player's walking animations from the texture atlas. These are stored in the global
  // animation manager so any sprite can access them.
  const anims = this.anims;
  anims.create({
    key: "misa-left-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });
  anims.create({
    key: "misa-right-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-right-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });
  anims.create({
    key: "misa-front-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-front-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });
  anims.create({
    key: "misa-back-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-back-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });

  const camera = this.cameras.main;
  camera.startFollow(player);
  camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

  cursors = this.input.keyboard.createCursorKeys();

  // Help text that has a "fixed" position on the screen
  this.add
    .text(16, 16, 'Arrow keys to move\nPress "D" to show hitboxes', {
      font: "18px monospace",
      fill: "#000000",
      padding: { x: 20, y: 10 },
      backgroundColor: "#ffffff"
    })
    .setScrollFactor(0)
    .setDepth(30);

  // Debug graphics
  this.input.keyboard.once("keydown_D", event => {
    // Turn on physics debugging to show player's hitbox
    this.physics.world.createDebugGraphic();

    // Create worldLayer collision graphic above the player, but below the help text
    const graphics = this.add
      .graphics()
      .setAlpha(0.75)
      .setDepth(20);
    worldLayer.renderDebug(graphics, {
      tileColor: null, // Color of non-colliding tiles
      collidingTileColor: new Phaser.Display.Color(243, 134, 48, 255), // Color of colliding tiles
      faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Color of colliding face edges
    });
  });
}

 update(time, delta) {
  const speed = 175;
  const prevVelocity = player.body.velocity.clone();

  // Stop any previous movement from the last frame
  player.body.setVelocity(0);

  // Horizontal movement
  if (cursors.left.isDown) {
    player.body.setVelocityX(-speed);
  } else if (cursors.right.isDown) {
    player.body.setVelocityX(speed);
  }

  // Vertical movement
  if (cursors.up.isDown) {
    player.body.setVelocityY(-speed);
  } else if (cursors.down.isDown) {
    player.body.setVelocityY(speed);
  }

  // Normalize and scale the velocity so that player can't move faster along a diagonal
  player.body.velocity.normalize().scale(speed);

  // Update the animation last and give left/right animations precedence over up/down animations
  if (cursors.left.isDown) {
    player.anims.play("misa-left-walk", true);
  } else if (cursors.right.isDown) {
    player.anims.play("misa-right-walk", true);
  } else if (cursors.up.isDown) {
    player.anims.play("misa-back-walk", true);
  } else if (cursors.down.isDown) {
    player.anims.play("misa-front-walk", true);
  } else {
    player.anims.stop();

    // If we were moving, pick and idle frame to use
    if (prevVelocity.x < 0) player.setTexture("atlas", "misa-left");
    else if (prevVelocity.x > 0) player.setTexture("atlas", "misa-right");
    else if (prevVelocity.y < 0) player.setTexture("atlas", "misa-back");
    else if (prevVelocity.y > 0) player.setTexture("atlas", "misa-front");
  }
 }
}

class SceneB extends Phaser.Scene{

	constructor(){
		super({key: 'sceneB'});
	}


 preload() {
  this.load.image("tiles", "../assets/tilesets/modern-day-shop-add-on-right.png");
  this.load.tilemapTiledJSON("map", "../assets/tilemaps/store.json");

  // An atlas is a way to pack multiple images together into one texture. I'm using it to load all
  // the player animations (walking left, walking right, etc.) in one image. For more info see:
  //  https://labs.phaser.io/view.html?src=src/animation/texture%20atlas%20animation.js
  // If you don't use an atlas, you can do the same thing with a spritesheet, see:
  //  https://labs.phaser.io/view.html?src=src/animation/single%20sprite%20sheet.js
  this.load.atlas("atlas", "../assets/atlas/atlas.png", "../assets/atlas/atlas.json");
}

 create() {
  const map = this.make.tilemap({ key: "map" });

  // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in
  // Phaser's cache (i.e. the name you used in preload)
  const tileset = map.addTilesetImage("store", "tiles");

  // Parameters: layer name (or index) from Tiled, tileset, x, y
  const storeLayer = map.createStaticLayer("Tile Layer 1", tileset, 0, 0);

  storeLayer.setCollisionByProperty({ collides: true });

  // By default, everything gets depth sorted on the screen in the order we created things. Here, we
  // want the "Above Player" layer to sit on top of the player, so we explicitly give it a depth.
  // Higher depths will sit on top of lower depth objects.
  storeLayer.setDepth(10);

  // Object layers in Tiled let you embed extra info into a map - like a spawn point or custom
  // collision shapes. In the tmx file, there's an object layer with a point named "Spawn Point"
  const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");
  const Shop = map.findObject("Objects", obj => obj.name === "Shop");

  // Create a sprite with physics enabled via the physics system. The image used for the sprite has
  // a bit of whitespace, so I'm using setSize & setOffset to control the size of the player's body.
  player = this.physics.add
    .sprite(spawnPoint.x, spawnPoint.y, "atlas", "misa-front")
    .setSize(30, 40)
    .setOffset(282, 202);

  // Watch the player and worldLayer for collisions, for the duration of the scene:
  this.physics.add.collider(player, worldLayer);



  // Create the player's walking animations from the texture atlas. These are stored in the global
  // animation manager so any sprite can access them.
  const anims = this.anims;
  anims.create({
    key: "misa-left-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });
  anims.create({
    key: "misa-right-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-right-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });
  anims.create({
    key: "misa-front-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-front-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });
  anims.create({
    key: "misa-back-walk",
    frames: anims.generateFrameNames("atlas", { prefix: "misa-back-walk.", start: 0, end: 3, zeroPad: 3 }),
    frameRate: 10,
    repeat: -1
  });

  const camera = this.cameras.main;
  camera.startFollow(player);
  camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

  cursors = this.input.keyboard.createCursorKeys();

  // Help text that has a "fixed" position on the screen
  this.add
    .text(16, 16, 'Arrow keys to move\nPress "D" to show hitboxes', {
      font: "18px monospace",
      fill: "#000000",
      padding: { x: 20, y: 10 },
      backgroundColor: "#ffffff"
    })
    .setScrollFactor(0)
    .setDepth(30);

  // Debug graphics
  this.input.keyboard.once("keydown_D", event => {
    // Turn on physics debugging to show player's hitbox
    this.physics.world.createDebugGraphic();

    // Create worldLayer collision graphic above the player, but below the help text
    const graphics = this.add
      .graphics()
      .setAlpha(0.75)
      .setDepth(20);
    storeLayer.renderDebug(graphics, {
      tileColor: null, // Color of non-colliding tiles
      collidingTileColor: new Phaser.Display.Color(243, 134, 48, 255), // Color of colliding tiles
      faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Color of colliding face edges
    });
  });
}

 update(time, delta) {
  const speed = 175;
  const prevVelocity = player.body.velocity.clone();

  // Stop any previous movement from the last frame
  player.body.setVelocity(0);

  // Horizontal movement
  if (cursors.left.isDown) {
    player.body.setVelocityX(-speed);
  } else if (cursors.right.isDown) {
    player.body.setVelocityX(speed);
  }

  // Vertical movement
  if (cursors.up.isDown) {
    player.body.setVelocityY(-speed);
  } else if (cursors.down.isDown) {
    player.body.setVelocityY(speed);
  }

  // Normalize and scale the velocity so that player can't move faster along a diagonal
  player.body.velocity.normalize().scale(speed);

  // Update the animation last and give left/right animations precedence over up/down animations
  if (cursors.left.isDown) {
    player.anims.play("misa-left-walk", true);
  } else if (cursors.right.isDown) {
    player.anims.play("misa-right-walk", true);
  } else if (cursors.up.isDown) {
    player.anims.play("misa-back-walk", true);
  } else if (cursors.down.isDown) {
    player.anims.play("misa-front-walk", true);
  } else {
    player.anims.stop();

    // If we were moving, pick and idle frame to use
    if (prevVelocity.x < 0) player.setTexture("atlas", "misa-left");
    else if (prevVelocity.x > 0) player.setTexture("atlas", "misa-right");
    else if (prevVelocity.y < 0) player.setTexture("atlas", "misa-back");
    else if (prevVelocity.y > 0) player.setTexture("atlas", "misa-front");
  }
 }

}

var config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  parent: "game-container",
  pixelArt: true,
  physics: {
    default: "arcade",
    arcade: {
      gravity: { y: 0 }
    }
  },
  scene: [SceneA, SceneB]
  x: number;
  y: number;
};

var game = new Phaser.Game(config);
let cursors;
let player;
let showDebug = false;

#4

I’m working directly with scene manipulation in my project right now (trying to figure it out that is) but my game only has one continuous scene running, but my UI i’m working on is parallel scenes running at the same time (with the flag in the constructor set with super({key: ‘scenename’, active: true})

Here is a good example of scenes being triggered from one another, you just need to set up inside your listener the condition that if it is met to trigger the other scene.

https://labs.phaser.io/edit.html?src=src\scenes\change%20scene%20from%20objects.js


#5

Ok, would the solution be something like this:

 // Watch the player and worldLayer for collisions, for the duration of the scene:
this.physics.add.collider(player, worldLayer, Store);

// Event listener for store

this.player.body.oncollide(true); // player collides with the store object
this.player.body.onstore = true; // Emit 'onstore' when player collides with the store.

this.store.addListener('store', this.respondToEvent);
}

respondToEvent(){
    this.scene.start('sceneB');
}

#6

Sorry you have a lot of code posted up there :slight_smile: theres a couple things you have posted too that have syntax errors so hard to run without modifying that block you posted.

i would do the same thing you are doing with the add.collider call at the top. i looked it up here:
https://photonstorm.github.io/phaser3-docs/Phaser.Physics.Arcade.Factory.html#collider__anchor

from the API, it states that you past in as arg0 a body, arg1 as the other body, and then arg2 as the callback.

so given that, i would do something like this:

this.physics.add.collider(this.player, this.store, ()=>{
   this.scene.start('sceneB');
});

Again this is hard though without being able to test, and i haven’t worked with collisions too much myself, but this should probably get you started.

EDIT: I didn’t see if you had an actual the store object as a physics object, you may need to change it to one as i’m not sure if it’ll be viable to pass into that collider call.


#7

Update: I got the map and character to load using the solution offered. But my sprite is not responding to keyboard arrow inputs. Why does this happen?

Here is the error message:
Uncaught TypeError: Cannot read property ‘isParent’ of undefined
at World.collideObjects (phaser.js:86147)
at Collider.update (phaser.js:82286)
at World.step (phaser.js:85422)
at World.update (phaser.js:85374)
at EventEmitter.emit (phaser.js:2027)
at Systems.step (phaser.js:26783)
at SceneManager.update (phaser.js:45932)
at Game.step (phaser.js:109993)
at TimeStep.step (phaser.js:106785)
at step (phaser.js:66250)

Thank you for the code it look a lot better than what I had. The map won’t load and when I inspect it in chrome it gives me the error message of “Uncaught syntax error expected token { index.js: 9”. Also, am I supposed to give the objects physics and instantiate them?Like this:

player = this.physics.add
     .sprite(spawnPoint.x, spawnPoint.y, "atlas", "misa-front")
     .setSize(30, 40)
     .setOffset(0, 24);

 Store = this.physics.add
 .setSize(41, 39)
.setOffset(409, 1163);


// Watch the player and worldLayer for collisions, for the duration of the scene:
this.physics.add.collider(player, worldLayer);

// Event listener for store


this.physics.add.collider(this.player, this.Store, ()=>{
this.scene.start('sceneB');
});

Here is a link to the file: index.js


#8

Ok, here is an update. I am trying to use a sprite instead of an object in order to facilitate a collision that will change the scene of the game to the store. Here is the code I have right now.

// Create a sprite with physics enabled via the physics system. The image used for the sprite has
// a bit of whitespace, so I'm using setSize & setOffset to control the size of the 
player's body.
player = this.physics.add
.sprite(spawnPoint.x, spawnPoint.y, "atlas", "misa-front")
.setSize(30, 40)
.setOffset(0, 24);
 entrance = this.physics.add
    .sprite(enterstore.x, enterstore.y, "store")
    .setSize(41, 39)
    .setOffset(409, 1163);
 // Watch the player and worldLayer for collisions, for the duration of the scene:
 this.physics.add.collider(player, worldLayer);
this.physics.add.collider(player, entrance, enterstore, null, this);

enterstore(player, entrance)
{
	this.scene.start('sceneB');
}

The problem I am having is that my map still loads up but my character won’t load and the camera goes to the 0,0 position. here is the error I got from inspecting it:

Uncaught TypeError: Cannot read property ‘x’ of null
at SceneA.create (index.js:67)
at SceneManager.create (phaser.js:45999)
at SceneManager.loadComplete (phaser.js:45893)
at LoaderPlugin.emit (phaser.js:2007)
at LoaderPlugin.loadComplete (phaser.js:88695)
at LoaderPlugin.fileProcessComplete (phaser.js:88654)
at ImageFile.onProcessComplete (phaser.js:4033)
at Image.data.onload (phaser.js:6634)

Here is a link to the acutal files of the project:

https://drive.google.com/file/d/1hAVbznPVmudmORioaJTlA61mLNDlJNBW/view?usp=sharing


#9

Update: I changed the condition for switching scenes to something simple as pushing a button and I was to resolve a bunch of problems with the store map. However, when I do try to change scenes the whole game freezing and when I go to inspect it I get this error message:

Uncaught TypeError: Cannot read property ‘velocity’ of undefined
at SceneA.update (index.js:145)
at Systems.step (phaser.js:26785)
at SceneManager.update (phaser.js:45932)
at Game.step (phaser.js:109993)
at TimeStep.step (phaser.js:106785)
at step (phaser.js:66250)

Also, get these warning messages:

phaser.js:51943 Invalid Animation Key, or Key already in use: misa-left-walk
create @ phaser.js:51943
phaser.js:51943 Invalid Animation Key, or Key already in use: misa-right-walk
create @ phaser.js:51943
phaser.js:51943 Invalid Animation Key, or Key already in use: misa-front-walk
create @ phaser.js:51943
phaser.js:51943 Invalid Animation Key, or Key already in use: misa-back-walk

What do you all think should be done? Also, here is a code snippet of what is it referring to:

  const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");
  const entrance = map.findObject("Objects", obj => obj.name === "store");

// Create a sprite with physics enabled via the physics system. The image used for the sprite has
 // a bit of whitespace, so I'm using setSize & setOffset to control the size of the 
player's body.
 player = this.physics.add
.sprite(spawnPoint.x, spawnPoint.y, "atlas", "misa-front")
.setSize(30, 40)
.setOffset(0, 24);




// Watch the player and worldLayer for collisions, for the duration of the scene:
this.physics.add.collider(player, worldLayer);



// Create the player's walking animations from the texture atlas. These are stored in the global
// animation manager so any sprite can access them.
const anims = this.anims;
anims.create({
key: "misa-left-walk",
frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }),
frameRate: 10,
repeat: -1
});
anims.create({
key: "misa-right-walk",
frames: anims.generateFrameNames("atlas", { prefix: "misa-right-walk.", start: 0, end: 3, zeroPad: 3 }),
frameRate: 10,
repeat: -1
});
anims.create({
key: "misa-front-walk",
frames: anims.generateFrameNames("atlas", { prefix: "misa-front-walk.", start: 0, end: 3, zeroPad: 3 }),
frameRate: 10,
repeat: -1
});
anims.create({
key: "misa-back-walk",
frames: anims.generateFrameNames("atlas", { prefix: "misa-back-walk.", start: 0, end: 3, zeroPad: 3 }),
frameRate: 10,
repeat: -1
});

const camera = this.cameras.main;
camera.startFollow(player);
camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

cursors = this.input.keyboard.createCursorKeys();

// Help text that has a "fixed" position on the screen
this.add
.text(16, 16, 'Arrow keys to move\nPress "D" to show hitboxes', {
  font: "18px monospace",
  fill: "#000000",
  padding: { x: 20, y: 10 },
  backgroundColor: "#ffffff"
})
.setScrollFactor(0)
.setDepth(30);

// Debug graphics
this.input.keyboard.once("keydown_D", event => {
// Turn on physics debugging to show player's hitbox
this.physics.world.createDebugGraphic();

// Create worldLayer collision graphic above the player, but below the help text
const graphics = this.add
  .graphics()
  .setAlpha(0.75)
  .setDepth(20);
worldLayer.renderDebug(graphics, {
  tileColor: null, // Color of non-colliding tiles
  collidingTileColor: new Phaser.Display.Color(243, 134, 48, 255), // Color of colliding tiles
  faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Color of colliding face edges
});
});

    this.input.keyboard.once("keydown_S", event => {

        this.scene.start('sceneB');

    },this);
}

update(time, delta) {
const speed = 175;
const prevVelocity = player.body.velocity.clone();

// Stop any previous movement from the last frame
player.body.setVelocity(0);

// Horizontal movement
if (cursors.left.isDown) {
player.body.setVelocityX(-speed);
} else if (cursors.right.isDown) {
player.body.setVelocityX(speed);
}

Also, I still need to come up with a condition of the either the collision or overlap so that a collision of a player into a tiled map object will cause event that changes a scene. Is it possible to have physics applied to tiled map objects?