摘要
物理切割第二部分,切割多個物體,利用 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~~
源碼在我的微信公衆號回覆關鍵詞【物理切割】即可獲得