Hey there!
This is my first post on this forum, and I've decided to make a new topic since there was not really any good answer I could find while browsing the topic here on the site!
My problem is as such: I have created an infinite runner with randomly generated platforms made in a prefab that get killed when they leave the screen and then reused in a platformPool. They get instansiated with a preset speed at which the level moves and moves from right to left accordingly. I ofcourse also have a runner player sprite that moves equally as fast in the opposite direction.
The problem occurs when I try to implement some kind of incremental increase of the speed at which all the elements in the level moves. I would like to have a timer that counts upwards on the game screen, and have the game react to certain time points on the timer, at which it increases the speed of the level, making the game more and more difficult. I have gotten it to work to some extent by just updating the levelSpeed variable in a function that keeps track of a counter and then applying this new levelspeed to the character in the update method. This works, but the problem occurs when a platform is already generated on the screen, at which it already has gotten the prior value of the levelSpeed variable set. This results in the character moving forward on the x axis of the game as he has a higher x velocity than the current platforms -x velocity. How do I make sure that an already generated platform also receives the new level-speed? All the newly created platforms ofcourse receive the speed increase as they work by the same levelSpeed-variable when they first get created.
Here is the code. I wasn't sure if you guys would like to look at the code in its entirety so I pasted it here, even though a lot of the code is irrelevant to the current problem.
Thanks in advance!
Game code:
var KnightRunner = KnightRunner || {}
KnightRunner.GameState = {
init: function () {
// Pool of floors
this.floorPool = this.add.group()
// Pool of platforms
this.platformPool = this.add.group()
// pool of coins
this.coinsPool = this.add.group()
this.coinsPool.enableBody = true
// pool of enemies
this.enemyPool = this.add.group()
this.enemyPool.enableBody = true
// gravity
this.game.physics.arcade.gravity.y = 1200
// max jump distance
this.maxJumpDistance = 120
// Move player with keyboard
this.cursors = this.game.input.keyboard.createCursorKeys()
// coins
this.myCoins = 0
// Speed level
this.levelSpeed = 250
this.total = 1
},
create: function () {
// Moving background
this.background = this.add.tileSprite(0, 0, this.game.world.width, this.game.world.height, 'background')
this.background.tileScale.y = 1
this.background.autoScroll(-this.levelSpeed / 6, 0)
this.game.world.sendToBack(this.background)
// Create the player
this.knight = this.add.sprite(100, 50, 'knight')
this.knight.anchor.setTo(0.5, 1)
// Creating hitboxes for the knight animations, attack
this.hitboxes = this.add.group()
this.hitboxes.enableBody = true
this.knight.addChild(this.hitboxes)
this.hitbox1 = this.hitboxes.create(0, 0, null)
this.hitbox1.body.setSize(20, 60, 10, -50)
this.hitbox1.name = 'attack'
this.hitbox1.body.allowGravity = false
this.animAttack = this.knight.animations.add('attacking', Phaser.Animation.generateFrameNames('attack', 0, 13, '.png', 4), 60, false, false)
this.knight.animations.add('running', Phaser.Animation.generateFrameNames('run', 0, 7, '.png', 4), 15, true, false)
this.knight.animations.add('jumping', Phaser.Animation.generateFrameNames('jump', 0, 3, '.png', 4), 15, false, false)
this.knight.animations.add('blocking', Phaser.Animation.generateFrameNames('block', 0, 3, '.png', 4), 15, false, false)
this.game.physics.arcade.enable(this.knight)
this.game.physics.arcade.enable(this.hitbox1)
// Change player bounding box
this.knight.body.setSize(45, 50, -10, 8)
this.knight.play('running')
// hard-code first platform
this.currentPlatform = new KnightRunner.Platform(this.game, this.floorPool, 20, 0, 200, -this.levelSpeed, this.coinsPool, this.enemyPool)
this.platformPool.add(this.currentPlatform)
// Coin sound
this.coinSound = this.add.audio('coinSound')
this.loadLevel()
this.disableAllHitboxes()
// Show number of coins
let style = {font: '30px Arial', fill: '#fff'}
this.coinsCountLabel = this.add.text(10, 20, '0', style)
// Create TImer
this.timerLabel = this.add.text(10, 60, '0', style)
this.timer = this.time.create(false)
this.timer.loop(10000, this.updateCounter, this)
this.timer.start()
if (this.total < 20) {
this.levelSpeed += 5
this.timerLabel.text = this.total
}
},
update: function () {
if (this.knight.alive) {
// Iterating through alive platforms to add collision
this.platformPool.forEachAlive(function (platform, index) {
this.game.physics.arcade.collide(this.knight, platform, this.onGround, null, this)
// Check if a platform needs to be killed
if (platform.length && platform.children[platform.length - 1].right < 0) {
platform.kill()
}
}, this)
if (this.knight.body.touching.down) {
this.knight.body.velocity.x = this.levelSpeed
this.hitbox1.body.velocity.x = this.knight.body.velocity.x - this.levelSpeed
} else {
this.knight.body.velocity.x = 0
this.hitbox1.body.velocity.x = this.knight.body.velocity.x
}
console.log(this.levelSpeed)
console.log(this.total)
this.game.physics.arcade.collide(this.hitbox1, this.enemyPool, this.hitEnemy, null, this)
this.game.physics.arcade.overlap(this.knight, this.coinsPool, this.collectCoin, null, this)
// this.game.physics.arcade.collide(this.knight, this.enemyPool, this.jumpEnemy, null, this)
// kill coins that leave the screen
this.coinsPool.forEachAlive(function (coin) { if (coin.right <= 0) { coin.kill() } }, this)
// kill enemies that leave the screen
this.enemyPool.forEachAlive(function (enemy) { if (enemy.right <= 0) { enemy.kill() } }, this)
if (this.knight.body.touching.down) {
this.knight.body.velocity.x = this.levelSpeed
this.hitbox1.body.velocity.x = this.knight.body.velocity.x - this.levelSpeed
} else {
this.knight.body.velocity.x = 0
this.hitbox1.body.velocity.x = this.knight.body.velocity.x
}
if (this.cursors.left.isDown) {
this.knight.play('blocking')
}
if (this.cursors.right.downDuration(140)) {
this.knight.play('attacking')
this.enableHitBox()
this.time.events.add(Phaser.Timer.SECOND * 0.3, this.disableAllHitboxes, this)
}
if (this.cursors.up.isDown || this.game.input.activePointer.isDown) {
this.knightJump()
} else if (this.cursors.up.isUp || this.game.input.activePointer.isUp) {
this.isJumping = false
}
if (this.currentPlatform.length && this.currentPlatform.children[this.currentPlatform.length - 1].right < this.game.world.width) {
this.createPlatform()
}
// Check if player needs to die
if (this.knight.top >= this.game.world.height || this.knight.left <= 0) {
this.gameOver()
}
}
},
updateCounter: function () {
this.total++
this.levelSpeed += 20
},
enableHitBox: function () {
for (let i = 0; i < this.hitboxes.children.length; i++) {
if (this.hitboxes.children[i].name === 'attack') {
this.hitboxes.children[i].reset(0, 0)
}
}
},
increaseLevelSpeed: function () {
this.levelSpeed += 5
},
disableAllHitboxes: function () {
this.hitboxes.forEachExists(function (hitbox) {
hitbox.enableBody = false
hitbox.kill()
})
},
knightJump: function () {
if (this.knight.body.touching.down) {
// Starting point of the jump
this.startJumpY = this.knight.y
// Keep track of the fact that it is jumping
this.isJumping = true
this.jumpPeak = false
this.knight.animations.play('jumping')
this.knight.body.velocity.y = -300
} else if (this.isJumping && !this.jumpPeak) {
let distanceJumped = this.startJumpY - this.knight.y
if (distanceJumped <= this.maxJumpDistance) {
this.knight.animations.play('jumping')
this.knight.body.velocity.y = -300
} else {
this.jumpPeak = true
}
}
},
onGround () {
if (this.knight && this.cursors.right.downDuration(140)) {
this.knight.play('attacking')
this.enableHitBox()
this.time.events.add(Phaser.Timer.SECOND * 0.3, this.disableAllHitboxes, this)
} else if (this.knight && this.cursors.left.isDown) {
this.knight.play('blocking')
} else {
this.knight.play('running')
}
},
// Debugging
render: function () {
this.game.debug.body(this.knight)
this.game.debug.body(this.hitbox1)
},
makeArray: function (start, end) {
let myArray = []
for (let i = start; i < end; i++) {
myArray.push(i)
}
return myArray
},
loadLevel: function () {
this.createPlatform()
},
createPlatform: function () {
let nextPlatformData = this.generateRandomPlatform()
// Check to see if there is a "dead" platform that can be used
if (nextPlatformData) {
this.currentPlatform = this.platformPool.getFirstDead()
// if Not
if (!this.currentPlatform) {
this.currentPlatform = new KnightRunner.Platform(this.game, this.floorPool, nextPlatformData.numTiles,
this.game.world.width + nextPlatformData.separation,
nextPlatformData.y, -this.levelSpeed, this.coinsPool, this.enemyPool)
// If
} else {
this.currentPlatform.prepare(nextPlatformData.numTiles,
this.game.world.width + nextPlatformData.separation,
nextPlatformData.y, -this.levelSpeed)
}
this.platformPool.add(this.currentPlatform)
}
},
generateRandomPlatform () {
let data = {}
// Distance from previous platform
let minSeparation = 60
let maxSeparation = 250
data.separation = minSeparation + Math.random() * (maxSeparation - minSeparation)
// y in regards to previous platform
let minDifferenceY = -120
let maxDifferenceY = 120
data.y = this.currentPlatform.children[0].y + minDifferenceY + Math.random() * (maxDifferenceY - minDifferenceY)
data.y = Math.max(50, data.y)
data.y = Math.min(this.game.world.height - 50, data.y)
// number of tiles
let minTiles = 12
let maxTiles = 20
data.numTiles = minTiles + Math.random() * (maxTiles - minTiles)
return data
},
collectCoin: function (knight, coin) {
coin.kill()
this.myCoins++
this.coinSound.play()
this.coinsCountLabel.text = this.myCoins
},
jumpEnemy: function (knight, enemy) {
if (enemy.body.touching.up) {
enemy.kill()
console.log('Killed by jumping on head')
} else {
this.gameOver()
}
},
hitEnemy: function (hitbox, enemy) {
enemy.play('dying')
enemy.kill()
console.log('Killed by hitEnemy')
},
gameOver: function () {
this.knight.kill()
this.updateHighscore()
console.log(this.knight.frame)
// Game over overlay
this.overlay = this.add.bitmapData(this.game.width, this.game.height)
this.overlay.ctx.fillStyle = '#000'
this.overlay.ctx.fillRect(0, 0, this.game.width, this.game.height)
// Sprite for the overlay
this.panel = this.add.sprite(0, this.game.height, this.overlay)
this.panel.alpha = 0.55
// overlay raising tween animation
let gameOverPanel = this.add.tween(this.panel)
gameOverPanel.to({y: 0}, 500)
// Stop all movement after overlay reeaches top
gameOverPanel.onComplete.add(function () {
this.background.stopScroll()
let style = {font: '30px Arial', fill: '#fff'}
this.add.text(this.game.width / 2, this.game.height / 2, 'GAME OVER', style).anchor.setTo(0.5)
style = {font: '20px Arial', fill: '#fff'}
this.add.text(this.game.width / 2, this.game.height / 2 + 50, 'High score:' + this.highScore, style).anchor.setTo(0.5)
this.add.text(this.game.width / 2, this.game.height / 2 + 80, 'Your score:' + this.myCoins, style).anchor.setTo(0.5)
style = {font: '10px Arial', fill: '#fff'}
this.add.text(this.game.width / 2, this.game.height / 2 + 120, 'Tap to play again', style).anchor.setTo(0.5)
this.game.input.onDown.addOnce(this.restart, this)
}, this)
gameOverPanel.start()
},
restart: function () {
this.game.state.start('Game')
},
updateHighscore: function () {
this.highScore = +window.localStorage.getItem('highScore')
// Do we have a new high score
if (this.highScore < this.myCoins) {
this.highScore = this.myCoins
window.localStorage.setItem('highScore', this.highScore)
}
}
}
Platform constructor
var KnightRunner = KnightRunner || {}
KnightRunner.Platform = function (game, floorPool, numTiles, x, y, speed, coinsPool, enemyPool) {
Phaser.Group.call(this, game)
this.tileSize = 32
this.game = game
this.enableBody = true
this.floorPool = floorPool
this.coinsPool = coinsPool
this.enemyPool = enemyPool
this.speed = speed
this.prepare(numTiles, x, y, speed)
}
KnightRunner.Platform.prototype = Object.create(Phaser.Group.prototype)
KnightRunner.Platform.prototype.constructor = KnightRunner.Platform
KnightRunner.Platform.prototype.prepare = function (numTiles, x, y, speed) {
// Make sure alive
this.alive = true
var i = 0
while (i < numTiles) {
let floorTile = this.floorPool.getFirstExists(false)
if (!floorTile) {
floorTile = new Phaser.Sprite(this.game, x + i * this.tileSize, y, 'floor')
} else {
floorTile.reset(x + i * this.tileSize, y)
}
this.add(floorTile)
i++
}
// Set physics properties
this.setAll('body.immovable', true)
this.setAll('body.allowGravity', false)
this.setAll('body.velocity.x', speed)
this.addCoins(speed)
this.addEnemy(speed)
}
KnightRunner.Platform.prototype.kill = function () {
this.alive = false
this.callAll('kill')
let sprites = []
this.forEach(function (tile) {
sprites.push(tile)
}, this)
sprites.forEach(function (tile) {
this.floorPool.add(tile)
}, this)
}
KnightRunner.Platform.prototype.addCoins = function (speed) {
let coinsY = 90 + Math.random() * 110
let hasCoin
this.forEach(function (tile) {
// 40% chance
hasCoin = Math.random() <= 0.01
if (hasCoin) {
let coin = this.coinsPool.getFirstExists(false)
if (!coin) {
coin = new Phaser.Sprite(this.game, tile.x, tile.y - coinsY, 'coin')
this.coinsPool.add(coin)
} else {
coin.reset(tile.x, tile.y - coinsY)
}
coin.body.velocity.x = speed
coin.body.allowGravity = false
}
}, this)
}
KnightRunner.Platform.prototype.addEnemy = function (speed) {
let hasEnemy
this.forEach(function (tile) {
// 10 % chance
hasEnemy = Math.random() <= 0.03
if (hasEnemy) {
let enemy = this.enemyPool.getFirstExists(false)
if (!enemy) {
enemy = new Phaser.Sprite(this.game, tile.x, tile.y - 30, 'skeletonWarrior', 'attack0001.png')
enemy.anchor.setTo(0.5, 0.5)
enemy.scale.x *= -1
enemy.animations.add('attacking', Phaser.Animation.generateFrameNames('attack', 1, 19, '.png', 4), 15, true, false)
enemy.animations.add('dying', Phaser.Animation.generateFrameNames('die', 1, 20, '.png', 4), 30, false, false)
enemy.animations.play('attacking')
this.enemyPool.add(enemy)
} else {
enemy.reset(tile.x, tile.y - 30)
enemy.animations.play('attacking')
}
this.game.physics.arcade.enable(enemy)
enemy.body.velocity.x = speed
enemy.body.allowGravity = false
}
}, this)
}