跳至正文

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(); } }, });
标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注