生命不歇,挖坑不止!另一种挖洞算法的实现!

2023-10-22 16:40

本文主要是介绍生命不歇,挖坑不止!另一种挖洞算法的实现!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这次就不用物理链条了,换一种方式实现。

回顾

在 物理挖洞-优化篇 和 物理挖洞-实现篇 中介绍了一种用多边形链条组件(cc.PhysicsChainCollider)实现物理挖洞的方法。这次打算用多边形碰撞组件(cc.PhysicsPolygonCollider)去实现物理挖洞。

建议先看前两篇的讲解,有助于更快理解这篇文章。

效果预览

微信小游戏-ios-端效果预览

实现步骤

整体思路是,先用 Clipper 去计算多边形,接着用 poly2tri 将多边形分割成多个三角形,最后用多边形刚体填充。

引入第三方库

Clipper

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

http://jsclipper.sourceforge.net

为什么这次不用 物理挖洞-实现篇 中的 PolyBool 呢?

经测试发现 Clipper 的效率会比 PolyBool 高,并且 Clipper 内置了一个方法可以明确知道哪些多边形是洞。

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);
// }

扩展多边形碰撞的组件

为了方便管理多边形碰撞组件,新建一个脚本 PhysicsPolygonColliderEx.ts

初始化

因为物理碰撞体需要物理刚体,我们可以加一些限制,并把这个菜单指向物理碰撞体的菜单中。

const { ccclass, property, menu, requireComponent } = cc._decorator;
@ccclass
@menu("i18n:MAIN_MENU.component.physics/Collider/PolygonEX-lamyoung.com")
@requireComponent(cc.RigidBody)
export default class PhysicsPolygonColliderEx extends cc.Component {
}

我们就可以在刚体节点中添加这个插件脚本了。

既然要用到多边形碰撞体,就定义一个多边形碰撞体数组。

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

因为 Clipper 中计算的结构是 {X,Y}

所以加个变量记录多边形顶点信息。

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) });
}

计算多边形

参考 Clipper 中的使用例子,写一个多边形差集调用。

//polyDifference(poly: cc.Vec2[]) {
const cpr = new ClipperLib.Clipper();
const subj_paths = this._polys;
const clip_paths = [this._convertVecArrayToClipperPath(poly)]
cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true);
cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);
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);

分割多边形并添加刚体

参考 poly2tri 中的使用,写一个多边形分割成三角形的调用。记得要把上面返回的数据转成 poly2tri 中可以使用的数据格式。

// 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.addHoles(holes);swctx.triangulate();const triangles = swctx.getTriangles();// 逐一处理三角形...
}

然后再逐一处理分割好的三角形,修改 cc.PhysicsPolygonColliderpoints 属性。

// 逐一处理三角形...
for (const tri of triangles) {let c = this._physicsPolygonColliders[_physicsPolygonColliders_count];if (!c) {//没有的话就创建c = this.addComponent(cc.PhysicsPolygonCollider);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);elsebreak;}}
}

涂抹地形

整体思路和 物理挖洞-优化篇 和 物理挖洞-实现篇 差不多。不清楚的话,可以回看这两篇文章。

这次不同的是,加了一个涂抹步长控制,当涂抹间隔太小的时候,就不参与计算。

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;
}

小结

以上为白玉无冰使用 Cocos Creator v2.3.3 开发"物理挖洞之多边形碰撞体挖洞"的技术分享。如果对你有点帮助,欢迎分享给身边的朋友。

视频讲解

感谢各位看官的支持,如果这篇文章能给你带来一点点的帮助,白玉无冰感到非常愉快。如果可以点个在看的话,我会非常振奋开心。当然,请白玉无冰喝一杯奶茶的话,我会感到更加的满足。

其实,写这一篇图文花了一整天的时间(不算上之前的查阅资料及代码的实现),但是看到一篇文章的完成,富有满满的成就感。

不懂大家是否理解了这种实现方式,不知道是否需要补充一个视频讲解。

这样吧,如果这篇文章在看超过30,我就补录一篇视频讲解!

准备去研究新东西喽!冲呀!大家一定要多多实践练习!

点击“阅读原文”查看更多精彩

“在看”是最大的鼓励▼

这篇关于生命不歇,挖坑不止!另一种挖洞算法的实现!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/262527

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo