playMaker

Author Topic: Add FSM Template at Runtime? [SOLVED]  (Read 22169 times)

Krileon

  • Full Member
  • ***
  • Posts: 107
Add FSM Template at Runtime? [SOLVED]
« on: December 18, 2013, 10:56:08 PM »
I've found the best way to do Debuffs/Buffs and things of the sort is to attach the script then have it remove it self when the debuff/buff has expired. For PlayMaker the best way to do this would be to add a template at runtime anytime you want to add the debuff/buff. Ideally when doing this it should also check to see if the template already exists and if it does optionally replace it or do nothing. This would be EXTREMELY handy.

Is something like this available? Is there API for even adding an FSM at runtime? If it's not available, but API is there to do it I'll g ahead and just make a new action for it.

Note I've tried other alternatives like having an FSM disabled then enabling it, sending an event, or flipping a variable and having it check every frame if the variable changed. All of these work, but none of them are ideal. Sure it works if you have a hand full of debuffs/buffs, but lets say you've dozens upon dozens it quickly begins to fail as an option to do either of those.
« Last Edit: June 20, 2014, 02:05:23 PM by Alex Chouls »

jeanfabre

  • Administrator
  • Hero Member
  • *****
  • Posts: 15594
  • Official Playmaker Support
Re: Add FSM Template at Runtime?
« Reply #1 on: December 19, 2013, 02:45:32 AM »
Hi,

 never done it before, but I found a method for this:

Code: [Select]
public FsmTemplate temp;

void Start () {
PlayMakerFSM _comp = this.gameObject.AddComponent<PlayMakerFSM>();
_comp.SetFsmTemplate(temp);
}

and that works :)

bye,

 Jean

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #2 on: December 19, 2013, 12:51:32 PM »
Awesome, I'll make a playmaker action that does that and see how it goes.

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #3 on: December 25, 2013, 12:36:09 PM »
Ok, I was able to add the template pretty easily. Performance seams ok thus far. Now I need to destroy the template (I'll have this done from the FSM, it needs to destroy it self). This allows for a debuff to run for a specific amount of time and get rid of it self. This is critical usage as adding a ton of disabled FSMs per debuff per object would be insane for example.

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #4 on: December 25, 2013, 02:27:16 PM »
Ok, the below action will add a FSM Template at runtime.

It'll also optionally check if the FSM Template should be unique or not. If it's Unique then it's a unique instance and won't bother checking if an instance of the same FSM Template already exists. If it's not unique then it'll check if the FSM name (if supplied) exists or the FSM template exists. If either of them do exist when it's not unique then it won't add the FSM template.

You can optionally set the FSM to be enabled or disabled on creation so you can optionally enable it later as needed.

Last but not least you can optionally send variables to the newly added template. This will initially disable the FSM, add the template, change the variables, then enable the FSM so it can properly start with the variables added.

Code: [Select]
using System.Collections.Generic;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
[ActionCategory(ActionCategory.StateMachine)]
public class AddFsmTemplate : FsmStateAction {
[RequiredField]
public FsmOwnerDefault gameObject;
[RequiredField]
public FsmTemplate template;
public FsmString name;
public FsmBool active;
public FsmBool unique;
[CompoundArray("Variables", "Name", "Variable")]
[RequiredField]
public FsmString[] variableNames;
[RequiredField]
public FsmVar[] variables;

private GameObject previousGo;
private List<PlayMakerFSM> fsms;

public override void Reset() {
gameObject = null;
template = null;
name = new FsmString { UseVariable = true };
active = new FsmBool { Value = true };
unique = new FsmBool { Value = false };
variableNames = new FsmString[0];
variables = new FsmVar[0];
}

public override void OnEnter() {
var go = Fsm.GetOwnerDefaultTarget( gameObject );

if ( go == null ) {
return;
}

bool exists = false;

if ( ! unique.Value ) {
if ( go != previousGo ) {
fsms = new List<PlayMakerFSM>();

fsms.AddRange( go.GetComponents<PlayMakerFSM>() );

previousGo = go;
}

if ( fsms.Count > 0 ) foreach ( PlayMakerFSM fsm in fsms ) {
if ( ( ( name.Value != "" ) && ( fsm.FsmName == name.Value ) ) || ( ( fsm.FsmTemplate != null ) && ( fsm.FsmTemplate.name == template.name ) ) ) {
exists = true;
}
}
}

if ( ! exists ) {
PlayMakerFSM newFsm = go.AddComponent<PlayMakerFSM>();

if ( name.Value != "" ) {
newFsm.FsmName = name.Value;
}

if ( ( ! active.Value ) || ( variableNames.Length > 0 ) ) {
newFsm.enabled = false;
}

newFsm.SetFsmTemplate( template );

if ( variableNames.Length > 0 ) {
if ( variableNames.Length > 0 ) for ( int i = 0; i < variableNames.Length; i++ ) {
if ( ! variableNames[i].IsNone ) {
NamedVariable target = newFsm.Fsm.Variables.GetVariable( variableNames[i].Value );

if ( target != null ) {
variables[i].ApplyValueTo( target );
}
}
}

if ( active.Value && ( ! newFsm.enabled ) ) {
newFsm.enabled = true;
}
}

if ( ! unique.Value ) {
fsms.Add( newFsm );
}
}

Finish();
}
}
}

Now all that's left is a "DestroyFSM" action to completely get rid of the FSM component. I have yet to figure this one out yet, but once I do this can be used very effectively for spell debuffs, temporary affects like drag on a rigidbody to simulate "slowing", and much much more.

Regarding performance using the above I saw very little loss, if any at all, so it seams perfectly safe to use as needed.
« Last Edit: December 25, 2013, 03:57:27 PM by Krileon »

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #5 on: December 25, 2013, 04:22:17 PM »
Ok, the below action will delete an FSMs self. I had to loop through the FSMs on the owner then compare to the current FSM to implement this though. Pretty silly to have to actually. The FSM should be updated with a variable that stores its component or at least a utility to more efficiently grab the component. Anyway the below works.

Code: [Select]
using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
[ActionCategory(ActionCategory.StateMachine)]
public class DestroyFSM : FsmStateAction {

public override void OnEnter() {
if ( Owner != null ) {
PlayMakerFSM[] fsms = Owner.GetComponents<PlayMakerFSM>();

if ( fsms.Length > 0 ) foreach ( PlayMakerFSM fsm in fsms ) {
if ( fsm.Fsm.CompareTo( Fsm ) == 0 ) {
Object.Destroy( fsm );
}
}
}

Finish();
}
}
}

I now no longer need to have a dozen objects checking every frame if they've changed variables or waiting for an event. I can now add them, with the needed variables, and remove them automatically. My "Slow" debuff is working fantastically. I don't see any performance loss from using this either.

Alex Chouls

  • Administrator
  • Hero Member
  • *****
  • Posts: 4005
  • Official Playmaker Support
    • LinkedIn
Re: Add FSM Template at Runtime?
« Reply #6 on: December 25, 2013, 04:38:08 PM »
Very cool stuff Krileon!

In DestroyFSM you should be able to just use Object.Destroy(fsm.Owner);

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #7 on: December 25, 2013, 08:16:50 PM »
Very cool stuff Krileon!

In DestroyFSM you should be able to just use Object.Destroy(fsm.Owner);
That'd destroy the gameObject. I want to just destroy the FSM. The structure is Component.Fsm.etc, but in the action "this" and "Fsm" are both the ".Fsm" part of the structure. I can't find a way to go back by 1 and get the component object so it could be destroyed. Only solution I found thus far was to parse out all the owners FSM components, compare to the component to destroy, once matched destroy it. This works flawlessly, but I believe there has to be another way (in a normal script you'd just destroy "this").

Anyway, this is totally awesome. I have 2 scenarios already implemented in my game. 1 is a "Slow" template that can take a "Drag" variable and apply drag for a set time limit to one of my rigidbodies then restore to normal and remove it self. I have another that is a "Scare" template that causes a rigidbody to reverse its direction by sending a new event state to the controller FSM (another custom action, I wrote a rigidbody collisions action to mimic normal controller usage).
« Last Edit: December 25, 2013, 09:08:56 PM by Krileon »

Alex Chouls

  • Administrator
  • Hero Member
  • *****
  • Posts: 4005
  • Official Playmaker Support
    • LinkedIn
Re: Add FSM Template at Runtime?
« Reply #8 on: December 25, 2013, 10:46:41 PM »
In an action fsm.Owner should reference the PlayMakerFSM component.

Owner references the GameObject.

If I remember correctly... not at my computer...

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #9 on: December 26, 2013, 08:20:34 AM »
Ah, I was trying to use this.Owner. That works perfectly! Below is the final destroy fsm action, works perfectly.

Code: [Select]
using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
[ActionCategory(ActionCategory.StateMachine)]
public class DestroyFSM : FsmStateAction {

public override void OnEnter() {
if ( Fsm.Owner != null ) {
Object.Destroy( Fsm.Owner );
}

Finish();
}
}
}

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #10 on: December 27, 2013, 11:07:41 PM »
Upgraded Add Fsm Template with a variable to replace a matching template. This basically can be used to refresh a buff, etc..

Code: [Select]
using System.Collections.Generic;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
[ActionCategory(ActionCategory.StateMachine)]
public class AddFsmTemplate : FsmStateAction {
[RequiredField]
public FsmOwnerDefault gameObject;
[RequiredField]
public FsmTemplate template;
public FsmString name;
public FsmBool active;
public FsmBool unique;
public FsmBool replace;
[CompoundArray("Variables", "Name", "Variable")]
[RequiredField]
public FsmString[] variableNames;
[RequiredField]
public FsmVar[] variables;

private GameObject previousGo;
private List<PlayMakerFSM> fsms;

public override void Reset() {
gameObject = null;
template = null;
name = new FsmString { UseVariable = true };
active = new FsmBool { Value = true };
unique = new FsmBool { Value = false };
replace = new FsmBool { Value = false };
variableNames = new FsmString[0];
variables = new FsmVar[0];
}

public override void OnEnter() {
var go = Fsm.GetOwnerDefaultTarget( gameObject );

if ( go == null ) {
return;
}

bool exists = false;

if ( ( ! unique.Value ) || replace.Value ) {
if ( go != previousGo ) {
fsms = new List<PlayMakerFSM>();

fsms.AddRange( go.GetComponents<PlayMakerFSM>() );

previousGo = go;
}

if ( fsms.Count > 0 ) foreach ( PlayMakerFSM fsm in fsms ) {
if ( ( ( name.Value != "" ) && ( fsm.FsmName == name.Value ) ) || ( ( fsm.FsmTemplate != null ) && ( fsm.FsmTemplate.name == template.name ) ) ) {
if ( replace.Value ) {
Object.Destroy( fsm );
} else {
exists = true;
}
}
}
}

if ( ! exists ) {
PlayMakerFSM newFsm = go.AddComponent<PlayMakerFSM>();

if ( name.Value != "" ) {
newFsm.FsmName = name.Value;
}

if ( ( ! active.Value ) || ( variableNames.Length > 0 ) ) {
newFsm.enabled = false;
}

newFsm.SetFsmTemplate( template );

if ( variableNames.Length > 0 ) {
if ( variableNames.Length > 0 ) for ( int i = 0; i < variableNames.Length; i++ ) {
if ( ! variableNames[i].IsNone ) {
NamedVariable target = newFsm.Fsm.Variables.GetVariable( variableNames[i].Value );

if ( target != null ) {
variables[i].ApplyValueTo( target );
}
}
}

if ( active.Value && ( ! newFsm.enabled ) ) {
newFsm.enabled = true;
}
}

if ( ( ! unique.Value ) || replace.Value ) {
fsms.Add( newFsm );
}
}

Finish();
}
}
}

Krileon

  • Full Member
  • ***
  • Posts: 107
Re: Add FSM Template at Runtime?
« Reply #11 on: December 28, 2013, 01:13:34 PM »
Upgraded Add Fsm Template even more. Now it can return and store the fsm component object. It can also send an event if the template already exists or when the template is replaced. There's also a new variable under the replace event to force replace to not destroy the fsm. The primary usage for that is if you want your own cleanup inside of a different fsm, but you still want it to add the fsm.

Code: [Select]
using System.Collections.Generic;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
[ActionCategory(ActionCategory.StateMachine)]
public class AddFsmTemplate : FsmStateAction {
[RequiredField]
public FsmOwnerDefault gameObject;
[RequiredField]
public FsmTemplate template;
public FsmString name;
public FsmBool active;
public FsmBool unique;
public FsmBool replace;
[CompoundArray("Variables", "Name", "Variable")]
[RequiredField]
public FsmString[] variableNames;
[RequiredField]
public FsmVar[] variables;
[ObjectType(typeof(PlayMakerFSM)), UIHint(UIHint.Variable)]
public FsmObject storeComponent;
[ActionSection("Exists Event")]
public FsmEventTarget existsEventTarget;
public FsmString existsSendEvent;
[ActionSection("Replace Event")]
public FsmBool doNotDestroy;
public FsmEventTarget replaceEventTarget;
public FsmString replaceSendEvent;

private GameObject previousGo;
private List<PlayMakerFSM> fsms;

public override void Reset() {
gameObject = null;
template = null;
name = new FsmString { UseVariable = true };
active = new FsmBool { Value = true };
unique = new FsmBool { Value = false };
replace = new FsmBool { Value = false };
variableNames = new FsmString[0];
variables = new FsmVar[0];
storeComponent = null;
existsEventTarget = null;
existsSendEvent = null;
doNotDestroy = new FsmBool { Value = false };
replaceEventTarget = null;
replaceSendEvent = null;
}

public override void OnEnter() {
var go = Fsm.GetOwnerDefaultTarget( gameObject );

if ( go == null ) {
return;
}

bool exists = false;

if ( ( ! unique.Value ) || replace.Value ) {
if ( go != previousGo ) {
fsms = new List<PlayMakerFSM>();

fsms.AddRange( go.GetComponents<PlayMakerFSM>() );

previousGo = go;
}

if ( fsms.Count > 0 ) foreach ( PlayMakerFSM fsm in fsms ) {
if ( ( ( name.Value != "" ) && ( fsm.FsmName == name.Value ) ) || ( ( fsm.FsmTemplate != null ) && ( fsm.FsmTemplate.name == template.name ) ) ) {
if ( replace.Value ) {
if ( replaceSendEvent.Value != "" ) {
Fsm.Event( replaceEventTarget, replaceSendEvent.Value );
}

if ( ! doNotDestroy.Value ) {
Object.Destroy( fsm );
}
} else {
storeComponent.Value = fsm;
exists = true;
}
}
}
}

if ( ! exists ) {
PlayMakerFSM newFsm = go.AddComponent<PlayMakerFSM>();

if ( name.Value != "" ) {
newFsm.FsmName = name.Value;
}

if ( ( ! active.Value ) || ( variableNames.Length > 0 ) ) {
newFsm.enabled = false;
}

newFsm.SetFsmTemplate( template );

if ( variableNames.Length > 0 ) {
if ( variableNames.Length > 0 ) for ( int i = 0; i < variableNames.Length; i++ ) {
if ( ! variableNames[i].IsNone ) {
NamedVariable target = newFsm.Fsm.Variables.GetVariable( variableNames[i].Value );

if ( target != null ) {
variables[i].ApplyValueTo( target );
}
}
}

if ( active.Value && ( ! newFsm.enabled ) ) {
newFsm.enabled = true;
}
}

if ( ( ! unique.Value ) || replace.Value ) {
fsms.Add( newFsm );
}

storeComponent.Value = newFsm;
} else {
if ( existsSendEvent.Value != "" ) {
Fsm.Event( existsEventTarget, existsSendEvent.Value );
}
}

Finish();
}
}
}
« Last Edit: December 28, 2013, 01:30:44 PM by Krileon »

ClaudioFreda

  • Playmaker Newbie
  • *
  • Posts: 9
Re: Add FSM Template at Runtime?
« Reply #12 on: February 20, 2014, 07:35:25 AM »
I've noticed that FSM instantiated with this action do not receive TriggerEnter/Stay/Exit events. I've looked at the code and cannot understand why.

Does this problem happen for everybody?

jeanfabre

  • Administrator
  • Hero Member
  • *****
  • Posts: 15594
  • Official Playmaker Support
Re: Add FSM Template at Runtime?
« Reply #13 on: February 24, 2014, 08:01:47 AM »
Hi,

 uhm, likely because Fsm needs to be explicitly told to implement these performant sensitive colliders events.

I never played with this action. Maybe Krileon or you can provide a working sample, then I'll look into this.

bye,

 Jean

Alex Chouls

  • Administrator
  • Hero Member
  • *****
  • Posts: 4005
  • Official Playmaker Support
    • LinkedIn
Re: Add FSM Template at Runtime?
« Reply #14 on: March 03, 2014, 12:06:27 PM »
For performance reasons you have to manually subscribe to these Unity events.

I've added a Unity Event Handling section on the wiki that should help:
https://hutonggames.fogbugz.com/default.asp?W1098

Ideally event settings in the template should bubble up to the running FSM. I'll look into this...