原文地址:http://bbs.9ria.com/thread-49066-1-1.html
目前还只有基础引擎部分,勉强算个demo吧…东方系列的同人 游戏
在效率优化上做了很大工夫,主流机器应该在10%以内。
(服务器最近老被攻击,请用firefox以防万一)
//初版
http://www.hellov.com/demo/superReimu/001
//4月22 _ 加入 地图部分、怪物、大地图、新的检测机制
http://www.hellov.com/demo/superReimu/002
wsad 移动 , j 攻击 k跳跃
-
随游戏的进度,一起讨论游戏的难点
本文主要介绍难点思路,只要你认真研究过act游戏,绝对有一部分是你所能用到的。授人以鱼,不如授人以渔。
简称:
tile = 游戏里的板砖,即地板。
我采用了60fps速度,一是提供客户端标准的流畅度,以及更敏感的效率观测
碰撞检测
act游戏第一个重点就是高效的碰撞检测,:
碰撞检测的思路一般就两种,基于绝对坐标系进行阵列式检测,这种方法被广泛 应用于老游戏里,比如第一版的超级mario。效率非常高,缺点是自由性较弱,无法做到任意形状的地面。
另一种就是像素检测了,可以提供很自由的地形,物件。现在电脑硬件很发达了,这些检测带来的消耗微乎其微。
经过几种检测思路反复测试,我采用的是getObjectsUnderPoint 和 bitmap的hittest 结合的混合检测
将所有参与碰撞的tile统一放入一个spite,使用sprite的getObjectsUnderPoint 获得 鼠标下面的objects数组集合,当长度>0,即产生了碰撞,这个检测是像素级的。
如果你的tile是shape或者旋转了的规则bitmap,这是毫无问题的,不论圆形,或者斜着的长方形,都会按照像素级取得正确的object集合。
而且getObjectsUnderPoint 效率是极高的,比遍历tile去进行bitmap.hittest要高,与hittestPoint是相当的。
但是,假如你的地图需要支持外载地图的话, flash将载入的透明图象会识别为全部都含有像素值,透明区域也不例外,虽然看不见,但其实只是透明度为0而已。
所以,当你载入一张透明图后,你会发现人物会站在图的透明部分。
所以如果要实现自定义的地图,我们需要对刚刚所得到的集合进行再一次检测验证。 这个就很简单了,先判断是不是bitmap,是的话,再进行bitmap.hittest即可。
再说一下我为什么用点与bitmap进行检测,人物的身体不可能只用一个点去检测,而也不可能用身体去进行像素检测(每个动作形状都不同)。
一般会想到用一个矩形区域放置在人物身上,以此矩形与地面的检测来判定。
但是,其实采用多个点进行检测,效率会更高,也更方便一些处理。 身体的检测点布局一般是这样:
具体检测方法就很简单了,人物的速度向量向右,那么就取右边那几个点,向上就取上面的。以此类推。
最后补充一下, 如果不需要实现自定义路面,而只通过矢量图或者绘图api绘制的位图来铺设游戏地板,那么你可以直接使用sprite的hitTestPoint方法,可以省去一个校验过程,碰撞检测效率大概会提高40%左右(不过相对于后期整个游戏,这个提高可能并不明显)。
游戏分层
游戏不可能只有一个tile层,那样会很单调,一般可能有下面几个层:
- 背景层
- 远景层
- 近景层
- tile层
- tile皮肤层
- 演员层(主角与怪物)
- 特效层 (法术、光影、天气 效果)
- 前景层(装饰用的花花草草,墙壁之类)
- UI层
- 遮挡层 (场景切换之用)
这里面有几个层是需要等速卷轴的,所以这里不得不提getObjectsUnderPoint 的 bug(或者本来 设计就是如此)
先来看两个demo
- private var pane: Sprite;
- public function Tester()
- {
- var tile: Bitmap = new Bitmap(new BitmapData(100, 100, false, 0 ) );
- this.pane = new Sprite();
- pane.addChild(tile );
- addChild(pane );
- this.addEventListener(Event.ENTER_FRAME, function(e: Event):void
- {
- pane.x ++;
- } );
- stage.addEventListener(MouseEvent.CLICK, onStateClick );
- }
- private function onStateClick(e: MouseEvent): void
- {
- trace(pane.getObjectsUnderPoint(new Point(e.stageX, e.stageY ) ).length );
- }
这段简单的代码里,tile 就是地板,pane就是装地板的容器,运行这个demo,点移动的黑块(其实是pane在移动),就会输出1。鼠标所点位置就好比主角脚下的点。
这里我们可以得到准确的答案。
- this.mainPane = new Sprite();
- this.mainPane.addChild(this.pane );
- trace(pane.getObjectsUnderPoint(new Point(e.stageX, e.stageY ) ).length );
其实不难发现,我们能点出“1”的地方还是在左上角,也就是 检测机制中所应用到的偏移量是以容器的坐标来决定的,而因为被装入了别的容器,自己的坐标一直是0,0,所以产生了以上结果。
于是,把等速移动的几个层一起装入某个“主层”这种很合理的做法就行不通了(除非你愿意遍历tile,但性能上会弱)。 这个问题耽搁了我一天时间,希望其他人不要再重蹈覆辙。
目前正研究地图的动态生成与关卡衔接,下次更新这部分
有空继续补,如有错漏疑问欢迎提出。
------------------------ 4月21 ----------------
这几天业余时间太少,游戏的进展有点慢,这些天总算把地图方面的结构和 算法敲定了。
就像我的回复一样,我现在把getObjectUnderPoint在 这个项目中否定了,引用一下我对闪刀浪子的回复:
在静态检测时我也没发现任何问题,但是当有卷轴时,我发现游戏主容器在高速运动的时候(即镜头跟踪),getObjectUnderPoint发生了巨大的偏差,这个偏差目测刚好是一帧的时间。
当我跳跃起来落下去的时候,地图容器因为镜头跟踪,地面会向上高速移动,在平面上走路的怪物居然会陷入地面中,(怪和人使用同样的检测原理),我反复排查,发现在刚才的那一瞬间,怪物是检测不到自己站在地面上的。这个情形只能想象一下…带有此bug的例子已经没有了。
也就是说,getObjectUnderPoint的检测存在延迟,原因我个人猜想是:
getObjectUnderPoint是以当前屏幕的渲染像素去判定的,所以当帧还未被渲染时,在逻辑中产生的判定是上一帧的,而bitmapdata的检测是基于内存中的 数据进行判定,需要输入相对坐标就是一个证据。
为了证明我的想法,我又测试了静态下的情况,结果也有偏差,参见我这个帖子:
http://flash.9ria.com/thread-50230-1-1.html
虽然很小,但是我认为已经没有理由再浪费时间在这个 API上了
现在我改用了新的思路,以下将慢慢道来地图是如何创建以及如何做到高效率检测的:
1。 利用坐标系拼成地图
首先,我从一开始就已经说了这个游戏的理念是要能自由编辑地图的,所以很自然要用到tile拼接。好处很明显:一个地图一般只有最多几十种地图块,反复利用后生成地图,下载量极小,并且也是 制作地图编辑器的基本要素。这个概念可能在RPG中会更常见一些。
有的卷轴游戏喜欢制作一整张地图,这样是不符合正统游戏 开发的,不推荐。小型游戏,或者不准备做成网络版的可以这样做。
2。 整合Tile为一张大图
这是我最初的所走的弯路,我将tile放入sprite,然后再检测他们。 实际上应当建立一张bitmapdata,按坐标将tile的像素copy过去。这样整张图的障碍信息都存在于这张图里了。检测时只需要用bitmapdata.hittest检测一次即可,省去了循环的时间。
bitmapdata.hittest非常精准,目前还未发现任何奇怪的现象发生。
这个思路给大地图的性能带来了质的提升:在接近6000个tile组成的7000*2000大小的地图中,cpu从 80%降到了20%。
另外提一下:不要怀疑copyPixel的性能,其速度非常之快。在上面的地图生成中,我根本感觉不到任何卡顿现象。
再另外提一下:为了再次提高检测性能,我去研究了卡马克算法,以希望降低卷轴消耗和检测消耗,目前结果似乎让人失望,有空继续研究一下。
参见此帖: http://flash.9ria.com/thread-50570-1-2.html
3。 突破bitmapdata的像素极限
极限是:最大宽度或高度为 8,192 像素,并且像素总数不能超过 16,777,216 像素。超出这个条件,会报错。
一般来说act游戏很难超出这个大小,不过为了更好的拓展性,我还是决定搞 定它。
方法就有点标题party了,只是创建多个bitmapdata连接起来。当宽度超过7000的时候就用一个新的bitmapdata继续渲染。至于高度我就不理会了,这是横版act,不是“是男人就下一百层”,7000的极限高度完全足够。(啥,为什么极限不是8192? 因为越接近这个值,越容易出现奇妙的现象,不信你创建一个,移动一下看看)
晚上来补图和部分代码
目前正使劲挤出时间研究AI和实时打击感。下次就更新这个吧(话说帖子要爆了,我删减了一点例子代码)