这里是SpaceWar游戏开发的一些感想。游戏视频可以在这里看到
这个游戏是为了参加频道内1MGames游戏开发比赛而制作的。从2月2号开始铺底层,断断续续地做到18号,差不多做了十天。
游戏内容方面
当时刚开始做这款游戏是想着做一个ASCII Sector
的复刻版本。那个游戏也是我很喜欢的一款游戏,不过人家全屏都是字符,而且是回合制的(老Roguelike了),而且人家不是开源的,在2012年作者就断更了。所以我其实一直想做个他的实时的复刻版。所以这游戏一开始的命名是Space Sector
。
因为游戏只能在1M大小内,所以一开始就打算使用TileSheet,每个Tile都是8x8的,不含有声音。说白了就是打算纯纯靠代码硬抗(毕竟咱也只会写写代码)。
我自己本来是有个SDLEngine,但是那个太大了编译出来就3M了(其实开Release和编译器优化,然后再拿upx压一压能压到400KB,不过当时还没接触这些),然后我就放弃了打算从头再整个小引擎,就选了glfw+glad的组合。花了四五天时间搞了个tinyengine。
然后做着做着发现,ASCII Sector
东西怎么这么多啊(我本以为有ECS在手可以很快地搞出来),其中主要是卡在了地牢生成的部分,因为ASCII Sector
他的设定是有很多星球,玩家可以在这些星球之间跃迁,在各地进行交易和接任务。然后因为他的星球太多了,我要做的话还得整个地图编辑器,所以我就打算改变一下,整个随机地牢,玩家可以在地牢里面挖矿,然后拿到其他星球去卖(没错就是我DigAndFight游戏的内容😁),但是这方面花了我挺长时间(主要是迁移DigAndFight的代码)。然后我搞到一半的时候感冒了,在家躺了三四天🙄
等我好的时候,我感觉这个计划8大行,没多少时间了,所以我放弃了复刻。当时已经做了飞机的移动,我就想要不干脆就做成空战得了,所以又做了子弹,然后两种飞机,雷达地图等东西,一直搞到18号完成。
其实一开始是想做塔防类的,玩家有个能量塔不断地给玩家积攒能量,玩家可以花费能量建造防御工事,然后去进攻别人的能量塔(能量塔的图像废案还在TileSheet里面),但是时间实在是不大够,所以改成做空战了(当时还有许多bug没调)。
其实游戏中的FreightShip
翻译应该是运货机而不是防御机,因为一开始复刻的ASCII Sector
需要飞船运货。
做的过程中感觉只有这点东西不行,所以又把miniaudio抓来加了声音。
我在开始做的时候一度认为会超出1M,要用upx压一压,结果做出来之后带资源才900KB多,属实是高估自己了。
技术方面
这次制作只能说成也ECS败也ECS。众所周知ECS是个非常灵活的系统,开发者可以自由地为Entity添加组件。一开始的开发进度很快,这归功于ECS系统。
但是等我代码多起来的时候就开始乱了,具体有三点表现:
-
增加新组件的时候会有功能和其他组件重合,并且组件和系统之间的调用太杂乱了。这个我承认是我自己设计的问题。
-
每次使用组件的时候,都需要先查询一次是否存在此组件,不然会有未定义行为(访问了未定内存)。这里有两点坏处:
-
每次使用组件前都需要查询,查询会搜索一次Hash表,然后如果找到了,紧接着调用
Get
或者Use
方法时会立刻再找一次表,很浪费时间(事后想了想其实可以缓存的,但是当时急于赶进度懒得加,后面这个时间浪费就表现出来了) -
程序中有很多指向
Entity
的指针,比如控制器。但是当一个Entity死亡后,他不会被销毁内存,而是放入内存池中,他的组件也会回到内存池,但是会被置为空。这就导致控制器其实并不知道自己控制的Entity是否死亡了,必须每回合通过IsAlive()
方法来查询。这就导致增加了很多的IsAlive()
语句。
-
-
有时候会莫名其妙地使用重生的Entity,这是因为Entity回到内存池然后又被拿出来的原因。但是这个时候Entity所代表的的实体可能已经完全变化了(可能上次是飞船,这次是子弹),所以会导致程序混乱。主要是因为我已开始太自信了,没有加上
IsAlive()
在实体死亡的时候替换他。
而且我在编写的过程中逐渐意识到,其实我并不需要ECS系统,我老老实实用Sprite和继承就可以了,因为我发现在代码逐渐增加的过程中,ECS本身就趋向于可以使用Sprite表现的情况,而由于ECS的存在我的代码反而更加凌乱了。
然后是关于智能指针的思考。确实,我们应该尽量地使用智能指针。我现在已经开始大量用unique_ptr
了,但是这次的制作让我看到了shared_ptr
的力量。很多时候其实用智能指针就可以解决问题,但是我自己传递裸指针造成了很多麻烦。