
就是这个列表。当我们要给PlayMaker行为树的每一个状态state添加行为的时候,就是在这个行为菜单上找到你想要的行为并添加上去。
那既然有这么多的行为我们为什么还要扩展嗯?那当然是不是所有的行为都能满足要求的,比如你可能希望材质的颜色能线性插值变化,或者控制动画的播放,更为精确的检测地面碰撞体。而有些插件会自带与PlayMaker有关的扩展行为脚本。比如我正在使用的2D Toolkit插件,就有与PlayMaker合作使用的资源包,可以更好的在PlayMaker行为树上控制动画的播放。当然不是什么都和PlayMaker合作的,自然就需要自己造轮子了。
我们将从最简单的扩展脚本。
比如我觉得行为菜单中的IntAdd不适合我,我就可以创建一个新的脚本IntAddV2.内容如下
- using System;
-
- namespace HutongGames.PlayMaker.Actions
- {
- [ActionCategory(ActionCategory.Math)]
- [Tooltip("Adds a value to an Integer Variable. Uses FixedUpdate")]
- public class IntAddV2 : FsmStateAction
- {
- [RequiredField]
- [UIHint(UIHint.Variable)]
- public FsmInt intVariable;
-
- [RequiredField]
- public FsmInt add;
-
- public bool everyFrame;
-
- public override void Reset()
- {
- intVariable = null;
- add = null;
- everyFrame = false;
- }
-
- public override void OnPreprocess()
- {
- Fsm.HandleFixedUpdate = true;
- }
-
- public override void OnEnter()
- {
- intVariable.Value += add.Value;
- if (!everyFrame)
- {
- base.Finish();
- }
- }
-
- public override void OnFixedUpdate()
- {
- intVariable.Value += add.Value;
- }
- }
- }
如脚本内容可见,我们要想在行为菜单上找到这个需要将其归纳进命名空间
namespace HutongGames.PlayMaker.Actions
还需要让它继承FsmStateAction来使用 这个类的回调函数
特性[ActionCategory(ActionCategory.Math)] 则是标记你这个扩展脚本在行为菜单的类型,比如我这个是数学型,我们就可以在这里找到它

[Tooltip("")]则是提示这个行为脚本的功能用途作用,可以看到我们点击的时候下面就会显示

你可能已经注意会有一个重载了基类的override方法,这些就类似于继承MonoBehaviour的Awake,Start,Update等的回调函数,在行为树运行到这个行为的时候它们就会相应的被调用。
而Reset()同样也是对应的,你需要在一开始的时候重置你设置的变量

当你需要使用Variable中的变量时,就需要使用PlayMaker中的变量,即Fsm+基础数据类型,

例如本案例中的FsmInt,其他还有什么FsmBool,最后一个变量public bool everyFrame;也是非常常见的。这决定你是否需要每帧循环这些这个动作Action,我们在OnEnter()中需要判断是否是开启everyFrame ,base.Finish()表示结束这个回调。
在保存好脚本后回到我们的PlayMaker编辑器,在一个状态上添加这个IntAddV2
可以看到添加的特性【RequireField】会要求你添加某个类型的字段,
而[UIHint(UIHint.Variable)]特性则会有最右边两条杠提示你要使用变量,如果你强制使用变量则需要在这个变量上添加这两个特性。

看完最简单我再来分享几个其它类型的行为脚本扩展吧
我们再为敌人写一个判断脚本,就写一个判断敌人和玩家的方向吧
- using System;
-
- namespace HutongGames.PlayMaker.Actions
- {
- [ActionCategory("Enemy")]
- [Tooltip("Check whether target wher to object")]
- public class CheckTargetDirection : FsmStateAction
- {
- [RequiredField]
- public FsmOwnerDefault gameObject;
-
- [RequiredField]
- public FsmGameObject target;
-
- public FsmEvent aboveEvent;
- public FsmEvent belowEvent;
- public FsmEvent rightEvent;
- public FsmEvent leftEvent;
-
- [UIHint(UIHint.Variable)]
- public FsmBool aboveBool;
-
- [UIHint(UIHint.Variable)]
- public FsmBool belowBool;
-
- [UIHint(UIHint.Variable)]
- public FsmBool rightBool;
-
- [UIHint(UIHint.Variable)]
- public FsmBool leftBool;
-
- private FsmGameObject self;
- private FsmFloat x;
- private FsmFloat y;
-
- public bool everyFrame;
-
- public override void Reset()
- {
- gameObject = null;
- target = null;
- everyFrame = false;
- }
-
- public override void OnEnter()
- {
- self = base.Fsm.GetOwnerDefaultTarget(gameObject);
- DoCheckDirection();
- if (!everyFrame)
- {
- base.Finish();
- }
- }
-
- public override void OnUpdate()
- {
- DoCheckDirection();
- }
-
- private void DoCheckDirection()
- {
- float num = self.Value.transform.position.x;
- float num2 = self.Value.transform.position.y;
- float num3 = target.Value.transform.position.x;
- float num4 = target.Value.transform.position.y;
- if(num < num3)
- {
- base.Fsm.Event(rightEvent);
- rightBool.Value = true;
- }
- else
- {
- rightBool.Value = false;
- }
- if (num > num3)
- {
- base.Fsm.Event(leftEvent);
- leftBool.Value = true;
- }
- else
- {
- leftBool.Value = false;
- }
- if (num2 < num4)
- {
- base.Fsm.Event(aboveEvent);
- aboveBool.Value = true;
- }
- else
- {
- aboveBool.Value = false;
- }
- if (num2 > num4)
- {
- base.Fsm.Event(belowEvent);
- belowBool.Value = true;
- return;
- }
- belowBool.Value = false;
- }
- }
- }
此处我们需要了解什么是FsmOwnerDefault类型,这个是判断行为是使用与使用者本身还是特殊的游戏对象,在OnEnter函数中,我们使用base.Fsm.GetOwnerDefaultTarget(gameObject);初始化并赋值给self变量名,然后再我们的DoCheckDirection来判断。当判断条件通过时,我们需要发送相应的事件。


用FsmEvnet来创建FSM事件变量,用base.Fsm.Event() 发送事件,事件就是这个里面的,可作为行为的过渡条件

我们再写一个行为Action与碰撞物理检测有关的吧,比如控制Rigibody2d上![]()
- using System;
- using UnityEngine;
-
- namespace HutongGames.PlayMaker.Actions
- {
- [ActionCategory(ActionCategory.Physics2D)]
- [Tooltip("Set rigidbody 2D interpolation mode to Extrapolate")]
- public class SetExtrapolate : ComponentAction<Rigidbody2D>
- {
- [RequiredField]
- [CheckForComponent(typeof(Rigidbody2D))]
- [Tooltip("This Object requires aRigibody2D componennt attached")]
- public FsmOwnerDefault gameObject;
-
- public override void Reset()
- {
- gameObject = null;
- }
-
- public override void OnEnter()
- {
- DoSetExtrapolate();
- base.Finish();
- }
-
- private void DoSetExtrapolate()
- {
- GameObject owenrDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);
- if (base.UpdateCache(owenrDefaultTarget))
- {
- return;
- }
- base.rigidbody2d.interpolation = RigidbodyInterpolation2D.Interpolate;
- }
- }
- }
可以看到我们不再继承FsmStateAction,而是更改了能直接访问Unity自带组件功能的类ComponentAction<>

同时我们还使用了 [CheckForComponent(typeof(Rigidbody2D))]特性,
它将检查你是否天剑了typeof()里面的组件类型,如果没有会有红色提示
以及方法UpdateCache()也是判断是否找到组件的。

再来写一个控制组件SpriteRenderer是否启用的扩展行为脚本
- using System;
- using UnityEngine;
-
- namespace HutongGames.PlayMaker.Actions
- {
- [ActionCategory("GameObject")]
- [Tooltip("Set sprite renderer to active or inactive. Can only be one sprite renderer on object. ")]
- public class SetSpriteRenderer : FsmStateAction
- {
- [RequiredField]
- public FsmOwnerDefault gameObject;
-
- public FsmBool active;
-
- public override void Reset()
- {
- gameObject = null;
- active = false;
- }
-
- public override void OnEnter()
- {
- if(gameObject != null)
- {
- GameObject ownerDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);
- if(ownerDefaultTarget != null)
- {
- SpriteRenderer component = ownerDefaultTarget.GetComponent
(); - if(component != null)
- {
- component.enabled = active.Value;
- }
- }
- }
- }
- }
- }
再来写一个逻辑相关的,比如我们可能需要同时判断多个Bool条件同时的对错,那么原生的BoolTest不能满足我们(太繁琐了),这时候就需要写一个新的BoolTestMore
- using System;
-
- namespace HutongGames.PlayMaker.Actions
- {
- [ActionCategory(ActionCategory.Logic)]
- [Tooltip("set true if all the given Bool Variables are are equal to thier Bool States.")]
- public class BoolTestMore : FsmStateAction
- {
- [RequiredField]
- [UIHint(UIHint.Variable)]
- [Tooltip("This must be the number used for Bool States")]
- public FsmBool[] boolVariables;
-
- [RequiredField]
- [Tooltip("This must be the number used for Bool States")]
- public FsmBool[] boolStates;
-
- public FsmEvent trueEvent;
- public FsmEvent falseEvent;
-
- [UIHint(UIHint.Variable)]
- public FsmBool storeResult;
-
- public bool everyFrame;
-
- public override void Reset()
- {
- boolVariables = null;
- boolStates = null;
- trueEvent = null;
- falseEvent = null;
- storeResult = null;
- everyFrame = false;
- }
-
- public override void OnEnter()
- {
- DoAllTrue();
- if (everyFrame)
- {
- base.Finish();
- }
- }
-
- public override void OnUpdate()
- {
- DoAllTrue();
- }
-
- private void DoAllTrue()
- {
- if(boolVariables.Length == 0 || boolStates.Length == 0)
- {
- return;
- }
- if(boolVariables.Length != boolStates.Length)
- {
- return;
- }
- bool flag = true;
- for (int i = 0; i < boolVariables.Length; i++)
- {
- if(boolVariables[i].Value != boolStates[i].Value)
- {
- flag = false;
- break;
- }
- }
- storeResult.Value = flag;
- if (flag)
- {
- base.Fsm.Event(trueEvent);
- return;
- }
- base.Fsm.Event(falseEvent);
- }
- }
- }
给定的两个Bool数组成员的数量要相等同时不为零才会执行判断,多考虑一点我们可能需要将判断结果保持在一个FsmBool变量上。并且当flag为true的时候我们发送trueEvent,false 的时候发送falseEvent。
最后再来写一个如何自己平常写的脚本使用,比如我们再自己的脚本上创建了一个int值,我们需要在PlayMaker达到某个条件的时候减一下值,
- using System;
- using UnityEngine;
-
- namespace HutongGames.PlayMaker.Actions
- {
- [ActionCategory("Data")]
- [Tooltip("Sends a Message to PlayerData to send and receive data.")]
- public class DecrementPlayerDataInt : FsmStateAction
- {
- [RequiredField]
- [Tooltip("GameManager reference)]
- public FsmOwnerDefault gameObject;
- [RequiredField]
- public FsmString intName;
- public override void Reset()
- {
- gameObject = null;
- intName = null;
- }
- public override void OnEnter()
- {
- GameObject ownerDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);
- if (ownerDefaultTarget == null)
- {
- return;
- }
- GameManager componet = ownerDefaultTarget.GetComponent
(); - if (componet == null)
- {
- Debug.Log(" could not find a GameManager on this object!");
- return;
- }
- componet.DecrementDataInt(intName.Value);
- base.Finish();
- }
- }
- }
我们通过调用GameManager上的方法,实现减去我们储存的int变量的值。