Cocos Creator:奇妙的物理世界_物理切割

前言

游戏开发中,物理系统的试用越来越多,导致试用花样也是越来越多,只有你想不到的,没有做不到的。前段时间自己在其他地方看见了一个比较好玩的物理切割类的游戏,出于好奇自己也实验了一把。其实游戏已经上线小游戏平台(微信,抖音,今日头条,搜索《切片小达人》)一周多了,一直没时间整理文章,今天终于可以整理一下了。

效果展示

体验路径

微信小游戏搜索:切片小达人

抖音或者今日头条搜索:切片小达人

视频连接:哔哩哔哩 或者浏览器输入(https://www.bilibili.com/video/bv1QK411N7Ae)

正文

1.demo效果展示

2.文档资料

在这里给大家推荐两篇文章,有兴趣的朋友可以看一下,

CocosCreator之KUOKUO带你做物理切割https://blog.csdn.net/kuokuo666/article/details/104411083

多边形切割https://blog.csdn.net/zengyun_cool_2008/article/details/80745234

3.切割

切割:字面意思理解,将一个平面分成几个部分。运用到游戏中就是将处于两点(起点和终点)之间连线上的物体,以连线作为分割线,分割成多个部分。

要达到切割,首先要知道几个数据:

  • 1.切割线的起点和终点
  • 2.多边形物体的边缘坐标点

有了上边两个数据,我们可以算出:

  • 1.切割线是否与多边形物体有交点
  • 2.交点的坐标
  • 3.是否满足切割条件
  • 4.切割后新的坐标点

如果仅仅是切割,做到上边的这几点差不多就可以了。但是,咱们今天要说的是物理切割,也就是满足物理效果的切割。

3.1 creator 物理系统

文档:物理与碰撞系统http://docs.cocos.com/creator/manual/zh/physics/

  • 开启物理系统:
物理系统默认是关闭的,如果需要开启物理系统需要手动通过代码实现
onLoad() {
        var manager = cc.director.getCollisionManager();
        manager.enabled = true;
        manager.enabledDebugDraw = true;
        manager.enabledDrawBoundingBox = true;
        cc.director.getPhysicsManager().enabled = true;
    }
  • 给需要切割的物体绑定多边形刚体组件

3.2 物理切割

  • 检测起点和终点 绘制切割线
start() {
    this.initEvent();
}
initEvent(): void {
    this.node.on(cc.Node.EventType.TOUCH_START, this.uiNodeStartEvent, this);
    this.node.on(cc.Node.EventType.TOUCH_MOVE, this.uiNodeMoveEvent, this);
    this.node.on(cc.Node.EventType.TOUCH_END, this.uiNodeEndEvent, this);
    this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.uiNodeEndEvent, this);
}

uiNodeStartEvent(e: cc.Event.EventTouch): void {
    this.touchStartLocal = null;
    this.touchMoveLocal = null;
    this.touchStartLocal = this.node.convertToNodeSpaceAR(e.getLocation());
    //初始化切割线的位置和长度
    this.line.setPosition(this.touchStartLocal);
    this.line.width = 0;

}
//
uiNodeMoveEvent(e: cc.Event.EventTouch): void {
    if (!this.touchStartLocal) return;
    console.log("移动");
    this.touchMoveLocal = this.node.convertToNodeSpaceAR(e.getLocation());
    //计算切割线旋转角度 和长度
    this.line.angle = -this.rotationTarget(this.touchStartLocal, this.touchMoveLocal) + 90;
    this.line.width = Math.abs(this.line.getPosition().sub(this.touchMoveLocal).mag());
}

//
uiNodeEndEvent(e: cc.Event.EventTouch): void {
    if (!this.touchStartLocal) return;
    let touchEndLocal: cc.Vec2 = this.node.convertToNodeSpaceAR(e.getLocation());
    let touchStartLocal: cc.Vec2 = this.node.convertToNodeSpaceAR(e.getStartLocation());
    this.line.width = 0;
}
  • 检测切割

检测切割的步骤:

      cc.RayCastType.Any
      检测射线路径上任意的碰撞体,一旦检测到任何碰撞体,将立刻结束检测其他的碰撞体,最快。

      cc.RayCastType.Closest
      检测射线路径上最近的碰撞体,这是射线检测的默认值,稍慢。

      cc.RayCastType.All
      检测射线路径上的所有碰撞体,检测到的结果顺序不是固定的。在这种检测类型下,一个碰撞体可能会返回多个结果,这是因为 box2d 是通过检测夹具(fixture)来进行物体检测的,而一个碰撞体中可能由多个夹具(fixture)组成的,慢。更多细节可到 物理碰撞组件 查看。

      cc.RayCastType.AllClosest
      检测射线路径上所有碰撞体,但是会对返回值进行删选,只返回每一个碰撞体距离射线起始点最近的那个点的相关信息,最慢。
  • 2.碰撞体筛选

    因为咱们在上一步检测的是所有的刚体,而在这种检测类型下,一个碰撞体可能有多个返回结果,所以需要进行筛选,筛选的目的在于确定这个碰撞体与射线相交(其实大家也可以是试用AllClosest类型,这样就用进行筛选了)

recalcResults(type: number, touchStartLocal: cc.Vec2, touchEndLocal: cc.Vec2): void {
    let colliderArr = [];
    //获取所有刚体
    let result = cc.director.getPhysicsManager().rayCast(touchStartLocal, touchEndLocal, cc.RayCastType.All);
    for (let i = 0; i < result.length; i++) {
        #遍历每一个结果,筛选出之前没有出现的碰撞体,并且tag介于1-99直接的刚体(水果)
        let collider: any = result[i].collider;
        if (collider.tag > 0 && collider.tag < 100) {   //切割的是水果  并且切割的collider 没有切割
            let isExist: boolean = false;
            for (let j = 0; j < colliderArr.length; j++) {
                if (collider == colliderArr[j]) {
                    isExist = true;
                    break;
                }
            }
            if (!isExist) {
                colliderArr.push(collider);
                //进行切割
                let cutState = this.checkCutPolygon(type, touchStartLocal, touchEndLocal, collider);
            }
        }

    }
}
  • 3.切割
/**
 * @param touchStartLocal   起点坐标 
 * @param touchEndLocal   终点坐标
 * @param collider     将要切割的刚体
 */
private checkCutPolygon(type, touchStartLocal, touchEndLocal, collider): boolean {
    let body = collider.body;
    let points = collider.points;
    let tag: number = collider.tag;
    // 转化为本地坐标
    let localPoint1 = cc.Vec2.ZERO;
    let localPoint2 = cc.Vec2.ZERO;
    body.getLocalPoint(touchStartLocal, localPoint1);
    body.getLocalPoint(touchEndLocal, localPoint2);

    let isCut: boolean = false;
    let splitResults = [];
    let intersectPoint = [];
    //线段切割刚体
    this.lineCutPolygon(localPoint1, localPoint2, points, splitResults, intersectPoint);
    if (splitResults.length <= 0) {
        return false;
    }

    //回收本体
    collider.node.destroy();
    #克隆
    for (let j = 0; j < splitResults.length; j++) {
        let splitResult = splitResults[j];
        if (splitResult.length < 3) continue;
        this.cloneNode(collider.node, splitResult);
    }

}

至于详细的两点所连线段对多边形的切割,大家可以看gitee中的demo,或者看上边提供的多边形切割连接

  • 4.克隆

物体的切割克隆,实际上就是相同的物体,只不过自身的边缘点不同而已

cloneNode(node: cc.Node, splitResult: Array<any>): boolean {
    //更具节点名字在对象池中获取节点
    let isOk: boolean = false;
    let fruitNode: cc.Node = cc.instantiate(this.fruit);
    fruitNode.position = node.position;
    fruitNode.angle = node.angle;
    fruitNode.name = node.name;
    try {
        this.node.addChild(fruitNode);
        let collider = fruitNode.getComponent(cc.PhysicsPolygonCollider);
        fruitNode.getComponent(cc.PhysicsPolygonCollider).friction = 0.01;
        collider.points = splitResult;
        collider.apply();
        fruitNode.getComponent(Fruit).draw();
        isOk = true;
    } catch (error) {
        console.log("出现异常--,克隆", error);
        fruitNode.destroy();
        isOk = false;
    }
    return isOk;
}
  • 5.遮罩

知道了物体切割后的自身碰撞的边缘点,那么只需要对每个新物体更具碰撞点就行多边形遮罩就可以了

创建切割物体挂在脚本,代码如下

@ccclass
export default class Fruit extends cc.Component {
    onLoad() {
    }
    start() {
        this.draw();
    }
    public draw() {
        //获取碰撞包围盒子的点
        let points: any = this.getComponent(cc.PhysicsPolygonCollider).points;
        let mask = this.getComponent(cc.Mask);
        let graphics: cc.Graphics = (<any>mask)._graphics;
        // // @ts-ignore
        // const grapics = mask._graphics;
        //获取绘制 grapics
        // let grapics:cc.Graphics=this.getComponent(cc.Graphics); 
        // 擦除之前绘制的所有内容的方法。
        graphics.clear();
        // console.log(points);
        // console.log(graphics);
        let len: number = points.length;
        //移动路径起点到坐标(x, y)
        graphics.moveTo(points[len - 1].x, points[len - 1].y);
        for (let i = 0; i < points.length; i++) {
            graphics.lineTo(points[i].x, points[i].y);
        }
        graphics.strokeColor.fromHEX('#000000');
        graphics.lineWidth = 2;
        graphics.fill();
        graphics.stroke();
    }
    update(dt) { }
}

上边的这种遮罩方式,随着物体数量的变多,drawcall会上涨的很明显,导致游戏性能降低,具体的解决办法大家可以在论坛中搜索白玉无冰大佬的多边形裁剪图片(非mask,使用mesh),新增 gizmo 支持 多边形裁剪图片(非mask,使用mesh),新增 gizmo 支持

4.地址

5.推荐

大家可以微信扫描下边二维码进行体验

22赞

###MARK.

mark 。

mark下

mmmark

可以 厉害。

插眼插眼插眼

mmmark

mark…

《我特能吃》 玩法一模一样,就动画不一样哈哈哈哈。:grin:

mark…

mark…

mark yixia

mark
henbang

mark物理切割

Mark~!
2D Polygon Physics Cut

mark!!!