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

前言

游戏开发中,物理系统的试用越来越多,导致试用花样也是越来越多,只有你想不到的,没有做不到的。前段时间自己在其他地方看见了一个比较好玩的物理切割类的游戏,出于好奇自己也实验了一把,一直没时间整理文章,今天终于可以整理一下了。

效果展示

image

视频连接:视频播放

正文

1.demo效果展示

image

2.切割

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

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

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

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

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

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

2.1 creator 物理系统

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


    image

2.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;
}
  • 检测切割

检测切割的步骤:

  • 1.获取切割线上的所有刚体(可以使用物理系统自带的射线检测)

    详细文档射线检测
    在这里给大家列出一下射线检测的类型

      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 支持]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352