从去年三月份到现在,《匣》断断续续的也经历一年多的开发,这几天剧情系统也算是稳定下来了,后面应该不会再进行大概了,一开始还搞了个节点编辑器,结果后来整个剧情跳转逻辑改了,编辑器也没用了。后面有时间还是想把编辑器搞起来,否则配表真的是太麻烦了。

简介

  《匣》是一款剧情向的2d游戏,所有整个剧情系统是基于文字内容的,并不需要什么人物的行为。基于这一点,整个剧情系统主体分为下面三部分

  • StoryManager
  • StoryData/StoryHandler
  • TriggerData/TriggerHandler

StoryManager

  这个管理器并不直接管理剧情,它既不存储剧情数据,也不定义某个剧情的表现形式。当你传了一个剧情数据或者是触发器数据给它时,它会根据数据类型选择对应的Handler再具体去做某件事。系统的核心部分是没有对数据进行管理的,所以数据怎么存储可以由外界定义。

StoryHandler

  按照我们的设计需要,剧情被分为了普通,CG,顾客等几种,每种剧情都有着自己的表现方式。所有就有个了StoryHandler(剧情处理器)的概念。每种类型的剧情都对应一个处理器。处理器核心代码只有一个接口和一个泛型基类。泛型基类的方法只有OnHandleStory是其派生类必须实现的,并且要定义好剧情处理完成的时机去调用callback。其他方法一般不用重写。

public interface IStoryHandler
{
    /// <summary>
    /// 对应的故事类型
    /// </summary>
    Type StoryDataType { get; }
    /// <summary>
    /// 处理剧情数据
    /// </summary>
    /// <param name="storyDataBase">剧情</param>
    /// <param name="callback">完成后的回调</param>
    void Handle(StoryDataBase storyDataBase, Action callback = null);
}

public abstract class StoryHandlerBase<T> : IStoryHandler where T : StoryDataBase
{
    public Type StoryDataType => typeof(T);
	/// <summary>
	/// 处理剧情数据
	/// </summary>
	/// <param name="storyDataBase">剧情</param>
	/// <param name="callback">完成后的回调</param>
    public void Handle(StoryDataBase storyDataBase, Action callback = null)

    /// <summary>
    /// 开始故事前检查是否满足触发条件
    /// </summary>
    /// <param name="storyData"></param>
    /// <returns></returns>
    protected virtual bool OnCheckStory(T storyData)

    /// <summary>
    /// 处理故事
    /// </summary>
    /// <param name="storyData"></param>
    /// <param name="callback">故事结束后的回调</param>
    protected abstract void OnHandleStory(T storyData, Action callback);

    /// <summary>
    /// 故事结束后处理一些事情,比如后续剧情
    /// </summary>
    /// <param name="storyData"></param>
    protected virtual void OnStoryEnd(T storyData)
}

TriggerHandler

  一个剧情结束后,可能会触发其他剧情,但触发方式不一样,有的可能会立即触发,有的可能会隔几天,等等。这样一来,就需要多种触发器来定义一个剧情被触发的方式。
  触发器的核心代码和剧情处理器基本一致,并且更简单一些,也是一个接口和一个泛型基类。
  在匣开发过程中,并不是每一种触发器数据都对应一个处理器。有些触发器可能会在别的地方处理。这一点是同剧情处理器不一样(如果某种类型的剧情没有定义对应的处理器,该数据在传给StoryManager后会抛出一个异常)

/// <summary>
/// 故事触发处理器
/// </summary>
public interface ITriggerHandler
{
    Type TriggerDataType { get; }
    void Handle(ITriggerData triggerData);
}

/// <summary>
/// 故事触发数据的处理器基类
/// </summary>
/// <typeparam name="T">触发器类型</typeparam>
public abstract class StoryTriggerHandlerBase<T> : ITriggerHandler where T : ITriggerData
{
    public Type TriggerDataType => typeof(T);
    public void Handle(ITriggerData triggerData)
    protected abstract void HandleTriggerData(T triggerData);
}

总结

  有了这些基础代码之后,再对应去实现所需要的剧情表现形式,可以实现一个相对比较丰富的文字剧情系统。