原文链接:大佬实现
先说一下整体思路:
1:首先给一个待切割物体一个刚体组件(RigidBody)和多边形物理碰撞组件(PhysicsPolygonCollider)和一个切割脚本组件(主要负责绘制填充多边形轮廓),有人会问怎么要用物理组件,不用物理组件不行吗,加物理组件的原因是物理组件模拟了现实中的碰撞运动,摩擦等。
2:上面的准备之后就可以监听根节点的滑动事件根据滑动开始点和结束点进行切割操作,切割线必须经过刚体才可以实现切割,切割动作可以得到两个或者更多的与多边形碰撞体相交的点,利用这些点求出刚体节点坐标系下的点,然后在利用求出的交点与多边形的几条边进行比对,看一下交点在那条边上(利用交点到直线的距离近似计算下面会降到)。知道了交点在那条边上之后就可以将切割轮廓点集合放到两个数组里面了,一个数组存放的是本体切割之后的轮廓,一个数组存放的是切掉的轮廓数组,问题是切割本体的轮廓是可以知道的,直接将得的的数组替换PhysicsPolygonCollider的points属性重新apply,绘制一下轮廓就OK了,但是切割掉的那一部分怎么搞?
3:切割掉的一部分其实也是一个小刚体跟本体没什么区别唯一一个区别就PhysicsPolygonCollider的points不同,这样就好解决了,重新初始化一个本体,放到根节点下就OK了。这样就是整个切割的逻辑思路,下面开始上代码:
const {ccclass, property} = cc._decorator;
@ccclass
export default class Item extends cc.Component {
onLoad () {
this.draw();
}
draw () {
const points = this.getComponent(cc.PhysicsPolygonCollider).points;
const ctx = this.getComponent(cc.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();
}
}
这个是被切割本体上需要挂载的脚本,实际上就是绘制多边形轮廓并且填充的,这里只支持纯色的填充,如果想用图片的话还需要Mask来做,这个后面会讲到。另外就是切割逻辑了:
import Item from './cutItem'
const {ccclass, property} = cc._decorator;
@ccclass
export default class CutMain extends cc.Component {
@property(cc.Graphics)
pen: cc.Graphics = null;
private startPoint: cc.Vec2 = cc.v2(0,0);
private endPoint: cc.Vec2 = cc.v2(0,0);
private startP: cc.Vec2 = cc.v2(0,0);
private endP: cc.Vec2 = cc.v2(0,0);
physicManager: cc.PhysicsManager = null;
onLoad () {
this.physicManager = cc.director.getPhysicsManager();
this.physicManager.enabled = true;
this.physicManager.debugDrawFlags = 0;
}
start () {
this.node.on(cc.Node.EventType.TOUCH_MOVE,this.tapMove,this);
this.node.on(cc.Node.EventType.TOUCH_END,this.tapEnd,this);
}
tapMove(event: cc.Event.EventTouch) {
const curLocation = this.node.convertToNodeSpaceAR(event.getLocation());
this.startP = event.getStartLocation();
this.startPoint = this.node.convertToNodeSpaceAR(event.getStartLocation());
this.pen.clear();
this.pen.moveTo(this.startPoint.x,this.startPoint.y);
this.pen.lineTo(curLocation.x,curLocation.y);
this.pen.stroke();
}
tapEnd(event: cc.Event.EventTouch) {
this.pen.clear();
this.endPoint = this.node.convertToNodeSpaceAR(event.getLocation());
this.endP = event.getLocation();
this.cut(this.startP,this.endP);
}
/**
* @param {cc.Vec2} startPoint 世界坐标
* @param {cc.Vec2} endPoint
*/
cut(startPoint: cc.Vec2,endPoint: cc.Vec2) {
// 这里的射线检测Closest是检测最近的多边形 稍微慢些
const result1 = this.physicManager.rayCast(startPoint,endPoint,cc.RayCastType.Closest);
const result2 = this.physicManager.rayCast(endPoint,startPoint,cc.RayCastType.Closest);
if (result1.length === 0 || result2.length === 0) {
cc.warn('无碰撞体');
return;
}
if (result1[0].collider !== result2[0].collider) {
cc.warn('不是单独碰撞体');
return;
}
if (!(result1[0].collider instanceof cc.PhysicsPolygonCollider)) {
cc.warn('非多边形物理碰撞盒无points属性');
return;
}
// 射线检测到穿过那个碰撞体
const collider = result1[0].collider;
// 射线与碰撞体相交的点1
const resultPoint0 = result1[0].point;
// 射线与碰撞体相交的点2
const resultPoint1 = result2[0].point;
console.log(`point0 is ${resultPoint0},point1 is ${resultPoint1}`);
// 将射线与碰撞体相交的点转为刚体局部坐标
const localPoint0 = cc.Vec2.ZERO;
const localPoint1 = cc.Vec2.ZERO;
collider.body.getLocalPoint(resultPoint0,localPoint0);
collider.body.getLocalPoint(resultPoint1,localPoint1);
const points = collider.points;
let index1,index2;
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(localPoint0,p1,p2)) {
index1 = i;
}
if(this.pointInLine(localPoint1,p1,p2)) {
index2 = i;
}
if(index1 !== undefined && index2 !== undefined) {
break;
}
}
console.log(`多边形边数从第三象限开始,点1落在了第${index1}条边上,点2落在了第${index2}条边上,`);
const array1 = [];
const array2 = [];
// 碰到 index1 或 index2 标志
let time = 0;
// 依次将切割后的本体轮廓线,切割掉的轮廓线放入数组
for (let i = 0; i < points.length; i++) {
let temp = points[i].clone();
if (time === 0) {
array1.push(temp);
} else {
array2.push(temp);
}
if ((i === index1 || i === index2) && time === 0) {
array1.push(i === index1 ? localPoint0.clone() : localPoint1.clone());
array2.push(i === index1 ? localPoint0.clone() : localPoint1.clone());
time = 1;
} else if ((i === index1 || i === index2) && time === 1) {
array2.push(i === index1 ? localPoint0.clone() : localPoint1.clone());
array1.push(i === index1 ? localPoint0.clone() : localPoint1.clone());
time = 0;
}
}
// 将本体的points替换成array1
collider.points = array1;
collider.apply();
collider.node.getComponent(Item).draw();
// 克隆一个本体作为第二个
const cloneNode = cc.instantiate(collider.node);
this.node.addChild(cloneNode);
const comp = cloneNode.getComponent(cc.PhysicsPolygonCollider);
comp.points = array2;
comp.apply();
cloneNode.getComponent(Item).draw();
}
pointInLine(point: cc.Vec2,start: cc.Vec2,end: cc.Vec2) {
const offsetDis = 1;
return cc.Intersection.pointLineDistance(point,start,end,true) < offsetDis;
}
// update (dt) {}
}