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~~

源碼在我的微信公衆號回覆關鍵詞【物理切割】即可獲得

微信公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章