In this development cycle my main aim is to create characters that move and interact with keyboard inputs as well as giving them collision properties to interact with the existing map from . I aim for the characters also to have sprites that change depending on the user input and how they respond to the movement.
Usability Features
Key Variables
Variable Name
Use
velocity(X,Y)
The speed assigned to the player when key inputs are pressed determining how fast they move
player / faune
Variable that stores all of the information and properties about the character.
left, right, up, down
Stores the information about keyboard inputs allowing them to be assigned to player movement
config
Stores information about the game resolution, physics and other data to start the game correctly.
Pseudocode
// create config and information about the games properties
config
height: 1120
width: 640
physics: no gravity
scene: preloader, game
end object
// initialise variables and attributes for the game
procedure create
player = generatePlayer
camera.follow(player)
player.collideWorldBounds = true
// create the variables for the keyboard input values
left = key bind for left arrow key
right = key bind for right arrow key
up = key bind for arrow key
down = key bind for down key
// what the character should do upon given keyboard inputs
if (input = up) {
velocity.x = 0
velocity.y = -velocity
this.player.animations.play('up');
if (input = down) {
velocity.x = 0
velocity.y = -velocity
this.player.animations.play('down');
if (input = left) {
velocity.x = -velocity
velocity.y = 0
this.player.animations.play('left');
if (input = right) {
velocity.x = -velocity
velocity.y = 0
this.player.animations.play('right');
// stationary
if (input = none) {
velocity.x = 0
velocity.y = 0
this.player.animations.play('standing');
end procedure
Development
Outcome
Lots of the parts of the code such as the index.html file remain the same in this second cycle but most of the change will take place in the game.ts file since that contains most of the player and world attributes. I have also separated out parts of the code into their own files such as the Preloading features to make the game.ts and main.ts less crowded and easier to read.
import Phaser from "phaser";
export default class Preloader extends Phaser.Scene
{
constructor()
{
super('preloader')
}
// Preloading now loads sprites for the character and the JSON
// that has information on the frames to work with the animations
preload()
{
this.load.image('tiles', 'tiles/Serene_Village_16x16_extruded.png');
this.load.tilemapTiledJSON('mainmap', 'tiles/mainmaptest.json');
this.load.atlas('faune', 'character/faune.png', 'character/faune.json')
}
create()
{
this.scene.start('game')
}
}
(Lines 39-120)
House.setCollisionByProperty({ collides: true})
Housedecor.setCollisionByProperty({ collides: true })
Houseontop.setCollisionByProperty({ collides : true })
Bushes.setCollisionByProperty({collides: true})
Rocks.setCollisionByProperty({ collides: true })
Island1.setCollisionByProperty({ collides: true })
water.setCollisionByProperty({ collides: true})
Island2.setCollisionByProperty({ collides: true})
Bushes.setCollisionByProperty({ collides: true })
Tree1.setCollisionByProperty({ collides: true })
Tree2.setCollisionByProperty({ collides: true })
Tree3.setCollisionByProperty({ collides: true })
Tree4.setCollisionByProperty({ collides: true })
// Creates physics object for the character and setting the idle frame
// Also setting the property of the object and sizes for the collision border
this.faune = this.physics.add.sprite(480, 235, 'faune', 'walk-down-3.png')
this.faune.body.setSize(this.faune.width * 0.8, this.faune.height * 0.7)
this.anims.create({
key: 'faune-idle-down',
frames: [{ key: 'faune', frame: 'walk-down-3.png'}]
})
this.anims.create({
key: 'faune-idle-up',
frames: [{ key: 'faune', frame: 'walk-up-3.png'}]
})
this.anims.create({
key: 'faune-idle-side',
frames: [{ key: 'faune', frame: 'walk-side-3.png'}]
})
this.anims.create({
key: 'faune-run-down',
frames: this.anims.generateFrameNames('faune', {start: 1.0, end: 8.0, prefix: 'run-down-', suffix: '.png'}),
repeat: -1,
frameRate: 15
})
this.anims.create({
key: 'faune-run-up',
frames: this.anims.generateFrameNames('faune', {start: 1.0, end: 8.0, prefix: 'run-up-', suffix: '.png'}),
repeat: -1,
frameRate: 15
})
this.anims.create({
key: 'faune-run-side',
frames: this.anims.generateFrameNames('faune', {start: 1.0, end: 8.0, prefix: 'run-side-', suffix: '.png'}),
repeat: -1,
frameRate: 15
})
//Adding colliders to the layers neccersary
this.faune.anims.play('faune-idle-down')
this.physics.add.collider(this.faune, Island1)
this.physics.add.collider(this.faune, Rocks)
this.physics.add.collider(this.faune, Island2)
this.physics.add.collider(this.faune, water)
this.physics.add.collider(this.faune, House)
this.physics.add.collider(this.faune, Housedecor)
this.physics.add.collider(this.faune, Tree1)
this.physics.add.collider(this.faune, Tree2)
this.physics.add.collider(this.faune, Tree3)
this.physics.add.collider(this.faune, Tree4)
this.physics.add.collider(this.faune, Houseontop)
this.cameras.main.startFollow(this.faune, true,)
this.cameras.main.setBounds(-436, -200.5, 1833, 887, true)
// this.cameras.main.centerOn(innerWidth, innerHeight)
}
//Updates every frame to check for whether any keys are being pressed and
//if they are update with the corresponding animation defined earlier
update(t: number, dt: number){
if (!this.cursors || !this.faune)
{
return
}
const speed = 100;
if (this.cursors.left?.isDown)
{
this.faune.anims.play('faune-run-side', true)
this.faune.setVelocity(-speed, 0)
this.faune.scaleX = -1
this.faune.body.offset.x = 12
}
else if (this.cursors.right?.isDown)
{
this.faune.anims.play('faune-run-side', true)
this.faune.setVelocity(speed, 0)
this.faune.scaleX = 1
this.faune.body.offset.x = 4
}
else if (this.cursors.up?.isDown) {
this.faune.anims.play('faune-run-up', true)
this.faune.setVelocity(0, -speed)
}
else if (this.cursors.down?.isDown) {
this.faune.anims.play('faune-run-down', true)
this.faune.setVelocity(0, speed)
}
else
{
const parts = this.faune.anims.currentAnim.key.split('-')
parts[1] = 'idle'
this.faune.anims.play(parts.join('-'))
this.faune.setVelocity(0, 0)
}
}
}
Challenges
Throughout this development cycle I faced many challenges since I now had to work with new aspects such as player inputs as well as responding collision to the character. Problems arose when trying to create these aspects of the game due to some outdated functions and documentation regarding the phaser library. An example of this I found was the way layers with collisions in Phaser used to be initialised by createStaticLayer.map('name', tileset)
However my solution to this old outdated function was to instead initialise the layers as their own independent variables by writing const name = map.createLayer('name', tileset). This also was useful later on when trying to group different layers that needed collision together for more readable code.
Another problem I had with this development cycle was trying to get the camera to follow the camera but then not go outside the boundaries I had set. For some reason the game boundaries were not being set to the pixel measurements I had provided so having to find a workaround for this was quite difficult.
Testing
Evidence for testing
Tests
Test
Instructions
What I expect
What actually happens
Pass/Fail
1
Run code
Character should appear on screen on top of the map
Character texture not loaded with texture frame missing
Fail
2
Press keys / WASD
The player should respond by moving around the screen in the given direction
As expected
Pass
3
Press arrow keys
Characters sprite to change to a different corresponding animation
Character's sprite was half cut off implying the amount the sprite needs to change was too much or too little
Fail
4
Move character into an object with collision properties
Character to stop moving and not get past the object or wall
As expected
Pass
After this I went back to work out why the sprite issues weren't working and was due to the increments of how the image sprite stages were going up, to fix this I changed them from decimal increments to integer stages.
game.ts
//Changing the border to size to feel more responsive
this.faune = this.physics.add.sprite(480, 235, 'faune', 'walk-down-3.png')
this.faune.body.setSize(this.faune.width * 0.5, this.faune.height * 0.7)
this.anims.create({
key: 'faune-idle-down',
frames: [{ key: 'faune', frame: 'walk-down-3.png'}]
})
this.anims.create({
key: 'faune-idle-up',
frames: [{ key: 'faune', frame: 'walk-up-3.png'}]
})
this.anims.create({
key: 'faune-idle-side',
frames: [{ key: 'faune', frame: 'walk-side-3.png'}]
})
// Changing the animations to go up in integers that correspond with the correct frames of the animation
this.anims.create({
key: 'faune-run-down',
frames: this.anims.generateFrameNames('faune', {start: 1, end: 8, prefix: 'run-down-', suffix: '.png'}),
repeat: -1,
frameRate: 15
})
this.anims.create({
key: 'faune-run-up',
frames: this.anims.generateFrameNames('faune', {start: 1, end: 8, prefix: 'run-up-', suffix: '.png'}),
repeat: -1,
frameRate: 15
})
this.anims.create({
key: 'faune-run-side',
frames: this.anims.generateFrameNames('faune', {start: 1, end: 8, prefix: 'run-side-', suffix: '.png'}),
repeat: -1,
frameRate: 15
})
update(t: number, dt: number){
if (!this.cursors || !this.faune)
{
return
}
const speed = 100;
if (this.cursors.left?.isDown)
{
this.faune.anims.play('faune-run-side', true)
this.faune.setVelocity(-speed, 0)
this.faune.scaleX = -1
this.faune.body.offset.x = 24
}
else if (this.cursors.right?.isDown)
{
this.faune.anims.play('faune-run-side', true)
this.faune.setVelocity(speed, 0)
this.faune.scaleX = 1
this.faune.body.offset.x = 8
}
else if (this.cursors.up?.isDown) {
this.faune.anims.play('faune-run-up', true)
this.faune.setVelocity(0, -speed)
}
else if (this.cursors.down?.isDown) {
this.faune.anims.play('faune-run-down', true)
this.faune.setVelocity(0, speed)
}
else
{
const parts = this.faune.anims.currentAnim.key.split('-')
parts[1] = 'idle'
this.faune.anims.play(parts.join('-'))
this.faune.setVelocity(0, 0)
}
}
}
Tests
Test
Instructions
What I expect
What actually happens
Pass/Fail
1
Run code
Character should appear on screen on top of the map
As expected
Pass
2
Press keys / WASD
The player should respond by moving around the screen in the given direction
As expected
Pass
3
Press arrow keys
Characters sprite to change to a different corresponding animation
As expected
Pass
4
Move character into an object with collision properties
Character to stop moving and not get past the object or wall
As expected
Pass
Here is a video of me demonstrating working character movements on a map made for testing and demonstrating working collisions too through debugging the player hitbox to make it more visual.