摘要
有趣的物理小游戏总能让小伙伴们爱不释手,而 CocosCreator 的内置物理引擎让其开发变得更加简单。今天 KUOKUO 就为大家带来物理切割,从零开始做出切割单个物体!
正文
使用版本
CocosCreator 版本 2.1.3
最终效果
层级管理
如图,在层级管理器中有 3 部分:画布,物理层,绘图层。其中画布只是作为触摸节点,绘图层用于绘制红线,物理层中放入物理刚体。脚本有三个,其中的 setting 仅仅是开启物理和物理绘制,cut-item 脚本用于绘制刚体,下面给出代码。
draw 方法会根据物理包围盒的 points 填充绘图,注意要使用多边形碰撞盒。
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();
}
鼠标划线
鼠标划线很简单,就是获取起点和当前点然后绘图线段。在 cut-main 脚本里注册 touchmove 事件。
this.node.on(cc.Node.EventType.TOUCH_MOVE, (e) => {
this.draw.clear();
const startPoint = e.getStartLocation();
this.draw.moveTo(startPoint.x, startPoint.y);
this.draw.lineTo(e.getLocationX(), e.getLocationY());
this.draw.stroke();
}, this);
射线检测
什么是射线检测?就是给定两个点,返回这两个点之间的物理碰撞盒。很显然,我们松手的那一刻,划线起点与终点就是我们要的那两个点!我们把事件监听方法提出来,放在 onLoad 里面。
onLoad () {
this.registerEvent();
}
registerEvent () {
this.node.on(cc.Node.EventType.TOUCH_MOVE, (e) => {
this.draw.clear();
const startPoint = e.getStartLocation();
this.draw.moveTo(startPoint.x, startPoint.y);
this.draw.lineTo(e.getLocationX(), e.getLocationY());
this.draw.stroke();
}, this);
this.node.on(cc.Node.EventType.TOUCH_END, (e) => {
this.draw.clear();
const p1 = e.getStartLocation();
const p2 = e.getLocation();
// 核心逻辑
this.cut(p1, p2);
}, this);
}
接下来我们讲 cut 方法。
核心逻辑
首先我们要调用射线检测的方法,模式分为四种:Any,Closest,All,AllClosest。本次教程针对单个碰撞体,使用 Closest 模式,每个模式更详细的信息可以去查下官方文档。因为我们要获取所有的交点,所以要正反来两次射线检测。
const result1 = cc.director.getPhysicsManager().rayCast(point1, point2, cc.RayCastType.Closest);
const result2 = cc.director.getPhysicsManager().rayCast(point2, point1, 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;
let localPoint1 = cc.Vec2.ZERO;
let localPoint2 = cc.Vec2.ZERO;
collider.body.getLocalPoint(result1[0].point, localPoint1);
collider.body.getLocalPoint(result2[0].point, localPoint2);
万事俱备,我们现在知道了碰撞体与射线的两个交点,那么我们只要把碰撞体的 points 以两个点为界限分割为两部分即可!但是,首先我们要先找到交点在那条线上,先封装个判断方法:
/** 近似判断点在线上 */
pointInLine (point, start, end) {
const dis = 1;
return cc.Intersection.pointLineDistance(point, start, end, true) < dis;
}
然后我们去查找那两个点究竟在那两条线上,用下标去表示。
const points = collider.points;
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}`);
OK!我们已经知道了那两个交点在哪里,我们下一步就是新建两个数组,按照顺序分好类,一边一个!
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 ? localPoint1.clone() : localPoint2.clone());
array2.push(i === index1 ? localPoint1.clone() : localPoint2.clone());
time = 1;
} else if ((i === index1 || i === index2) && time === 1) {
array2.push(i === index1 ? localPoint1.clone() : localPoint2.clone());
array1.push(i === index1 ? localPoint1.clone() : localPoint2.clone());
time = 0;
}
}
cc.log(array1,array2);
逻辑不难,拿 time 作为碰到交点的标志量,顺利的分为两个数组。
拷贝节点
接下来就简单了,将本体重置为 array1 状态,拷贝一个本体,设置 array2 状态,再执行下自身脚本上的 draw 方法绘图。
// 设置第一个碰撞体
collider.points = array1;
collider.apply();
collider.node.getComponent(Item).draw();
// 克隆一个本体作为第二个
const cloneNode = cc.instantiate(collider.node);
this.gameLayer.addChild(cloneNode);
const comp = cloneNode.getComponent(cc.PhysicsPolygonCollider);
comp.points = array2;
comp.apply();
cloneNode.getComponent(Item).draw();
怎么样?如果感觉理解的差点意思,下方有源码获取方式哦!
结语
有意思吧!我们在第二部分教程讲讲多个物体的切割。
O(∩_∩)O~~
源码在我的微信公众号回复关键词【物理切割】即可获得