摘要
物理切割第二部分,切割多个物体,利用 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~~
源码在我的微信公众号回复关键词【物理切割】即可获得