• [Unity好插件之PlayMaker]PlayMaker如何扩展额外创建更多的脚本


    学习目标:

    如果你正在学习使用PlayMaker的话,那么本篇文章将非常的适用。关于如何连线则是你自己的想法。本篇侧重于扩展适用更多的PlayMaker行为Action,那么什么是PlayMaker行为Action呢?

    就是这个列表。当我们要给PlayMaker行为树的每一个状态state添加行为的时候,就是在这个行为菜单上找到你想要的行为并添加上去。

    那既然有这么多的行为我们为什么还要扩展嗯?那当然是不是所有的行为都能满足要求的,比如你可能希望材质的颜色能线性插值变化,或者控制动画的播放,更为精确的检测地面碰撞体。而有些插件会自带与PlayMaker有关的扩展行为脚本。比如我正在使用的2D Toolkit插件,就有与PlayMaker合作使用的资源包,可以更好的在PlayMaker行为树上控制动画的播放。当然不是什么都和PlayMaker合作的,自然就需要自己造轮子了。


    学习内容:

     我们将从最简单的扩展脚本。

    比如我觉得行为菜单中的IntAdd不适合我,我就可以创建一个新的脚本IntAddV2.内容如下

    1. using System;
    2. namespace HutongGames.PlayMaker.Actions
    3. {
    4. [ActionCategory(ActionCategory.Math)]
    5. [Tooltip("Adds a value to an Integer Variable. Uses FixedUpdate")]
    6. public class IntAddV2 : FsmStateAction
    7. {
    8. [RequiredField]
    9. [UIHint(UIHint.Variable)]
    10. public FsmInt intVariable;
    11. [RequiredField]
    12. public FsmInt add;
    13. public bool everyFrame;
    14. public override void Reset()
    15. {
    16. intVariable = null;
    17. add = null;
    18. everyFrame = false;
    19. }
    20. public override void OnPreprocess()
    21. {
    22. Fsm.HandleFixedUpdate = true;
    23. }
    24. public override void OnEnter()
    25. {
    26. intVariable.Value += add.Value;
    27. if (!everyFrame)
    28. {
    29. base.Finish();
    30. }
    31. }
    32. public override void OnFixedUpdate()
    33. {
    34. intVariable.Value += add.Value;
    35. }
    36. }
    37. }

    如脚本内容可见,我们要想在行为菜单上找到这个需要将其归纳进命名空间 
    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)]特性则会有最右边两条杠提示你要使用变量,如果你强制使用变量则需要在这个变量上添加这两个特性。

     

    看完最简单我再来分享几个其它类型的行为脚本扩展吧

    其它扩展:

    我们再为敌人写一个判断脚本,就写一个判断敌人和玩家的方向吧

    1. using System;
    2. namespace HutongGames.PlayMaker.Actions
    3. {
    4. [ActionCategory("Enemy")]
    5. [Tooltip("Check whether target wher to object")]
    6. public class CheckTargetDirection : FsmStateAction
    7. {
    8. [RequiredField]
    9. public FsmOwnerDefault gameObject;
    10. [RequiredField]
    11. public FsmGameObject target;
    12. public FsmEvent aboveEvent;
    13. public FsmEvent belowEvent;
    14. public FsmEvent rightEvent;
    15. public FsmEvent leftEvent;
    16. [UIHint(UIHint.Variable)]
    17. public FsmBool aboveBool;
    18. [UIHint(UIHint.Variable)]
    19. public FsmBool belowBool;
    20. [UIHint(UIHint.Variable)]
    21. public FsmBool rightBool;
    22. [UIHint(UIHint.Variable)]
    23. public FsmBool leftBool;
    24. private FsmGameObject self;
    25. private FsmFloat x;
    26. private FsmFloat y;
    27. public bool everyFrame;
    28. public override void Reset()
    29. {
    30. gameObject = null;
    31. target = null;
    32. everyFrame = false;
    33. }
    34. public override void OnEnter()
    35. {
    36. self = base.Fsm.GetOwnerDefaultTarget(gameObject);
    37. DoCheckDirection();
    38. if (!everyFrame)
    39. {
    40. base.Finish();
    41. }
    42. }
    43. public override void OnUpdate()
    44. {
    45. DoCheckDirection();
    46. }
    47. private void DoCheckDirection()
    48. {
    49. float num = self.Value.transform.position.x;
    50. float num2 = self.Value.transform.position.y;
    51. float num3 = target.Value.transform.position.x;
    52. float num4 = target.Value.transform.position.y;
    53. if(num < num3)
    54. {
    55. base.Fsm.Event(rightEvent);
    56. rightBool.Value = true;
    57. }
    58. else
    59. {
    60. rightBool.Value = false;
    61. }
    62. if (num > num3)
    63. {
    64. base.Fsm.Event(leftEvent);
    65. leftBool.Value = true;
    66. }
    67. else
    68. {
    69. leftBool.Value = false;
    70. }
    71. if (num2 < num4)
    72. {
    73. base.Fsm.Event(aboveEvent);
    74. aboveBool.Value = true;
    75. }
    76. else
    77. {
    78. aboveBool.Value = false;
    79. }
    80. if (num2 > num4)
    81. {
    82. base.Fsm.Event(belowEvent);
    83. belowBool.Value = true;
    84. return;
    85. }
    86. belowBool.Value = false;
    87. }
    88. }
    89. }

     此处我们需要了解什么是FsmOwnerDefault类型,这个是判断行为是使用与使用者本身还是特殊的游戏对象,在OnEnter函数中,我们使用base.Fsm.GetOwnerDefaultTarget(gameObject);初始化并赋值给self变量名,然后再我们的DoCheckDirection来判断。当判断条件通过时,我们需要发送相应的事件。

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

    我们再写一个行为Action与碰撞物理检测有关的吧,比如控制Rigibody2d上

    1. using System;
    2. using UnityEngine;
    3. namespace HutongGames.PlayMaker.Actions
    4. {
    5. [ActionCategory(ActionCategory.Physics2D)]
    6. [Tooltip("Set rigidbody 2D interpolation mode to Extrapolate")]
    7. public class SetExtrapolate : ComponentAction<Rigidbody2D>
    8. {
    9. [RequiredField]
    10. [CheckForComponent(typeof(Rigidbody2D))]
    11. [Tooltip("This Object requires aRigibody2D componennt attached")]
    12. public FsmOwnerDefault gameObject;
    13. public override void Reset()
    14. {
    15. gameObject = null;
    16. }
    17. public override void OnEnter()
    18. {
    19. DoSetExtrapolate();
    20. base.Finish();
    21. }
    22. private void DoSetExtrapolate()
    23. {
    24. GameObject owenrDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);
    25. if (base.UpdateCache(owenrDefaultTarget))
    26. {
    27. return;
    28. }
    29. base.rigidbody2d.interpolation = RigidbodyInterpolation2D.Interpolate;
    30. }
    31. }
    32. }

    可以看到我们不再继承FsmStateAction,而是更改了能直接访问Unity自带组件功能的类ComponentAction<>

    同时我们还使用了        [CheckForComponent(typeof(Rigidbody2D))]特性,

    它将检查你是否天剑了typeof()里面的组件类型,如果没有会有红色提示

     以及方法UpdateCache()也是判断是否找到组件的。

    再来写一个控制组件SpriteRenderer是否启用的扩展行为脚本

    1. using System;
    2. using UnityEngine;
    3. namespace HutongGames.PlayMaker.Actions
    4. {
    5. [ActionCategory("GameObject")]
    6. [Tooltip("Set sprite renderer to active or inactive. Can only be one sprite renderer on object. ")]
    7. public class SetSpriteRenderer : FsmStateAction
    8. {
    9. [RequiredField]
    10. public FsmOwnerDefault gameObject;
    11. public FsmBool active;
    12. public override void Reset()
    13. {
    14. gameObject = null;
    15. active = false;
    16. }
    17. public override void OnEnter()
    18. {
    19. if(gameObject != null)
    20. {
    21. GameObject ownerDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);
    22. if(ownerDefaultTarget != null)
    23. {
    24. SpriteRenderer component = ownerDefaultTarget.GetComponent();
    25. if(component != null)
    26. {
    27. component.enabled = active.Value;
    28. }
    29. }
    30. }
    31. }
    32. }
    33. }

     再来写一个逻辑相关的,比如我们可能需要同时判断多个Bool条件同时的对错,那么原生的BoolTest不能满足我们(太繁琐了),这时候就需要写一个新的BoolTestMore

    1. using System;
    2. namespace HutongGames.PlayMaker.Actions
    3. {
    4. [ActionCategory(ActionCategory.Logic)]
    5. [Tooltip("set true if all the given Bool Variables are are equal to thier Bool States.")]
    6. public class BoolTestMore : FsmStateAction
    7. {
    8. [RequiredField]
    9. [UIHint(UIHint.Variable)]
    10. [Tooltip("This must be the number used for Bool States")]
    11. public FsmBool[] boolVariables;
    12. [RequiredField]
    13. [Tooltip("This must be the number used for Bool States")]
    14. public FsmBool[] boolStates;
    15. public FsmEvent trueEvent;
    16. public FsmEvent falseEvent;
    17. [UIHint(UIHint.Variable)]
    18. public FsmBool storeResult;
    19. public bool everyFrame;
    20. public override void Reset()
    21. {
    22. boolVariables = null;
    23. boolStates = null;
    24. trueEvent = null;
    25. falseEvent = null;
    26. storeResult = null;
    27. everyFrame = false;
    28. }
    29. public override void OnEnter()
    30. {
    31. DoAllTrue();
    32. if (everyFrame)
    33. {
    34. base.Finish();
    35. }
    36. }
    37. public override void OnUpdate()
    38. {
    39. DoAllTrue();
    40. }
    41. private void DoAllTrue()
    42. {
    43. if(boolVariables.Length == 0 || boolStates.Length == 0)
    44. {
    45. return;
    46. }
    47. if(boolVariables.Length != boolStates.Length)
    48. {
    49. return;
    50. }
    51. bool flag = true;
    52. for (int i = 0; i < boolVariables.Length; i++)
    53. {
    54. if(boolVariables[i].Value != boolStates[i].Value)
    55. {
    56. flag = false;
    57. break;
    58. }
    59. }
    60. storeResult.Value = flag;
    61. if (flag)
    62. {
    63. base.Fsm.Event(trueEvent);
    64. return;
    65. }
    66. base.Fsm.Event(falseEvent);
    67. }
    68. }
    69. }

    给定的两个Bool数组成员的数量要相等同时不为零才会执行判断,多考虑一点我们可能需要将判断结果保持在一个FsmBool变量上。并且当flag为true的时候我们发送trueEvent,false 的时候发送falseEvent。

    最后再来写一个如何自己平常写的脚本使用,比如我们再自己的脚本上创建了一个int值,我们需要在PlayMaker达到某个条件的时候减一下值,

    1. using System;
    2. using UnityEngine;
    3. namespace HutongGames.PlayMaker.Actions
    4. {
    5. [ActionCategory("Data")]
    6. [Tooltip("Sends a Message to PlayerData to send and receive data.")]
    7. public class DecrementPlayerDataInt : FsmStateAction
    8. {
    9. [RequiredField]
    10. [Tooltip("GameManager reference)]
    11. public FsmOwnerDefault gameObject;
    12. [RequiredField]
    13. public FsmString intName;
    14. public override void Reset()
    15. {
    16. gameObject = null;
    17. intName = null;
    18. }
    19. public override void OnEnter()
    20. {
    21. GameObject ownerDefaultTarget = base.Fsm.GetOwnerDefaultTarget(gameObject);
    22. if (ownerDefaultTarget == null)
    23. {
    24. return;
    25. }
    26. GameManager componet = ownerDefaultTarget.GetComponent();
    27. if (componet == null)
    28. {
    29. Debug.Log(" could not find a GameManager on this object!");
    30. return;
    31. }
    32. componet.DecrementDataInt(intName.Value);
    33. base.Finish();
    34. }
    35. }
    36. }

    我们通过调用GameManager上的方法,实现减去我们储存的int变量的值。 


    学习产出:

     就此我们实现了多个种类的扩展方法,但其实仅仅这些还不足以支撑起整个游戏的行为逻辑,而网路上有关扩展的视频文章也是屈指可数,所以想要全面学习使用,建议打开PlayMaker原生配的行为脚本,学习仿造它的脚本结构,并结合自己的想法,这样才能写出全面的行为扩展来。

  • 相关阅读:
    3.2python基础02
    面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!
    上海00后985毕业女生月薪1.2w,想找年薪40万程序员,网友表示很不理解
    操作系统篇之虚拟内存
    webpack-cl明明已经安装了,但是还是会报未安装
    jquery点击禁用
    hexo主题应用
    arduino - 用 arduino zero 开发板来学习理解arduino软件编程细节
    Ladybug 全景相机, 360°球形成像,带来全方位的视觉体验
    明远智睿IMX6Q烧写Ubuntu18系统,此方法同样适合其它系统!
  • 原文地址:https://blog.csdn.net/dangoxiba/article/details/127953429