专栏首页白玉无冰CocosCreator之KUOKUO带你做物理切割(第二部分)

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();
}

本文分享自微信公众号 - 白玉无冰(lamyoung-com)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 终极 shell 脚本 快速入门指南 (四) 条件

    和许多语言一样, shell 脚本条件语句使用if。其中的条件通常是用[]包裹起来,也可以使用test命令。

    白玉无冰
  • JavaScript 正则表达式(RegExp)实用指南 (二)【译】

    在 JavaScript 正则表达式(RegExp)实用指南(一) 介绍了正则表达式的含义,创建方法和测试方法,接下来我们来看下正则表达式中的特殊字符等。

    白玉无冰
  • 笑容逐渐消失? shader 编程入门实战 ! Cocos Creator!

    在 Cocos Creator 编辑器中,新建一个材质 Material,Effect 选择为 gradient,拖入两张图片。

    白玉无冰
  • ES6中提升效率的新方法,多学一点是一点。

    ES6,ECMAScript6是目前js的新标准,又说是现在的es6是2015年发出来的,所以又称它为ECMAScript2015,所以说es6就是es2015...

    web前端教室
  • 【STM32H7教程】第34章 STM32H7的定时器应用之TIM1-TIM17的PWM实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

    armfly
  • 锲而不舍 —— M 是怎样找工作的?(八)

    在 schedule 函数中,我们简单提过找一个 runnable goroutine 的过程,这一讲我们来详细分析源码。

    梦醒人间
  • 如何使用 TIMSDK 的自定义字段?

    "用户资料自定义字段","好友自定义字段","群成员维度自定义字段","群维度自定义字段" 均已键值对 Key-Value 形式存储并使用。扩展相应的字段 Ke...

    腾讯云-ahqzhang
  • ECMAScript6 基础知识点(上)

    ECMAScript 是 JavaScript 的组成部分,它规范了 JS 的语法(解析规则,关键字,语句,声明,操作等)、类型(布尔型,数字,字符串,对象等)...

    Nian糕
  • 数值积分|高斯积分

    如图a所示。这样当然会造成很大的误差。如果在区间内部找两个点,且通过这两个点的直线与区间端点构成的梯形面积最大限度地接近精确值,即图b中A1+A2=A3,这就是...

    fem178
  • 感觉身体被掏空?你需要这 5 个「吸猫/狗」小程序来补一补

    现在,微博上最受欢迎的网红已经不是漂亮的小姐姐们了,取而代之而是各种各样可爱的小动物们。

    知晓君

扫码关注云+社区

领取腾讯云代金券