【Unity】 HTFramework框架(三十)TaskEditor任务编辑器

更新日期:2020年2月14日。
Github源码:[点我获取源码]
Gitee源码:[点我获取源码]

TaskEditor简介

任务编辑器,可以自定义任务点,设置任务达成条件,多个任务点组成一个任务内容,使用一系列任务内容完成角色扮演的设计。

使用TaskEditor

创建Task Content Asset

Task Content Asset(任务内容资源)为TaskEditor使用的标准资源,创建方法:
Project界面右键 -> Create -> HTFramework -> TaskContentAsset

如下,新创建的Task001,点击Open按钮(或双击资源)便可以打开TaskEditor窗口编辑此资源:
在这里插入图片描述

打开TaskEditor窗口

打开任意一个TaskContentAsset资源后,TaskEditor初始窗口如下图:
在这里插入图片描述

TaskEditor窗口详解

在这里插入图片描述
①.Task Content List(任务内容列表)
1.这里显示所有的任务内容,点击右上角的+按钮可以新增任务内容,或者创建新的任务内容类型。
2.选中任意一个任务内容后,点击右端的铅笔按钮可以打开并编辑此任务内容的脚本,点击垃圾桶按钮可以删除此任务内容。

②.Task Property(任务内容属性)
1.选中任意一个任务内容后,这里显示该任务内容的属性。

③.Task Point Area(任务点显示区域)
1.选中任意一个任务内容后,这里显示该任务内容的所有任务点。
2.右键点击可以新增任务点,或者创建新的任务点类型。

任务内容

任务内容属性详解

在这里插入图片描述

当在①面板中选中一个任务内容时,②面板则显示此任务内容的属性:

1.ID:任务内容唯一标识符,不能重复(默认会自动累加,永不重复,前提是你不手动修改)。

2.Name:任务内容名称简述。

3.Details:任务内容详述。

4.Target:任务内容默认自带的一个GameObject属性,可以通过拖拽关联至场景、预制体中的任意对象,拖拽后通过ID关联,在任何时候都能找到该目标。

不过,上述为TaskContentDefault默认任务内容,我们点击编辑脚本,可以看到里面什么也没有:

    /// <summary>
    /// 默认的任务内容
    /// </summary>
    [TaskContent("默认")]
    public sealed class TaskContentDefault : TaskContentBase
    {
        
    }

新建任务内容类型

框架会自带TaskContentDefault这个默认任务内容类型,通过选择New Task Content Script选项启动创建向导来快捷创建新的任务内容类。
在这里插入图片描述

如下,我们新建一个任务内容类型TaskKill

[TaskContent("击杀任务")]
public class TaskKill : TaskContentBase
{
	protected override void OnStart()
    {
        base.OnStart();
    }
    protected override void OnUpdate()
    {
        base.OnUpdate();
    }
    protected override void OnComplete()
    {
        base.OnComplete();
    }

#if UNITY_EDITOR
    protected override int OnPropertyGUI()
    {
        int height = base.OnPropertyGUI();

		GUILayout.BeginHorizontal();
        GUILayout.Label("[新建任务内容]");
        GUILayout.EndHorizontal();

        height += 20;

        return height;
    }
#endif
}

然后我们就可以直接在编辑面板创建该类型的任务内容:
在这里插入图片描述

新建任务内容

新建如下这样一个常见的击杀任务:
在这里插入图片描述
通过重写OnPropertyGUI方法可以扩展任务内容属性面板UI:

#if UNITY_EDITOR
    protected override int OnPropertyGUI()
    {
        int height = base.OnPropertyGUI();

        return height;
    }
#endif

height为延续属性面板高度的参数,每添加一行UI,理论上将height+=20最合适,如下,我们在TaskKill添加一个属性:

[TaskContent("击杀任务")]
public class TaskKill : TaskContentBase
{
    //怪物地图标记
    public int Sign;
    
#if UNITY_EDITOR
    protected override int OnPropertyGUI()
    {
        int height = base.OnPropertyGUI();

        GUILayout.BeginHorizontal();
        GUILayout.Label("怪物地图标记:");
        Sign = EditorGUILayout.IntField(Sign);
        GUILayout.EndHorizontal();

        height += 20;

        return height;
    }
#endif
}

查看属性面板的变化:
在这里插入图片描述

任务点

一个任务内容包含多个任务点,任务内容和任务点都有一个完成标记,默认情况下,任务内容的完成标记会在其所有任务点的完成标记均为true时为true

任务点属性详解

在这里插入图片描述

1.ID:任务点唯一标识符,不能重复(默认会自动累加,永不重复,前提是你不手动修改)。

2.Name:任务点名称简述。

3.Details:任务点详述。

4.Target:任务点目标。

5.触发方式:此为默认任务点携带的属性。

6.指引时高亮目标:此为默认任务点携带的属性。

7.持续时间:此为默认任务点携带的属性。

如下,我们点击【铅笔】按钮,打开TaskPointDefault默认任务点:

    [TaskPoint("默认")]
    public sealed class TaskPointDefault : TaskPointBase
    {
        [SerializeField] private TaskTrigger _trigger = TaskTrigger.MouseClick;
        [SerializeField] private bool _highlighting = true;
        [SerializeField] private float _duration = 0;
        private TaskTarget _target;

        protected override void OnStart()
        {
            base.OnStart();

            if (GetTarget == null)
            {
                Log.Error("任务点 " + GetName + " 的目标为空,这是不被允许的!");
            }
            else
            {
                _target = GetTarget.GetComponent<TaskTarget>();
            }
        }
        protected override void OnUpdate()
        {
            base.OnUpdate();

            switch (_trigger)
            {
                case TaskTrigger.MouseClick:
                    if (Main.m_Input.GetButtonDown(InputButtonType.MouseLeft))
                    {
                        if (Main.m_Controller.RayTargetObj && Main.m_Controller.RayTargetObj == GetTarget)
                        {
                            Complete();
                        }
                    }
                    break;
                case TaskTrigger.StateChange:
                    if (_target != null && _target.State == TaskTargetState.Done)
                    {
                        Complete();
                    }
                    break;
            }
        }
        protected override void OnGuide()
        {
            base.OnGuide();

            if (_highlighting && GetTarget)
            {
                Collider collider = GetTarget.GetComponent<Collider>();
                if (collider && collider.enabled)
                {
                    switch (Main.m_TaskMaster.GuideHighlighting)
                    {
                        case MouseRay.HighlightingType.Normal:
                            GetTarget.OpenHighLight();
                            break;
                        case MouseRay.HighlightingType.Flash:
                            GetTarget.OpenFlashHighLight();
                            break;
                        case MouseRay.HighlightingType.Outline:
                            GetTarget.OpenMeshOutline();
                            break;
                    }
                }
            }
        }
        protected override IEnumerator OnComplete()
        {
            yield return base.OnComplete();

            if (!_duration.Approximately(0))
            {
                yield return YieldInstructioner.GetWaitForSeconds(_duration);
            }
        }

        /// <summary>
        /// 默认的任务点触发类型
        /// </summary>
        public enum TaskTrigger
        {
            /// <summary>
            /// 鼠标点击目标触发
            /// </summary>
            MouseClick,
            /// <summary>
            /// 目标状态变为Done时触发
            /// </summary>
            StateChange
        }

#if UNITY_EDITOR
        protected override int OnPropertyGUI()
        {
            int height = base.OnPropertyGUI();

            GUILayout.BeginHorizontal();
            GUILayout.Label("触发方式:", GUILayout.Width(90));
            _trigger = (TaskTrigger)EditorGUILayout.EnumPopup(_trigger);
            GUILayout.EndHorizontal();

            height += 20;

            GUILayout.BeginHorizontal();
            GUILayout.Label("指引时高亮目标:", GUILayout.Width(90));
            _highlighting = EditorGUILayout.Toggle(_highlighting);
            GUILayout.EndHorizontal();

            height += 20;

            GUILayout.BeginHorizontal();
            GUILayout.Label("持续时间:", GUILayout.Width(90));
            _duration = EditorGUILayout.FloatField(_duration);
            GUILayout.EndHorizontal();

            height += 20;

            return height;
        }
#endif
    }

新建任务点类型

框架会自带TaskPointDefault这个默认任务点类型,通过空白处点击右键,选择New Task Point Script选项启动创建向导来快捷创建新的任务点类。
在这里插入图片描述
如下,我们新建一个任务点类型TaskPointKill

[TaskPoint("TaskPointKill")]
public class TaskPointKill : TaskPointBase
{
	protected override void OnStart()
    {
        base.OnStart();
    }
    protected override void OnUpdate()
    {
        base.OnUpdate();
    }
	protected override void OnGuide()
    {
        base.OnGuide();
    }
	protected override IEnumerator OnComplete()
    {
        yield return base.OnComplete();
    }
	protected override void OnAutoComplete()
    {
        base.OnAutoComplete();
    }
    protected override void OnEnd()
    {
        base.OnEnd();
    }

#if UNITY_EDITOR
    protected override int OnPropertyGUI()
    {
        int height = base.OnPropertyGUI();

		GUILayout.BeginHorizontal();
        GUILayout.Label("[新建任务点]");
        GUILayout.EndHorizontal();

		height += 20;

        return height;
    }
#endif
}

然后我们就可以直接在编辑面板创建该类型的任务点:
在这里插入图片描述

新建任务点

我们新建如下两个任务点,用来细分任务内容:
在这里插入图片描述
不过,我们的任务点目前并不知道如何才算杀了黄蜂,所以我们要在TaskPointKill中写一些东西:

[TaskPoint("击杀任务点")]
public class TaskPointKill : TaskPointBase
{
    //任务的击杀目标
    public string KillTarget;
    //任务的击杀数量
    public int KillNumber;

    protected override void OnUpdate()
    {
        base.OnUpdate();

        if (KillPool.Target[KillTarget].KillNumber >= KillNumber)
        {
            Complete();
        }
    }
    
	protected override IEnumerator OnComplete()
    {
        yield return base.OnComplete();

        //等待1秒后再改变自身完成状态
        yield return YieldInstructioner.GetWaitForSeconds(1);
    }

#if UNITY_EDITOR
    public override int OnPropertyGUI()
    {
        int height = base.OnPropertyGUI();

        GUILayout.BeginHorizontal();
        GUILayout.Label("任务击杀目标:");
        KillTarget = EditorGUILayout.TextField(KillTarget);
        GUILayout.EndHorizontal();

        height += 20;

        GUILayout.BeginHorizontal();
        GUILayout.Label("任务击杀数量:");
        KillNumber = EditorGUILayout.IntField(KillNumber);
        GUILayout.EndHorizontal();

        height += 20;

        return height;
    }
#endif
}

查看属性面板的变化:
在这里插入图片描述

任务点完成

任务点的OnUpdate会每帧执行,当此任务内容激活,且此任务点激活时:

    public override void OnUpdate()
    {
        base.OnUpdate();

        if (KillPool.Target[KillTarget].KillNumber >= KillNumber)
        {
            Complete();
        }
    }

所以,在OnUpdate中判断合适的时机,调用Complete(),便是标记此任务点完成。

任务点依赖

对于如上的两个简单的任务点,不存在任何的依赖(连线),也就不存在任何的先后关系,先杀大黄蜂或是先杀小黄蜂都可以,只要两个任务点完成,整个任务内容就算完成。
但是,如果我们的要求是先杀3只小黄蜂,然后再杀2只大黄蜂,那么就要用到接下来的任务点依赖了。
在这里插入图片描述
注意:任务点杀死2只大黄蜂的左侧连接至任务点杀死3只小黄蜂的右侧,表示任务点杀死2只大黄蜂依赖于杀死3只小黄蜂,如果A依赖于B,则必须B任务点完成以后,A任务点才会激活。

注意:请不要在任务内容和任务点中定义不可序列化类型的字段,对于GameObject这个常用的类型,理论上他是可以序列化的,但我们的TaskContentAsset资源是全局的,并不针对某一个Prefab,所以GameObject在这里也是不可序列化类型。

注意:在这里可以使用TaskGameObject代替GameObject。

如下,定义一个TaskGameObject字段,在OnPropertyGUI中必须使用TaskGameObjectField才能画出该字段:

    public TaskGameObject Target;

#if UNITY_EDITOR
    public override int OnPropertyGUI()
    {
        int height = base.OnPropertyGUI();

        GUILayout.BeginHorizontal();
        GUILayout.Label("任务击杀目标:");
        KillTarget = EditorGUILayout.TextField(KillTarget);
        GUILayout.EndHorizontal();

        height += 20;

        GUILayout.BeginHorizontal();
        GUILayout.Label("任务击杀数量:");
        KillNumber = EditorGUILayout.IntField(KillNumber);
        GUILayout.EndHorizontal();

        height += 20;

        TaskGameObject.DrawField(Target, "Target:", 50, Anchor.width);

        height += 20;

        return height;
    }
#endif

查看属性面板变化:
在这里插入图片描述
我们可以拖拽Scene中的任意GameObject到属性面板:
在这里插入图片描述
我们把鼠标放在TaskGameObject字段的名称(比如这里的Target)上,会显示该对象的GUID,任何时候,当对象丢失时,任务执行器会通过GUID找到他,只要场景中存在这个对象,不管他是名字改变了,还是层级改变了!只有点击后面的垃圾桶按钮才能彻底删除这个对象。

在代码中总控

1、首先,需要将TaskContentAsset资源指定给TaskMaster(静态指定和动态指定均可):
在这里插入图片描述
2、然后,开始整个Task流程:

        //重新编译所有任务内容
        Main.m_TaskMaster.RecompileTaskContent();
        //任务流程开始
        Main.m_TaskMaster.Begin();

3、终止整个Task流程:

        //任务流程终止
        Main.m_TaskMaster.End();

4、监听全局事件:

        //任务流程开始事件
        EventTaskBegin
        //任务流程结束事件
        EventTaskEnd
        //任意任务内容激活事件
        EventTaskContentStart
        //任意任务内容完成事件
        EventTaskContentComplete
        //任意任务点激活事件
        EventTaskPointStart
        //任意任务点完成事件
        EventTaskPointComplete

运行时检视面板

在编辑器中运行时将会出现运行时检视面板(Runtime Data),主要用以调试或数据监测,目前面板如下:
在这里插入图片描述
1.No Runtime Data!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神码编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值