Indie Game程序开发杂谈

Intro

一些杂谈

转眼间,我连研究生也已经毕业一年,我逐渐意识到“时间过的快”并非一种对当前时间流速的客观感受,而是一种在瞬间对已经逝去却又碌碌无为的光阴所感到的遗憾。四年在英国的生活如同一场奇妙的梦。现在我庆幸自己在无论在曼城的那三年还是伦敦那一年都没有忘记常出去走走,但要把我现在的状态那会给几年前的我看,想必我是不会满意的。因为那时候我总觉得,过去那么长时间,我总该学会画画,总该做出了一些自己的项目吧。但是那时候我肯定也没有意识到,人对未来的自己抱有期望是不切实际的,因为时间的流逝本身并不会造就你的成就,只会在皮肤上留下痕迹,能够成就自己的,只有那个在当下行动的自己。当我们离开校园,年龄与精力的那条曲线的斜率逐渐减小,能够实现自己未完成的事情的时间机会也只会越来越少,把梦托给未来的自己是一种不负责任,因为未来的自己,大概率比现在的自己受到现实的引力更大一些。你现在就是长大后的你,再往后,或许就是年轻时的自己了。

在毕业前的最后两三个月里,我陷入一种莫名其妙的懊悔,我懊悔自己在最好的四年中还是太碌碌无为了;我懊悔自己在英国甚至没有尝试过去融入中国人以外的圈子,没能认识更多的人,发展更多的关系;即使我一直懂得要去把握当下,在一个阶段结束的时候,也会懊悔太多的事情。在我需要去找工作的时候,我开始恐慌,仿佛一个人在法庭上被判刑后押进监狱的那种后悔:你浑噩度过了你在外的自由之身的那段时间,进去之后,你曾经所想追求的那些东西,都不再有机会了。这种对失去自由,失去追求理想的可能性的恐惧,填补了我每次在凌晨五点醒来时的焦虑空缺。好在那段时间公寓里还剩了不少的苏格兰威士忌和波特酒,让我在凌晨五点根本就醒不来(笑)。

要是有人说独立游戏开发是我这种懦弱的人逃避打工的借口,我大概很难否认。但谁让这件事又是我唯一能看到出路,能让自己的梦想有机会实现的一条路呢?好巧不巧,我手里又握着这样的一个机会:与Cheshirecrow合作的Echoes of Eta独立游戏项目,到底是这个机会更加难得,还是UCL应届毕业生的身份更加珍贵?我一直很难回答,但无论如何,当下的我会选择径直去做自己真正想做的事。但我也知道,现在的我有这样的选项是无比幸运的,甚至可以说是一种privilege,离不开父母的支持,以及遇到有能力又志同道合的合作者。我此时付出的一切努力和别人看来可能辛苦的东西,对我来说都令我欢愉,我此刻所做的每一件事情都不再是“为了以后”,而是成为了我的当下。当然了,独立游戏这件事情,肯定有不少失败的可能性,但对此我欣然接受,此时此刻能够全心全意投入在这件事情上,已经是莫大的幸运。

好了,胡言乱语时间结束,让我们来谈谈这个项目还有技术!

关于这个项目

这个项目大概从2024年初就开始了,但那时候我还没毕业,UCL的软工课程说难不算太难,但肯定说不上水,至少,那时候能让我专注在项目开发上的时间并不充裕。那时候项目的主程序甚至不是我,而是一个来自委内瑞拉的小伙Normar,他的代码说不上多好,但起码看得出来还是专业的,而我完全抽不出大块的时间放在项目上,一直到毕业前,对项目的贡献也都说不上充裕。直到去年秋天,我毕业回国了,Normar那边却因为国家的变动和他自己的身体问题,经济问题越来越难以支撑他继续做下去。直到冬天的时候,我们也很难联系得上他了,几乎宣告了他的退出,因此我不得不继承整个复杂的over-enginerring的代码库,试图在那之上继续编写功能。然而经过一个月的尝试,我发现在试图在他的基础上编写新的feature越来越困难,有些复杂的部分我根本无法理解,甚至到底那些代码能够正常工作,那些不对,甚至哪些根本就从来没有用都不知道。无奈之下,我们两个花了一晚上在白板上列出了当时所有的能够运行的feature,然后评估了完全舍弃所有当时的代码,重构整个程序部分代码的工作量,得出的结论是:如果我全职放在上面,应该只需要1个月,就可以还原当时所有功能。当然了,那时候距离我实习结束还有两个月,每天最多只有四五个小时能放在这上面,最终整个重构差不多直到我实习结束后的两周全职工作之后才大致还原了所有当时已有的feature,也就是差不多今年3月初的样子。

在重构时,通过对他的代码结构的反思,以及我自己在重构时遇到的问题,我总结了一些想法,我想这样的问题大概广泛存在于许多类型的游戏开发中。虽然我现在觉得我的想法可能也不一定对,毕竟这个游戏目前还处于prototype阶段,在后期项目成熟后我可能也会有不一样的结论,但就目前而言,我想记录一下这些经验之谈。这里我只想写一些很少有人说过的点,至于保持consistency,规范的naming convention或是勤写注释和doc这些都是些人尽皆知的老生常谈了,在此也就不作复述。

为什么游戏项目前期应该尽量避免工程化代码?

你无法泛化所有的情况

你无法泛化所有的情况,你越是试图去泛化游戏编程中的逻辑,你的代码越是臃肿曲折。接受一个事实:游戏编程的逻辑本来就是各种特殊情况的组合碰撞,你永远绕不过这一步。很多时候,硬编码并不那么坏,他们才是游戏编程中最实用的工具。

在构建prototype阶段,非常多的东西都处于一种”一个类型只有一样存在“的功能,比如说,你可能只有一种怪,一把枪,一个玩家主角,一种地图,一种…或许几种道具,anyway,在这种阶段运用程序代码的抽象化能力为武器,怪物,角色抽象为基类是一般程序开发中会想到的思路,但这种思路在prototype阶段尤其致命,在未来的程序需求尚不明确的情况下,自作聪明“假设”未来的情况是非常坏的思路,当你在试图总结“共通属性”并写一个共通的基类的时候,意味着你对于未来所有子类应当共享的属性和功能至少有一个基础的把握,但绝大多数情况下,对于有任何一点复杂性的游戏来说,这都是难以实现的,你越想要再基类中塞入尽可能多的功能覆盖更广的功能需求,想要在子类里只有最少量的代码,越反是会得到一个臃肿的基类和好几个甚至更加臃肿的子类。

利用面向对象编程中的派生与抽象是合理的,但是游戏编程天然的具有不按套路出牌性,假设游戏中的对象永远在一个固定的框架内行动必须要有相当绝对的前提(比如卡牌游戏),想要通过单个周到照顾所有情况的 控制器代码来管理所有情况,只会被越来越多的例外情况压得臃肿不堪,相反的,想要增加代码复用性和可读性的话,将基类或者控制器类提供的函数接口功能粒度最小化,然后利用这些反复去利用这些小粒度的接口来实现每一个你想要的功能。而不是试图将他们归纳为一个集大成者,以为精炼了功能,当你真想利用这个庞然大物的时候,只有无数的例外情况等你去特例化,你非但没有减少工作量,为了处理特例化,你需要更绕弯且难以理解的代码去handle。

Don’t do it until you need it, you can always do it later.

过度工程化的另一个特征,就是你写了大量的,全面的代码并提供了丰富的功能,但他们一大半都没有被真正使用到,那些代码可能在整个过程一次都不会被调用。这种过度工程化的危害有很多,比如同样的,代码臃肿,庞大,对源码库不熟悉的人会看起来非常confusing,自己甚至以后都会看不懂自己写的什么,etc.

但是我个人认为这些“预留功能”最大的危害是,这些功能可能从来都没有被测试过,你自己都不确定他们可以如预期般运行。而随着代码版本更迭,他们就存在于那里,只要没有编译错误,好像就也能和其他经过测试的代码一同运行似的,他们也不会被跟着一起被改进,直到某一天你发现了它,或在无意间被使用了,它才第一次被运行,而这个模块的代码可能从一开始就无法正常运行,也有可能因为游戏的各种特殊情况使它无法如期运行,甚至整个代码的逻辑都是行不通的,无论如何,它大概率无法正常工作,因为他从来也没有work过。

你可能会担心,如果有些接口或是功能没有预留出来,未来需要修改它的时候会不会造成麻烦,甚至需要重构一部分的代码,事实上,你完全不用担心这一点,当你需要拓展你现有的代码(比如一个方法需要新增一个参数),在你加上它,然后修改(重构)所有这个代码的引用时,你一定会知道你自己在做什么,因为在你真的需要这个功能的时候,你自然知道你需要加上什么。这种小范围的重构的复杂度与工作量其实远没有想象的那么复杂,他可能只需要你查找那个符号的全部引用并作几个重复的修改,这种工作量比起找出代码中由缺乏维护的古早代码所带来的莫名其妙的bug,可小太多了。

插件 or 手搓?

我与我的主美Cheshirecrow在早期经常意见不同的一点就是,到底一个feature用插件更好还是自己手写更好,通常我的主张一般都是自己手搓,因为我觉得自己搓的代码才看得懂,易于控制。而他则一般会倾向于选择时间性比价更高的插件来实现一些简单的功能。不过到现在,通过各种尝试,我们都在自己的意见坚持上,动摇了不少,意见也就都不那么绝对了。

一般根据过来人的经验来讲,prototype讲究快速出成果,因此怎么快怎么来,那corgi engine或者game creator这样的低代码甚至开箱即用插件便成了首选。但我后来发现,人们对prototype和demo的定义并不太统一,第一步先做个demo演示下游戏的理想效果的开发者也不在少数。还有像我们这样把prototype当alpha版本做的(因为游戏玩法比较特殊如果不做细演示不了),很难说做完一个最低限度的prototype就把整个游戏丢掉,好像是个game jam做的半成品都不是的东西似的。那既然这样,项目工程文件大概也会被一直用下去,那一个草率的用插件堆出来的半成品就显得不太合理。

话说回来,手搓一个feature对于项目来说,其最明显的劣势就是时间消耗。比起使用插件花个几个小时研究清楚就可以即插即用,手写的feature可能需要几天甚至一两周,大概率功能不如插件全面,自己的代码大概还不如插件中的被广泛测试过的代码稳定。但是最终在一个成熟的项目中,还是有大量的代码需要手动编写,即使甚至有部分功能可能存在于插件中。为什么我们仍然需要手写许多功能呢?因为插件最大的劣势就是它的使用限制会限制你的发挥。

在使用插件享受它带来的便利性时,它可以一直帮助快速实现你想要的任何功能——直到有一天你发现有个功能用这个插件无论如何都实现不了,而你已经有大量的功能依赖于这个插件,现在又不得不依赖这个插件时。如果插件本身有bug或者与你的游戏开发环境不兼容,你甚至不得不为它写大量的代码来从外部修复它本身的问题。在部分时候,尝试兼容这个插件或修复这个代码本身花出去的努力甚至大于从零开始实现,而此时你的代码结构已与那个插件高度耦合,此时再考虑绕过插件甚至摆脱插件去重构只会更加话时间。

 

No Comments

Add your comment

Translate/繁简转换