We have to create a game with Phaser 3 for a university project. We opted for a classic jump and round game.
I chose individual scenes to achieve the 15 levels. Now I have encountered the problem that the run animation does not work in the scene for some reason. However, I don't know exactly why so at least it doesn't make sense to me that it doesn't work. Maybe someone has an idea or knows what I did wrong.
// Level 1
var player;
var stars;
var bombs;
var platforms;
var cursors;
var score = 0;
var gameOver = false;
var scoreText;
class GameScene extends Phaser.Scene {
constructor (config) {
super(config);
//To use the scene in the game
Phaser.Scene.call(this, { key: 'gamescene'});
}
preload () {
this.load.image('sky', 'img/sky.png');
this.load.image('ground', 'img/platform.png');
this.load.image('star', 'img/star.png');
this.load.image('bomb', 'img/bomb.png');
this.load.spritesheet('dude', 'img/dude.png', { frameWidth: 32, frameHeight: 48 });
}
create () {
// A simple background for our game
this.add.image(400, 300, 'sky');
// The platforms group contains the ground and the 2 ledges we can jump on
platforms = this.physics.add.staticGroup();
// Here we create the ground.
// Scale it to fit the width of the game (the original sprite is 400x32 in size)
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
// Now let's create some ledges
platforms.create(600, 400, 'ground');
platforms.create(50, 250, 'ground');
platforms.create(750, 220, 'ground');
// The player and its settings
player = this.physics.add.sprite(100, 450, 'dude');
// Player physics properties. Give the little guy a slight bounce.
player.setBounce(0.2);
player.setCollideWorldBounds(true);
// Our player animations, turning, walking left and walking right.
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
// Input Events
cursors = this.input.keyboard.createCursorKeys();
// Some stars to collect, 12 in total, evenly spaced 70 pixels apart along the x axis
stars = this.physics.add.group({
key: 'star',
repeat: 11,
setXY: { x: 12, y: 0, stepX: 70 }
});
stars.children.iterate(function (child) {
// Give each star a slightly different bounce
child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
});
bombs = this.physics.add.group();
// The score
scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
// Collide the player and the stars with the platforms
this.physics.add.collider(player, platforms);
this.physics.add.collider(stars, platforms);
this.physics.add.collider(bombs, platforms);
// Checks to see if the player overlaps with any of the stars, if he does call the collectStar function
this.physics.add.overlap(player, stars, collectStar, null, this);
this.physics.add.collider(player, bombs, hitBomb, null, this);
}
update() {
if (gameOver)
{
return;
}
if (cursors.left.isDown)
{
player.setVelocityX(-160);
player.anims.play('left', true);
}
else if (cursors.right.isDown)
{
player.setVelocityX(160);
player.anims.play('right', true);
}
else
{
player.setVelocityX(0);
player.anims.play('turn');
}
if (cursors.up.isDown && player.body.touching.down)
{
player.setVelocityY(-330);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Related
In my sprite sheet I have two separate frames for when the character is not moving, one is for when she was moving left and then stopped and the other for when she was moving right and then stopped, so i need it to fire up on sort of a key up event. I've seen very similar question here, but the answer didn't work for me, it said to add:
scene.input.keyboard.on('keyup-LEFT', function()
{
});
but it just broke my code, screen goes black and nothing happens, i tried adding that code in either create() and update()
So i tried instead:
this.input.keyboard.on('keyup-LEFT', function()
{
player.setVelocityX(0);
player.anims.play('stopLeft');
});
this.input.keyboard.on('keyup-RIGHT', function()
{
player.setVelocityX(0);
player.anims.play('stopRight');
});
It doesn't break my code, but also nothing happens, I also tried player instead of scene or this, (player is a variable to which my sprite is assigned), but that also broke my code, just like scene did.
BTW, stopLeft and stopRight works fine, because i tested them on normal this.input.keyboard.createCursorKeys() events.
The most common way to implement player movement and/or input is, to put the logic in the update function, and check if the keys are pressed/down (isDown).
I would only use this.input.keyboard, for hot-key like 'ESC' or some special hot-keys.
Here a short working demo:
(for more details checkout this official example)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 100 }
}
},
scene: {
preload,
create,
update
},
banner: false
};
let player;
let cursor;
let animsKeyText;
function preload() {
this.load.spritesheet('dude', 'https://labs.phaser.io/src/games/firstgame/assets/dude.png', { frameWidth: 32, frameHeight: 48 });
}
function create() {
let floor = this.add.rectangle(0, config.height, config.width, 20, 0xcdcdcd)
.setOrigin(0, .5);
animsKeyText = this.add.text(10, 10, 'current anims-key: ??')
this.physics.add.existing(floor, true);
player = this.physics.add.sprite(100, 10, 'dude')
.setCollideWorldBounds(true);
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'stop-left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 1 }),
frameRate: 10,
repeat: 0
});
this.anims.create({
key: 'stop-right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 6 }),
frameRate: 10,
repeat: 0
});
this.physics.add.collider(player, floor);
cursors = this.input.keyboard.createCursorKeys();
}
function update() {
if (cursors.left.isDown) {
player.setVelocityX(-160);
player.anims.play('left', true);
}
else if (cursors.right.isDown) {
player.setVelocityX(160);
player.anims.play('right', true);
}
else {
player.setVelocityX(0);
if (player.anims && player.anims.currentAnim) {
// just to prevent of keys "stop-stop....-right" or so
if (player.anims.currentAnim.key.indexOf('stop') == -1) {
let newKey = `stop-${player.anims.currentAnim.key}`;
player.anims.play(newKey, true);
}
}
}
if (player.anims && player.anims.currentAnim) {
animsKeyText.setText(`current anims-key: ${player.anims.currentAnim.key}`);
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I'm confused about the 'animationcomplete' notification. I thought I would receive the notification one time when the animation I listen finishes. It turns out I will receive the notification n times. Here is the code.
class BootScene extends Phaser.Scene {
constructor() {
super({ key: 'BootScene' });
}
preload() {
this.load.spritesheet('brawler', 'https://raw.githubusercontent.com/photonstorm/phaser3-examples/master/public/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
}
create() {
this.anims.create({
key: 'win',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 30, 31 ] }),
frameRate: 8,
repeat: 0,
});
const cody = this.add.sprite(200, 70);
cody.setScale(8);
let btn = this.add.rectangle(500, 70, 200, 150, '#000')
this.add.text(500, 70, 'click')
btn.setInteractive();
btn.on('pointerdown', () => {
console.log('pointerdown');
cody.play('win');
cody.on('animationcomplete', (e) => {
console.log(e.key);
});
});
}
}
var config = {
width: 800,
height: 500,
backgroundColor: '#555',
scene: [BootScene]
}
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I put two console.log in there, one for pointerdown and the other for animationcomplete.
First time I click the btn, I get one pointerdown and one animationcomplete. Second time I click the btn, I get one pointerdown and two animationcomplete.
How do I get just one animationcomplete no matter I click the btn n-th time?
As #Ourobours mentioned you are attaching, on each click, new event handler for animationcomplete.
An Since you can attach multiple event-handlers for the same event, all of them will be executed the next time it fires, ans so on.
You would only have to move the animationcomplete event-handler out of the pointerdown event handler function, and everything should work as you would expect it.
Here a working demo:
class BootScene extends Phaser.Scene {
constructor() {
super({ key: 'BootScene' });
}
preload() {
this.load.spritesheet('brawler', 'https://raw.githubusercontent.com/photonstorm/phaser3-examples/master/public/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
}
create() {
this.anims.create({
key: 'win',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 30, 31 ] }),
frameRate: 8,
repeat: 0,
});
const cody = this.add.sprite(200, 70);
cody.setScale(8);
let btn = this.add.rectangle(500, 70, 200, 150, '#000')
this.add.text(500, 70, 'click')
btn.setInteractive();
cody.on('animationcomplete', (e) => {
console.log(e.key);
});
btn.on('pointerdown', () => {
console.log('pointerdown');
cody.play('win');
});
}
}
var config = {
width: 800,
height: 500,
backgroundColor: '#555',
scene: [BootScene],
banner:false
}
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I am learning Phaser 3 by creating a small platform game, everything works as I want, but when the player loads there is some space between him and the ground, what is causing this and how can I fix it? I want him to be standing on the ground without any space between, here is a picture:
https://ibb.co/Px65JMR
and here is my js code:
var config = {
type: Phaser.AUTO,
width: 900,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 }
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
var game = new Phaser.Game(config);
function preload() {
this.load.image('sky', 'assets/world/sky.png');
this.load.image('ground', 'assets/world/ground.png');
this.load.image('platform', 'assets/world/platform.png');
this.load.spritesheet('player', 'assets/characters/player.png', { frameWidth: 32, frameHeight: 48 });
}
function create() {
this.add.image(400, 300, 'sky');
platforms = this.physics.add.staticGroup();
ground = this.physics.add.staticGroup();
platforms.create(600, 400, 'platform');
platforms.create(50, 250, 'platform');
platforms.create(750, 220, 'platform');
ground.create(500, 550, 'ground')
// Creating the player
player = this.physics.add.sprite(50, 350, 'player');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
player.body.setGravityY(300)
this.physics.add.collider(player, ground);
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [{ key: 'player', frame: 4 }],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('player', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
}
function update() {
cursors = this.input.keyboard.createCursorKeys();
if (cursors.left.isDown) {
player.setVelocityX(-160);
player.anims.play('left', true);
}
else if (cursors.right.isDown) {
player.setVelocityX(160);
player.anims.play('right', true);
}
else {
player.setVelocityX(0);
player.anims.play('turn');
}
if (cursors.up.isDown && player.body.touching.down) {
player.setVelocityY(-330);
}
}
this.load.spritesheet('player', 'assets/characters/player.png', { frameWidth: 32, frameHeight: 48 });
Did you make sure that the frameHeight of the sprite is 48 and not 32x32?
Also, I'd recommend you to add debug=true
to your configuration:
arcade: {
debug: true,
gravity: { y: 300 }
}
By doing so, each object will be marked by a purple outline, I bet you will figure it out in no time.
possible solution 1:
try to reduce the second param here:
ground.create(500, 550, 'ground')
to - lets say- 450.
possible solution 2:
ground.create(500, 550, 'ground').setSize(500,500, true);
ground.refresh();
I am creating a skateboarding game in JavaScript using the Phaser 3 framework. I have loaded the ramp image onto the screen, and I am currently using the "arcade" physics engine in my file. I know I have to use the matter physics engine to have a non-rectangular hitbox. How do I implement it with the triangular hitbox?
I have the .png image file of the ramp, along with the .json file for its hitbox.
I tried following a tutorial off of a website on how to create new hitboxes for the matter physics engine. Everything ended up falling off the screen and I couldn't figure out how to use the .json file for the ramp.
//Configurations for the physics engine
var physicsConfig = {
default: 'arcade',
arcade : {
debug : true //CHANGE THIS TO TRUE TO SEE LINES
}
}
//Game configurations
var config = {
type: Phaser.AUTO,
width: 1200 ,
height: 600,
physics: physicsConfig,
scene: {
preload: preload,
create: create,
update: update
}
}
//Start the game
var game = new Phaser.Game(config);
function preload() {
//Images
this.load.image('sky', 'archery_assets/images/sky.png');
this.load.image('ground', 'skate_assets/images/ground.png');
this.load.image('up_ramp', 'skate_assets/images/up_ramp.png')
//Spritesheets
this.load.spritesheet('player', 'skate_assets/spritesheets/skater.png', {frameWidth: 160, frameHeight: 160});
}
function create() {
//Background
skyImg = this.add.image(600, 300, 'sky');
//Scale the images
skyImg.setDisplaySize(1200, 600);
groundImg = this.add.image(600, 600, 'ground');
groundImg.setDisplaySize(1200, 250);
//Create the player
this.player = this.physics.add.sprite(100, 410, 'player');
this.player.setCollideWorldBounds(true);
//Rolling animation
this.anims.create({
key: 'move',
frames: this.anims.generateFrameNumbers('player', {start: 0, end: 3}),
frameRate: 16,
repeat: -1 // <-- keeps the rolling animation going
});
//Pushing animation
this.anims.create({
key: 'push',
frames: this.anims.generateFrameNumbers('player', {start: 4, end: 8}),
frameRate: 16
});
//Start and keep the rolling animation going
this.player.anims.play('move', true);
//Up ramp (1st ramp)
this.upRamp = this.physics.add.sprite(700, 330, 'up_ramp');
this.upRamp.setSize(320, 150).setOffset(0, 175);
this.upRamp.enableBody = true;
this.upRamp.setImmovable();
this.upRamp.body.angle = 150;
//Input
this.cursors = this.input.keyboard.createCursorKeys();
//Spacebar
this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
this.physics.add.collider(this.player, this.upRamp);
}
function update() {
//Set variable for push speed
var playerPushSpeed = 0;
//If the spacebar is pressed
if (this.spacebar.isDown) {
//Play the push animation
this.player.anims.play('push', true);
//Push speed
playerPushSpeed += 175;
//Move player
this.player.setVelocityX(playerPushSpeed);
}
if (this.upRamp.body.touching.left) {
this.player.setVelocityY(-200);
}
}
I need to know how to implement the .png image of the ramp along with its .json hitbox file, so that the player can properly "ride" up the ramp.
You'll have to use the physics: { default: 'matter' } in order to change the hitbox's shape. Use this code snippet for reference:
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
physics: {
default: 'matter',
matter: {
gravity: {
y: 0
},
debug: true
}
},
scene: {
create: create
}
};
var game = new Phaser.Game(config);
function create ()
{
this.matter.world.setBounds();
this.matter.add.rectangle(200, 200, 200, 200, {
chamfer: { radius: [230, 0, 200, 0 ] }
});
this.matter.add.mouseSpring();
}
You could also use a PhysicsEditor, you can check this tutorial on how to implement different shapes.
EDIT:
You can use console.log(this.matter.bodies) to check other available shapes to implement.
I am using Matter.js to build games. Starting from one of their examples: Pyramid, I started playing around. The code fro this specific example is as follows:
var Example = Example || {};
Example.pyramid = function() {
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Composites = Matter.Composites,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
World = Matter.World,
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create(),
world = engine.world;
// create renderer
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
showAngleIndicator: true
}
});
Render.run(render);
// create runner
var runner = Runner.create();
Runner.run(runner, engine);
// add bodies
var stack = Composites.pyramid(100, 258, 15, 10, 0, 0, function(x, y) {
return Bodies.rectangle(x, y, 40, 40);
});
World.add(world, [
stack,
// walls
Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
Bodies.rectangle(0, 300, 50, 600, { isStatic: true }),
Bodies.rectangle(400, 605, 800, 50, { isStatic: true })
]);
// add mouse control
var mouse = Mouse.create(render.canvas),
mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: {
visible: false
}
}
});
World.add(world, mouseConstraint);
// keep the mouse in sync with rendering
render.mouse = mouse;
// fit the render viewport to the scene
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: 800, y: 600 }
});
// context for MatterTools.Demo
return {
engine: engine,
runner: runner,
render: render,
canvas: render.canvas,
stop: function() {
Matter.Render.stop(render);
Matter.Runner.stop(runner);
}
};
};
I am trying to modify the code in order to disallow the sprites in the pyramid to be moved by mouse. The part of code responsible for creating those objects is this:
// add bodies
var stack = Composites.pyramid(100, 258, 15, 10, 0, 0,
function(x, y) {
return Bodies.rectangle(x, y, 40, 40);
});
I have tried searching among the options that can be passed to Bodies.rectangle, but found nothing.
How to achieve this?
You can use the collisionFilter property to do this.
Collision filters use groups, masks and categories to determine which bodies collide. I'll ignore groups in this example. By default, bodies belong to category 1 and have all masked enabled:
const objValsToBase = (o, base) =>
Object.fromEntries(Object.entries(o)
.map(([k, v]) => [k, v.toString(base)]));
const circle = Matter.Bodies.circle(200, 100, 50);
// show decimal (base 10)
console.log(circle.collisionFilter);
// show bits (base 2)
console.log(objValsToBase(circle.collisionFilter, 2));
// show hex (base 16)
console.log(objValsToBase(circle.collisionFilter, 16));
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
To get two bodies to stop colliding, you can set the category on one group to 0b10, which is 0x2 in hex and 2 in decimal, and set the mask on the other to turn off the 2 bit: 0b01. 0b01 also disables collisions you might have with other categories which are unused by default but might be used in your code sooner or later, so you might prefer 0xfffffd since (0xd).toString(2) => '1101'.
Below is the pyramid example code modified to disable mouse-pyramid collision. I also ran it through prettier and added a circle that collides with everything to show that the mouse still works on that:
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Composites = Matter.Composites,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
World = Matter.World,
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create(),
world = engine.world;
// create renderer
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
showAngleIndicator: true,
},
});
Render.run(render);
// create runner
var runner = Runner.create();
Runner.run(runner, engine);
// add bodies
var stack = Composites.pyramid(
100,
258,
15,
10,
0,
0,
function (x, y) {
return Bodies.rectangle(x, y, 40, 40, {
collisionFilter: {mask: 0xfffffd}, // <--
});
}
);
World.add(world, [
stack,
// walls
Bodies.circle(200, 100, 50),
Bodies.rectangle(400, 0, 800, 50, {isStatic: true}),
Bodies.rectangle(800, 300, 50, 600, {isStatic: true}),
Bodies.rectangle(0, 300, 50, 600, {isStatic: true}),
Bodies.rectangle(400, 605, 800, 50, {isStatic: true}),
]);
// add mouse control
var mouse = Mouse.create(render.canvas),
mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
collisionFilter: {category: 0b10}, // <--
constraint: {
stiffness: 0.2,
render: {
visible: false,
},
},
});
World.add(world, mouseConstraint);
// keep the mouse in sync with rendering
render.mouse = mouse;
// fit the render viewport to the scene
Render.lookAt(render, {
min: {x: 0, y: 0},
max: {x: 800, y: 600},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
See also:
How can I change the collisionFilter of an object so it can no longer interact with MouseConstraint (MatterJS)
How to get only a single body to move using Matter-js Mouse
How to prevent sprites to be moved by mouse in Matter.js? #681