查看: 8980|回复: 17

[胡扯] 【更新物理引擎功能展示】为啥做动作游戏不用PGMMV而用RM做

[复制链接]
梦石
2
星屑
5065
在线时间
706 小时
注册时间
2018-12-11
回帖
213

MZ评测员

发表于 2020-10-2 23:04:34 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 lisliz 于 2020-10-5 16:12 编辑

今天酒喝多了,话就有点多。之前我在招募板块发帖动作游戏企划招队友,有人喊我用PGMMV来着。
用pgmmv直接写好的脚本做动作游戏,确实看起来会更快,但是你要想,pgmmv它再好,始终是别人写好给你的,你并不懂里面是什么原理,这样你就很难定制自己想要的东西,而且你要了解它还会有很大的接触成本。

这样就好比各位做一款好点的剧情游戏不让你用原创素材一样,是非常麻烦的,非原创素材(类比PGMMV),他并不是写给你的游戏的,而且你多半还改不了它,无形中限制了你去精雕细琢的可能性。

我截图一哈这两天空洞骑士STEAM底下的评价哈,大家看看。
QQ截图20201002212357.png
你说人家游戏为啥就好玩,“好玩”这两个字分量有多重?那大家觉得用人家做好的战斗插件或者说写好的PGMMV能达到这个分量么?
这种玩法上登峰造极的作品,肯定是需要一个非常强大的策划,和一个什么好玩的东西都能实现的神仙(gong ju ren)程序。所以,看上面的红色字体,现在知道原因了吧。


好的,我们接下来就在MZ里实现一个小型物理引擎。以下折叠过于硬核,慎点!包括碰撞检测,各种数学,物理等烧脑逻辑。
[fold=MZ里物理引擎实现的过程]
一个2D横板动作游戏,恐怕最重要的核心脚本就是物理引擎了。它负担着一个游戏的碰撞判定,物体的运动轨迹计算,受力分析,实现动量定理等能体现经典力学的公式。所有的关卡设计都要在这个系统上进行书写,它是游戏世界的法则,是万物的规律,还直接影响玩家的操作和手感。其重要性不言而喻,甚至可以直接用生死线来衡量。
我对这个系统的重视度已经拉到五颗星。
而偏偏这个系统还是最最最难做好和技术力要求最最高的,对于程序来说简直可以算是美术里的清明上河图。
为什么说它要求的技术力是顶尖水平,可以看看这个知乎上的讨论:https://www.zhihu.com/question/4 ... =970094161945346048

要设计物理引擎,首先要实现碰撞检测功能,因为要执行物理法则,必须知道两个物体是否相撞。动作游戏里比较常见的地形有矩形,三角形等。

两个多边形(以下均为凸多边形)是否相交,你们说在脚本里怎么判定?
相交有两种情况,一种是一个多边形完全容纳了另一个多边形。
情况A:像下面这个三角形和矩形。
QQ截图20201002214812.png

对于情况A,如果一个多边形所有顶点都在另一个多边形内部,就可以认为该多边形完全在另一个多边形内部。
关于某个点是否在多边形内部,可以利用向量叉积去判断,参考资料:https://www.cnblogs.com/calence/p/9988172.html

我先把向量叉积的实现脚本贴出来,等下会多次用到这个。
[pre lang="javascript"]// 两个向量的叉积
Utils.vectorProduct = function(vc1, vc2) {
        return vc1.x * vc2.y - vc1.y * vc2.x;
};

//【点basePoint和点p1连成的向量】和【点basePoint和点p2连成的向量】的叉积
Utils.basePointVectorProduct = function(basePoint, p1, p2) {
        const vc1 = {x:p1.x - basePoint.x,y:p1.y - basePoint.y};
        const vc2 = {x:p2.x - basePoint.x,y:p2.y - basePoint.y};
        return this.vectorProduct(vc1, vc2);
};
[/pre]

893858-20181120120118008-1960762888.png
如图,例如多边形ABCDE,求点P是否在多边形内部。对于程序,只需要依次判断,向量叉积AB*AP,BC*BP,CD*CP,DE*DP,EA*EP的符号是否全部相同,这个不难理解,向量叉积本身就是判断A向量在B向量的哪一侧,如果全部都在同一侧(符号相同),自然就在多边形内部。
但是要注意一种特殊情况,就是点P在多边形的边界上,例如在边AB上,这种向量叉积AB*AP=0,会导致程序判定出问题,因此如果发现叉积=0的情况则该轮结果舍弃,不判断。大家可以看到我的实现里,会判断pd1是否等于0,如果是则不赋值给下一轮判断,等于直接跳过。
[pre lang="javascript"]// 点是否在凸多边形中
Utils.isPointInConvexPolygon = function(p, vertices) {
        let vertex = vertices[0];
        let pd = 0;
        vertices.push(vertex);
        for(let i = 1; i < vertices.length; i++) {
                const pd1 = this.basePointVectorProduct(vertex, p, vertices);
                if(pd1 * pd < 0) {
                        vertices.pop();
                        return false;
                }
                if(pd1 !== 0) {
                        pd = pd1;
                }
                vertex = vertices;
        }
        vertices.pop();
        return true;
};[/pre]
这样,依次判断多边形A的所有顶点是否在多边形B中,再判断多边形B的所有顶点是否在多边形A中,就可以判断出上述情况A的发生了。
然而,这还不够,两个多边形相交还有另一种情况。
情况B:另外一种是有一截露在外面,如下:
无标题2.png
这种情况,两个多边形的边必然有相交的情况。所以这个问题转化成两个多边形的边(线段)是否相交。关于线段是否相交,还是要用向量叉积去算。参考这个文章:https://www.cnblogs.com/tuyang1129/p/9390376.html

两个线段,AB和CD是否相交,则判断向量叉积AB*AC于AB*AD结果异号,且向量叉积CD*CA于CD*CB结果异号。但是凡事都有特殊情况,倘若AB和CD在同一条直线上,则前面叉积结果都会是0。无法用叉积的方法判断。这种情况需要判断A是否在CD上或B是否在CD上,则代表AB与CD相交。
另外,特殊情况还有如下两种:
第一种:A和C是同一个点,等于AB和CD两条线段首尾相连
第二种:A在线段CD上,但AB与CD不在一条直线上
以上所有情况我在代码里都注明了,大家可以看看我的js实现。
[pre lang="javascript"]// 两个线段是否相交
Utils.isSegmentIntersect = function(line1, line2) {
        const pd1 = this.basePointVectorProduct(line1.p1, line1.p2, line2.p1);
        const pd2 = this.basePointVectorProduct(line1.p1, line1.p2, line2.p2);
        const pd3 = this.basePointVectorProduct(line2.p1, line2.p2, line1.p1);
        const pd4 = this.basePointVectorProduct(line2.p1, line2.p2, line1.p2);
        const c1 = pd1 * pd2;
        const c2 = pd3 * pd4;
        if(c1 > 0 || c2 > 0) {
                return false;
        }
        else if(c1 < 0 && c2 < 0) {
                return true;
        }
        else if(pd1 === 0 && pd2 === 0 && pd3 === 0 && pd4 === 0) {   // 特殊情况,ABCD在同一直线上
                const b1 = this.pointBigger(line1.p1, line2.p1);   // 判断点A是否在CD上
                const b2 = this.pointBigger(line1.p1, line2.p2);
                if(b1 === 2 || b2 === 2) {                        // 特殊情况,A和C是同一个点,或者A和D是同一个点
                        return true;
                }
                if(b1 ^ b2) {
                        return true;
                }
                const b3 = this.pointBigger(line1.p2, line2.p1);   // 判断点B是否在CD上
                const b4 = this.pointBigger(line1.p2, line2.p2);
                if(b3 === 2 || b4 === 2) {                        // 特殊情况,B和C是同一个点,或者B和D是同一个点
                        return true;
                }
                if(b3 ^ b4) {
                        return true;
                }
                return false;
        }
        else {                //两线段不平行但顶点重合,或者A线段的顶点在B线段上
                return true;
        }
};
Utils.pointBigger = function(p1, p2) {
        if(p1.x > p2.x) { return 1; }
        if(p1.x === p2.x && p1.y > p2.y) { return 1;}
        if(p1.x === p2.x && p1.y === p2.y) { return 2; }
        return 0;
};
[/pre]

好的,终于要大功告成了,两个多边形是否相交,只需判断情况1和情况2是否发生。因此:
[pre lang="javascript"]// 两个凸多边形是否相交
Utils.isConvexPolygonIntersect = function(vertices1, vertices2) {
        if(this.isConvexPolygonContains(vertices1, vertices2)) {    // 多边形1是否被多边形2包含
                return true;
        }
        if(this.isConvexPolygonContains(vertices2, vertices1)) {    // 多边形2是否被多边形1包含
                return true;
        }
        let vertex1 = vertices1[0];
        for(let i = 1; i < vertices1.length; i++) {                                        // 多边形1是否有任意一条边与多边形2相交
                const line1 = {p1: vertex1, p2: vertices1};
                let vertex2 = vertices2[0];
                for(let j = 1; j < vertices2.length; j++) {
                        const line2 = {p1: vertex2, p2: vertices2[j]};
                        if(this.isSegmentIntersect(line1, line2)) {
                                return true;
                        }
                        vertex2 = line2.p2;
                }
                vertex1 = line1.p2;
        }
        return false;
};[/pre]

你以为到这就完成了么,还早哩!我技术最高难度代表岂是这么简单就能搞定的?现在有一个判断是否碰撞的算法,但是他不能帮我们计算下面这种情况:
例如,当游戏时间在第一帧时,紫色是矩形物体的所在位置,而到了第二帧,矩形移动到红色框的位置,我们通过算法已知矩形与三角形发生了碰撞,但是:如何计算出矩形与三角形正好相切(绿色矩形)的所在位置?
逐像素去扫描?你觉得JS引擎是否能受的了这计算量?
QQ截图20201002230007.png
解决这个问题之前,我想先介绍一下如何将物理引擎接入到RM中。首先,物理引擎最终输出的是物体的位置。我们可以将RM里的Game_CharacterBase加装成碰撞对象。
Game_CharacterBase里要定义如下这么几个变量,才能让物理引擎运转起来。
  • 物体的当前位置px/py
  • 经过物理引擎修正后的位置rx/ry
  • 物体的速度vx/vy
  • 物体的质量m
  • 物体的弹力elastic
  • 物体的表面摩擦力friction

具体的思路就是每帧都根据物体当前的速度运算出当前物体应该运动到哪里了,然后再用碰撞检测算法把物体修正到一个正确的位置避免卡墙等问题发生。

RM里,Game_CharacterBase里决定该对象在屏幕中显示的坐标的函数是screenX()和screenY(),所以我们只要把物理引擎的运算结果输出到这两个函数里,就可以在RM里看到实际效果。
上面那个矩形和三角形的问题,我是采用了二分逼近法去避免逐像素去分析。

具体思路就是,将红色矩形拉回到距离紫色矩形1/2的位置,看看是否相交,如果是继续拉回离紫色矩形1/4的位置,否则,往反方向拉1/4的位置。循环8次,直到逼近最终的结果。




[/fold]

好,经过国庆这几天几乎没日没夜摧残身心的脚本编写和调试,终于重构了一个还算看得过去的运行在RPGMAKER MZ上的2D横板物理引擎。
说起重构,因为之前也有在RMMV上编写过2D物理引擎,所以这次已经是个人的第四代重构版本了。第二代版本发布于今年2月份,作为《喵可莉的兔玩偶》STEAM版本的彩蛋房间小游戏,但是因为被玩家吐槽手感屎,所以发布第二天就删除掉了。
第三代版本发布于今年8月22日,作为《喵可莉的兔玩偶》STEAM上的DLC内容的一个隐藏要素(小游戏)呈现给玩家,现今仍然可以游玩到。

以下折叠是第四代重构物理引擎实现的功能展示:

[fold=惯性和摩擦力]
1.gif
大家可以通过GIF看到我站在一个左右来回移动的飞板上。随着飞板突然改变方向,我的身体并没有立刻也跟着改变方向,而是因为惯性往原来方向滑动一小段距离,但因为摩擦力的原因最终还是和飞板的速度保持一致。
[/fold]
[fold=重力和跳跃]
2.gif
GIF里这个白毛猫猫就是我,我给自己写了二段跳和空中冲刺的能力。看起来有没有觉得手感不错?
[/fold]
[fold=地形和碰撞]
我最大限度的保留了RM原有的地图和事件功能,因此,RM的地图编辑器和事件编辑器仍然正常可用。
QQ截图20201005151536.png
上图是物理引擎功能展示所用地图在RM本体里的表现。并且,还可以实现6楼所述的特殊地形,如下图GIF:

3.gif
[/fold]
[fold=动量定理和弹性碰撞]
在这个物理引擎中,物体都有一个elastic(弹力)参数,elastic=0,代表该物体执行的是完全非弹性碰撞,反之elastic=1,该物体执行的是完全弹性碰撞。当然,也能为elastic赋值0.5代表这个物体碰撞介于弹性碰撞和非弹性碰撞之间。

值得一提的是,在游戏中,有一类物体虽然拥有碰撞判定,但是它的质量为无穷大,也不受任何外力影响。这类物体类似于上面的飞板,不会被任何碰撞改变轨迹。这类物体发生弹性碰撞时,处理会比较特殊,但还是遵循物理学法则的。
我们着重展示下质量非无穷大的物体的碰撞计算结果。

4.gif
上面的GIF,我的角色是白毛,质量m=1且elastic=0,然后NPC角色黑毛也是一个质量m=1且elastic=0的物体,我可以把她顶起来,推下去,但因为摩擦力和重力的原因,推动很困难的。

5.gif
这张GIF和上面的一样,但我把黑毛的质量m改成了0.2,根据动量定理,我能更轻易的把她推走,而且能顶的更高。
那如果我再把黑毛的elastic(弹力)参数设置成1会发生什么?大家请看下图。

6.gif
当然,如果黑毛是一个弹力十足的物体(elastic=1),那我们俩执行的碰撞就是完全弹性碰撞,而且黑毛本身也会在地板上“弹个不停”,这是物理法则,对吧!
[/fold]
[fold=感想]
我都快神经衰弱了,这东西是真的难做好。国庆五天没日没夜的就肝这个了,代码改来改去的。现在这个物理引擎也有局限性,而且我还只是挑好的展示,还有一些判定有BUG,我先躺会儿,晚上继续修。

之前做喵兔老是觉得自己是程序不会画画没文学功底做剧情游戏很揭短,好嘛,我们换个游戏类型。然后做了动作游戏才知道,这两种游戏都不是省油的灯,各有各的难点。说出来不怕你们笑,其实我是很想做和rabi-ribi,空洞骑士一样好玩的游戏的,想的是好,做起来可真要命。
[/fold]

评分

参与人数 3赞 +3 收起 理由
哇哇哇啊叭叭 + 1 感觉楼主肝到呲血
多啦A户 + 1 精品文章
百里_飞柳 + 1 大工程

查看全部评分

梦石
1
星屑
23236
在线时间
9564 小时
注册时间
2012-6-19
回帖
7018

开拓者短篇九导演组冠军

发表于 2020-10-3 00:04:12 | 显示全部楼层
那为啥要用rm做?直接用pixi自己搭一套呗
回复

使用道具 举报

梦石
0
星屑
8026
在线时间
1051 小时
注册时间
2012-4-3
回帖
1270

开拓者

发表于 2020-10-3 00:55:45 来自手机 | 显示全部楼层
感觉物理碰撞用公式总是不够用的感觉。毕竟不规则多边形书写难度呈指数型增长。如果碰撞可以像素化,我觉得可以参考参考a星寻路与黑白路径图。以图片的像素颜色判断攻击范围,读取范围,成为读取图片的像素颜色,或者透明度,这又有点像鼠标的透明度感应范围。如果可以在读取图片获取必要信息,而且能够保证效率的话,我觉得不规则多边形碰撞可以一解。

点评

像素范围也许可以通过控制比例处理一下微妙的效率关系,其实也不完全是1:1的像素比例,a星黑白路径图也有1:20的呢。  发表于 2020-10-3 12:06
像素点判断效率是真的低,我亲测过,基本上没办法满足60帧的需求。我用的是canvas.getImageData这个函数,不知道是不是我的问题。  发表于 2020-10-3 08:35
回复

使用道具 举报

梦石
2
星屑
5065
在线时间
706 小时
注册时间
2018-12-11
回帖
213

MZ评测员

 楼主| 发表于 2020-10-3 08:30:16 | 显示全部楼层
本帖最后由 lisliz 于 2020-10-3 08:53 编辑
喵呜喵5 发表于 2020-10-3 00:04
那为啥要用rm做?直接用pixi自己搭一套呗


很多游戏都会写一套自己的战斗系统,但很少有游戏会自己写一套OpenGL出来,对吧。

有些轮子需要自己造,因为做出好游戏必须走定制,但有些轮子可以用别人的。RM和PGMMV或者UNITY肯定多少都有能用的轮子,所以从pixijs自己搭(甚至从汇编自己写),就用不了这些不怎么需要定制轮子了。

所以这个地方就挺主观的,这个帖子不应该这样取名,搞得好像大家都用RM更好似的,对不起dbq可能给大佬造成误会了!用RM,是因为我更了解它,能更快把它的轮子用好。跟我说MZ天下第一!
回复

使用道具 举报

梦石
1
星屑
23236
在线时间
9564 小时
注册时间
2012-6-19
回帖
7018

开拓者短篇九导演组冠军

发表于 2020-10-3 08:51:59 | 显示全部楼层
lisliz 发表于 2020-10-3 08:30
很多游戏都会写一套自己的战斗系统,但很少有游戏会自己写一套OpenGL出来,对吧。

有些轮子需要自己造, ...

但pixi从概念上接近你说的OpenGL啊
mz或者pgmmv都是在这上面针对特定游戏类型包了一层

你不想用mz的默认游戏类型已经等于放弃它大部分已经包好的代码了……
回复

使用道具 举报

梦石
2
星屑
5065
在线时间
706 小时
注册时间
2018-12-11
回帖
213

MZ评测员

 楼主| 发表于 2020-10-3 09:07:59 | 显示全部楼层
本帖最后由 lisliz 于 2020-10-3 09:24 编辑
喵呜喵5 发表于 2020-10-3 08:51
但pixi从概念上接近你说的OpenGL啊
mz或者pgmmv都是在这上面针对特定游戏类型包了一层


我还是很想保留事件的功能和RM自带地图编辑器的作用的。物理引擎都尽量在Game_Map和Game_CharacterBase上扩展,尽量不破坏它原本的功能。

然后就是放弃一部分包好的代码,哎,真的我觉得必须放弃掉,不这样做不好游戏的。空洞骑士那么多好玩的技能,如果战斗引擎不是你自己写的你咋实现蹭墙跳,空中前冲,向后闪避这种好玩有趣的技能。哎,真的不是吃饱了没事干自己写一个出来显摆,你说不是自己写的战斗引擎。自己却有一万个好玩的技能和关卡设计想要发挥,一万个啊,各种各样的都有啊,不是自己写的战斗引擎你说咋实现,没法整啊。

我举个例子哈,我以前玩游戏玩过这种地形,就是这种土堆,上面能站人,也能从下面跳上去,还能从左边和右边进来。

QQ截图20201003092334.png

QQ截图20201003092324.png

这合理吗?这薄荷里!!!这个土堆根本不是普通的碰撞体,他只有上面有判定,底下是没有的,像这种恶心奇葩的需求以后关卡设计还有很多很多。简直了,如果用别人写好的插件我都不知道怎么实现。
回复 1 0

使用道具 举报

梦石
1
星屑
23236
在线时间
9564 小时
注册时间
2012-6-19
回帖
7018

开拓者短篇九导演组冠军

发表于 2020-10-3 09:16:14 | 显示全部楼层
lisliz 发表于 2020-10-3 09:07
我还是很想保留事件的功能和RM自带地图编辑器的作用的。物理引擎都尽量在Game_Map和Game_CharacterBase上 ...

我的意思是,game_map 和 game_characterbase 也放弃

点评

233,如果这俩都不用了,再加上战斗系统也不用,那RM90%的轮子都废了。这还是有点可怕的。  发表于 2020-10-3 09:30
回复

使用道具 举报

梦石
0
星屑
35248
在线时间
4171 小时
注册时间
2007-12-15
回帖
9489
发表于 2020-10-3 09:36:14 | 显示全部楼层
本帖最后由 89444640 于 2020-10-3 09:40 编辑
lisliz 发表于 2020-10-3 09:07
我还是很想保留事件的功能和RM自带地图编辑器的作用的。物理引擎都尽量在Game_Map和Game_CharacterBase上 ...


MZ那个地图素材总数量限制很烦,遮挡不分级更烦。
这种土堆应该能拉下跳下去的,从下面跳上去这个功能,反正xp那个act我是没辙了,只能平台左右留口,不能从下面跳上去,合金弹头也没拉下按跳跃式下跳一层的功能XD,但是人家关卡设计不一样不需要下跳。
然后我记得,如果是曲线运动需要算切线,什么sin之类的,动作游戏看上去是美工玩的,实际上全是程序在玩。
光画图像没问题,什么贴壁三角跳,蹭墙下滑所有互动我都能画出来,但是,rm程序里面,尤其是XP里面,很多时候,没法照gif模拟的那么好好运行,
比如贴壁,跳跃中左侧或者右侧顶到墙,继续按着方向键不放转化为贴壁,用小刀扎着墙慢慢下滑,然后再按跳跃会往反方向跳一段距离如果有墙重复,没墙下落,纯剧情没事,允许玩家操作打死我也写不出来。
写出一个靠谱的能兼容rpg模式的act引擎真是挺难的,尤其是自己看得懂的。

点评

设计越好,程序越强大,游戏就越好玩,比如以撒,脸黑没救系列XD,我还是老老实实玩文字游戏,玩家选择指令看表演吧XD  发表于 2020-10-3 09:48
是这样的,我真的就遇到过最难最恶心的系统就是这个了。  发表于 2020-10-3 09:43
回复

使用道具 举报

梦石
2
星屑
5065
在线时间
706 小时
注册时间
2018-12-11
回帖
213

MZ评测员

 楼主| 发表于 2020-10-5 16:13:05 | 显示全部楼层
顶一下,更新了这两天爆肝出来的rmmz搭载的2D物理引擎展示
回复

使用道具 举报

梦石
8
星屑
2872
在线时间
476 小时
注册时间
2010-9-11
回帖
480
发表于 2020-10-5 18:03:11 | 显示全部楼层
大神! 但是想问如果想要从物理开始做起... 为什么不要用Unity
逻辑学好的话甚至不用编程

虽然觉得把物理引擎导入RMMZ有点本末倒置 但是这世界就需要这种人:)

点评

就算用unity我也会写一套自己的物理引擎的,因为要更好的定制关卡内容和特色玩法,然后用RM只是情怀嘛,我喜欢用js系的rm  发表于 2020-10-5 19:29
Paku
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部