【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)

最后一个寒假说没就没。。。


话说我等Creator 的物理引擎等了好久好久,终于。。。。。。还是没等到。。。。。。

我们今天就用碰撞检测系统和一些算法简单的模拟一下2D平台动作的物理特性吧


先来看一下我们的主角
【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第1张图片

是不是很可爱啊

所以我们要给她起一个可爱的名字,

就叫她大马猴吧。。。

水平移动:

我们先来实现一下按键控制大马猴左右移动的效果

用渲染节点里的单色Sprite(后面的平台也用这个)制作一个地面平台,把大马猴放在地面上
【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第2张图片

新建MonkeyControl脚本,挂载在大马猴节点上

MonkeyControl.js

cc.Class({
    extends: cc.Component,

    properties: {
        speed: 0,
        maxSpeed:0,
        acce: 0,
        direction: 0
    },

    onLoad: function () {
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
    },

    onDestroy() {
        cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
    },

    onKeyDown(event) {
        switch (event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
                this.direction = -1;
                break;
            case cc.KEY.d:
            case cc.KEY.right:
                this.direction = 1;
                break;
        }
    },

    onKeyUp(event) {
        switch (event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
            case cc.KEY.d:
            case cc.KEY.right:
                this.direction = 0;
                break;
        }
    },

    update: function (dt) {
        if (this.direction === 0) {
            if (this.speed > 0) {
                this.speed -= this.acce * dt;
                if (this.speed <= 0) this.speed = 0;
            }
            else if (this.speed < 0) {
                this.speed += this.acce * dt;
                if (this.speed >= 0) this.speed = 0;
            }
        }
        else {
            this.speed += (this.direction > 0 ? 1 : -1) * this.acce * dt;
            if (Math.abs(this.speed) > this.maxSpeed) {
                this.speed = this.speed > 0 ? this.maxSpeed : -this.maxSpeed;
            }
        }

        this.node.x += this.speed * dt;
    },
});

我们在大马猴的控制脚本里加了四个属性
speed:x轴的速度
maxSpeed:x轴的最大速度,避免大马猴越跑越快
acce:x轴加速度
direction:按键控制方向,-1:左(按下左键),1:右(按下右键),0:不动(松开按键)

通常对物体运动的位置计算会放在update函数里,核心逻辑就是:位移=速度x时间,速度=加速度x时间

update这个函数是一个循环周期函数,每隔一个固定的时间(dt)就会调用一次

这里的if,else部分就是为了得出本次update的速度,然后根据速度乘时间更新本次大马猴的位置

如果感觉不好理解,是时候拿出高中物理课本复习一遍了

设置一下参数:

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第3张图片

效果如下:

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第4张图片

跳跃运动

突然脑子里响起了广播体操的音乐。。。

修改MonkeyControl.js

cc.Class({
    extends: cc.Component,

    properties: {
        speed: cc.v2(0, 0),
        maxSpeed: cc.v2(0, 0),
        acce: 0,
        direction: 0,
        jumpSpeed:0,
        gravity:0,
        groundY: 0
    },

    onLoad: function () {
        this.groundY = this.node.y;
        this.jumping = false;
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
    },

    onDestroy() {
        cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
    },

    onKeyDown(event) {
        switch (event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
                this.direction = -1;
                break;
            case cc.KEY.d:
            case cc.KEY.right:
                this.direction = 1;
                break;
            case cc.KEY.w:
            case cc.KEY.up:
                if(!this.jumping){
                    this.jumping = true;
                    this.speed.y = this.jumpSpeed;
                }

        }
    },

    onKeyUp(event) {
        switch (event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
            case cc.KEY.d:
            case cc.KEY.right:
                this.direction = 0;
                break;
        }
    },

    update: function (dt) {
        if (this.jumping) {
            this.speed.y += this.gravity * dt;
            if (Math.abs(this.speed.y) > this.maxSpeed.y) {
                this.speed.y = this.speed.y > 0 ? this.maxSpeed.y : -this.maxSpeed.y;
            }
        }

        if (this.direction === 0) {
            if (this.speed.x > 0) {
                this.speed.x -= this.acce * dt;
                if (this.speed.x <= 0) this.speed.x = 0;
            }
            else if (this.speed.x < 0) {
                this.speed.x += this.acce * dt;
                if (this.speed.x >= 0) this.speed.x = 0;
            }
        }
        else {
            this.speed.x += (this.direction > 0 ? 1 : -1) * this.acce * dt;
            if (Math.abs(this.speed.x) > this.maxSpeed.x) {
                this.speed.x = this.speed.x > 0 ? this.maxSpeed.x : -this.maxSpeed.x;
            }
        }

        this.node.x += this.speed.x * dt;
        this.node.y += this.speed.y * dt;
        if(this.node.y <= this.groundY){
            this.node.y = this.groundY;
            this.jumping = false;
        }
    },
});

这里我们给大马猴又加了几个属性

jumpSpeed:起跳速度
gravity:重力
groundY:大马猴落到地面时的Y轴位置
同时把speed和maxSpeed都变成了二维的

测试参数:
【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第5张图片

效果如下:
【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第6张图片

利用碰撞检测实现平台动作

首先我们再添加一个平台节点,然后给地面,平台和大马猴都添加碰撞组件

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第7张图片

修改MonkeyControl.js

cc.Class({
    extends: cc.Component,

    properties: {
        speed: cc.v2(0, 0),
        maxSpeed: cc.v2(0, 0),
        acce: 0,
        direction: 0,
        jumpSpeed: 0,
        gravity: 0,
    },

    onLoad: function () {
        cc.director.getCollisionManager().enabled = true;
        cc.director.getCollisionManager().enabledDebugDraw = true;
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);

        this.jumping = false;

        this.collisionX = 0;//x轴是否碰撞,0:没有碰撞,-1:左方有碰撞,1:右方有碰撞
        this.collisionY = 0;

        this.touchingNumber = 0;//同时碰撞物体的个数
    },

    onDestroy() {
        cc.director.getCollisionManager().enabled = false;
        cc.director.getCollisionManager().enabledDebugDraw = false;
        cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
    },

    onKeyDown(event) {
        switch (event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
                this.direction = -1;
                break;
            case cc.KEY.d:
            case cc.KEY.right:
                this.direction = 1;
                break;
            case cc.KEY.w:
            case cc.KEY.up:
                if (!this.jumping) {
                    this.jumping = true;
                    this.speed.y = this.jumpSpeed;
                }

        }
    },

    onKeyUp(event) {
        switch (event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
            case cc.KEY.d:
            case cc.KEY.right:
                this.direction = 0;
                break;
        }
    },

    onCollisionEnter: function (other, self) {

        this.node.color = cc.Color.RED;

        this.touchingNumber++;

        // 1st step 
        // get pre aabb, go back before collision
        var otherAabb = other.world.aabb;
        var otherPreAabb = other.world.preAabb.clone();

        var selfAabb = self.world.aabb;
        var selfPreAabb = self.world.preAabb.clone();

        // 2nd step
        // forward x-axis, check whether collision on x-axis
        selfPreAabb.x = selfAabb.x;
        otherPreAabb.x = otherAabb.x;

        if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) {
            if (this.speed.x < 0 && (selfPreAabb.xMax > otherPreAabb.xMax)) {
                this.node.x = otherPreAabb.xMax - this.node.parent.x;
                this.collisionX = -1;
            }
            else if (this.speed.x > 0 && (selfPreAabb.xMin < otherPreAabb.xMin)) {
                this.node.x = otherPreAabb.xMin - selfPreAabb.width - this.node.parent.x;
                this.collisionX = 1;
            }

            this.speed.x = 0;
            other.touchingX = true;
            return;
        }

        // 3rd step
        // forward y-axis, check whether collision on y-axis
        selfPreAabb.y = selfAabb.y;
        otherPreAabb.y = otherAabb.y;

        if (cc.Intersection.rectRect(selfPreAabb, otherPreAabb)) {
            if (this.speed.y < 0 && (selfPreAabb.yMax > otherPreAabb.yMax)) {
                this.node.y = otherPreAabb.yMax - this.node.parent.y;
                this.jumping = false;
                this.collisionY = -1;
            }
            else if (this.speed.y > 0 && (selfPreAabb.yMin < otherPreAabb.yMin)) {
                this.node.y = otherPreAabb.yMin - selfPreAabb.height - this.node.parent.y;
                this.collisionY = 1;
            }

            this.speed.y = 0;
            other.touchingY = true;
        }

    },

    onCollisionExit: function (other) {
        this.touchingNumber--;
        if (this.touchingNumber === 0) {
            this.node.color = cc.Color.WHITE;
        }

        if (other.touchingX) {
            this.collisionX = 0;
            other.touchingX = false;
        }
        else if (other.touchingY) {
            other.touchingY = false;
            this.collisionY = 0;
            this.jumping = true;
        }
    },

    update: function (dt) {
        if (this.collisionY === 0) {//如果Y轴没有碰撞
            this.speed.y += this.gravity * dt;
            if (Math.abs(this.speed.y) > this.maxSpeed.y) {
                this.speed.y = this.speed.y > 0 ? this.maxSpeed.y : -this.maxSpeed.y;
            }
        }

        if (this.direction === 0) {
            if (this.speed.x > 0) {
                this.speed.x -= this.acce * dt;
                if (this.speed.x <= 0) this.speed.x = 0;
            }
            else if (this.speed.x < 0) {
                this.speed.x += this.acce * dt;
                if (this.speed.x >= 0) this.speed.x = 0;
            }
        }
        else {
            this.speed.x += (this.direction > 0 ? 1 : -1) * this.acce * dt;
            if (Math.abs(this.speed.x) > this.maxSpeed.x) {
                this.speed.x = this.speed.x > 0 ? this.maxSpeed.x : -this.maxSpeed.x;
            }
        }

        if (this.speed.x * this.collisionX > 0) {
            this.speed.x = 0;
        }

        this.node.x += this.speed.x * dt;
        this.node.y += this.speed.y * dt;
    },
});

碰撞检测系统有三个检测碰撞的函数,分别是:

onCollisionEnter(other,self):碰撞体刚碰撞时触发

onCollisionStay(other,self):碰撞体持续碰撞时触发

onCollisionExit(other):碰撞体分开时触发

他们传入的参数other和self指的是什么呢?

指的就是他们的碰撞组件

比如我们在onCollisionEnter函数里写上这样两行代码

other.node.color = cc.Color.RED;
self.node.color = cc.Color.GREEN;

就可以实现让两个碰撞组件的节点变色的效果

那碰撞组件(这里用的是cc.BoxCollider)里还有什么属性方法呢?

通常我们遇到这种问题时,第一反应就是去查一下api,可是人生充满意外,Creator的api并不全,很多东西都没有

这里我给新手介绍一个查看属性的方法

我们这次不用模拟器,选择浏览器(谷歌浏览器)运行,按下F12启动调试窗口,然后在onCollisionEnter函数的第一行代码旁边单击设置一个断点,然后刷新浏览器

这样我们的程序就会运行到断点的位置,如下图

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第8张图片

这时就可以进行相应的变量查看、测试

在控制台输入other,敲回车,就可以得到other组件的全部属性方法,真的是全部哦,一点都不会少(有时候函数名忘了,不查api,通过这种方式获得提示也是一种方法)

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第9张图片

这里我们用到的属性主要是world里的几个
aabb:axis-aligned bounding box,轴对齐包围盒,也就是碰撞检测的区域
aabb.x、aabb.y:碰撞盒的位置,矩形的中心位置
aabb.xMin、aabb.xMax、aabb.yMin、aabb.yMax:碰撞盒的边缘位置,矩形的四个边

preAabb的属性跟aabb相同,代表着碰撞盒的上一次位置,我们利用这个”上一次位置”就可以实现“前面有障碍物,大马猴走不过去”的效果了

简单的思路就是:如果发生了不可移动的碰撞(障碍物),就退回到“上一次位置”

现在我们再来看MonkeyContro.js的代码

onCollisionEnter里我们做的事情就是保存上一次计算碰撞组件的位置,如果发生碰撞了就返回上一次位置,分别进行了x轴检测和y轴检测,如果想加深理解,可以尝试自己画个图

算了,我知道你也不会画

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第10张图片

上图就是X轴碰撞的情况,Y轴碰撞的情况类似

最后来看一下效果吧

【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)_第11张图片

跟随平台移动

镜头跟随

添加触摸按键

虚拟摇杆

找个天气好的日子再写

你可能感兴趣的:(Cocos,Creator实战教程)