Maison >développement back-end >Tutoriel C#.Net >C#/étapes d'implémentation basées sur l'arbre de comportement Unity [Pure code]
Ce qui suit est une pure référence de code, sans l'édition de nœuds et la configuration d'exportation du plug-in Unity. Le concept d'arbre de comportement et les divers plug-ins Unity ne sont pas introduits ici. Le code est résumé et affiné sur la base du 2D Game Kit de Unity, qui sera très utile pour apprendre et comprendre les arbres de comportement !
using BTAI; using UnityEngine; public class TestBT : MonoBehaviour, BTAI.IBTDebugable { Root aiRoot = BT.Root(); private void OnEnable() { aiRoot.OpenBranch( BT.If(TestVisibleTarget).OpenBranch( BT.Call(Aim), BT.Call(Shoot) ), BT.Sequence().OpenBranch( BT.Call(Walk), BT.Wait(5.0f), BT.Call(Turn), BT.Wait(1.0f), BT.Call(Turn) ) ); } private void Turn() { Debug.Log("执行了 Turn"); } private void Walk() { Debug.Log("执行了 Walk"); } private void Shoot() { Debug.Log("执行了 Shoot"); } private void Aim() { Debug.Log("执行了 Aim"); } private bool TestVisibleTarget() { var isSuccess = UnityEngine.Random.Range(0, 2) == 1; Debug.Log("执行了 TestVisibleTarget Result:" + isSuccess); return isSuccess; } private void Update() { aiRoot.Tick(); } public Root GetAIRoot() { return aiRoot; } }
using System.Collections.Generic; using UnityEngine; /// <summary> /// 这只是脚本系统 /// 行为树会从Root节点开始遍历子节点。Update中执行 /// 每个节点都有相关的操作,但是基本上就是返回三种状态 /// ● Success: 节点成功完成任务 /// ● Failure: 节点未通过任务 /// ● Continue:节点尚未完成任务。 /// 但是每个节点的父节点对子节点的结果处理方式还不同。 例如 /// ● Test 节点: 测试节点将调用其子节点并在测试为真时返回子节点状态,如果测试为假,则返回Failure而不调用其子节点。 /// 行为树的一种构造方式如下: /// Root aiRoot = BT.Root(); /// aiRoot.Do( /// BT.If(TestVisibleTarget).Do( /// BT.Call(Aim), /// BT.Call(Shoot) /// ), /// BT.Sequence().Do( /// BT.Call(Walk), /// BT.Wait(5.0f), /// BT.Call(Turn), /// BT.Wait(1.0f), /// BT.Call(Turn) /// ) /// ); ///然后在Update中 调用 aiRoot.Tick() 。 刚刚构造的行为树是怎么样的检查过程呢? ///1、首先检查TestVisibleTarget是否返回Ture,如果是继续执行子节点执行Aim函数和Shoot函数 ///2、TestVisibleTarget是否返回false,if节点将返回Failure, 然后Root 将转向下一个子节点。这是个Sequence节点,它从执行第一个子节点开始。 /// 1)将调用Walk函数,直接返回 Success,以便Sequence将下一个子节点激活并执行它。 /// 2)执行Wait 节点,只是要等待5秒,还是第一次调用,所以肯定返回Running状态, 当Sequence从子节点上得到Running状态时,不会更改激活的子节点索引,下次Update的时候还是从这个节点开始执行 ///3、Update的执行,当Wait节点等待的时间到了的时候,将会返回Success, 以便序列将转到下一个孩子。 ///脚本中的Node列表 /// Sequence: //一个接一个地执行子节点。如果子节点返回: //●Success:Sequence将选择下一帧的下一个孩子开始。 //●Failure:Sequence将返回到下一帧的第一个子节点(从头开始)。 //●Continue:Sequence将在下一帧再次调用该节点。 //RandomSequence: // 每次调用时,从子列表中执行一个随机子节点。您可以在构造函数中指定要应用于每个子项的权重列表作为int数组,以使某些子项更有可能被选中。 //Selector : //按顺序执行所有子项,直到一个返回Success,然后退出而不执行其余子节点。如果没有返回Success,则此节点将返回Failure。 // Condition : // 如果给定函数返回true,则此节点返回Success;如果为false,则返回Failure。 // 与其他依赖于子节点结果的节点链接时很有用(例如,Sequence,Selector等) // If : //调用给定的函数。 // ●如果返回true,则调用当前活动的子级并返回其状态。 // ●否则,它将在不调用其子项的情况下返回Failure // While: //只要给定函数返回true,就返回Continue(因此,下一帧将再次从该节点开始,而不会评估所有先前的节点)。 //子节点们将陆续被执行。 //当函数返回false并且循环中断时,将返回Failure。 // Call //调用给定的函数,它将始终返回Success。是动作节点! //Repeat //将连续执行给定次数的所有子节点。 //始终返回Continue,直到达到计数,并返回Success。 //Wait //将返回Continue,直到达到给定时间(首次调用时开始),然后返回Success。 //Trigger //允许在给定的动画师animator中设置Trigger参数(如果最后一个参数设置为false,则取消设置触发器)。始终返回成功。 //SetBool //允许在给定的animator中设置布尔参数的值。始终返回成功 //SetActive //设置给定GameObject的活动/非活动状态。始终返回成功。 /// </summary> namespace BTAI { public enum BTState { Failure, Success, Continue, Abort } /// <summary> /// 节点 对象工厂 /// </summary> public static class BT { public static Root Root() { return new Root(); } public static Sequence Sequence() { return new Sequence(); } public static Selector Selector(bool shuffle = false) { return new Selector(shuffle); } public static Action RunCoroutine(System.Func<IEnumerator<BTState>> coroutine) { return new Action(coroutine); } public static Action Call(System.Action fn) { return new Action(fn); } public static ConditionalBranch If(System.Func<bool> fn) { return new ConditionalBranch(fn); } public static While While(System.Func<bool> fn) { return new While(fn); } public static Condition Condition(System.Func<bool> fn) { return new Condition(fn); } public static Repeat Repeat(int count) { return new Repeat(count); } public static Wait Wait(float seconds) { return new Wait(seconds); } public static Trigger Trigger(Animator animator, string name, bool set = true) { return new Trigger(animator, name, set); } public static WaitForAnimatorState WaitForAnimatorState(Animator animator, string name, int layer = 0) { return new WaitForAnimatorState(animator, name, layer); } public static SetBool SetBool(Animator animator, string name, bool value) { return new SetBool(animator, name, value); } public static SetActive SetActive(GameObject gameObject, bool active) { return new SetActive(gameObject, active); } public static WaitForAnimatorSignal WaitForAnimatorSignal(Animator animator, string name, string state, int layer = 0) { return new WaitForAnimatorSignal(animator, name, state, layer); } public static Terminate Terminate() { return new Terminate(); } public static Log Log(string msg) { return new Log(msg); } public static RandomSequence RandomSequence(int[] weights = null) { return new BTAI.RandomSequence(weights); } } /// <summary> /// 节点抽象类 /// </summary> public abstract class BTNode { public abstract BTState Tick(); } /// <summary> /// 包含子节点的组合 节点基类 /// </summary> public abstract class Branch : BTNode { protected int activeChild; protected List<BTNode> children = new List<BTNode>(); public virtual Branch OpenBranch(params BTNode[] children) { for (var i = 0; i < children.Length; i++) this.children.Add(children[i]); return this; } public List<BTNode> Children() { return children; } public int ActiveChild() { return activeChild; } public virtual void ResetChildren() { activeChild = 0; for (var i = 0; i < children.Count; i++) { Branch b = children[i] as Branch; if (b != null) { b.ResetChildren(); } } } } /// <summary> /// 装饰节点 只包含一个子节点,用于某种方式改变这个节点的行为 /// 比如过滤器(用于决定是否允许子节点运行的,如:Until Success, Until Fail等),这种节点的子节点应该是条件节点,条件节点一直检测“视线中是否有敌人”,知道发现敌人为止。 /// 或者 Limit 节点,用于指定某个子节点的最大运行次数 /// 或者 Timer节点,设置了一个计时器,不会立即执行子节点,而是等一段时间,时间到了开始执行子节点 /// 或者 TimerLimit节点,用于指定某个子节点的最长运行时间。 /// 或者 用于产生某个返回状态, /// </summary> public abstract class Decorator : BTNode { protected BTNode child; public Decorator Do(BTNode child) { this.child = child; return this; } } /// <summary> /// 顺序节点 (从左到右依次执行所有子节点,只要子节点返回Success就继续执行后续子节点,直到遇到Failure或者Runing, /// 停止后续执行,并把这个节点返回给父节点,只有它的所有子节点都是Success他才会向父节点返回Success) /// </summary> public class Sequence : Branch { public override BTState Tick() { var childState = children[activeChild].Tick(); switch (childState) { case BTState.Success: activeChild++; if (activeChild == children.Count) { activeChild = 0; return BTState.Success; } else return BTState.Continue; case BTState.Failure: activeChild = 0; return BTState.Failure; case BTState.Continue: return BTState.Continue; case BTState.Abort: activeChild = 0; return BTState.Abort; } throw new System.Exception("This should never happen, but clearly it has."); } } /// <summary> /// 选择节点从左到右依次执行所有子节点 ,只要遇到failure就继续执行后续子节点,直到遇到一个节点返回Success或Running为止。向父节点返回Success或Running /// 所有子节点都是Fail, 那么向父节点凡湖Fail /// 选择节点 用来在可能的行为集合中选择第一个成功的。 比如一个试图躲避枪击的AI角色, 它可以通过寻找隐蔽点, 或离开危险区域, 或寻找援助等多种方式实现目标。 /// 利用选择节点,他会尝试寻找Cover,失败后在试图逃离危险区域。 /// </summary> public class Selector : Branch { public Selector(bool shuffle) { if (shuffle) { var n = children.Count; while (n > 1) { n--; var k = Mathf.FloorToInt(Random.value * (n + 1)); var value = children[k]; children[k] = children[n]; children[n] = value; } } } public override BTState Tick() { var childState = children[activeChild].Tick(); switch (childState) { case BTState.Success: activeChild = 0; return BTState.Success; case BTState.Failure: activeChild++; if (activeChild == children.Count) { activeChild = 0; return BTState.Failure; } else return BTState.Continue; case BTState.Continue: return BTState.Continue; case BTState.Abort: activeChild = 0; return BTState.Abort; } throw new System.Exception("This should never happen, but clearly it has."); } } /// <summary> /// 行为节点 调用方法,或运行协程。完成实际工作, 例如播放动画,让角色移动位置,感知敌人,更换武器,播放声音,增加生命值等。 /// </summary> public class Action : BTNode { System.Action fn; System.Func<IEnumerator<BTState>> coroutineFactory; IEnumerator<BTState> coroutine; public Action(System.Action fn) { this.fn = fn; } public Action(System.Func<IEnumerator<BTState>> coroutineFactory) { this.coroutineFactory = coroutineFactory; } public override BTState Tick() { if (fn != null) { fn(); return BTState.Success; } else { if (coroutine == null) coroutine = coroutineFactory(); if (!coroutine.MoveNext()) { coroutine = null; return BTState.Success; } var result = coroutine.Current; if (result == BTState.Continue) return BTState.Continue; else { coroutine = null; return result; } } } public override string ToString() { return "Action : " + fn.Method.ToString(); } } /// <summary> /// 条件节点 调用方法,如果方法返回true则返回成功,否则返回失败。 /// 用来测试当前是否满足某些性质或条件,例如“玩家是否在20米之内?”“是否能看到玩家?”“生命值是否大于50?”“弹药是否足够?”等 /// </summary> public class Condition : BTNode { public System.Func<bool> fn; public Condition(System.Func<bool> fn) { this.fn = fn; } public override BTState Tick() { return fn() ? BTState.Success : BTState.Failure; } public override string ToString() { return "Condition : " + fn.Method.ToString(); } } /// <summary> /// 当方法为True的时候 尝试执行当前 子节点 /// </summary> public class ConditionalBranch : Block { public System.Func<bool> fn; bool tested = false; public ConditionalBranch(System.Func<bool> fn) { this.fn = fn; } public override BTState Tick() { if (!tested) { tested = fn(); } if (tested) { // 当前子节点执行完就进入下一个节点(超上限就返回到第一个) var result = base.Tick(); // 没执行完 if (result == BTState.Continue) return BTState.Continue; else { tested = false; // 最后一个子节点执行完,才会为Ture return result; } } else { return BTState.Failure; } } public override string ToString() { return "ConditionalBranch : " + fn.Method.ToString(); } } /// <summary> /// While节点 只要方法 返回True 就执行所有子节点, 否则返回 Failure /// </summary> public class While : Block { public System.Func<bool> fn; public While(System.Func<bool> fn) { this.fn = fn; } public override BTState Tick() { if (fn()) base.Tick(); else { //if we exit the loop ResetChildren(); return BTState.Failure; } return BTState.Continue; } public override string ToString() { return "While : " + fn.Method.ToString(); } } /// <summary> /// 阻塞节点 如果当前子节点是Continue 说明没有执行完,阻塞着,执行完之后在继续它后面的兄弟节点 不管成功失败。 /// 如果当前结点是最后一个节点并执行完毕,说明成功!否则就是处于Continue状态。 /// 几个基本上是抽象节点, 像是让所有子节点都执行一遍, 当前子节点执行完就进入下一个节点(超上限就返回到第一个) /// </summary> public abstract class Block : Branch { public override BTState Tick() { switch (children[activeChild].Tick()) { case BTState.Continue: return BTState.Continue; default: activeChild++; if (activeChild == children.Count) { activeChild = 0; return BTState.Success; } return BTState.Continue; } } } public class Root : Block { public bool isTerminated = false; public override BTState Tick() { if (isTerminated) return BTState.Abort; while (true) { switch (children[activeChild].Tick()) { case BTState.Continue: return BTState.Continue; case BTState.Abort: isTerminated = true; return BTState.Abort; default: activeChild++; if (activeChild == children.Count) { activeChild = 0; return BTState.Success; } continue; } } } } /// <summary> /// 多次运行子节点(一个子节点执行一次就算一次) /// </summary> public class Repeat : Block { public int count = 1; int currentCount = 0; public Repeat(int count) { this.count = count; } public override BTState Tick() { if (count > 0 && currentCount < count) { var result = base.Tick(); switch (result) { case BTState.Continue: return BTState.Continue; default: currentCount++; if (currentCount == count) { currentCount = 0; return BTState.Success; } return BTState.Continue; } } return BTState.Success; } public override string ToString() { return "Repeat Until : " + currentCount + " / " + count; } } /// <summary> /// 随机的顺序 执行子节点 /// </summary> public class RandomSequence : Block { int[] m_Weight = null; int[] m_AddedWeight = null; /// <summary> /// 每次再次触发时,将选择一个随机子节点 /// </summary> /// <param name="weight">保留null,以便所有子节点具有相同的权重。 /// 如果权重低于子节点, 则后续子节点的权重都为1</param> public RandomSequence(int[] weight = null) { activeChild = -1; m_Weight = weight; } public override Branch OpenBranch(params BTNode[] children) { m_AddedWeight = new int[children.Length]; for (int i = 0; i < children.Length; ++i) { int weight = 0; int previousWeight = 0; if (m_Weight == null || m_Weight.Length <= i) {//如果没有那个权重, 就将权重 设置为1 weight = 1; } else weight = m_Weight[i]; if (i > 0) previousWeight = m_AddedWeight[i - 1]; m_AddedWeight[i] = weight + previousWeight; } return base.OpenBranch(children); } public override BTState Tick() { if (activeChild == -1) PickNewChild(); var result = children[activeChild].Tick(); switch (result) { case BTState.Continue: return BTState.Continue; default: PickNewChild(); return result; } } void PickNewChild() { int choice = Random.Range(0, m_AddedWeight[m_AddedWeight.Length - 1]); for (int i = 0; i < m_AddedWeight.Length; ++i) { if (choice - m_AddedWeight[i] <= 0) { activeChild = i; break; } } } public override string ToString() { return "Random Sequence : " + activeChild + "/" + children.Count; } } /// <summary> /// 暂停执行几秒钟。 /// </summary> public class Wait : BTNode { public float seconds = 0; float future = -1; public Wait(float seconds) { this.seconds = seconds; } public override BTState Tick() { if (future < 0) future = Time.time + seconds; if (Time.time >= future) { future = -1; return BTState.Success; } else return BTState.Continue; } public override string ToString() { return "Wait : " + (future - Time.time) + " / " + seconds; } } /// <summary> /// 设置动画 trigger 参数 /// </summary> public class Trigger : BTNode { Animator animator; int id; string triggerName; bool set = true; //如果 set == false, 则重置trigger而不是设置它。 public Trigger(Animator animator, string name, bool set = true) { this.id = Animator.StringToHash(name); this.animator = animator; this.triggerName = name; this.set = set; } public override BTState Tick() { if (set) animator.SetTrigger(id); else animator.ResetTrigger(id); return BTState.Success; } public override string ToString() { return "Trigger : " + triggerName; } } /// <summary> /// 设置动画 boolean 参数 /// </summary> public class SetBool : BTNode { Animator animator; int id; bool value; string triggerName; public SetBool(Animator animator, string name, bool value) { this.id = Animator.StringToHash(name); this.animator = animator; this.value = value; this.triggerName = name; } public override BTState Tick() { animator.SetBool(id, value); return BTState.Success; } public override string ToString() { return "SetBool : " + triggerName + " = " + value.ToString(); } } /// <summary> /// 等待animator达到一个状态。 /// </summary> public class WaitForAnimatorState : BTNode { Animator animator; int id; int layer; string stateName; public WaitForAnimatorState(Animator animator, string name, int layer = 0) { this.id = Animator.StringToHash(name); if (!animator.HasState(layer, this.id)) { Debug.LogError("The animator does not have state: " + name); } this.animator = animator; this.layer = layer; this.stateName = name; } public override BTState Tick() { var state = animator.GetCurrentAnimatorStateInfo(layer); if (state.fullPathHash == this.id || state.shortNameHash == this.id) return BTState.Success; return BTState.Continue; } public override string ToString() { return "Wait For State : " + stateName; } } /// <summary> /// 设置 GameObject 的激活状态 /// </summary> public class SetActive : BTNode { GameObject gameObject; bool active; public SetActive(GameObject gameObject, bool active) { this.gameObject = gameObject; this.active = active; } public override BTState Tick() { gameObject.SetActive(this.active); return BTState.Success; } public override string ToString() { return "Set Active : " + gameObject.name + " = " + active; } } /// <summary> /// 等待animator从SendSignal状态机行为 接收信号。 SendSignal : StateMachineBehaviour /// </summary> public class WaitForAnimatorSignal : BTNode { // 进入或退出动画都为 False, 只有执行中为True internal bool isSet = false; string name; int id; public WaitForAnimatorSignal(Animator animator, string name, string state, int layer = 0) { this.name = name; this.id = Animator.StringToHash(name); if (!animator.HasState(layer, this.id)) { Debug.LogError("The animator does not have state: " + name); } else { SendSignal.Register(animator, name, this); } } public override BTState Tick() { if (!isSet) return BTState.Continue; else { isSet = false; return BTState.Success; } } public override string ToString() { return "Wait For Animator Signal : " + name; } } /// <summary> /// 终止节点 切换到中止 状态 /// </summary> public class Terminate : BTNode { public override BTState Tick() { return BTState.Abort; } } /// <summary> /// Log 输出Log 的节点 /// </summary> public class Log : BTNode { string msg; public Log(string msg) { this.msg = msg; } public override BTState Tick() { Debug.Log(msg); return BTState.Success; } } } #if UNITY_EDITOR namespace BTAI { public interface IBTDebugable { Root GetAIRoot(); } } #endif
using BTAI; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace Gamekit2D { /// <summary> /// 运行是查看 行为树中所有节点的状态 /// </summary> public class BTDebug : EditorWindow { protected BTAI.Root _currentRoot = null; [MenuItem("Kit Tools/Behaviour Tree Debug")] static void OpenWindow() { BTDebug btdebug = GetWindow<BTDebug>(); btdebug.Show(); } private void OnGUI() { if (!Application.isPlaying) { EditorGUILayout.HelpBox("Only work during play mode.", MessageType.Info); } else { if (_currentRoot == null) FindRoot(); else { RecursiveTreeParsing(_currentRoot, 0, true); } } } void Update() { Repaint(); } void RecursiveTreeParsing(Branch branch, int indent, bool parentIsActive) { List<BTNode> nodes = branch.Children(); for (int i = 0; i < nodes.Count; ++i) { EditorGUI.indentLevel = indent; bool isActiveChild = branch.ActiveChild() == i; GUI.color = (isActiveChild && parentIsActive) ? Color.green : Color.white; EditorGUILayout.LabelField(nodes[i].ToString()); if (nodes[i] is Branch) RecursiveTreeParsing(nodes[i] as Branch, indent + 1, isActiveChild); } } void FindRoot() { if (Selection.activeGameObject == null) { _currentRoot = null; return; } IBTDebugable debugable = Selection.activeGameObject.GetComponentInChildren<IBTDebugable>(); if (debugable != null) { _currentRoot = debugable.GetAIRoot(); } } } }
Vous pouvez visualiser l'arbre de comportement où se trouve l'objet TestBT.cs dans le menu "Kit Tools /Behaviour Tree Debug"
Recommandations associées :
Écrire une extension PHP en utilisant C/C++
[Tutoriel c#] Types de données C#
Vidéo : Tutoriel C#
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!