一款简单好用的Unity任务系统
Posted 长江很多号
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一款简单好用的Unity任务系统相关的知识,希望对你有一定的参考价值。
1 前言
都2022年了,元宇宙的热风,把游戏开发给带火了。说不定打开文章的你,就是在做元宇宙游戏,哈哈
先说一下【XX是什么】。任务系统
是一个引导玩家进行游戏的系统。有些玩家进入游戏,不知道怎么玩,那可以点开任务按钮,出来一个弹窗,就能告诉你一步步该做啥。
任务系统需要策划同学,写好任务线。复杂的游戏,还需要主任务线,和子任务线。
游戏基本上都需要任务系统,从头自己写一个,是不合算的,也不靠谱。可以基于一个好用的框架,扩展开发。本人也一样,先找了个不错的任务系统(Unity插件),然后做扩展开发(特别是任务跟后台的交互)。
本文主要目的,就是讨论这个任务系统(名字叫DialogueQuests)的原理,以及怎么用。这对设计一个任务系统,也是有很大的参考意义的。
插件的地址:Unity Assets Store - DialogueQuests
插件是收费的,也不算贵。咱们要支持正版嘛,当然了,如果你是纯学习用(反正我相信你真的是学习用^^),可以私信我获取源码喔.
需要说明的是,这个系统只是纯客户端使用,任务不从后台下发。如果你需要和后台交互,需要区分主任务,子任务,只要按需来定制即可,还是很好扩展的。
本文分三部分,一个是任务系统的介绍,一个是怎么用该任务系统,最后做一些分析。
先上效果图,再继续废话哈。
2 任务系统介绍
我们先无脑想一下,一个任务系统需要啥?(不考虑和后台的交互)
(a) 全局管理类,管理所有的任务进度,触发对应的弹窗;
(b) 事件监听类,用于监听任务是否完成;
© 弹窗显示类,用于显示/隐藏弹窗;
好了,接下来,我们看看DialogueQuests
任务系统有没有这些。这个插件,打开后,发现包含一些预制件,和脚本文件。我们先把这些拿出来看一下。
2.1 重要的预制件
2.1.1 DQManager
这个预制件,用于做各种初始化。你需要把这个预制件,拖到游戏的scene中。
我们打开这个预制件看看。
发现一些全局脚本,都在这加载初始化。所以我们要用这个任务系统,首先就是把DQManager预制件,拖到scene中。
TheLoader脚本,加载了其他的预制件!特别是DQCanvas!
2.1.2 DQCanvas
这个主要是UI相关。如果你要修改UI,比如弹窗样式,只要打开这个预制件修改。
2.2 重要的脚本类
2.2.1 NarrativeData
单例。记录数据,特别是任务进度,任务数量。还包括保存任务,加载历史任务。
//Quest data
public Dictionary quests_status = new Dictionary(); //0=NotStarted, 1=Ongoing, 2=Completed, 3=Failed
public Dictionary quests_progress = new Dictionary(); //ID is quest_id+title
public void Save();//存储历史数据,把NarrativeData转为二进制数据存到文件
public static NarrativeData Load(string filename);//加载历史数据
public static NarrativeData Get();//获得数据
//设置进度
public void SetQuestProgress(string quest_id, string progress, int value);
2.2.2 NarrativeManager
单例。最核心的类,启动任务,完成任务,轮询状态,触发UI显示(例如对话框)。
public UnityAction onQuestStart;//任务开始的函数处理
public UnityAction onQuestComplete;//任务结束的函数处理
public UnityAction onQuestFail;//任务失败的函数处理
//任务参与者
public List actor_list = new List();
//任务数据
public List quest_list = new List();
//被触发的event列表
private List trigger_list = new List();
//event的处理,例如弹窗,结束任务等
private Queue event_line_queue = new Queue();
//逐帧更新,如果trigger_list或event_line_queue有数据,则处理
public void Update();
//启动event
public void StartEvent(NarrativeEvent narrative_event);
//显示弹窗
public void StartDialogue(DialogueMessage dialogue);
//启动任务
public void StartQuest(QuestData quest);
//完成任务
public void CompleteQuest(QuestData quest);
2.2.3 NarrativeEvent
NarrativeEvent 是Monobehavior,有Awake, Start和Destroy等函数。用于接收一个事件,并做对应的处理(通过NarrativeEffect),事件触发还有条件,通过NarrativeCondition判断。
//记录所有的event
private static List event_list = new List();
//触发event的条件
private List conditions = new List();
//挂载当前gameobject的effects,也可以挂载在子gameobject下,那样的event存在下面的event_lines
private List effects = new List();
//event的子gameobject下的各种对象,例如dialogueMessage,DialogueChoice,NarrativeEffect
private List event_lines = new List();
//触发event,条件负责,则添加到NarrativeManager的trigger_list
private void OnTriggerEvent(Actor player);
NarrativeEvent 触发的类型:
public enum NarrativeEventType
Manual = -2, //By script only
AutoTrigger = -1, //As soon as conditions are met
InteractActor = 0, //When interact with actor
NearActor = 2, //Just go near an actor
AtStart = 5, //After scene load
EnterRegion = 10, //Enter a region
LeaveRegion = 11, //Exit a resion
AfterEvent = 20, //After another event
2.2.4 NarrativeEventLine
event触发后的处理,请看NarrativeEvent 有个List。可以是弹对话框,可以是完成某个任务,且可以叠加。即,一个event可以产生多个执行效果。
public class NarrativeEventLine
public GameObject game_obj;
public NarrativeEvent parent;//对应的event
public DialogueMessage dialogue = null;//对话框
public List choices = new List();//选择框
public List conditions = new List();//处理条件
public List effects = new List();//处理效果
2.2.5 Actor
是一个MonoBehaviour,作为Component,绑定在所以跟任务相关的NPC中。
public ActorData data; //actor关键数据,如is_player
public UnityAction onInteract; //两个actor交流的回调函数
public UnityAction onNear; //两个actor靠近
private List events_list;//标注trigger_actor为当前actor的event列表
private static List actor_list = new List();//静态全局变量,所有的actor
void Start()
events_list = NarrativeEvent.GetAllOf(this);//获得event_list
foreach (NarrativeEvent evt in events_list)
evt.AddActor(this);//记录onInteract和onNear,指向NarrativeEvent.OnTriggerEvent, 例如当2个actor靠近,就可以触发对应的event
if (NarrativeControls.Get())
NarrativeControls.Get().onPressTalk += OnClick;//对话点击,触发事件
NarrativeControls.Get().onPressTalkMouse += OnClick;
//检查actor是否距离靠近等事件
void Update();
2.2.6 NarrativeEffect
收到event后的各种处理效果,可以叠加多个。类型很多,一般和UI无关。
public class NarrativeEffect : MonoBehaviour
//类型
public NarrativeEffectType type;
//不同类型的处理
public void Trigger(Actor player);
可处理的类型例子:
public enum NarrativeEffectType
StartEvent = 20,
StartQuest = 30,
CancelQuest = 31,
CompleteQuest = 32,
PlayMusic=42,
GainGold=80,
OpenShop=84,
2.2.7 DialogueMessage
也是收到event后的处理结果数据,主要是弹出对话框。注意该类只有数据,UI由DialoguePanel显示。
public class DialogueMessage : MonoBehaviour
//对应的参与者,可以是主角,也可以是NPC
public ActorData actor;
//对话框内容
public string text;
//获得text
public string GetText();
3 任务系统使用
3.1 把DQManager拖到场景中
目的就是做各种初始化工作,加载各种预制件。特别是DQCanvas。
3.2 新建Quest和Actor
Quest代表任务,Actor代表任务参与者。这些都可以提前建立好。
在Project面板,Assets/Resources下,建两个目录。
Quests 和 Actors。
然后,Quests目录下,新建任务数据Quest。
为什么菜单就可以建立呢?
因为QuestData类,做了如下声明:
建立好了,把quest_id,任务图标,标题写上。
同理,Actor也创建一下。需要注意的是,一般只能有一个actor是is_player。代表玩家主角。
3.3 主角和NPC添加Actor组件,然后绑定数据
比如主角,这么添加:
其他NPC,也是同理。
3.4 NPC添加一个事件
我们可以在scene下,建一个DialogueQuest,来专门处理任务相关。
比如,现在就想让NPC等着主角来找它,说第一句话。
如上,建一个节点,加NarrativeEvent和NarrativeCondition组件。用于有条件的接收某个event。
比如,我们写的Event条件是,Near Actor
,即,需要2个Actor靠近,才触发该event!
接着,在该节点下,建立处理办法,比如弹出对话框呀,完成某个任务呀啥的。
3.5 事件节点下,建多个子节点,作为处理办法
每个子节点,都是一个事件的处理办法。比如,一个FirstTalk节点,下面挂了3个子节点, Msg1, Msg2, Choices。分别对应2个对话框,和1个选择框。
Msg1子节点,做如下编辑,添加DialogueMessage组件,并填写对话内容。
又比如FirstYes节点,用于监听用户点击了上面的选择框的YES。并挂了3个子节点,对应Msg1,Msg2, Quest。分别对应2个对话框,1个任务启动。
按照上面的步骤,你已经可以让任务启动起来了,并且可以执行完一个任务,启动一个新的任务。
4 代码分析
下面分析任务系统的一些核心功能!
4.1 NarrativeEvent触发流程
以最初的任务,即一个NPC(Actor)等待一个主角(另一个Actor)靠近,从而发起第一次对话,来讨论流程。
4.1.1 Actor的Update函数,查询是否有其他actor靠近
如果有,触发所有trigger_type是NearActor
的NarrativeEvent
。
4.1.2 OnTriggerEvent添加事件到list
OnTriggerEvent判断是否条件满足(例如是否触发过),满足,调用
NarrativeManager.Get().AddToTriggerList(this);
4.1.3 NarrativeManager的Update函数,轮询List并处理
关键代码如下图。
Update函数,轮询查trigger_list
是否大于0,如果是,则读取到最优的event,调用StartEvent
函数。
来看一下StartEvent
都干啥了。
public void StartEvent(NarrativeEvent narrative_event)
//1. 触发同在一个gameObject下的effects
narrative_event.TriggerEffects();
//2. NarrativeEventLine存了当前event挂载的gameObject的子gameObject,所挂载的其他effect, dialogueMessage等
//存下来,Update将轮询,并一一触发
foreach (NarrativeEventLine line in current_event.GetLines())
event_line_queue.Enqueue(line);
比较简单,先触发挂在同一个节点下的effects。
然后,子节点的effects,记录在NarrativeEventLine
,把它们添加到event_line_queue
。 这样NarrativeManage
r的Update
函数,又会轮询到,并做处理。
4.2 任务启动流程
4.1只提到了,event如何监听,以及如何处理。
处理最主要的,一个是任务状态修改,一个是弹窗。我们先看下任务状态如何修改,尤其是任务启动流程
4.2.1 NarrativeEvent收到trigger函数
上面4.1节,提到了narrative_event.TriggerEffects();
,这就走到了NarrativeEvent的trigger函数。
public class NarrativeEffect : MonoBehaviour
public void Trigger(Actor player)
if (type == NarrativeEffectType.StartQuest)
QuestData quest = (QuestData)value_data;
NarrativeManager.Get().StartQuest(quest);
很简单,判断是否要StartQuest
,是的话,则调用NarrativeManager.StartQuest
。
4.2.2 NarrativeManager 调用StartQuest
这个函数也简单, 一方面,修改quest的状态为1(即任务启动)。
另一方面,onQuestStart
类似于函数指针,指向了QuestBox
的ShowBox
。 所以,onQuestStart.Invoke
的效果,就是显示一个UI,提醒用户任务开始。
public class NarrativeManager
public void StartQuest(QuestData quest)
if (quest != null && !NarrativeData.Get().IsQuestStarted(quest.quest_id))
//把quest设置为开始
NarrativeData.Get().StartQuest(quest.quest_id);
//显示一个toast dialogue,提醒任务开始
if (onQuestStart != null)
onQuestStart.Invoke(quest);
if (quest is QuestAutoData)
((QuestAutoData)quest).OnStart();
4.3 任务弹窗显示
当启动任务或完成任务,会显示一个弹窗。
这个怎么实现呢?
首先,弹窗的UI,在前面2.1.2节提到的DQCanvas预制件制作好了(预制件在TheLoader脚本加载)。
打开看一下:
对应的脚本,在QuestBox。我们看下这个脚本。
onQuestStart指针,指向一个匿名函数,函数参数是quest,内容是显示一个弹窗。这和4.2.2节的代码,对上了!
4.4 任务面板的显示
首先,显示任务面板,只要一句话,就实现:
QuestPanel.Get().Show();
怎么实现的呢?
其实和4.3节差不多。
首先,DQCanvas的预制件,先画好UI。
然后,QuestPanel
脚本,做好显示/隐藏即可。
以上是关于一款简单好用的Unity任务系统的主要内容,如果未能解决你的问题,请参考以下文章