# 实现步骤

## 引入第三方库

### Clipper

`Clipper` 是一个强大的用于多边形计算的运算库。前往下面这个地址下载，并作为插件导入 `creator`

http://jsclipper.sourceforge.net

### poly2tri

`poly2tri` 是一个把多边形分割成三角形的库。下载地址如下：

https://github.com/r3mi/poly2tri.js

`poly2tri` 的使用有一些要注意的，大致就是不能有重复的点，不能有相交的形状。

## 初始化准备

```// onLoad() {
// 多点触控关闭
cc.macro.ENABLE_MULTI_TOUCH = false;
cc.director.getPhysicsManager().enabled = true;

this.node_dirty.on(cc.Node.EventType.TOUCH_START, this._touchMove, this);
this.node_dirty.on(cc.Node.EventType.TOUCH_MOVE, this._touchMove, this);
// }```

## 扩展多边形碰撞的组件

### 初始化

```const { ccclass, property, menu, requireComponent } = cc._decorator;
@ccclass
@requireComponent(cc.RigidBody)
export default class PhysicsPolygonColliderEx extends cc.Component {
}```

`private _physicsPolygonColliders: cc.PhysicsPolygonCollider[] = [];`

`private _polys: { X: number, Y: number }[][] = [];`

```private _convertVecArrayToClipperPath(poly: cc.Vec2[]) {
return poly.map((p) => { return { X: p.x, Y: p.y } });
}

private _convertClipperPathToPoly2triPoint(poly: { X: number, Y: number }[]) {
return poly.map((p) => { return new poly2tri.Point(p.X, p.Y) });
}```

```init(polys: cc.Vec2[][]) {
this._polys = polys.map((v) => { return this._convertVecArrayToClipperPath(v) });
}```

### 计算多边形

```//polyDifference(poly: cc.Vec2[]) {
const cpr = new ClipperLib.Clipper();
const subj_paths = this._polys;
const clip_paths = [this._convertVecArrayToClipperPath(poly)]
const subject_fillType = ClipperLib.PolyFillType.pftEvenOdd;
const clip_fillType = ClipperLib.PolyFillType.pftEvenOdd;
const solution_polytree = new ClipperLib.PolyTree();
cpr.Execute(ClipperLib.ClipType.ctDifference, solution_polytree, subject_fillType, clip_fillType);
const solution_expolygons = ClipperLib.JS.PolyTreeToExPolygons(solution_polytree);
this._polys = ClipperLib.Clipper.PolyTreeToPaths(solution_polytree);```

### 分割多边形并添加刚体

```// polyDifference(poly: cc.Vec2[]) {
let _physicsPolygonColliders_count = 0;
for (const expolygon of solution_expolygons) {
const countor = this._convertClipperPathToPoly2triPoint(expolygon.outer);
const swctx = new poly2tri.SweepContext(countor);
const holes = expolygon.holes.map(h => { return this._convertClipperPathToPoly2triPoint(h) });
swctx.triangulate();
const triangles = swctx.getTriangles();
// 逐一处理三角形...
}```

```// 逐一处理三角形...
for (const tri of triangles) {
let c = this._physicsPolygonColliders[_physicsPolygonColliders_count];
if (!c) {
//没有的话就创建
c.friction = 0;
c.restitution = 0;
this._physicsPolygonColliders[_physicsPolygonColliders_count] = c;
}
c.points = tri.getPoints().map((v, i) => {
return cc.v2(v.x, v.y)
});
c.apply();
_physicsPolygonColliders_count++;
}
// 剩余不要用的多边形清空。
this._physicsPolygonColliders.slice(_physicsPolygonColliders_count).forEach((v => {
if (v.points.length) {
v.points.length = 0;
v.apply();
}
}));```

### 绘制泥土

```if (i === 0) ctx.moveTo(v.x, v.y);
else ctx.lineTo(v.x, v.y);```

### 添加命令队列

```private _commands: { name: string, params: any[] }[] = [];

pushCommand(name: string, params: any[]) {
this._commands.push({ name, params });
}```

```lateUpdate(dt: number) {
if (this._commands.length) {
// 每帧执行命令队列
for (let index = 0; index < 2; index++) {
const cmd = this._commands.shift();
if (cmd)
this[cmd.name](...cmd.params);
else
break;
}
}
}```

## 涂抹地形

```private _touchStartPos: cc.Vec2;
private _touchStart(touch: cc.Touch) {
this._touchStartPos = undefined;
this._touchMove(touch);
}

private _touchMove(touch: cc.Touch) {
const regions: cc.Vec2[] = [];
const pos = this.graphics.node.convertToNodeSpaceAR(touch.getLocation());

const count = DIG_FRAGMENT;
if (!this._touchStartPos) {
// 画一个圆（其实是多边形）
for (let index = 0; index < count; index++) {
const r = 2 * Math.PI * index / count;
const x = pos.x + DIG_RADIUS * Math.cos(r);
const y = pos.y + DIG_RADIUS * Math.sin(r);
regions.push(this._optimizePoint([x, y]));
}
this._touchStartPos = pos;
} else {
const delta = pos.sub(this._touchStartPos);
// 手指移动的距离太小的话忽略
if (delta.lengthSqr() > 25) {
// 这里是合并成一个顺滑的图形  详细上一篇文章
const startPos = this._touchStartPos;
for (let index = 0; index < count; index++) {
const r = 2 * Math.PI * index / count;
let vec_x = DIG_RADIUS * Math.cos(r);
let vec_y = DIG_RADIUS * Math.sin(r);
let x, y;
if (delta.dot(cc.v2(vec_x, vec_y)) > 0) {
x = pos.x + vec_x;
y = pos.y + vec_y;
} else {
x = startPos.x + vec_x;
y = startPos.y + vec_y;
}
regions.push(this._optimizePoint([x, y]));
}
this._touchStartPos = pos;
}
}

if (regions.length)
this.polyEx.pushCommand('polyDifference', [regions, this.graphics]);
}

private _touchEnd(touch: cc.Touch) {
this._touchStartPos = undefined;
}```

# 小结

0 条评论

### 相关文章

TouchBlocker 是用来限制可点击的节点的独立组件，完整文件在 eazax-ccc/component 目录下。

• ### 盯着双11开喵铺里的小人许久，我也写了一个！cocos creator !

◇ 打开支付宝，天猫双11合伙人全面开喵铺的活动映入眼帘。点击进去后，我竟然盯着小人走路许久，琢磨着，自己也写个玩玩吧！

• ### go 无锁队列

无锁队列适用场景： 　　　　　两个线程之间的交互数据，　一个线程生产数据，　另外一个线程消费数据，效率高 缺点：需要使用固定分配的空间，不能动态增加／减少长度...

• ### 微软 VSCode IDE 源码分析揭秘

? 作者：zanewang，腾讯 CSIG 工程师 ? 目录 (1)简介 (2)技术架构 (3)启动主进程 (4)实例化服务 (5)事件分发 (6)进程...

• ### 基于动态代理 Mock Dubbo 服务的实现方案

公司目前 Java 项目提供服务都是基于 Dubbo 框架的，而且 Dubbo 框架已经成为大部分国内互联网公司选择的一个基础组件。

• ### 自适应比特率流媒体与CDN性能

本篇是来自Seattle Video Tech 2019年3月的演讲，演讲者是来自Brightcove的研究员Yuriy Reznik，主题是“自适应比特率流媒...

• ### SpringFramework之ViewResolver优化

Springboot中，对mvc进行自动化配置时在WebMvcAutoConfiguration中会自动注入InternalResourceViewResolv...