Basic for a platform game (mobile)

I would like to ask the community for help in this topic to create a code base for a platform game. I know that there is a Mario tutorial and case examples, but they seem very complicated for beginners.

  1. First. Create two sprites called base and add a simple (non-physical) collider.

  1. Create 3 simple buttons.

  1. Create a player sprite and insert a simple (non-physical) collider.

  1. Create a script called “move” in javascript. Add the following code.

cc.Class({
extends: cc.Component,
properties: {
buttonleft: cc.Button,
buttonright: cc.Button,
buttonjump: cc.Button,
player: cc.Node,
speed: 0,
speedj: 0,
movejumpv: false,
moveleftv: false,
moverightv: false,
gravidade: true,
estanoar: true
},
onLoad: function () {
this.buttonleft.node.on(cc.Node.EventType.TOUCH_START, this.moveleft, this);
this.buttonleft.node.on(cc.Node.EventType.TOUCH_END, this.moveleftend, this);
this.buttonright.node.on(cc.Node.EventType.TOUCH_START, this.moveright, this);
this.buttonright.node.on(cc.Node.EventType.TOUCH_END, this.moverightend, this);
this.buttonjump.node.on(cc.Node.EventType.TOUCH_START, this.movejump, this);
this.buttonjump.node.on(cc.Node.EventType.TOUCH_END, this.movejumpend, this);
cc.director.getCollisionManager().enabled = true;
cc.director.getCollisionManager().enabledDebugDraw = true;
cc.director.getCollisionManager().enabledDrawBoundingBox = true;
cc.director.getPhysicsManager().enabled = true;
},
onCollisionEnter: function (other) {
this.node.color = cc.Color.RED;
this.gravidade = false;
this.estanoar = false;
},
onCollisionStay: function (other) {
},
onCollisionExit: function (other) {
this.node.color = cc.Color.WHITE;
this.gravidade = true;
this.estanoar = true;
},
moveleft: function (buttonleft) {
this.moveleftv = true;
},
moveleftend: function (buttonleftend) {
this.moveleftv = false;
},
moveright: function (buttonright) {
this.moverightv = true;
},
moverightend: function (buttonrightend) {
this.moverightv = false;
},
movejump: function (buttonjump) {
if (!this.estanoar){
this.movejumpv = true;
}
},
movejumpend: function (buttonjumpend) {
this.movejumpv = false;
},
update (dt) {
if(this.gravidade){
this.node.y += -200 * dt;
}
if(this.moveleftv){
this.node.x += -200 * dt;
}
if(this.moverightv){
this.node.x += 200 * dt;
}
if(this.movejumpv){
var jumpUp = cc.moveBy(0.6, cc.v2(0, 40)).easing(cc.easeCubicActionOut());
var jumpDown = cc.moveBy(0.6, cc.v2(0, -38)).easing(cc.easeCubicActionIn());
var seq = cc.sequence(jumpUp, jumpDown);
this.node.runAction(jumpUp);
}
},
});

  1. Add the move script to the player. Drag the three buttons to the script window. Drag the player’s node as well.

  1. Play simulator.

Can someone help me with the code, so that it doesn’t cross obstacles.

Box collider only detect collision, add rigid body. You should study examples.

Thanks for the answer.

In the case examples, they use a common (non-physical) collider. Now I used “physics box collider” and “rigid body” on the player and the platform. SUCCESS! However, when the character moves, he loses his physics. I’m moving the character with the following code:

if(this.moveleftv){
this.node.x += -200 * dt;
}
if(this.moverightv){
this.node.x += 200 * dt;
}

I also tried the following code:

if(this.moveleftv){
this.node.setPosition(this.node.position.x-200dt,this.node.position.y);
}
if(this.moverightv){
this.node.setPosition(this.node.position.x+200
dt,this.node.position.y);
}

Even so, the player continues to lose physics.

In tutorial, you will find script to stop character when it touches platform.

I think you should follow tutorial step by step when you have no idea what is happening. It will save your time than waiting someone guides you.

I will try to follow the examples. But no example uses the “physics box collider”. They use the common collider.

In “new project” have project name “Example Collection”, create it. I have all basic features.

When using physics you should not move objects directly as node.x += or node.setPosition.
you should use some rigidbody functions as rigidbody.linearVelocity or rigidbody.addForce

It is complicated, to have something acceptable it has taken me a long time and look at many examples. In the end, I decided to go use the physics engine.
Anyway, here is my implementation without the physics engine:

first, create a new class to manage the colliders (sory many comments are in spanish, but you can use google translator)

/* Coded by Diego Toritto, feel free to make modifications and use it in your projects, 
   give credits if possible, if not, it doesn't matter.
 */

export enum HitOn {
    characterLeft = -1,      platformRight = -1,
    characterRight = 1,      platformLeft = 1,
    characterTop = 2,        platformBottom = 2,
    characterBottom = -2,    platformTop = -2,
    none = 0,
    tolerance = 10,
}

type PlatformColliderCallback = (hitOn: HitOn, exceeded: number, platform: any, character: any) => void;
type PlatformColliderExitCallback = (hittedOn: HitOn) => void;


/** LIMITATIONS:
 *      - If two platforms colliders are too close (<= characterAABB.width) you can get  
 *        unexpected behaviours.
 *      - Only works with rectangle colliders
*/ 
export class PlatformCollider  
{
    
    /** Tolerancia en eje Y en px. Cuando hay dos bloques pisos seguidos, y el character va corriendo, si no hay
     *  un poco de tolerancia, el personaje cae al vacio. Ejemplo: Mario corriendo no cae al vacio en agujeros estrechos), 
     */
    yTolerance: number = 2; // luego setearlo a 1 o 2

    private onHitCb: PlatformColliderCallback = null;
    private onExitCb: PlatformColliderCallback = null;
    private thisArgCb: any = null;

    /** Count how many colliders has touched the player at same, ex: the player is over two platforms */
    public touchingYCount: number = 0;
    /** Count how many colliders has touched the player at same, ex: the player is next to two platforms */
    public touchingXCount: number = 0;
    
    /** Set the callbacks
     * 
     * @param thisArg only pass `this`
     * @param onHitCb (hitOn: HitOn, exceeded: number) => void;
     * @param onExitCb (hittedOn: HitOn) => void;
     */
    setCallbacks (thisArg: any, onHitCb: PlatformColliderCallback, onExitCb: PlatformColliderExitCallback) : void
    {
        this.onHitCb = onHitCb;
        this.onExitCb = onExitCb;
        this.thisArgCb = thisArg;
    }

    /** Difference in px to set the sprite right to the platform left @example this.node.x += this.platformCollider.toPlatformLeft (platf, chara);*/
    toPlatformLeft (platf: any, chara: any) : number
    {
        return -1 * Math.abs (platf.world.aabb.xMin - chara.world.aabb.xMax);
    }
    /** Difference in px to set the sprite left to the platform right @example this.node.x += this.platformCollider.toPlatformRight (platf, chara);*/
    toPlatformRight (platf: any, chara: any) : number
    {
        return Math.abs (platf.world.aabb.xMax - chara.world.aabb.xMin);
    }    
    

    /** Call this from your onCollisionExit() */
    public onCollisionExit (platf: any, chara: any) : void
    {        
        // Si ha tocado esta plataforma
        if (platf.touchingY)
        {
            this.onExitCb.call (this.thisArgCb, platf.touching);
            this.touchingYCount--;
            platf.touchingY = false;
            platf.touching = HitOn.none;
        }

        // Si ha tocado esta plataforma
        else if (platf.touchingX) 
        {
            this.onExitCb.call (this.thisArgCb, platf.touching);
            this.touchingXCount--;
            platf.touchingX = false;       
            platf.touching = HitOn.none;
        }        
    }    

    /** Call this from your onCollisionEnter() */
    public onBoxColliderEnter (platf: any, chara: any) : void 
    {        
        let platfAabb = platf.world.aabb; let platfPreAabb = platf.world.preAabb.clone();
        let charaAabb = chara.world.aabb; let charaPreAabb = chara.world.preAabb.clone();

        // Sirve para ver, en el siguiente if, si hay una intersección, entonces quiere decir que hubo una colision en el eje Y
        charaPreAabb.y = charaAabb.y;
        platfPreAabb.y = platfAabb.y;   
       
        // Character in vertical collision.
        if (cc.Intersection.rectRect (charaPreAabb, platfPreAabb)) 
        {
            this.onVerticalCollision (platf, chara);
            return;
        }

        // Sirve para ver, en el siguiente if, si hay una intersección, entonces quiere decir que hubo una colision en el eje X        
        charaPreAabb.x = charaAabb.x; 
        platfPreAabb.x = platfAabb.x;
        
        // Character in horizontal collision
        if (cc.Intersection.rectRect (charaPreAabb, platfPreAabb))
        {
            // Este se llama acá para tener una tolerancia en el eje Y
            if (charaAabb.yMin > platfAabb.yMax - Math.min (this.yTolerance, platf.world.aabb.height - 1))
            {
                this.onVerticalCollision (platf, chara, true); 
                return;
            }

            this.onHorizontalCollision (platf, chara);
            return;
        }
        

    }

    private onHorizontalCollision (platf: any, chara: any) : void 
    {
        let exceeded: number;
        let hitOn: HitOn;

        // si la posiscion pre es menor a la current OR character.parte_derecha == platforma.parte_izquierda
        if (chara.world.preAabb.x < chara.world.aabb.x || platf.world.aabb.xMin === chara.world.aabb.xMax)
        {
            exceeded = -1 * Math.abs (platf.world.aabb.xMin - chara.world.aabb.xMax);
            hitOn = HitOn.platformLeft; 
            
        }
        else
        {
            exceeded = Math.abs (platf.world.aabb.xMax - chara.world.aabb.xMin);                
            hitOn = HitOn.platformRight;
        }


        platf.touchingX = true;
        this.touchingXCount++;

        platf.touching = hitOn;
        this.onHitCb.call (this.thisArgCb, hitOn, exceeded, platf, chara);
    }


    private onVerticalCollision (platf: any, chara: any, hitInTolerance: boolean = false) : void 
    {
        let exceeded: number = 0;
        let hitOn: HitOn = HitOn.none;

        if (hitInTolerance)
        {
            hitOn = HitOn.tolerance;
            exceeded = Math.abs (platf.world.aabb.yMax - chara.world.aabb.yMin);
        }
        // Si el pre está más abajo que el current OR character_top === platform_bottom
        else if (chara.world.preAabb.y < chara.world.aabb.y || platf.world.aabb.yMin === chara.world.aabb.yMax)
        {
            exceeded = -1 * Math.abs (platf.world.aabb.yMin - chara.world.aabb.yMax); 
            hitOn = HitOn.platformBottom; 
        }
        else // suelo de platform
        {
            exceeded = Math.abs (platf.world.aabb.yMax - chara.world.aabb.yMin);
            hitOn = HitOn.platformTop;
        }


        platf.touchingY = true;
        this.touchingYCount++;

        platf.touching = hitOn;
        this.onHitCb.call (this.thisArgCb, hitOn, exceeded, platf, chara);
    }

}

Then, include this file in your player script, and use like this:
onLoad:

        this.platformCollider = new PlatformCollider();
        this.platformCollider.setCallbacks (this, this.onHitPlatform, this.onExitPlatform);

Calls from CC Collision callbacks:

    onCollisionExit (other, self)
    {
        this.platformCollider.onCollisionExit (other, self);
    }

    // onCollisionStay (other, self) { }


    onCollisionEnter (other, self)
    {
        this.platformCollider.onBoxColliderEnter (other, self);
    }   

Then, create two methods where you need to implement the logic

onHitPlatform (hitOn: HitOn, exceeded: number, platf: any, chara: any) : void;
onExitPlatform (hittedOn: HitOn);

For example, in your onHitPlatform():

        // Player hits the bottom part of the platform, with his foots (bottom part of his collider)
        if (hitOn === HitOn.platformTop && this.onGround === false) 
        {
            this.setGrounded (true); // your function to set the player grounded, or simply this.grounded = true
            this.jump.speed = 0; // Set the jump speed to zero, because the player must be in ground
            this.node.y += exceeded; // move the player to the platform limits
        } 

I hope it works for you, but I still recommend using the physics engine (check Martin youtube channel for a tutorial: https://www.youtube.com/channel/UCe8Bfxd_3EKfvO24xbC3P3w)

1 Like