Unity StoryLine基于Node节点的剧情编辑器
文章目录
介绍
随着《完蛋我被美女包围了》等互动影视游戏的大火,短视频平台那些剧情节奏快一集一个反转的视频快速批量的产出,那么在游戏开发制作方面就必须跟上视频产出的速度,所以有一套剧情编辑器就可以加速和规范开发流程。
设计思路
需求分析
需求如下:
- 支持显示视频和图片,可在图片或视频上叠加
固定区域选项
、自定义位置选项
、QTE
、自定义位置按钮
- 视频播放完或者播放到某一时间会弹出选项,根据选项继续播放下一个视频或者图片,如果没有选项则自动播放
- 在播放下一个视频或图片之前,可以穿插小游戏玩法,比如视频播放完后进入一段打飞机游戏,小游戏失败可成功可进入不同的视频
- 叠加在视频或图片上的选项可配置 条件。条件用于判断是否显示选项,点击选项是否可触发。
- 在播放某个视频或者点击某个选项可触发自定义游戏内容,比如获取道具、获取成就等游戏业务逻辑。
具体分析:
-
作为剧情编辑器,一目了然的看到剧情走向和各种剧情分支是极其重要的,所以首先就摒弃了纯参数填写的编辑器方式,把各种参数交给产品策划填写,太复杂填写容易出错不运行游戏根本发现不了,而且可读性极差。所以我选择了基于Unity中GraphView的Node节点的形式制作视频节点编辑器,官方的ShaderGraph就是节点编辑。
-
决定好了在GraphView下用Node节点连线方式来做编辑器,那么首先根据需求拆分为
章节编辑器
和单元剧编辑器
编辑器 说明 章节编辑器 用于编辑故事分支走向 单元剧编辑器 用于编辑每个故事节点的具体内容:播放什么视频图片,穿插小游戏等随意组合 -
章节编辑器中则把节点分为:开始节点,结束节点,单元剧节点
节点名称 说明 开始节点 一个章节的开头,可能有具体的业务逻辑 结束节点 一个章节的结束,用于链接下一章节或者有具体的业务逻辑 单元剧节点 用于关联单元剧的 -
单元剧编辑器咋把节点分为:开始节点、结束节点、视频节点、图片节点、事件节点、小游戏节点、跳转Jump节点
节点名称 说明 开始节点 用于单元剧的开头 结束节点 单元剧结束,可有多个End节点,用于在章节编辑器中显示出口 视频节点 游戏播放视频时的数据编辑,用于配置显示选项、视频资源等 图片节点 游戏显示图片时的数据编辑,用于配置显示选项、视频资源等 事件节点 用于连接StoryLine编辑器和正常开发的业务逻辑。比如一些获取道具成就啥的,不应该内置在编辑器中,而是由主程序根据实际情况拓展 小游戏节点 主程序中实现小游戏接口即可在编辑器中选择小游戏。小游戏也是根据情况自行拓展的 跳转Jump节点 在单元剧编辑器中的每个节点都有一个唯一id,那么Jump跳转节点既可以直接跳转到某个id的节点播放。目的是可能有一些特殊用途。 -
在视频节点和图片节点中的选项里面可以添加条件,用于判断是否显示这个选项或者是否可以点击触发这个选项。那么这个条件的具体代码逻辑就不能内置在StoryLine包中,而是给Attribute特效,在主程序中给一个方法添加此特性,那么就可以在编辑器中选择这个条件方法。这样就可以连接编辑器和实际开发的业务需求了。
代码设计
有了大概的需求,那么就可以着手设计代码了。
事件系统
为了让StoryLine编辑器相对独立且有一定的拓展性,需要让游戏开发中部分业务逻辑可以在编辑器中编辑,那么我们就可以通过事件分发来实现。在业务逻辑中写一个事件分发所需要的数据结构,在编辑器中编辑对应的数据结构,然后StoryLine的事件系统把这个数据分发出去让业务层监听执行即可。
比如业务层需要在播放完一个视频后获取一个道具,作为独立package 的StoryLine肯定不知道有这个业务,所以业务层只需编写一个获取道具所需的数据结构(道具id,道具数量),然后StoryLine的事件系统分发这个数据结构,让业务层执行获取道具的任务即可。这样就可以任意拓展了。
对于事件系统代码设计,我参考了知名框架ET中的EventSystem。
新增了两个Attribute,用于收集所有的EventStruct和Event要执行的代码逻辑。
- EventAttribute:用于标记业务代码逻辑
- StoryLineEventAttribute:用于标记数据结构
[AttributeUsage(AttributeTargets.Class)]
public class BaseAttribute: Attribute
{
}
public class EventAttribute: BaseAttribute
{
public EventAttribute()
{
}
}
[AttributeUsage(AttributeTargets.Struct,AllowMultiple = true)]
public class StoryLineEventAttribute : Attribute
{
private long _eventId;
public long eventId => _eventId;
private string _eventName;
public string eventName => _eventName;
public StoryLineEventAttribute(long eventId,string eventName)
{
_eventId = eventId;
_eventName = eventName;
}
}
在CodeTypes.cs脚本中,收集上述的两个Attribute
public void Init(Assembly[] assemblies)
{
Dictionary<string, Type> addTypes = AssemblyHelper.GetAssemblyTypes(assemblies);
foreach ((string fullName, Type type) in addTypes)
{
this.allTypes[fullName] = type;
if (type.IsAbstract)
{
continue;
}
// 记录所有的有BaseAttribute标记的的类型
object[] objects = type.GetCustomAttributes(typeof(BaseAttribute), true);
foreach (object o in objects)
{
this.types.Add(o.GetType(), type);
}
object[] objectsStoryLineEventAttribute = type.GetCustomAttributes(typeof(StoryLineEventAttribute), true);
foreach (object o in objectsStoryLineEventAttribute)
{
this.types.Add(o.GetType(), type);
}
}
}
最后在EventSystem.cs中分发即可。遍历数据结构T对应的所有业务逻辑片段,然后挨个执行Handle即可
public void Publish<T>(T a) where T : struct
{
List<EventInfo> iEvents;
if (!this.allEvents.TryGetValue(typeof (T), out iEvents))
{
return;
}
foreach (EventInfo eventInfo in iEvents)
{
if (!(eventInfo.IEvent is AEvent<T> aEvent))
{
Debug.LogError($"event error: {eventInfo.IEvent.GetType().FullName}");
continue;
}
aEvent.Handle(a);
}
}
具体实现可以查看框架ET中的EventSystem。
数据结构
数据结构这边不多赘述,在章节编辑器中需要章节开始节点,章节结束节点,单元剧节点的数据。在单元剧编辑器中需要开始节点、结束节点、视频节点、图片节点、事件节点、小游戏节点、跳转Jump节点。
单元剧编辑器创建编辑的DialogueContainer的ScriptableObject对应了章节编辑器中单元剧节点的数据,章节编辑器本质就是创建编辑ChapterContainer的ScriptableObject。
编辑器
编辑器的开发中,我导入了Odin插件加速开发。
剧情编辑器
关于Node剧情编辑器,我从总体的构想上进行,实现一个可用的节点编辑器,而不是顺序性的实现一个简单的图。
下面以章节编辑器为示例。单元剧编辑大同小异,原理都是一样的。
GraphView
GraphView类是一个UIElements,继承于VisualElement,继承GraphView类以 创建自己的 UI 控件 ,GraphView提供的功能仅仅是提供图的一些基本功能。可以注册 添加/移除 节点/边 的事件,能获取到图的各种信息。
ISearchWindowProvider 接口
这个接口用于提供一个右键菜单,指明右键有哪些节点能被创建。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace StoryL