一款简单好用的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是NearActorNarrativeEvent
在这里插入图片描述

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。 这样NarrativeManager的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类似于函数指针,指向了QuestBoxShowBox。 所以,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脚本,做好显示/隐藏即可。

猜你喜欢

转载自blog.csdn.net/newchenxf/article/details/122432833