Circle colliders vs Box colliders

I’ve recently started my journey of learning about the phaser development process, so I am still new to it all. However, while trying to make a flappy bird clone, I encountered an issue with circle colliders. I’m unsure if it’s a bug or human error.

Whenever there is a collision when falling (using “gravity”), the circle collider tends to fall through the box collider. But it works completely fine whenever the circle collider collides on the left.

Here is an example of my problem
Flappy Issue

Here is how I’m adding the collisions

I can use a box collider, and it would be fine, but I prefer the circle collider because the bird is pretty much a circular shape which would make the gameplay more fun when you aren’t clipping pipes with the corners of the box collider. So please, any help is welcome

What happens if you use only

this.physics.add.collider(this.bird.bird, pipe.pipeGroup);

?

Same thing happens sadly.

@samme Do you have any other solutions? I’m still not finding the problem and I’m starting to think its a bug…

I’m not sure. If you post the whole scene code I can take a look.

@samme Here is my main scene. Thanks for helping me out btw!

import Phaser from 'phaser'

import Bird from '../../PlayScene-classes/Bird'
import PipeManager from '../../PlayScene-classes/PipeManager'
import Backdrop from "../../PlayScene-classes/Backdrop";
import Floor from "../../PlayScene-classes/Floor";
import CloudManager from "../../PlayScene-classes/CloudManager";
import UIManager from "../../PlayScene-classes/UIManager";

import eventsCenter from "../../PlayScene-classes/EventsCenter";
import {Difficulty} from "../../enums/States";

const States = {
    IDLE: 0,
    RUNNING: 1,
    DEAD: 2,
}

/**
 * @classdesc - This manages/updates/creates/etc. every object and manager in the game.
 * This class is vital to the game and without it, the game wouldn't run. This class is a child of the Phaser.Scene
 * class which allows for overriding methods such as preload, create, and update. It also allows for easy access of different
 * Phaser objects in the game since it inherits them from the Phaser.Scene class.
 * @class
 */
class PlayScene extends Phaser.Scene {
    /**
     * Setting up the default values for the class variables
     */
    constructor() {
        super('PlayScene')
        this.start = false
        this.inputDown = false
        this.currentState = States.IDLE
        this.deathDelayStart = null
        this.velocity = 200
    }
    
    init(data) {
        this.difficulty = data.difficulty
    }
    
    /**
     * This function is called before anything is drawn on the canvas.
     * This allows for asset preloading which prevents anything from drawing without
     * it first loading the asset.
     */
    preload() {
        if (this.difficulty === Difficulty.EASY) {
            this.velocity = 170
            this.pipeManager = new PipeManager(this, {pipeCount: 12, velocityX: -this.velocity, finalHorizontalOffset: 200, verticalOffset: 220, difficulty: this.difficulty})
        } else if (this.difficulty === Difficulty.MEDIUM) {
            this.velocity = 200
            this.pipeManager = new PipeManager(this, {pipeCount: 12, velocityX: -this.velocity, finalHorizontalOffset: 250, verticalOffset: 190, difficulty: this.difficulty})
        } else if (this.difficulty === Difficulty.HARD) {
            this.velocity = 200
            this.pipeManager = new PipeManager(this, {pipeCount: 12, velocityX: -this.velocity, finalHorizontalOffset: 300, horizontalOffset: 350, verticalOffset: 180, difficulty: this.difficulty})
        } else {
            this.velocity = 300
            this.pipeManager = new PipeManager(this, {pipeCount: 12, velocityX: -this.velocity, finalHorizontalOffset: 270, horizontalOffset: 300, verticalOffset: 200, difficulty: this.difficulty})
        }
        // object instantiation
        this.bird = new Bird(this)
        this.floor = new Floor(this, {})
        this.cityscape = new Backdrop(this)
        this.cityscape.velocity = this.velocity
        this.cloudManager = new CloudManager(this)
        this.uiManager = new UIManager(this)
        
        // preload calls
        this.bird.preload()
        this.pipeManager.preload()
        this.floor.preload()
        this.cityscape.preload()
        this.cloudManager.preload()
        this.uiManager.preload()
        
    }
    
    /**
     * This method is used to call all the appropriate create methods from all the needed managers and objects
     */
    create() {
        this.floor.create()
        this.updateVelocity()
        
        // bird setup
        this.bird.bottomBoundry = this.game.canvas.height - this.floor.getHeight()
        this.bird.create()
        
        // PipeManager setup
        this.pipeManager.create()
        
        // Background setup
        this.cityscape.create()
        this.cityscape.setPosition(0, this.game.canvas.height - this.floor.getHeight())
        
        // CloudManager setup
        this.cloudManager.verticalOffset = this.floor.getHeight() + this.cityscape.buildings.displayHeight
        this.cloudManager.create()
        
        
        // UIManager setup
        this.uiManager.create()
        
        // listens for the death event
        window.addEventListener('death', () => {
            this.handleDeath()
        })
        
        // input event listeners
        this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE)
        this.upKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP)
        this.input.keyboard.on('keyup-SPACE', () => {
            this.onSpaceUp()
        })
        this.input.keyboard.on('keyup-UP', () => {
            this.onSpaceUp()
        })
        
        // sets up collision checking
        this.handleCollisions()
    
        /** dev only */
        // this.bird.enableGodMode()
        
        // Hide the loading overlay
        eventsCenter.emit('loaded')
    }
    
    /**
     * Called whenever the space key is pressed and handles making the bird jump and starting the game
     */
    onSpaceDown() {
        console.log('space down')
        this.bird.onJump()
        if (this.currentState === States.IDLE) {
            this.startGame()
        }
    }
    
    /**
     * Called whenever the space key is released and calls onJumpRelease() from the bird object.
     */
    onSpaceUp() {
        this.bird.onJumpRelease()
        this.inputDown = false
    }
    
    /**
     * This will update the velocity to the velocity class variable for every object that needs to be updated.
     */
    updateVelocity() {
        this.floor.velocity = this.velocity
        this.pipeManager.setVelocityX(-this.velocity)
    }
    
    /**
     * This will check if the spacebar key is currently being pressed down. If true, it will call onSpaceDown()
     * and will set spaceDown to true, so it won't call onSpaceDown() again until the spacebar has been released.
     */
    handleInputDown() {
        if (!this.inputDown && (this.spaceKey.isDown || this.upKey.isDown)) {
            this.inputDown = true
            this.onSpaceDown()
        }
    }
    
    /**
     * The update method is called every frame and is used here to call all the update methods from all the
     * managers and objects that need it. It also determines when the game restarts after death and handles
     * the jumping input checking.
     * @param time - the time passed in milliseconds since the game started.
     * @param delta - the time in-between each frame.
     */
    update(time,delta) {
        this.handleInputDown()
        // calling update methods
        this.bird.update(time, delta)
        this.pipeManager.update(time, delta)
        this.cityscape.update()
        this.floor.update()
        this.cloudManager.update()
        
        if (this.currentState === States.DEAD) {
            if (!this.deathDelayStart)
                this.deathDelayStart = time
            if (time - this.deathDelayStart >= 1000) {
                this.deathDelayStart = null
                this.restart()
            }
        }
        
    }
    
    /**
     * Resetting everything that needs to be reset. Will basically set up everything back to
     * how they all started to complete the game loop.
     */
    restart() {
        this.currentState = States.IDLE
        this.start = false
        this.tweens.killAll()
        this.bird.resetG()
        this.pipeManager.resetG()
        this.cityscape.resetG()
        this.floor.resetG()
        this.cloudManager.resetG()
        this.uiManager.resetG()
    }
    
    /**
     * Starting the game by calling all the start methods needed
     */
    startGame() {
        this.currentState = States.RUNNING
        this.start = true
        this.pipeManager.startG()
        this.cityscape.startG()
        this.floor.startG()
        this.cloudManager.startG()
        this.uiManager.startG()
        
        /**
         * DEV ONLY - checking to ensure no new objects are being created and that the game won't get clouded with unmanaged objects
         */
        console.log(this.children.list.length)
    }
    
    /**
     * this method is only called once and will iterate through every pipe inside the pipe manager, and
     * will assign a collision between the pipe and the bird. This also will assign an overlap event and
     * callback to the bird and the checkpoint to give the player points
     */
    handleCollisions() {
        this.pipeManager.pipes.map(pipe => {
            this.physics.add.collider(this.bird.bird, pipe.pipeGroup, (e) => {
                window.dispatchEvent(new Event('death'))
            }, null, this)
            this.physics.add.overlap(this.bird.bird, pipe.checkpoint, () => {
                pipe.handleCheckpointOverlap()
            })
        })
    }
    
    /**
     * Called whenever the bird collides with the ground or a pipe. This will stop or slow all moving game-objects
     * to give the illusion of movement. And changes the game state.
     */
    handleDeath() {
        this.currentState = States.DEAD
        this.pipeManager.stopG()
        this.cityscape.stopG()
        this.floor.stopG()
        this.cloudManager.stopG()
        this.uiManager.stopG()
    }
}

export default PlayScene;

Lmk if you need more.

I guess I’ll need the whole project so I can run it, if that’s possible.

Yeah sure give me like 30 mins I’ll be back online to post it.

@samme look at your messages.
For anyone else with this issue I’ll post the solution here whenever it’s found.

Ok, so basically, thanks to @samme, what I did to fix this problem was set the overlapBias in the game config, physics section. In my understanding, the higher the value, the better it will be. However, I think that there are some drawbacks to setting it too high, so I would be careful with it. Also If anyone would like to reply and explain how it’s working, I would really appreciate it because I’m still not too sure what this property is doing.

Here is the documentation I found for it:


Here is my implementation
image

Its default value is 4, so based on your game, I would suggest testing and playing around with the value until you feel it’s right.

Also, after I set the value, in my case, the collider was jittering up and down whenever on a pipe after setting this value. So if anyone knows a fix or workaround for this, let me know.

When resolving collisions Phaser separates the two bodies only if

overlap < v1 + v2 + bias

I think in your case you had to increase the bias because of the very high gravity and because the pipe body positions were being tweened vertically. Tweening body positions can cause overlaps with zero velocity (making the right side of the inequality smaller), which can cause separation failures.

I think this is just the bird bouncing up and down on the pipe very quickly because of the high gravity. You could set bounce to 0 after the collision.

1 Like