《名为怪物的游戏》序章 Demo(ver 0.3)

序章说明

本次更新衔接了之前的 FC 小游戏篇,进入到游戏的正式篇(序章部分)。
序章讲述了主角 Ace 与女主角瞳相遇,并且进入到「Monster」之前的故事。

Demo 下载

此处为演示版的下载,请优先选择最新版,即版本号比较高的下载。
如果测试过程遇到 BUG,请联系 QQ:874811226 反馈。

v0.3.1

6月26日更新。

Windows 版:点击下载
Mac 版:点击下载

更新说明:

  • 修复对话角标不动问题
  • 减少部分调查事件对话内容
  • 修复对话框可能在不同分辨率显示不全问题

经过测试 Windows 10 发现了对话卡死问题(非必现问题)如果遇到这种情况请联系 QQ:874811226 反馈。

v0.3.0

本地下载:点击下载
百度云:https://pan.baidu.com/s/18ALimW9GmS8UXlqhpRdd_g 提取码:s5co

请下载最新版。

更新说明

目前的 Demo 版使用的素材均为临时素材,部分图片是草图甚至缺了一张 CG 用文字代替了。
因为正在赶进度,所以素材会比较潦草,不过到了正式发布的时候就会重新绘制。

小游戏从 cocos creator 移植到 unity 花了快一个月,接着制作序章基本是爆肝模式,从 6-11 开始,前两天其实就完成了,总的制作了差不多 10 天,从零开始构建了游戏的基本系统,然后测试加调整又花了两天,总的来说,这次的制作速度提高了不少
其实也就不到十分钟的长度而已,游玩十分钟,制作花十天。

这是什么人间疾苦!

小游戏关卡

进入游戏会先进入小游戏关卡,根据玩家通关与否会触发不同的剧情对话。
其实小游戏跟正式篇没有什么必然的关系,只是用来纪念我和 Cee 小时候玩 FC 游戏的童年而已。

星之魔女小游戏

场景系统

游戏的地图采用横版移动,在 RPG 游戏十分少见的类型。
因为这部新作本身就带有解谜要素,横版场景更适合「调查」,而且场景效果看起来也会比较好。
除此之外,采用横版地图还有一个考虑就是减少跑地图的时间,让玩家专注于剧情和玩法。

Ace房间场景
客厅场景
杂物间场景

对话系统

带有人物立绘的对话系统。

对话系统

事件系统

在场景中按[Z]键即可进行调查,因为横版的关系可能会出现多个调查对象重叠在一起的情况,所以我临时做了一个选择调查对象的 UI。

选择调查对象

后续计划

序章还没有接触到游戏的核心玩法,要等到下次更新的「第一章」。
提前发布序章 Demo 主要还是为了让等了很久的群友先体验一下。
再不发布新作品,连我自己都怀疑我们到底有没有在做游戏了[○・`Д´・ ○]
这次的更新也证明了我们是真的有在做游戏(~ ̄▽ ̄)~
之后也会在博客同步更新进度,建议还没入群的盆友加一下 QQ 群:84334403

因为写博客有点花时间,而且贴了代码感觉没什么人想看o(╥﹏╥)o
后面的博文就一切从简了,有时间的话,我会写制作花絮记录制作过程。

制作花絮

这一部分是与游戏预告无关的,记录游戏制作过程的花絮(技术向)。
如果想要了解这部游戏是怎么做出来的盆友可以看看。

如何实现RPG游戏的剧情文本?

众所周知,一部剧情向的游戏都有很长的剧情文本。
那么,剧情的文本到底是怎么写进游戏里面的?
看起来很简单的对话系统,其实要实现起来……也不难,但是难的如何将文字量庞大的语言用代码表达出来。

如果是文字量很小的游戏,可以用简单的代码控制:

1
Dialog.Show("小白", "我是小白,本作的主人公。");

只要提供一个对话方法:Dialog.Show(string name, string content) 调出对话框就行了。

其中 name 是人物名字,content 是对话内容。

按照上面的思路,写一段简单的对话可以用下面的形式:

1
2
3
4
5
6
7
8
9
Dialog.Show("小白", "可恶的魔王,快把公主放了!");
Dialog.Show("魔王", "哈哈哈!你就是勇者吗?");
Dialog.Show("魔王", "看起来弱的跟豆芽一样,想活命的话,就快点滚!");
Dialog.Show("魔王", "不过看你千辛万苦从新手村来到魔王城也不容易,本魔王就允许你带走一件宝贝。");
Dialog.Show("小白", "什么?这可是你说的!");
Dialog.Show("魔王", "当然,本魔王决不食言。");
Dialog.Show("小白", "那么,把公主给我吧!");

Dialog.Show("旁白", "勇者小白孤身前往魔王城救出公主,他的英勇事迹最终被写入史书中。");

但如果是长篇且文字很多的游戏,这种方法就行不通了。
像 GalGame 动则百万字,这样写怕不是要累死程序员。

而且把剧情文本写在代码里,不仅工作量大,也不利于本地化(多语言)。

指令集

在用 cocos creator 制作的时候,我采用的是十分常见的数据存储类型:JSON。

第一个文件用来保存文本:

1
2
3
4
5
6
7
{
"1": "现在进行基本操作的说明,方向键可以控制角色移动,空格键为确认键。",
"2": "另外,在场景或人物前面按下空格键可以进行调查,请大胆的进行探索吧。",
"3": "有关「线索」的说明。",
"4": "线索是你对于这个世界已掌握的情报,可以在菜单「情报」随时查看。",
"5": "调查场景有时能发现新的线索,得到的线索越多就越接近这个世界的真相。",
}

第二个文件用来保存文本调用及对应的指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"test": [
{
"type": "investigate",
"role": "story",
"message": [
"118"
]
},
{
"type": "img-show",
"src": "email4"
},
{
"type": "investigate",
"role": "story",
"message": [
"119", "120"
]
},
{
"type": "img-hidden",
}
]

通过第二个 JSON(指令集)来调用第一个对话中对应的文本,type 字段定义了不同的指令,例如显示普通的对话或者显示/隐藏图片。
这样虽然实现了剧情对话系统,但是工作量却异常恐怖,而且调试起来非常麻烦,因为对话文本都是用数字编号的形式,所以光看指令集根本看不懂对话内容是什么。

这种方式我叫它「指令集」的对话系统,因为不实用所以舍弃了。

命令集

指令集的缺点是无法看懂对话内容,那只要让对话连在一起就行了。
所以第二版的对话系统,我采用了 Excel 表格的方式进行配置。

excel配置剧情对话

用 excel 配置其实就是把指令集变成可视化的而已,看起来更直观。
上面所示的就是对话的配置,role 即角色名称,avatar 即显示的头像,action 可以增加一些特殊的事件,比如显示图片之类的,然后是 zh-cn 是简体中文对应的语言,zh-tw 就是繁体中文的文本,其他语言填写在对应的格子即可。

接着,还需要再新增一个 sheet 用来保存对话的指令:

命令集合

在指令里调用对话的 ID,这里我写了一个可以自动简化输入的符号 ~,意思是从 x 到 n 之间连续的数字。
比如 1~3,就会自动生成:c_00001c_00003c_00003 这三条对话指令。
因为是把命令整合在一起,所以叫它「命令集」。
这套系统解决了无法看到对话内容的问题,而且也大大减少了输入指令的工作量。

但麻烦的是要填充剧情对话的表格,这套对话系统还采用了双边立绘,填充剧情对话表格的工作量直接翻倍了。
而且这套系统的对话全部保存在一个 excel 表格里,后期文本量巨大,全部加载到内存占用空间。
所以「命令集」式的对话,也被弃用了。

文本集

既要解决可视化问题,又按需调用节省内存,就需要再次改进原系统。

最终版是我在制作「梦兽的世界」(宝可梦同人)时想到的。
由于宝可梦游戏的对话系统很简单,没有头像,只是单纯的文本。

宝可梦游戏的对话系统

所以最开始我是以简单文本(txt)形式保存剧情对话的:

1
2
3
4
???:欢迎来到死后的世界。
???:还记得以前的事情吗?
???:你为了保护在路边睡觉的猫,结果被一辆大货车撞到了。
???:那只猫就是我。

左边是角色名称,右边是对话内容,十分简洁。
然后对话的文本单独存放,每一段剧情对话都新建一个 TXT 保存:

文本集

当玩家与 NPC 对话的时候,根据需要取加载对应的本地文件,对话结束就释放内存节省空间。
这种方法我叫它「文本集」,就是新建许多文本的集合。
但是怪物游戏的对话系统可比这个复杂多了,所以我对这套系统进行了一些改进。
改进之后的文本变成了如下形式:

1
2
3
4
5
#xiaoxing,normal
为什么我就打不过!

#xiaoxing,smile
我要再玩一次。

井号开头的就是一条指令,左边是名字,右边是对应的立绘。
这样就实现了怪物游戏的对话系统。

但是新的问题又来了,剧情对话不仅仅只是显示文本而已,中间如果要显示图片呢?如果要停顿 1s 呢?
在还没改进这套系统之前,我是这样实现剧情的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void SelectGame()
{
WindowManager.ShowImage("computer_1");

MapManager.FadeIn("black", 1f, 1f, delegate
{
WindowManager.CallDialog("01_ace_room/08_night", APP);
});
}

private void APP()
{
WindowManager.HideImage();
WindowManager.ShowImage("computer_2");

WaitEvent(delegate
{
WindowManager.CallDialog("01_ace_room/09_app", PowerFailure);
}, 1.5f);
}

private void PowerFailure()
{
WindowManager.HideImage();
MapManager.FadeOut("black", 0.5f, 1f, delegate
{
WindowManager.CallDialog("01_ace_room/10_power_failure", delegate
{
MapManager.GoToMap("01_AceRoomChange", -2.8f, -0.8f, 1);
});
});
}

不仅要手动控制屏幕的淡入淡出、显示图片,而且这种无限嵌套的地狱也十分令人绝望。
改进之后,将这些特殊的指令也融入到文本里,最后就变成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#none
突然,脑海中浮现了一个画面……

#img-show,memory_recover
#wait,2

#ace,normal
这是小幸还有瞳……

#ace,normal
记忆好像……在一点点恢复……(头痛的感觉)

#img-hide

#ace,normal
果然猜的没错,我们是认识的,这样的话,最后的证据也有了。

#ace,normal
这一连串发生的怪事……我终于知道了。

#ace,normal
所有的谜题全部解开了!

井号开头的部分会被系统自动识别,不同的指令解析成对应的脚本执行。如此一来就不需要再手写代码控制显示图片和淡入淡出等操作了。
这套系统到最后的文本文件数量会非常多,不过这个缺点可以直接忽略就是了,因为文本文件基本不需要改动。

目前想到的最好方法就是文本集,但这样控制剧情还是避免不了要用代码,比如控制场景中的角色移动,如果能做到更完美的话,应该是不写一句代码就能控制整个游戏场景,很遗憾目前还不能做到完美,等到以后技术不断提高的时候就有可能实现了。就像最早的指令集到命令集,最后再到文本集的演变一样。

如何解决按键问题?

这里的按键问题指的是「并行的按键问题」。
场景中有很多东西在监听玩家的按键,比如按「Z」键可以调查,按「X」键可以打开菜单,按「方向键」可以控制角色在场景中移动。
这么多监听事件,就会存在并行监听的情况。
比如玩家与地图上的 NPC 对话,此时玩家再按「Z」键会怎样呢?
① 角色在地图上可以按[Z]来调查
② 在对话中,玩家按[Z]键会切换到下一句对话
注意,这两个按键是同时监听的,也就是说,玩家想要按[Z]键切换下一句对话的时候,同时也会触发场景调查的[Z]键监听,结果就是对话切换到下一句,而且又触发了调查事件。
为了解决并行按键同时执行的问题,就需要确定事件的执行优先级。
当打开新窗口的时候,按键的监听权应该转交给最高级的那个窗口。
比如上面的例子,在没与 NPC 对话之前,[Z]键是监听调查事件的;
而在于 NPC 对话的时候,新建了一个对话窗口,此时应该把监听权交给对话窗口。
可以用数据结构里的「栈」来实现这种对话系统。
当一个新窗口被创建时,原来的监听对象就被压入栈,在压入栈之前还要解除对象的事件监听。
当一个新窗口被关闭时,就从栈弹出这个窗口的对象,把这个窗口删掉,同时继续弹出下一个对象,将操作权限赋予这个对象,再压回栈里。

最后,回到开头的那句话 游玩十分钟,制作却要花十天。
虽然看起来很简单的事情,但是要实现起来可真不容易呀!!!

文章作者: 火烧兔子
文章链接: http://huotuyouxi.com/2021/06/22/monster-game-prologue/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 火兔游戏工作室