CocosCreator之KUOKUO带你做物理切割(第二部分)

摘要

物理切割第二部分,切割多个物体,利用 Mask 切割图片!

正文

使用版本

CocosCreator 版本 2.1.3

最终效果

多切割原理

通过上一部分的教程,我们已经知道了单切割的原理,但是很显然,KUOKUO 的第一篇博客显得不通用,只能切割一个物体。那么如何切割多个物体呢?

  • 第一步:分类,将不同碰撞体的点放入一个数组
  • 第二步:排除,将同碰撞体内部的点干掉
  • 第三步:排序,按照顺序两两分组,两两切割

第一步和第二步放在一起做,其中排除碰撞体内部的点很简单,两个结果合并,找同一点!

const result1 = cc.director.getPhysicsManager().rayCast(point1, point2, cc.RayCastType.All);
const result2 = cc.director.getPhysicsManager().rayCast(point2, point1, cc.RayCastType.All);
// 将结果二的方向反过来
result2.forEach(r => {
    r.fraction = 1 - r.fraction;
});
// 将结果合并
const results = result1.concat(result2);
cc.log(results);
// 然后我们将结果进行分类
let pairs = [];
for (let i = 0; i < results.length; i++) {
    let find = false;
    let result = results[i];
    for (let j = 0; j < pairs.length; j++) {
        let pair = pairs[j];
        // 以第一个点为参考,如果碰撞盒子是同一个,证明是同一个物体
        if (pair[0] && result.collider === pair[0].collider) {
            find = true;
            // 移除同碰撞体内部的多余的点,因为两个结果,只有内部的点是重叠的,找相同的点
            let r = pair.find((r) => {
                // 物理世界没有绝对相等,官方取的判断临界是根号 5,很小的距离来判断点在同一位置
                return r.point.sub(result.point).magSqr() <= 5;
            });
            // 如果有非常近的点,跳过 push,然后把里面的删去
            if (r) {
                pair.splice(pair.indexOf(r), 1);
            }
            else { 
                pair.push(result);
            }
            break;
        }
    }
    if (!find) {
        pairs.push([result]);
    }
}
cc.log(pairs);

这样我们就获得了一个数组,这个数组里每个数组都是一个碰撞体的点,再将每个碰撞体内部点,两两切割。

for (let i = 0; i < pairs.length; i++) {
    let pair = pairs[i];
    if (pair.length < 2) {
        continue;
    }
    // 根据远近,按顺序排队,这样每两个一组
    pair = pair.sort((a, b) => {
        if (a.fraction > b.fraction) {
            return 1;
        } else if (a.fraction < b.fraction) {
            return -1;
        }
        return 0;
    });
    cc.log(pair)
    // 将一个碰撞体上的所有点分成几个部分,比如两个交点就是两部分,四个交点就需要分成三部分
    let splitResults = [];
    // 每两个点一循环
    for (let j = 0; j < pair.length - 1; j+=2) {
        let r1 = pair[j];
        let r2 = pair[j+1];
        if (r1 && r2) {
            // 封装一个方法,将分割后的结果放入 splitResults 中
            this.split(r1.collider, r1.point, r2.point, splitResults);
        }
    }
    if (splitResults.length <= 0) {
        continue;
    }
    // 根据结果创建碰撞体
    let collider = pair[0].collider;
    let maxPointsResult;
    for (let j = 0; j < splitResults.length; j++) {
        let splitResult = splitResults[j];
        for (let k = 0; k < splitResult.length; k++) {
            if (typeof splitResult[k] === 'number') {
                splitResult[k] = collider.points[splitResult[k]];
            }
        }
        if (!maxPointsResult || splitResult.length > maxPointsResult.length) {
            maxPointsResult = splitResult;
        }
    }
    // 分割结果不构成图形
    if (maxPointsResult.length < 3) {
        continue;
    }
    // 设置本体
    collider.points = maxPointsResult;
    collider.apply();
    collider.node.getComponent(Item).draw();
    // 克隆 N 个
    for (let j = 0; j < splitResults.length; j++) {
        let splitResult = splitResults[j];
        if (splitResult.length < 3) continue;
        if (splitResult == maxPointsResult) continue;
        // 克隆本体作为第 N 个
        const cloneNode = cc.instantiate(collider.node);
        this.gameLayer.addChild(cloneNode);
        const comp = cloneNode.getComponent(cc.PhysicsPolygonCollider);
        comp.points = splitResult;
        comp.apply();
        cloneNode.getComponent(Item).draw();
    }
    
}

this.splite 这个方法实现原理同第一部分教程,不同的地方是,比如一个凹多边形,一刀切成了三部分,我们第一次将其分成两部分后,要找下第三部分的点在哪个部分,然后插入点,分割。

split (collider, point1, point2, splitResults) {
    let body = collider.body;
    let points = collider.points;
    // 转化为本地坐标
    let localPoint1 = cc.Vec2.ZERO;
    let localPoint2 = cc.Vec2.ZERO;
    body.getLocalPoint(point1, localPoint1);
    body.getLocalPoint(point2, localPoint2);
    let newSplitResult1 = [localPoint1, localPoint2];
    let newSplitResult2 = [localPoint2, localPoint1];
    // 同教程第一部分,寻找下标
    let index1 = undefined;
    let index2 = undefined;
    for (let i = 0; i < points.length; i++) {
        let p1 = points[i];
        let p2 = i === points.length - 1 ? points[0] : points[i + 1];
        if (this.pointInLine(localPoint1, p1, p2)) {
            index1 = i;
        }
        if (this.pointInLine(localPoint2, p1, p2)) {
            index2 = i;
        }
        if (index1 !== undefined && index2 !== undefined) {
            break;
        }
    }
    // cc.log(`点1下标${index1}`);
    // cc.log(`点2下标${index2}`);
    let splitResult = undefined;
    let indiceIndex1 = index1;
    let indiceIndex2 = index2;
    // 检测重叠部分
    if (splitResults.length > 0) {
        for (let i = 0; i < splitResults.length; i++) {
            let indices = splitResults[i];
            indiceIndex1 = indices.indexOf(index1);
            indiceIndex2 = indices.indexOf(index2);
            if (indiceIndex1 !== -1 && indiceIndex2 !== -1) {
                splitResult = splitResults.splice(i, 1)[0];
                break;
            }
        }
    }
    // 如果没有重叠
    if (!splitResult) {
        splitResult = points.map((p, i) => {
            return i;
        });
    }
    // 分割开两部分
    for (let i = indiceIndex1 + 1; i !== (indiceIndex2+1); i++) {
        if (i >= splitResult.length) {
            i = 0;
        }
        let p = splitResult[i];
        // 如果是下标,读数组
        p = typeof p === 'number' ? points[p] : p;
        if (p.sub(localPoint1).magSqr() < 5 || p.sub(localPoint2).magSqr() < 5) {
            continue;
        }
        newSplitResult2.push(splitResult[i]);
    }
    for (let i = indiceIndex2 + 1; i !== indiceIndex1+1; i++) {
        if (i >= splitResult.length) {
            i = 0;
        }
        let p = splitResult[i];
        p = typeof p === 'number' ? points[p] : p;
        if (p.sub(localPoint1).magSqr() < 5 || p.sub(localPoint2).magSqr() < 5) {
            continue;
        }
        newSplitResult1.push(splitResult[i]);
    }
    // 两个方向遍历完毕,装入结果
    splitResults.push(newSplitResult1);
    splitResults.push(newSplitResult2);
}

讲 10 句不如给出实际代码 1 句,所以 KUOKUO 给出了源码,在下方。

Mask实现

如图,根节点加 Mask 组件,子节点为精灵图片

修改 draw 方法:

draw () {
    const points = this.getComponent(cc.PhysicsPolygonCollider).points;
    const mask = this.getComponent(cc.Mask);
    // @ts-ignore
    const ctx = mask._graphics;
    ctx.clear();
    const len = points.length;
    ctx.moveTo(points[len - 1].x, points[len - 1].y);
    for (let i = 0; i < points.length; i++) {
        ctx.lineTo(points[i].x, points[i].y);
    }
    ctx.fill();
}
源码更新

源码还是在我的微信公众号回复关键词【物理切割】即可获得,我把第一部分的示例合并到了这里,所以打了集合包放了上去,还是回复【物理切割】即可获取最新的源码包!

结语

觉得写得不错,欢迎转载分享!点个在看吧!

O(∩_∩)O~~

源码在我的微信公众号回复关键词【物理切割】即可获得

微信公众号

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答: 在Cocos Creator中,常用的设计模式包括子弹工厂模式、单例模式和建造者模式。子弹工厂模式是通过工厂来创建子弹节点,根据提供的子弹种类、速度和方向来自动创建子弹节点并返回,实现了代码的精简和灵活性。\[1\]单例模式用于创建一个全局唯一的实例,可以在整个应用程序中共享和访问该实例,避免了多次实例化的问题。\[2\]建造者模式用于创建复杂的对象,通过将对象的构建过程分解为多个步骤,使得对象的构建更加灵活和可扩展。\[3\]这些设计模式在Cocos Creator中可以帮助开发者更好地组织和管理代码,提高开发效率和代码质量。 #### 引用[.reference_title] - *1* [CocosCreator进阶实战第四部分:工厂模式](https://blog.csdn.net/kuokuo666/article/details/103722679)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [设计模式---创建型模式(工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式)](https://blog.csdn.net/sinat_36499762/article/details/115624011)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KUOKUO众享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值