Character Layers - Flying Chars with Shadows
Press the arrow keys to move. This demonstrates the usage of character layers to create flying characters with separated shadows. The shadows are represented as characters in GridEngine, are non-colliding and live on the same character layer as the player.
Code
📖 How to execute examples locally
js
const game = new Phaser.Game(config(preload, create, update));
function preload() {
this.load.image("tiles", "../../assets/tf_jungle_tileset.png");
this.load.tilemapTiledJSON("jungle", "../../assets/jungle-char-layers.json");
this.load.spritesheet("player", "../../assets/characters.png", {
frameWidth: 52,
frameHeight: 72,
});
this.load.spritesheet("monster_bird", "../../assets/monster_bird1_with_shadow.png", {
frameWidth: 61,
frameHeight: 57,
});
}
function create() {
const tilemap = this.make.tilemap({ key: "jungle" });
tilemap.addTilesetImage("jungle", "tiles");
for (let i = 0; i < tilemap.layers.length; i++) {
const layer = tilemap.createLayer(i, "jungle", 0, 0);
layer.scale = 3;
}
const playerSprite = this.add.sprite(0, 0, "player");
playerSprite.scale = 1.5;
this.cameras.main.startFollow(playerSprite, true);
this.cameras.main.setFollowOffset(-playerSprite.width, -playerSprite.height);
const monsterBirdSprites = createBirdSprites.call(this);
const monsterBirdShadows = createShadowSprites.call(this);
createBirdAnimation.call(this);
this.anims.staggerPlay("bird", monsterBirdSprites, 100);
const gridEngineConfig = {
characters: [
{
id: "player",
sprite: playerSprite,
walkingAnimationMapping: 6,
startPosition: { x: 4, y: 4 },
charLayer: 'ground',
},
...monsterBirdSprites.map((sprite, i) => ({
id: "monster_bird_" + i,
sprite,
startPosition: { x: 7, y: 7 + i },
speed: 5,
charLayer: 'sky'
})),
...monsterBirdShadows.map((sprite, i) => ({
id: "monster_bird_shadow_" + i,
sprite,
startPosition: { x: 7, y: 7 + i },
speed: 5,
charLayer: 'ground',
collides: false
})),
],
};
this.gridEngine.create(tilemap, gridEngineConfig);
for (let i = 0; i < 10; i++) {
this.gridEngine.moveRandomly("monster_bird_" + i, 1000, 10);
}
this.gridEngine
.positionChangeStarted()
.subscribe(({ charId, exitTile, enterTile }) => {
if (isBird(charId)) {
this.gridEngine.moveTo('monster_bird_shadow_' + getBirdNum(charId), { x: enterTile.x, y: enterTile.y });
}
});
}
function update() {
const cursors = this.input.keyboard.createCursorKeys();
if (cursors.left.isDown) {
this.gridEngine.move("player", "left");
} else if (cursors.right.isDown) {
this.gridEngine.move("player", "right");
} else if (cursors.up.isDown) {
this.gridEngine.move("player", "up");
} else if (cursors.down.isDown) {
this.gridEngine.move("player", "down");
}
}
function getBirdNum(charId) {
return +charId[charId.length - 1]
}
function isBird(charId) {
return charId.startsWith('monster_bird_') && !charId.startsWith('monster_bird_shadow');
}
function createCroppedSprites(x, y, width, height) {
const croppedSprites = [];
for (let i = 0; i < 10; i++) {
const monsterBirdSprite = this.add.sprite(0, 0, "monster_bird");
monsterBirdSprite.setCrop(x, y, width, height);
monsterBirdSprite.scale = 3;
croppedSprites.push(monsterBirdSprite);
}
return croppedSprites;
}
function createBirdSprites() {
return createCroppedSprites.call(this, 0, 0, 61, 47);
}
function createShadowSprites() {
return createCroppedSprites.call(this, 22, 47, 16, 10);
}
function createBirdAnimation() {
this.anims.create({
key: "bird",
frames: this.anims.generateFrameNumbers("monster_bird", {
start: 0,
end: 2,
}),
frameRate: 10,
repeat: -1,
yoyo: true,
});
}
html
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="../styles.css" />
<script src="../../js/phaser-3.80.1.min.js"></script>
<script src="../../js/grid-engine-2.44.3.min.js"></script>
<script src="../../js/examplePhaserConfig.js"></script>
<script src="code.js"></script>
</head>
<body></body>
</html>
js
function config(preload, create, update) {
return {
title: "GridEngineExample",
render: {
antialias: false,
},
type: Phaser.AUTO,
plugins: {
scene: [
{
key: "gridEngine",
plugin: GridEngine,
mapping: "gridEngine",
},
],
},
scale: {
width: 700,
height: 528,
},
scene: {
preload: preload,
create: create,
update: update,
},
parent: "game",
backgroundColor: "#48C4F8",
input: {
mouse: {
preventDefaultWheel: false
},
touch: {
capture: false
}
}
};
}
css
body {
margin: 0;
padding: 0;
background: #1e1e20;
font-family: Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
color: #cacaca;
}
.settings {
margin-top: 20px;
display: flex;
flex-direction: column;
/* border: 1px solid white; */
background: #282828;
margin-bottom: 20px;
width: 640px;
padding: 0 30px;
}
label {
font-weight: bold;
margin-top: 30px;
margin-bottom: 10px;
}
select {
width: 100px;
}
input {
width: 100px;
}
button {
width: 120px;
margin-top: 30px;
margin-bottom: 20px;
}
.wait-timeout-group {
display: flex;
align-items: center;
justify-content: flex-start;
}
.wait-timeout-group input {
width: auto;
margin-right: 10px;
}
.wait-timeout-group label {
margin:0;
}
#json-config {
margin-bottom: 10px;
}
#json-config pre {
font-family: "Consolas", monospace;
}
.disabled {
display: none;
}