Simonnice

Help with increasing level speed of an infinite runner game

Recommended Posts

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)
}

 

Share this post


Link to post
Share on other sites

It would be nice to know how you did it if it's not over 9000 lines of code.

Anyways it is great that you've figured it out youself - very good for the learning experience.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.