我的第一个电子游戏磕磕碰碰做了一个月,现在终于在 itch.io 上发布了。尽管整体仍有很多不足和值得改进的地方,但作为我的第一次尝试来说,我已经很满意了。于是,我想要写一篇文章回顾和总结过去一个月制作《Heart Hunter》的体验和心路历程。

在此之前,如果你感兴趣,欢迎在 itch.io 上下载《Heart Hunter》,支持 Windows 和 MacOS。为了在 itch.io 上发布,游戏默认是英文的,你可以在游戏内按下 Ctrl/Command + L 来切换语言。此外,这个游戏其实也没有太多的文字,所以完全不用担心自己会看不懂。


3/18 23:04 更新:现在你可以不用下载,直接在浏览器上游玩!如果画面体验不佳,可以点击左下角的 Go Fullscreen 全屏游玩。


这是一个 8-bit 风格的 Rouge-like 游戏。在这个游戏里,子弹、血量和道具都是同一个东西,也就是各种不同类型的心脏,这意味着,玩家需要通过攻击敌人来获取更多的子弹、同时也补充血量,攻击敌人时实际上也在伤害自己,为了实现效益最大化,玩家也需要合理地管理自己的心脏库存。

  • 移动:按下 W、A、S、D 进行移动
  • 射击:左键单击鼠标进行射击
  • 近战攻击:右键单击鼠标进行近战攻击,距离很短、伤害不高,但是击退效果显著
  • 选取子弹:按下 Q/E 前后移动来选取要射出的心脏(被选中的心脏会显示得稍微大一些)
    • 按下 Ctrl/Command + Q/E 来快速移动到最前面或最后面
    • 滑动鼠标滚轮来快速移动选择

另外,只是提醒一下,游戏画面里,墙上那个稍微粗一些的白条,那个是门(因为我在线下找人试玩的时候几乎每个人都没认出来那是门😭)。


灵感来源

去年年底我写了一篇《A Librarian Heart》,文章内容与今天的话题其实无关,但标题这几个字让我有了一些联想。我当时正在神游,试图想象一个图书管理员的心脏会是什么样子的,或者说,什么样的心看起来才会让人联想到一个图书管理员?

我想到了一个暗绿色,有着墙纸一样条纹的心。

这种奇怪的想象几乎只能存在于电子游戏里,像是某种游戏道具,也正是因为这样的联想,我有了 Heart Hunter 最初的点子。这种联想的跳跃程度至今让我摸不着头脑——我是怎么从一篇略带悲伤的散文的标题,联想到一个通过射出心脏来攻击敌人的电子游戏的?

不管怎么样,我有了一个让我觉得很有意思的想法,那个时候我还卡在 Warstro 这个已经废弃的游戏项目上,当时用「快点做完这样就能做下一个了!」这样的想法来试图给自己打鸡血,结果根本不起作用。追求新鲜感兴许是我这个还不成熟的小孩子不得不顺应的规律,于是我告诉自己:与其卡在一个死胡同里,不如先跳出来干点别的!

关于游戏引擎

我接触到 LÖVE 引擎应该是因为《小丑牌》这个游戏,虽然我没玩过,但在得知它是用 LÖVE 引擎开发的之后,我就去了解了一下 。我对 LÖVE 的第一印象很好——轻量级,API 非常清晰,没有复杂的图形界面1,跨平台,很适合原型设计,也有不少人用它做出了成功的游戏。

LÖVE 引擎支持的语言是 Lua,在第 20 期周刊里提到,Lua 是一个发展非常缓慢、轻量、简单的编程语言。使用下来,我发现它和 Python 的语法有些相似,编写程序的时候也有初识 Java 时的那种爽感2,又没有写 Java 那么啰嗦。用 Lua 编写模块非常简单,只不过实现面向对象有些麻烦(这对游戏开发来说应该是劣势吧),但整体上还能接受。

至于之前的 Warstro,我算是在技术选型上干了件蠢事——我他妈用的是 React,因为觉得自己比较熟悉 Web 前端开发,所以选用的游戏框架是基于 React 的 boardgame.io。同样又被第 20 期周刊中引用的那篇文章说中了,JavaScript 就是一门发展速度过快而带来了很多问题的语言,我记得我在开发 Warstro 的时候花了不少时间来处理依赖问题,boardgame.io 这个框架也有些时间没更新了。另外,HTML 和 JSX 等类似的语言或数据结构,真的不是构建游戏 UI 的好形式,我算是吃到教训了。

在挑战 Unity 这个大家伙之前,我大概也会继续用 LÖVE 和 Lua 语言做一些游戏。开发《Heart Hunter》的过程也算是我和工具进行磨合的过程,引擎和语言都是我边做游戏边学的(自然也拖慢了项目的进度),吃到了不少教训。继续重构或者优化原有的代码我应该是没有耐心了,但下一次肯定能少踩很多坑。

意料之外的 8-bit 风格

在 itch.io 上发布并给游戏打标签的时候,我才意识到自己做了一个复古(retro)的 8-bit 风格游戏。之所以选用像素风格的贴图,是因为自己完全没有绘画基础,像素画的门槛相对较低。

由于不知道怎么画心形(是的,我真的不知道像素画要怎么画一个对称的心形),所以我在 Emojipedia 上找到了 SerenityOS 这个开源操作系统内置的 Emoji 图片。SerenityOS 是一个开源的操作引擎,以 BSD 协议授权。由于项目的主要开发者也单独开源了 Emoji 字体,所以我可以在遵循开源协议的基础上使用这些图片。

SerenityOS 的特色是 90 年代风格的复古用户界面,它的 Emoji 图片也是几乎不超过 10x10 大小的像素画。众所周知,Emoji 字符中有各种各样的心形符号,我从中选取了几个拿来用。

其实在选用这些图片之前,我根本没有意识到 SerenityOS 是一个复古风格的操作系统,我当时只是想要找一些像素风格的图片素材。

也不知为何,有可能是因为 LÖVE 引擎默认的背景色是纯黑的,让我无意识地联想到了《Undertale》。在选取背景音乐的时候,我也下意识地选取了一些类似风格的配乐,就连音效也有不少是听起来很不真实、明显是老式电子游戏里才会有的声音。后来我才知道那些音乐上标注的 chiptunearcade 标签是什么意思,其实就是常说的 8-bit 风格。

为何肉鸽

「肉鸽」是 Rouge-like 的非正式音译,其实应该叫「类 Rouge」游戏。Rouge 是一个上世纪八十年代的游戏,它的用户界面已经超出了现在人们所说的「复古」的范畴了。

最初的文字版《Rouge》

类 Rouge 游戏指的是玩法与《Rouge》类似的游戏,通常包括很强的随机性,例如随机生成的房间、随机出现的道具、随机发生的事件等等。肉鸽游戏的另一个重要元素是永久死亡(permanent death),一旦玩家死亡,这局游戏就结束了,一切都要重来,玩家要面临的又是一个随机生成的地图。

我玩过的肉鸽游戏其实不多,有《以撒的结合》《咩咩启示录》《地痞街区》和《挺进地牢》3,《元气骑士》也算轻肉鸽游戏(rouge-lite)。有一位不常玩游戏的朋友在试玩《Heart Hunter》的时候跟我说「这好像《元气骑士》啊」,因为玩法的确都是类似的。

那为什么想做肉鸽游戏呢?其实就像我也不知道为什么这个游戏做成了 8-bit 风格一样,当我开始做这个点子的时候,我下意识地就想到了「这应该是个肉鸽游戏」。也有可能是因为,射击要素让我第一时间认为这是一个战斗游戏,而我最喜欢的战斗游戏类型就是带有一些探索要素的 Rouge-like 吧。

《Heart Hunter》的肉鸽要素其实不强,因为它没有「随机道具」和很多肉鸽游戏都有的逐层深入(随着探索的关卡越多,之后的关卡会越来越难)的机制,而且整体的难度不高。

也因为我第一次设计这样的游戏,游戏的难度其实没有得到很好的把控。尤其是在游戏后期,当玩家探索过 10 个以上的房间之后,玩家积累的心脏数量就足以他轻松应对大部分房间内的敌人了,几乎不会有压力。

我之前想要设计一个动态难度系统,但那样的变更需要修改不少基础代码,改动代价有点大,这也是我在开发初期没有确定需求导致的。

恰到好处的拉扯感

在《Heart Hunter》中,玩家用来攻击敌人的是心脏,而心脏同时也是自己的血量,不少心脏也拥有特殊效果,能够作为提供增益或者控制敌人的游戏道具。这个设计把血量、弹药和道具三个游戏元素组合在了一起。

我其实没有太多的思考,所以一开始我并不确定这是不是个好想法,我甚至不知道他是否足够新奇,不过最后的效果还不错。

由于玩家一开始拥有的心脏数量是有限的,在前几个关卡攻击敌人之后,就不得不面临补充弹药的问题。敌人死亡后会掉落心脏,此时作为远程射手的玩家又不得不走进敌人堆里去捡掉落的心脏来为自己补充弹药,同时要避开其他还活着的敌人,避免扣血,得不偿失。

由于不同的敌人血量和行为有差异,所以在攻击之前,不得不仔细考虑自己应该先用现有的弹药攻击哪一个敌人。如果把原本不多的弹药都用到了一个血量很厚的大家伙身上,而这个怪物没有被杀死,所以没有掉落心脏来补充自己的弹药,就会陷入窘境。

此外,有的心脏还有特殊效果,比如闪光心脏可以眩晕敌人并给自己加速,适合逃跑;紫色心脏可以反弹并攻击多个敌人,适合找准角度攻击成群的敌人。这样的机制又鼓励玩家谨慎使用并有方法地管理自己的心脏——要不要把这颗心脏留着?这颗心脏对付某种类型的敌人会不会更有优势?用处不大的心脏占位置,要不要优先用掉?

由于玩家被攻击时消耗的永远是最后一颗心脏,而不像射击时可以自由选择,玩家在获得某些心脏时可能需要更小心谨慎一些,或者在拾取心脏时就制定好策略,最后拾取没用的、可以用作防御的心脏。

总之,我认为这种时而需要走近敌人、时而需要拉开距离,鼓励玩家根据有限资源和当前情况以及对未来的预测进行合理规划的机制,能够提供一种还算有趣的掌控感和拉扯感。

资源溢出和难易平衡

说完优点,来谈谈不足。

其实都不用玩到后期,在熟悉操作之后,玩家能很容易囤积用不完的心脏资源。当资源过剩,前文提到的管理就没什么意义了——反正心脏都有伤害,随便射就完事了,射完还能再捡一大堆。

会造成资源溢出的原因之一,是不同房间(关卡)的设置不太合理——有的房间非常简单,能用极低的代价获取较多的心脏(比如只有“图书管理员”存在的「书店」关卡);有的房间需要玩家应付数个皮糙肉厚的大家伙,但消耗和收获都不少。

另一个原因是敌人的血量设计不太合理,由于担心玩家没有足够数量的心脏击败敌人,所以大部分没有特殊效果的心脏都被设计成「可以一击杀死大部分敌人」,而这样的心脏也是最容易获取的。就算是那些血量较厚的敌人,也能用两到三颗心脏击败。

总而言之,这是一个数值设计上的问题。由于我自己是一个游戏开发新手,而这个游戏的机制本身也有些难平衡——如果子弹伤害太低或者敌人血量太厚,玩家就没有足够的子弹击败敌人;如果反过来,又会让游戏变得太简单。

不过,目前玩家打过 10 个房间就出现资源爆满的情况,大概是因为后者的情况。我大概还需要一些时间来调整数值。

近战攻击的引入

在游戏开发的初期,我遇到一个小问题——当玩家只剩下一颗心脏,射出这颗心脏玩家就会立刻死亡,但这颗心脏又是玩家所剩的唯一一颗子弹了。在这样的设计下,当玩家只剩一颗心脏,而地上又没有掉落物时,其实就已经宣告了死亡。

这种情况其实并不罕见,于是考虑过后,我加入了近战攻击——玩家可以不消耗心脏短距离攻击敌人。即使是在只剩一颗心脏的绝境下,玩家也有机会翻盘。

难搞的是,这又会导致一个数值设计的困境——如果近战攻击伤害太高,玩家可能不需要射击心脏就能击败敌人,这还有什么意义呢?如果伤害太低,又怎么指望玩家在残血的情况下用近战反败为胜呢?

最后我是这样写的:

damage = damage + dmgCoefficient / (1 - math.exp(-#self.hearts / 5))

用指数衰减的方法,让近战攻击的伤害根据心脏数量递减。递减的只是伤害的增量,即使是在玩家持有很多心脏的情况下,玩家的近战攻击依然有最低的保障(当然这个伤害也不高)。在只有一颗心脏的极限情况,玩家的近战攻击可以打出 65.17 的伤害(大部分敌人的血量为 100,最低的有 50,最高的有 250);在拥有 10 颗心脏的情况下,玩家只能打出约 20 的伤害。

同时,我还加入了「击退」效果,这样一来,近战攻击就不只是在特殊情况下才会使用的、远程攻击的下位替代了。玩家在拾取心脏时,也可以将敌人推开来给自己开路。

关于音乐和音效的一些思考

因为自己完全不会做音乐,一开始也没有去免费的音频网站上找免费的资源,所以我在小群里发布的第一个 Demo 版本实际上没有任何声音。那个时候我光是把功能做完,把 bug 修好,就废了很多精力了,也没有考虑过声音对游戏的影响。

我发现,实际上是在添加了战斗的背景音乐和击打的音效之后,我才开始觉得「这个游戏有点好玩」。

我认为原因可能是敌人死亡的、清空一个房间、出发某个效果所提供的视觉反馈,还不够直观和明显,尤其是画面本身就不够精美的情况下。在玩家清空一个房间之后播放通关音效,在击杀敌人之后播放敌人倒下的音效,能够提供及时且直白的正反馈。

在玩家不熟悉游戏机制的情况下,可能并不明白画面上某个贴图的消失意味着什么,但播放上扬的声音,能让玩家意识到——这是一件好事!

在一些比较难、出怪量较大的关卡中播放节奏感很强的音乐,也会让游戏体验变得愉快。就我自己的感受来讲,这样的音乐会让「如临大敌」的感受变成「又要爽快地大战一场了!」。不过,关于这一点,我还没有太多的思考,只是有这样感性的认识。

对游戏编程的理解

我在学校里学到的、以及自己从初中开始自学编程的软件设计经验,在游戏编程中,有很多都不适用了。能够迁移的,大概在于分层架构和模块化的思想,比如在通常软件开发中的表现层、业务逻辑层和数据持久层,到了游戏编程中,就变成了渲染层、游戏逻辑层和管理游戏数据和对象的层次。

不过,这样的分层对我来说似乎依然不够清晰,因为仍然有很多东西我不知道放在哪里,比如音频播放属于表现层和渲染层吗?输入设备与游戏的交互要怎样处理,放在游戏逻辑层合适吗?

逻辑层下也有很多需要细分的,比如地图生成的逻辑、物理逻辑、敌人生成、游戏状态的判断和处理、什么时候进入 Game Over 画面等等。游戏数据又有两个部分,一个是设计好的数值,比如敌人血量、子弹伤害、特殊道具效果等等;另一个是游戏进行时的数据或者存档,比如当前房间里有哪些敌人、有哪些子弹在飞、地上有什么掉落物等等。

毫不夸张地说,我有一半的时间都花在重构各种代码上了,因为游戏编程经验的缺失,我踩了不少坑。不过,下次我或许应该优先关注功能的实现,而非翻来覆去地优化代码结构,否则会陷入「过早优化」的困境。

角色设计

敌人的设计其实不是我在开发《Heart Hunter》时的重点,游戏中大部分的设计(其实是所有)都是我在上课摸鱼时直接在 Resprite 上画出来的,没有打草稿,总之是非常随意。

Lancer 的想法其实来自 Toby Fox 的《deltarune》,其中有一个主要角色也叫 Lancer,只不过到了我这里,Lancer 就和它的原义「枪骑兵」没有一点关系了。只是因为 Lancer 的读音和中文的「蓝色」很像,所以就画了个蓝色的球(?)。

小贴士:Lancer 头上的帽子无论如何都不会移动,这并不是因为它的脑袋摩擦系数几乎为零,而是因为 Eltrac 懒得画各个方向的帽子。

Librarian(图书管理员)的想法就来自文章最开始提到的《A Librarian Heart》这篇文章,不过这里的图书管理员和那篇文章里的图书管理员完全不是同一个东西。游戏中的图书管理员是一个书呆子,把书背在身上用手脚爬行。我也是画完才发现,它的形状和颜色都非常像一只乌龟。

小贴士:由于图书管理员在书架中间走习惯了,所以它只能横着走。这是病,会传染——用图书管理员掉落的绿色心脏攻击敌人,会让他们无法上下移动。

Spiger(蜘猪)是我高中时期就有的一个游戏生物点子,我觉得这个简单有趣的设计非常适合作为游戏中的小怪。画画的时候真的让我找回了高中在草稿纸上乱涂乱画的感觉。

小贴士:由于蜘猪体型巨大,所以需要更大的鼻子呼吸更多的空气才能保证身体功能,所以它的脑门上进化出了猪鼻子,这很合理。

Citrus Liluton 的名字,前半部分意思是「柑橘」,后半部分取自「琵琶鱼」,也就是灯笼鱼的拉丁语学名的后半部分。这是一个长得像橘子的和灯笼鱼的、在空中飞行的生物。我的想法是:它拥有优雅的名字和丑陋的外表,头上的悬挂的才是它真正的眼睛。

小贴士:Citrus 身上的黑色线条不是眼睛,那是它让图书管理员帮她用记号笔画上的。

Pokob 的名字是一个大概没几个人觉得好笑的小玩笑,是「Is it paint or ketchup or blood?」(这是油漆还是番茄酱还是血?)。一个装满神秘红色液体的会走路的瓶子,难道不让人感到好奇吗?Pokob 是血量最厚的怪物,随着血量的减少,它身体里装的液体也会变少。

小贴士:用鸡蛋撞石头,哪个先碎?答案:用心脏撞瓶子,心脏先碎——Pokob 受到心脏攻击时,会掉落一个碎裂的心脏。

关于这些角色掉落的心脏,大部分是与他们自身配色相同颜色的心脏,同时也与他们的行为和设计有关。比如蜘猪掉落的紫色心脏能够反弹,掉落的另一种黏黏心脏能够减速敌人五秒;Pokob 概率掉落的大血泵,能在击中敌人后像液体溅射一样,像八个方向分裂射出子弹。

不过,我觉得道具的设计其实仍有很多不足,比如黏黏心脏和闪光心脏的定位有些重合,显得很尴尬,而且,在一个射击为主要攻击方式的游戏中,减速和眩晕敌人兴许是没什么必要的。4


最后

感谢你读到这里!如果你有什么建议和反馈,欢迎到 itch.io 上《Heart Hunter》的页面中留言,或者在这篇文章下评论,任何建议都会很有帮助。


  1. 敲了这么短短几年代码,突然发现图形界面对我来说居然成了一种劣势 ↩︎

  2. 爽感可能来源于 # 运算符这样的伟大发明,为什么其他语言没有设计一个快速计算列表长度的运算符呢? 这明明是一个非常常见的操作。 ↩︎

  3. 说来有趣,《挺进地牢》是我在做《咩咩启示录》中文维基的时候,咩咩启示录贴吧吧主送给我的 ↩︎

  4. 游戏其实还有一个小 bug,闪光心脏提供的加速效果可以在 10s 内无限叠加,玩家可以通过连续射出多个闪光心脏来达到不可理喻的速度 ↩︎