Creating falling spikes with Phaser JS - Game Devlog #20
Written in June 28, 2021 - 🕒 4 min. readHey everyone! Today I’m finally back with another game devlog for Super Ollie, and I’m going to implement a classic element of a 2D platformer, a falling spike.
First I will create the spike
class, nothing crazy:
class Spike extends GameObjects.Sprite {
constructor({
scene,
x = 0,
y = 0,
asset = 'spike',
enablePhysics = true,
addToScene = true,
frame,
name,
}) {
super(scene, x, y, asset, frame);
this.setOrigin(0, 1);
this.setName(name || asset);
if (addToScene) {
scene.add.existing(this);
}
if (enablePhysics) {
scene.physics.add.existing(this);
this.body.setAllowGravity(false);
this.body.setImmovable(true);
}
}
}
Now in my dataLayer
loop (which I explained on the devlog 8) I will add a case for the spikes to be added.
// in the scene create function
const dataLayer = map.getObjectLayer('data');
dataLayer.objects.forEach((data) => {
const { x, y, name, height, width } = data;
if (name === 'spike') {
// TODO add spike logic here
}
});
Before going into the logic, my goal is to be able to add a spike wherever I want on Tiled
, and the code will parse it and magically make it work.
How does a falling spike works on platformer games? Well, when the player walks under one, it will fall on them, either right away or a couple of milliseconds later, and have that we need a custom collider object, that when the player interacts with it, it will trigger the spike to fall.
This is exactly what I want to automate, create a logic so that the spike will automatically decide where the custom collider object will be in the map.
if (name === 'spike') {
const spike = new Spike({
scene: this,
x,
y,
});
const customCollider = createColliderFromGameObjectToGround(
this,
spike,
mapSize,
this.mapData.dynamicLayers.ground
);
const { y: spikeY } = spike;
const collider = this.physics.add.overlap(
customCollider,
this.player,
() => {
// TODO handle spike falling
}
);
this.physics.add.overlap(
spike,
this.player,
() => {
// TODO handle spike hitting the player
}
);
}
The createColliderFromGameObjectToGround
function will have all the logic to get the spike position, find the nearest ground tile from that position, and place a custom collider there.
Inside this new function, I’m going to need 2 other functions, createInteractiveGameObject
, that I showed on the devlog 15, and a new function called isBelowTileCollidable
, that will receive a game object and check if the tile below that object is collidable or not.
const isBelowTileCollidable = (
gameObject,
dynamicLayer // single layer or GameObject Group
) => {
const { x, y, height } = gameObject;
const layers = dynamicLayer?.getChildren?.() || [dynamicLayer];
let tile = null;
let result = false;
layers.forEach((layer) => {
if (result) {
return;
}
tile = layer.getTileAtWorldXY(
x - 0.5,
y + height + 0.5
);
if (tile) {
result = tile?.properties?.collideUp;
}
});
return result;
};
For the createColliderFromGameObjectToGround
function, I will create a loop of the size of the map height, and from the spike, position start looking at the nearest ground tile, I will use the some
function, so I can early return from the loop when a ground tile is found.
const createColliderFromGameObjectToGround = (
scene,
gameobject,
mapSize,
mapLayer
) => {
let customCollider;
let result = false;
// TODO improve the size of this array
new Array(mapSize.height / gameobject.height).fill(null).some(
(val, index) => {
const posY = gameobject.y + (TILE_HEIGHT * (index + 1));
result = isBelowTileCollidable({
...gameobject,
y: posY,
},
mapLayer
);
if (result) {
const spikeHeight = gameobject.height + (posY - gameobject.y);
customCollider = createInteractiveGameObject(
scene,
gameobject.x,
// posY,
posY - spikeHeight + gameobject.height,
gameobject.width,
// spike.height,
spikeHeight,
gameobject.name
);
}
return result;
}
);
return customCollider;
};
With these 2 functions in place, I can come back to my dataLayer
loop and work on the spike logic, for now, I want the spike to fall when the player interacts with the trigger, and then make the spike reappear in the ceiling after 2 seconds.
if (name === 'spike') {
const spike = new Spike({
scene: this,
x,
y,
});
const customCollider = createColliderFromGameObjectToGround(
this,
spike,
mapSize,
this.mapData.dynamicLayers.ground
);
// if for some reason the collider was not created, do nothing.
if (customCollider) {
const { y: spikeY } = spike;
const collider = this.physics.add.overlap(
customCollider,
this.player,
() => {
if (!spike.body.allowGravity) {
spike.body.setAllowGravity(true);
this.time.delayedCall(2000, () => {
spike.body.setAllowGravity(false);
spike.body.setAcceleration(0, 0);
spike.body.setVelocity(0, 0);
spike.setY(spikeY);
spike.setActive(true);
spike.setVisible(true);
});
}
}
);
this.physics.add.overlap(
spike,
this.player,
() => {
if (spike?.visible) {
this.gameHud.decreasePlayerLife(5);
spike.setActive(false);
spike.setVisible(false);
}
}
);
} else {
spike.destroy();
}
}
With debug mode on, this is what the collider looks like in the game:
That’s all for today, see you in the next devlog!
Tags:
- coding
- games
- javascript
- phaser
- phaser 3
- game devlog
- gamedev
- skate platformer
- super ollie vs pebble corp
- webpack
- tiled
Related posts
Post a comment
Comments
No comments yet.