【unity】基于命令模式的剧情系统
前言
回想某次项目,我负责一个剧情模块的搭建。当时才疏学浅,只会用链式结构绑定每个UI对象,这种做法非常费事而且耦合过高。现在我的这个系统也算是给当年一个交代吧,以后有类似的需求也可以直接拿过来用。
系统简介
这是一个能够在Unity中运行的,基于命令模式的剧情系统,支持自定义命令,编辑剧情数据,查看历史对话等。
该系统旨在提高对于游戏剧情的开发效率,同时遵循开闭原则,以确保扩展性良好。
仓库地址->https://github.com/AshScops/Dialogue-style-plot-system
各个类的职责
CommandConfig
:继承自ScriptableObject
。它用于存储一系列命令对象。
CommandBase
:一个抽象类。它作为所有命令的基类,声明了三种抽象方法:
Execute
:在该命令对象出队后立刻被调用,且只调用一次。OnUpdate
:在Execute
后每帧被调用,直到返回true
;IsFinished
:返回值类型为bool
,在该命令对象执行完毕时返回true
,未执行完则返回false
。
CommandSender
:一个单例,包含一个命令队列。它将从外部传入的CommandConfig
中获取一系列命令对象并入队,按队列顺序执行各个命令。
PlotUISettings
:一个单例,负责在进入和退出剧情时,对某些参数进行设置和清理;同时对外暴露界面的像素大小、打字效果的速度等可供用户调整的参数。
PlotModule
:一个单例,用于存放各个Event
,并对外提供各类方法,专门负责来自模块外的交互。
以下是各个命令类,均继承自CommandBase
。
类名 | 可自定义的字段 | 该命令何时结束 | 注释 |
---|---|---|---|
HEADER |
章节名、该段剧情是否可跳过 | 立刻 | 负责进入剧情时的补间动画 |
Background |
背景图片 | 立刻 | 无 |
Delay |
需要等待的时间 | 等待自定义时间后 | 无 |
Dialogue |
说话者的名字、说话的内容 | 打字效果结束且用户点击后 | 无 |
Character |
说话者的图片 | 立刻 | 最多支持显示两个立绘 |
Decision |
用户可选择的对话选项 | 用户选择一个对话选项后 | 最多支持五个剧情选项 |
Predicate |
标志位,作为用户所选项对应的命令段的首位 | 立刻 | 最多支持五个剧情选项 |
END |
标志位,作为整个剧情的出口 | 永不 | 负责退出剧情时的补间动画 |
如何使用
初始化、开始和结束
plot_module.PlotModule
是专门供外部调用方法的类。
该系统初始化,仅需全局调用一次plot_command_executor.PlotModule.Instance.PlotInit(GameObject ui_prefab))
即可,界面会持久存在。
该类还提供了两个UnityEvent
:
plot_module.PlotModule.Instance.plotBegin;
plot_module.PlotModule.Instance.plotEnd;
当你想进入某段剧情时,首先确保已经初始化了,并且通过SetPlotConfig
设置好了剧情配置文件。然后调用plotBegin
即可开始剧情。
当你想在剧情结束时执行某些回调,你可以向plotEnd
注册它们,在剧情界面完全退出后会自动调用。
获取玩家的选择
在PlotModule
中提供了GetPlayerDecisions
方法,返回一个List<int>
,第i
位上的数x
意为玩家在第i
次选择了第X
个选项(均从0开始)。你可以在剧情结束后的任意时间获取它,它总是返回最近一次发生剧情的抉择。
配置剧情文件
HEADER
,负责配置章节标题,同时在代码中负责进入剧情时的补间动画。
Background
,负责配置剧情界面最底部的背景图片。注意:图片要放在Asset/Resource
文件夹下,暂时不能自定义存放路径,这个以后改进。
Delay
,等待自定义时间。由于补间动画的命令是立刻执行完的,而补间动画需要一定时间播放,由此产生的时间差就由它来填补。你也可以选择在合适的位置加入或不加入此命令,以实现对应效果。
Dialogue
,没什么好说的。唯一要注意的是:人名和文本不要太长,人名在15个汉字以内,文本大概在70个汉字左右,不然会溢出屏幕。
Character
,在两个立绘都不为空时,要使用focus
字段来选择高亮显示哪个立绘。注意:图片要放在Asset/Resource
文件夹下,暂时不能自定义存放路径,这个以后改进。
Decision
,文本不要太长,在15个汉字以内即可,太长了后面的文字会被隐藏。
Predicate
,拦截Decision
的选择。一旦玩家选择了某选项,则会使命令队列不断出队,直至检测到出队的元素为对应选项的Predicate
或是PredicateEnd
。也正是这个原因,该系统暂时不支持循环式的选择。这一点可能以后改进。
END
,意味着整个剧情的结束。一段剧情可以有多个END
出口。
尾声
显然,这个系统还不够完善,比如:没有和镜头控制、屏幕效果、音频播放相关的命令,不支持循环式对话,没有对抉择进行细分等。但时间紧迫,短期内不会再对这个系统进行修改了。