”想更快更方便地实现功能,就向上抽象。想实现更丰富的功能,就向下拆解。“
---某不知名程序员
起因
继续更深入地改造Fungus,碰上了Command。由于Fungus的原生Command的参数都是靠面板输入的(毕竟其原本的设计意图就是方便不懂编程的人靠界面操作来实现游戏逻辑),并没有提供给在代码环境中修改Command参数的接口,所以想通过数据驱动动态创建Command,要么给Command抽象类加上修改参数的接口并由子类实现修改参数的方法,要么自定义Command。基于开闭原则和避免隐藏的坑的目的,我选择了后者。
思考
我只是想给Command增加修改参数的方法,其它部分都要保留,所以只是简单的一层封装。
每个指令拥有的参数类型和数量都不一样,所以修改时传递的参数只能设为object[]。
为了让自定义Command能在flowchart中正常工作,设计时要参考功能类似的原生Command的实现方式,确保没有遗漏一些必要的步骤。例如,OnEnter方法是指令的入口、执行部分,执行完后要调用Continue(),使下一条指令执行。
工作
1、创建继承自Command的抽象类SMMCommand,它只有一个InitializeByParams虚方法
public virtual void InitializeByParams(object[] param)
{
}
2、自定义Command继承SMMCommand,实现InitializeByParams方法,初始化指令所需的成员变量,在OnEnter中执行指令要完成的工作。
例子:
1、NextInterlude:切换到下一个过场场景
其中InitializeByParams设置了下一个过场场景的名字sceneName。
OnEnter中调用了SMMGame.Instance.NextInterlude(sceneName);让SMMGame去根据场景名切换场景。还调用了Continue来执行下一条指令(不然该Block执行结束)。
GetButtonColor设置了该Command在面板上的颜色。
using UnityEngine;
using System.Collections;
using Assets.Scripts.messagesystem.impl;
using Assets.Scripts.messagesystem;
using DG.Tweening;
namespace Fungus.SMM
{
[CommandInfo("SMM",
"NextInterlude",
"Change scene to next interlude scene by its name.")]
[AddComponentMenu("")]
[ExecuteInEditMode]
public class NextInterlude : SMMCommand
{
[SerializeField] protected string sceneName;
public override void InitializeByParams(object[] param)
{
this.sceneName = param[0].ToString();
}
#region Public members
public override void OnEnter()
{
SMMGame.Instance.NextInterlude(sceneName);
Continue();
}
public override Color GetButtonColor()
{
return new Color32(100, 123, 232, 255);
}
#endregion
}
}
2、GetConnectedBlocks:设置面板上Block的Block连线对象
参照原生Menu类。重载这个方法,设置connectedBlocks的ref就能在面板上看到该Command所处Block与connectedBlocks之间画出了连线。(这是我不忍抛弃Fungus的一大原因,很直观地就能看到Block之间是否有关联,关联是否正确)。
public override void GetConnectedBlocks(ref List<Block> connectedBlocks)
{
var flowchart = GetFlowchart();
Block t_rightBlock = flowchart.FindBlock("FocusRight" + id.ToString());
Block t_wrongBlock = flowchart.FindBlock("FocusWrong" + id.ToString());
if(t_rightBlock != null)
{
connectedBlocks.Add(t_rightBlock);
}
if(t_wrongBlock != null)
{
connectedBlocks.Add(t_wrongBlock);
}
}
3、GetSummary:设置面板上Command显示的概要信息
public override string GetSummary()
{
if (texts == "")
{
return "Error: No texts";
}
return name + " : " + texts;
}
后续
原本很想自己实现一个剧情系统,然而Fungus配套齐全(对话,菜单,指令,视图),而且免费不难用,鉴于时间紧迫就继续用吧。另外看Fungus源码的过程中也看得到设计者挺强的,架构清晰,代码扩展性较强,还有一些我没见过的用法,也算是学习到了。不过从另一个角度说,代码量较大,冗余较多,注释还少。
至此,其实已经搭建了基本的剧情构建工作流程:
1、策划按格式写好各个Block和它们的Command
2、需要新的自定义Command就让程序去写
3、导入Unity的Flowchart窗口,查看是否有错,Block之间的连接是否正确,流程是否如预期等
4、运行游戏,测试查看各效果是否正常,Flowchart窗口也能直观地展现各个Block和当前运行的位置,还可以手动激活/取消激活Command。
这样对于少量的固定的剧情场景已经够用了。接下里为了复用剧情场景,就要在加载场景时动态地加载剧本txt来生成Blocks。