CocosCreator物理引擎Demo源码分析(1)-infinite-world

infinite-world示例展示了小球顺着山坡凹凸做左右滚动的效果。

技术点

1、山坡由数量不等动态生成的的竖条状方块组成。
2、每个方块动态添加RigidBody组件和PolygonCollider组件,使小球和山坡产生物理碰撞效果。
3、摄像机根据山坡的凹凸高度做动态缩放。
4、通过键盘或触摸来控制小球的左右滚动。

源码分析

camera-control.js

该源文件功能是根据小球在屏幕上的位置高度来控制摄像机的缩放。

cc.Class({
    extends: cc.Component,

    properties: {
        target: {
            default: null,
            type: cc.Node
        }
    },

    // LIFE-CYCLE CALLBACKS:

    onLoad () {
        this.camera = this.getComponent(cc.Camera);
    },

    onEnable: function() {
        // 将物理系统的调试绘制信息附加到指定摄像机上。
        // 使用摄像机时,如果使用到了物理系统或碰撞系统等内置渲染节点的系统,
        // 那么需要将它们的渲染节点也添加摄像机上。
        cc.director.getPhysicsManager().attachDebugDrawToCamera(this.camera);
    },

    onDisable: function() {
        // 将物理系统的调试绘制信息从指定摄像机上移除
        cc.director.getPhysicsManager().attachDebugDrawFromCamera(this.camera);
    },

    lateUpdate: function(dt) {
        // 此例中,this.target指小球,即将小球中心点转换为世界空间坐标系
        let targetPos = this.target.convertToWorldSpaceAR(cc.Vec2.ZERO);
        // 再转换为游戏Scene的(局部)空间坐标系,并调整摄像机到相应位置
        this.node.position = this.node.parent.convertToNodeSpaceAR(targetPos);
        // ratio的值区间将为 0 < ratio < 1
        let ratio = targetPos.y / cc.winSize.height;
        // 如小球位于屏幕中部,则摄像机缩放比例不变(即保持1),如屏幕上半部则缩小,如屏幕下半部则放大。
        // 极端情况下,如小球位于屏幕顶部,则缩小25%,如果小球位于屏幕底部,则放大25%。
        this.camera.zoomRatio = 1 + (0.5 - ratio) * 0.5;
    },
    // update (dt) {},
});

ball-control.js

该源文件功能是根据输入事件控制小球的运动方向和速度。

const MOVE_LEFT = 1; // 向左移动标志位
const MOVE_RIGHT = 2; // 向右移动标志位

cc.Class({
    extends: cc.Component,

    properties: {
        maxSpeed: 1200
    },

    // LIFE-CYCLE CALLBACKS:

    onLoad () {
        // 注册键盘按下和释放事件的回调
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);

        // 注册触摸事件的回调
        var canvas = cc.find('/Canvas');
        canvas.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
        canvas.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);

        this.moveFlags = 0;
    },

    start () {
        // start 在 onLoad 之后,此时RigidBody组件已经被加载进来
        this.body = this.getComponent(cc.RigidBody);        
    },

    onKeyDown(event) {
        switch(event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
                this.moveFlags |= MOVE_LEFT; // 添加向左移动的标志位
                this.updateMotorSpeed();
                break;
            case cc.KEY.d:
            case cc.KEY.right:
                this.moveFlags |= MOVE_RIGHT; // 添加向右移动的标志位
                this.updateMotorSpeed();
                break;
        }
    },

    onKeyUp (event) {
        switch(event.keyCode) {
            case cc.KEY.a:
            case cc.KEY.left:
                this.moveFlags &= ~MOVE_LEFT; // 清除向左移动标志
                break;
            case cc.KEY.d:
            case cc.KEY.right:
                this.moveFlags &= ~MOVE_RIGHT; // 清除向右移动标志
                break;
        }
    },

    onTouchStart: function(event) {
        let touchLoc = event.touch.getLocation();
        if (touchLoc.x < cc.winSize.width/2) {
            this.moveFlags |= MOVE_LEFT; // 添加向左移动的标志位
        } else {
            this.moveFlags |= MOVE_RIGHT; // 添加向右移动的标志位
        }
        this.updateMotorSpeed();
    },

    onTouchEnd: function(event) {
        let touchLoc = event.touch.getLocation();
        if (touchLoc.x < cc.winSize.width/2) {
            this.moveFlags &= ~MOVE_LEFT; // 清除向左移动标志
        } else {
            this.moveFlags &= ~MOVE_RIGHT; // 清除向右移动标志
        }
    },

    updateMotorSpeed() {
        // 判断this.body是否可用
        if (!this.body) {
            return;
        }
        var desiredSpeed = 0;
        if ((this.moveFlags & MOVE_LEFT) == MOVE_LEFT) {
            desiredSpeed = -this.maxSpeed;
        } else if ((this.moveFlags & MOVE_RIGHT) == MOVE_RIGHT) {
            desiredSpeed = this.maxSpeed;
        }
        // 设置小球刚体角速度来控制小球的运动方向和速度
        this.body.angularVelocity = desiredSpeed;
    },

    update (dt) {
        // 判断标志位是否为空(避免在没有事件触发时也去改变小球运动)
        if (this.moveFlags) {
            this.updateMotorSpeed();
        }
    },
});

infinite-world.js

该源文件功能是随着小球方向动态生成N个高度不等的方块,从而组成凹凸不平的山坡,每个方块都动态添加了物理组件,每个方块宽度的粒度越小,则山坡越平滑。方块的颜色默认为青色,由物理引擎的调试绘制标志位决定。


cc.Class({
    extends: cc.Component,

    properties: {
        pixelStep: 10, // 每个矩形的宽度(N个矩形组成一个山坡)
        xOffset: 0, // 当前最新创建矩形的x坐标
        yOffset: 240, // 山坡的最低高度

        target: {
            default: null,
            type: cc.Node
        }
    },

    // LIFE-CYCLE CALLBACKS:

    onLoad: function () {

        this.hills = [];
        this.pools = [];

        while (this.xOffset < 1200) {
            this.generateHill(10);
        }
    },

    // 生成一个竖条状矩形
    generateHillPiece(xOffset, points) {
        let hills = this.hills;
        let first = hills[0];
        // 若小球离第一个块的距离超过1000,则不再创建新的node,直接复用原有数组的第一个元素
        if (first && ((this.target.x - first.node.x) > 1000)) {
            first.node.x = xOffset;
            first.collider.points = points;
            first.collider.apply();
            hills.push(hills.shift());
            return;
        }

        let node = new cc.Node();
        node.x = xOffset;

        let body = node.addComponent(cc.RigidBody);
        body.type = cc.RigidBodyType.Static;

        let collider = node.addComponent(cc.PhysicsPolygonCollider);
        collider.points = points;
        collider.friction = 1;

        node.parent = this.node;
        hills.push({node:node, collider:collider});
    },

    // 生成山坡
    // 每座山坡由N个竖条状矩形组成,
    // 每座山坡的绘制都分成2步:第1步绘制上坡,第2步绘制下坡
    generateHill () {
        let pixelStep = this.pixelStep;
        let xOffset = this.xOffset;
        let yOffset = this.yOffset;

        // 山坡宽度,值区间 120-640
        let hillWidth = 120 + Math.ceil(Math.random()*26)*20;
        // 计算山坡由多少个矩形组成
        let numberOfSlices = hillWidth / pixelStep;

        let j;
        let points = [];

        // first step
        let randomHeight;
        if (xOffset === 0) {
            randomHeight = 0;
        } else {
            // make sure yOffset < 600
            randomHeight = Math.min(Math.random() * hillWidth / 7.5, 600 - yOffset);
        }

        yOffset += randomHeight;

        for (j = 0; j < numberOfSlices/2; j++) {
            points.length = 0;
            points.push(cc.v2(0, 0));
            // 计算弧度
            let rad = Math.cos(2*Math.PI/numberOfSlices*j);
            points.push(cc.v2(0, yOffset-randomHeight*rad));
            rad = Math.cos(2*Math.PI/numberOfSlices*(j+1));
            points.push(cc.v2(pixelStep, yOffset-randomHeight*rad));
            points.push(cc.v2(pixelStep, 0));

            this.generateHillPiece(xOffset + j*pixelStep, points);
        }

        yOffset += randomHeight;

        // second step
        if (xOffset === 0) {
            randomHeight = 0;
        } else {
            // make sure yOffset>240
            randomHeight = Math.min(Math.random() * hillWidth / 5, yOffset - 240);
        }

        yOffset -= randomHeight;

        for (j = numberOfSlices/2; j < numberOfSlices; j++) {
            points.length = 0;
            points.push(cc.v2(0, 0));
            // 计算弧度
            let rad = Math.cos(2*Math.PI/numberOfSlices*j);
            points.push(cc.v2(0, yOffset-randomHeight*rad));
            rad = Math.cos(2*Math.PI/numberOfSlices*(j+1));
            points.push(cc.v2(pixelStep, yOffset-randomHeight*rad));
            points.push(cc.v2(pixelStep, 0));

            this.generateHillPiece(xOffset + j*pixelStep, points);
        }

        yOffset -= randomHeight;

        this.xOffset += hillWidth;
        this.yOffset = yOffset;

    },

    update: function (dt) {
        if (!this.target) 
            return;
        // 如果小球离x轴边界不足1200,则创建新的hill
        while ((this.target.x + 1200) > this.xOffset) {
           this.generateHill();
        }
    },
});

你可能感兴趣的:(物理引擎)