《C156 糖水俱乐部》的开发工作是从四月底开始的,但是整个四月期间基本是在做准备工作。这些准备工作包括制作暂时的占位素材、策划游戏的玩法和剧情、实现一些程序上的基础设施等等。
来到五月,我刚考完期中考试,挑战杯的事情暂时结束,而且还遇到了五一假期,五月是我最闲的一个月,所以开发进度很快。

有一说一,我不太会写开发日志没,我会把这个过程写得很无聊,像一篇产品说明书。B 站一些 UP 可以把游戏开发的过程拍得很有意思,很厉害。当你开始持续做一件事情的时候,肯定是会无聊的。构思的过程比实现要简单的多,对于游戏开发来说,实现的过程尤其是挺痛苦的😖。
功能开发进展
在整个五月,最重要的进度是完整地实现了对话和调酒系统,对话与调酒是游戏的核心玩法。
对话系统 DialogueParser
虽然 Godot 目前有功能很完善的的对话插件,但我还是选择自己开发一个对话系统。
这样做的结果就是,DialogueParser 几乎没有多余的功能,它一共只有 500 多行的体量,却实现了所有必要的功能,并且原生支持调酒系统。
DialogueParser 可以读取并解析 .txt 扩展名的纯文本格式对话脚本,然后在游戏中通过一行简单的命令 DP.play("filepath") 来播放这段对话(也可以从某个特定标签开始,若不传第二个参数则默认从头开始)。
对话脚本的语法很简单,参考了 Ren’Py 的 label 和 jump 语法,可以在标签之间跳转:
# 首先要对所有角色进行注册
define character Alex
portrait "res://entities/gameplay/counter/characters/alex.tscn"
animations default,happy
theme_color "#8DEBFF"
voice "res://assets/audio/alex_pop.ogg"
end
label start
Luca >> 晚上好。
Alex >> 晚上好。
Alex:happy >> 现在我在笑😄。
jump next_part在对话脚本的开头,首先要对角色的立绘、动画、主题色等信息进行注册,这样在之后编写对话的时候就可以直接使用了。这一部分也可以单独保存成一个纯文本文件,之后在对话脚本的开头就可以用“import + 路径”的方式重复使用角色数据了。
角色立绘的显示、隐藏都可以用一行脚本方便地控制。并且,这些脚本有对应的内联形式,可以在一句对白播放的中途执行。
show Alex at 946,698
play Alex default
hide Alex
Alex >> show(Alex,946,698) >> play(Alex,happy) >> 我来了。
Alex >> hide(Alex)丰富的内联脚本也是我使用 >> 进行隔断,而不是只在名字后面加一个冒号的原因。使用 >> 可以把一句话分成多段,并在对话中插入需要运行的函数。并且 >> 在正常对话中出现的概率很小,如果确实有这个需要,可以使用转义字符 \>\> 来在对话中显示 >> (应该没有这种需求吧)。
还可以使用 await 等待一段时间,如果参数为 0,则等待玩家的输入。
Luca >> 前半句, >> await(0.5) >> 后半句。
Luca >> 点击之后才会显示后半句, >> await(0) >> 这是后半句。和其他所有的对话系统一样,DialogueParser 也支持分支选项,通过 choice 就可以触发:
choice
- 这是选项一! >> choice1
- 这是选项二! >> choice2
label choice1
Alex >> 你选了选项一。
jump continue
label choice2
Alex >> 你选了选项二。
jump continue
label continue
Luca >> 继续吧。
当遇到 choice 指令时,选项 UI 就会自动触发。我月底的时候还给所有的选项添加了辉光效果,虽然图片无法表现出来,不过实际效果还是不错的。这是一个“GlowButton”类,自带一个脚本用于实现这个效果,用来替代所有的普通 Button 类节点。希望 Demo 发布之后大家可以来体验。
并且支持根据条件进入分支:
switch boolean
case true >> jump label1
case false >> jump label2此外,DialogueParser 支持进行相机控制。因为《糖水俱乐部》使用了视差背景效果,在 2D 世界中通过视差模拟空间感,所以为了实现一些演出效果,需要在对话过程中控制相机的位置和缩放 (zoom)。
camera move 960,540
camera move 960,540 0.8
camera zoom 1.2
camera zoom 0.8 0.6
# 也可以使用复合的指令
camera move to 960,540 with zoom 1.2 0.8语法非常简单,通过以上代码就可以对相机的位置和缩放进行手动控制。同时,相机也有自动模式,使用 camera auto 进入自动相机模式后,相机会自动选取合适的位置和缩放比例,把场景中的所有角色肖像都包含进来。
调酒系统
另一个核心部分是调酒系统。虽然我在开发时一般会认为对话和调酒是两个部分,因为调酒系统涉及了非常非常多的其他内容。

但调酒系统应该是对话系统的一部分才对,因为游戏的核心玩法是根据玩家的调酒结果进入不同的对话分支,调酒系统在某种意义上只是一个更复杂的分支选项功能。
order order_code
switch result
case correct >> jump correct_drink
case wrong >> jump failed_drink
case hidden >> jump hidden_drink
label correct_drink
Luca >> 你要的[last_drink_name],好了。
Alex >> 谢谢。
jump after_order
label failed_drink
Alex >> ……这不是我要的。
jump after_order
label hidden_drink
Luca >> 虽然这不是我要的,但是我喜欢这个!
jump after_order在对话脚本中,使用 oder + oder_coed 即可触发调酒功能,然后使用 switch result 进入不同的对话分支。
oder_code 是这次调酒任务的代号,详细的调酒要求存储在一个 json 文件中。调酒系统会收到代号,并从 json 文件中找到详细的要求信息,然后在玩家点击“上酒”的一刻,计算出最终结果,例如“correct” “wrong”等等。这个计算过程还挺复杂的,甚至这一部分的代码长度已经超过了 DialogueParser,所以我觉得没必要展开。


为了支持“原创饮品”的功能,每一杯酒都会被从甜度、酸度、苦度等等多个方面进行评分。在计算结果时,除了可以按照固定配方进行匹配,还可以通过判断分数是否处于目标区间进行匹配。
调酒系统有许多内置变量,可以在对话系统中使用方括号语法插入,例如 last_drink_name 表示上一次调的酒的名字,last_drink_ingredients 是那杯酒的配料。
Luca >> [last_drink_name],好了。
Alex >> 里面有:[last_drink_ingredients]?
Luca >> 技法是:[last_drink_technique]。这些变量其实存储在 GM 单例中,正常的语法本该是 GM.last_drink_name,不过因为调酒香港信息较为常用,所以如果一个内联变量没有点语法,则默认它来自 GM 单例。
其他内容
除了对话和调酒以外,还有很多细枝末节的小更新。我每天都有记录当天完成的内容,所以我还能大概记得我这个月都做了哪些可以归类为“其他”的工作。

除了那两个代码为主的部分,其他的更新都是和美术效果相关的。例如 VHS Shader、辉光按钮以及上文提到的视差背景效果,对提高游戏的质感都有挺大帮助。


例如这是在应用了 VHS 着色器并调色之后的效果。同时也优化了 UI 的显示。

这是查看历史对话的 UI,使用了类打字机的设计,可以将最后一行对话滚动到屏幕中央。
除了美术之外,这个月我还为游戏的翻译工作做了准备,并且在之后为游戏加入功能都时候,都要同时考虑到翻译的问题,这是为了避免翻译问题像滚雪球一样越滚越大。
别的就没有什么了。

六月的计划
以上就是五月的开发进展,进入六月,期末月,显然开发时间就没有那么多了。
我的打算是在六月先完善 Steam 商店页,尽早公开这个游戏,尽可能拿更多的愿望单,虽然完成这个游戏的时间可能还需要好几个月。
不过我本来给《糖水俱乐部》的定位就是全流程只有几个小时的小游戏,所以我也会控制这个游戏的体量,避免我尝试把我想到的所有点子都加入游戏。
商店页计划在端午节上线,敬请关注。

扮演调酒师夏洛特,为形形色色的客人们送上调和着酒精、糖霜与爱的鸡尾酒。
每一次邂逅与告别之间,夏洛特都将对漫长的人生有新的理解。


