Phaser 3 & React + some game logic questions


#1

Hello there,
I’m a totally newbie if it comes to Phaser and I have two main questions about it (we’re talking about Phaser 3 here).
I’m gonna provide a little bit of context: so I’m creating a game, where math equations are falling from the top of the canvas, on the bottom there are spikes, if math equation reaches the bottom it’s being destroyed and a life is taken from the player. The player has to provide answers for the lowest math equation on the screen, before it gets to the spikes.

I’ve setted up codesandbox with what I’ve got now: https://codesandbox.io/s/2woopqx1vy

I want to embed this game inside React project, so my first question is how to properly use it with React, because I want to trigger actions on impact with spikes etc, that will update score, lives and push it to the backend after the game is finished.

The second question is: how to get always the lowest equation on screen (closest to the spikes) in a way that user can put answer in an input and it’ll be checked with the lowest equation (node?). For now I have only falling circles. I want to somehow get always the lowest circle visible and check if the string equation that it has is answered correctly, if yes the score should be increased and equation removed from the screen.

Any help will be much much appreciated.


#2

First Question

I would extends the Phaser.Game class and pass the Component as reference.

// phaserGame.js
import * as Phaser from 'phaser'
import MainScene from './scenes/mainScene'

class Game extends Phaser.Game {
  constructor(react) {
    const config = {
      backgroundColor: '#000000',
      width: 400,
      height: 300,
      scene: [MainScene]
    }
    super(config)
    this.react = react
  }
}

export default Game

In your MathGameContainer Component create your game like so:

// mathGameContainer.js
import React, { Component, Fragment } from 'react'
import PhaserGame from './components/PhaserGame'

class MathGameContainer extends Component {
  state = {
    lives: 3
  }

  componentDidMount() {
    this.game = new PhaserGame(this)
  }

  render() {
    return (
      <Fragment>
        <p>{this.state.lives}</p>
        <div id="gameContainer" />
      </Fragment>
    )
  }
}

export default MathGameContainer

Then you can set the react state from inside your mainScene

// mainScene.js
import * as Phaser from 'phaser'

export default class MainScene extends Phaser.Scene {
  constructor() {
    super({ key: 'MainScene' })
  }

  preload() {
    console.log('preload')
    this.game.react.setState({ lives: 4 })
  }
}


#3

And to change the lives from within React add the code below to your MathGameContainer Component

  addLife() {
    this.game.scene.keys.MainScene.events.emit('addLife')
  }

  render() {
    return (
      ...
        <button onClick={this.addLife.bind(this)}>Add 1 Live</button>
      ...
    )
  }

and this in your scene:

    this.events.on('addLife', () => {
      this.game.react.setState(prevState => {
        return { lives: prevState.lives + 1 }
      })
    })

Hope it helps :slight_smile:
Worked great when I tested it.


#4

Thank you for your response @yannick :smile: I really appreciate the attitude :slight_smile: Yes, that is working! Do you maybe have any idea for the second question? How to always get the lowest falling equation on screen and check is result inputted by user is correct? :slight_smile:

Best regards!


#5

You are creating a new bubble and destroying if after it hits a spike.

You should create a phaser group, with maybe 3 - 4 bubbles. And instead of destroying it, reset its position.

This is more efficient and you can then simply iterate through the bubbles group bubbles.children.each(bubble=>{}) and get the y value of each bubble. The bubble with the highest y value is the lowest.


#6

So basically what you’re trying to say is to:

  1. create a few bubbles within a group
  2. iterate (where? within update method?) constantly over them to get the lowest?
  3. If it’s solved correctly I should reset position of that bubble to somewhere over the screen and load new equation to it?
  4. If it reached the spikes remove one life and do the same as above

Would you mind, if you of course have the time, creating a small snippet with only that lowest bubble logic within update() or smth? :slight_smile:
My main concerns here are:

  1. how to in a proper way generate “new” bubbles within a group (the case is they should also keep falling faster and more of them should appear during the time of the game)
  2. would it be okay to iterate all the time over those bubbles? Should I do it in update()?

#7

Yes, this is exactly what I had in mind :slight_smile:

Unfortunately, I am the whole day on the road.

Have you already look at the phaser3 examples?


#8

Yes, yes, I’ve went through many of them, but wasn’t able to find something similar, I’m gonna investigate more and ask questions if something will be unclear :slight_smile:

Thank you very much!


#9

I had some time to make it :slight_smile:

export default class MainScene extends Phaser.Scene {
  bubbles
  spikes

  constructor() {
    super({ key: 'MainScene' })
  }

  create() {
    const SPIKE_WIDTH = 50
    const SPIKE_HEIGHT = 45

    this.bubbles = this.add.group()
    for (let i = 0; i < 4; i++) {
      let circle = new Bubble(this, this.cameras.main.width * Math.random(), -50, 50, 0x00ff00, 1)
      circle.activate(i)
      this.bubbles.add(circle)
    }

    this.spikes = this.physics.add.staticGroup({
      key: 'spike',
      repeat: Math.round(this.cameras.main.width / SPIKE_WIDTH),
      setXY: { x: 30, y: this.cameras.main.height - SPIKE_HEIGHT, stepX: 50 },
      setScale: { x: 0.2, y: 0.2 }
    })

    this.physics.add.collider(this.bubbles, this.spikes, (bubble, spike) => {
      bubble.dies()
    })
  }

  update() {
    // this gets the lowest bubble
    let lowestBubble = this.bubbles.children.getArray().reduce((acc, curr) => {
      return acc.y > curr.y ? acc : curr
    })
  }
}

class Bubble extends Phaser.GameObjects.Arc {
  constructor(scene, x, y, radius, fillColor, fillAlpha) {
    super(scene, x, y, radius, 0, 360, false, fillColor, fillAlpha)
    scene.add.existing(this)
    scene.physics.add.existing(this)
    this.body.setAllowGravity(false)
  }
  activate(delay) {
    this.scene.time.addEvent({
      delay: delay * 1000,
      callback: () => {
        this.body.setAllowGravity(true)
      }
    })
  }
  dies() {
    this.body.setAllowGravity(false)
    this.body.velocity.set(0, 0)
    this.y = -50
    this.activate(2)
  }
}