playMaker

Author Topic: Construct an FSM flow in runtime [SOLVED]  (Read 2584 times)

henriqueranj

  • Playmaker Newbie
  • *
  • Posts: 3
    • &ranj
Construct an FSM flow in runtime [SOLVED]
« on: January 19, 2018, 04:40:47 AM »
Hello, I have a game use-case where the player can program how the character will behave. For this, my current approach is to construct an FSM in runtime by adding states, actions and transitions. Each of the possible character's programmable actions are implemented as PlayMaker Templates and run via RunFSM actions.

So far, I have been able to create the FSM flow in runtime. However, I have not been able to make the state machine run through the newly added states and transitions. In runtime, the Editor does show a correct representation of the generated FSM flow. However, sometimes it gives out errors when the diagram is manipulated/focused at.

Question: Is there any update/apply/initialize/dirty that I need to call in the FSM so that it syncs with the newly generated FSM flow in runtime? Or is this just not possible at all with the current PlayMaker?

My prototyping code follows here:
Code: [Select]
protected void InitializeCode() {
    Fsm fsm = _playMaker.Fsm;
    Rect stateRect = new Rect(0, 0, 0, 50);
    List<FsmState> states = new List<FsmState>();
    // create start state
    FsmState startState = new FsmState(fsm) {
      Name = StartStateName,
      Position = stateRect
    };
    // set start state as start in fsm
    fsm.StartState = startState.Name;
    // traverse and create each template run state
    for (int i = 0; i < Templates.Count; i++) {
      // create template run state
      FsmState state = new FsmState(fsm) {
        Name = string.Format("Run Template #{0}", i),
        Position = BumpPosition(ref stateRect)
      };
      states.Add(state);
      // create run template action
      RunFSM runAction = new RunFSM() {
        finishEvent = _finishEvent
      };
      runAction.fsmTemplateControl.fsmTemplate = Templates[i];
      state.ActionData.SaveActions(state, new FsmStateAction[] { runAction });
    }

    // create end state
    FsmState endState = new FsmState(fsm) {
      Name = EndStateName,
      Position = BumpPosition(ref stateRect),
      Transitions = new FsmTransition[] { new FsmTransition { ToState = startState.Name, FsmEvent = _finishEvent } }
    };
    // add next frame event action
    endState.ActionData.SaveActions(endState, new FsmStateAction[] { new NextFrameEvent() { sendEvent = _finishEvent } } );

    // create transition from start state to first template run state
    startState.Transitions = new FsmTransition[] {
      new FsmTransition() {
        ToState = states[0].Name,
        FsmEvent = _startEvent
      }
    };

    // traverse template run states and connect each to the next
    for (int i = 1; i < states.Count; i++) {
      FsmTransition transition = new FsmTransition() {
        ToState = states[i].Name,
        FsmEvent = _finishEvent
      };
      states[i - 1].Transitions = new FsmTransition[] { transition };
    }

    // create transition from last template run state to the end state
    states[states.Count - 1].Transitions = new FsmTransition[] {
      new FsmTransition() {
        ToState = endState.Name,
        FsmEvent = _finishEvent
      }
    };

    // add start and end state to overall state list and set to fsm
    states.Add(startState);
    states.Add(endState);
    fsm.States = states.ToArray();
  }

  protected Rect BumpPosition(ref Rect rect) {
    rect.y += 80f;
    return rect;
  }
« Last Edit: January 19, 2018, 11:36:06 AM by djaydino »
Senior Game Programmer
https://ranj.com/portfolio

verybinary

  • Junior Playmaker
  • **
  • Posts: 81
    • The Museum of Digital
Re: Construct an FSM flow in runtime
« Reply #1 on: January 19, 2018, 05:03:11 AM »
id have a generic int called action1 and let the user choose action1 to be "fury smash"(or the int equivalent of "1"), when action1 gets called, id int switch to see if the player wanted to fury smash or heal. that int == 1, so on we go to the furysmash event. would this work for your case?

henriqueranj

  • Playmaker Newbie
  • *
  • Posts: 3
    • &ranj
Re: Construct an FSM flow in runtime
« Reply #2 on: January 19, 2018, 05:22:53 AM »
id have a generic int called action1 and let the user choose action1 to be "fury smash"(or the int equivalent of "1"), when action1 gets called, id int switch to see if the player wanted to fury smash or heal. that int == 1, so on we go to the furysmash event. would this work for your case?

Hi verybinary, thank you for your alternative. I considered that option too. However it would require a big diagram to support all of the available programmable actions in the game (~100), which would be hard to manage for the content designers.

With my current approach, content designers would only require to create actions' templates and the code in runtime would do the hard work.

Another alternative would be to create an Editor tool that would generate the FSM diagram (with your alternative flow) based on the available Templates files. However, for now I am trying to consider if it is possible at all to construct an FSM in runtime - that way I could even consider other angles on how to use PlayMaker in a project :)
Senior Game Programmer
https://ranj.com/portfolio

henriqueranj

  • Playmaker Newbie
  • *
  • Posts: 3
    • &ranj
Re: Construct an FSM flow in runtime
« Reply #3 on: January 19, 2018, 08:31:05 AM »
SOLVED. I got it working!!

I found out that there are some subtle initialisation procedures to get the FSM with the proper state and events:

1. For RunFSM action, you need to:
   - manually set the FSM related to this action;
   - call its Awake() from the Actions list of the state after you set it via ActionData.SaveActions() (a clone object must be created in the SaveActions).
Code: [Select]
      // create run template action
      RunFSM runAction = new RunFSM() {
        finishEvent = finishEvent
      };
      runAction.fsmTemplateControl.fsmTemplate = templates[i];
      state.ActionData.SaveActions(state, new FsmStateAction[] { runAction });
      state.Actions[0].Fsm = fsm;
      state.Actions[0].Awake();

2. To initialize the FSM to the generated flow in runtime, you can enable and disabled the PlayMakerFSM component:
Code: [Select]
    // force a reset to the fsm
    playMaker.enabled = false;
    playMaker.enabled = true;

3. For the events, make sure to get the ones pre-configured in the FSM, rather then creating your own (specially for the FINISHED event):
Code: [Select]
    // get pre-configured events in blank FSM
    _startEvent = FsmComponent.Fsm.Events.FirstOrDefault((e) => e.Name.CompareTo("START") == 0);
    _finishEvent = FsmComponent.Fsm.Events.FirstOrDefault((e) => e.Name.CompareTo("FINISHED") == 0);

With these changes, the FSM diagram is generated and runs perfectly. If anyone else is interested, here follows the prototype code I made to generate this.

Code: [Select]
public class FsmGenerator : MonoBehaviour {
  public PlayMakerFSM FsmComponent;
  public List<FsmTemplate> Templates;

  protected FsmEvent _finishEvent;
  protected FsmEvent _startEvent;

  protected void Start() {
    // get pre-configured events in blank FSM
    _startEvent = FsmComponent.Fsm.Events.FirstOrDefault((e) => e.Name.CompareTo("START") == 0);
    _finishEvent = FsmComponent.Fsm.Events.FirstOrDefault((e) => e.Name.CompareTo("FINISHED") == 0);
    // initialize FSM with generated flow
    InitializeFSM(FsmComponent, Templates, _startEvent, _finishEvent);
  }
 
  public void StartCode() {
    // force a reset to the fsm
    FsmComponent.enabled = false;
    FsmComponent.enabled = true;
    // send event to start simulation
    FsmComponent.SendEvent(_startEvent.Name);
  }

  protected void InitializeFSM(PlayMakerFSM playMaker, List<FsmTemplate> templates, FsmEvent startEvent, FsmEvent finishEvent) {
    Fsm fsm = playMaker.Fsm;
    Rect stateRect = new Rect(0, 0, 0, 50);
    List<FsmState> states = new List<FsmState>();
    // create start state
    FsmState startState = new FsmState(fsm) {
      Name = "Start",
      Position = stateRect
    };
    // set start state as start in fsm
    fsm.StartState = startState.Name;
    // traverse and create each template run state
    for (int i = 0; i < templates.Count; i++) {
      // create template run state
      FsmState state = new FsmState(fsm) {
        Name = string.Format("Run Template {0}", i),
        Position = BumpPosition(ref stateRect)
      };
      states.Add(state);
      // create run template action
      RunFSM runAction = new RunFSM() {
        finishEvent = finishEvent
      };
      runAction.fsmTemplateControl.fsmTemplate = templates[i];
      state.ActionData.SaveActions(state, new FsmStateAction[] { runAction });
      state.Actions[0].Fsm = fsm;
      state.Actions[0].Awake();
    }

    // create end state
    FsmState endState = new FsmState(fsm) {
      Name = "End",
      Position = BumpPosition(ref stateRect),
      Transitions = new FsmTransition[] { new FsmTransition { ToState = startState.Name, FsmEvent = finishEvent } }
    };
    // add next frame event action to end state
    endState.ActionData.SaveActions(endState, new FsmStateAction[] { new NextFrameEvent() { sendEvent = finishEvent } });

    // create transition from start state to first template run state
    startState.Transitions = new FsmTransition[] {
      new FsmTransition() {
        ToState = states[0].Name,
        FsmEvent = startEvent
      }
    };

    // traverse template run states and connect each to the next
    for (int i = 1; i < states.Count; i++) {
      FsmTransition transition = new FsmTransition() {
        ToState = states[i].Name,
        FsmEvent = finishEvent
      };
      states[i - 1].Transitions = new FsmTransition[] { transition };
    }

    // create transition from last template run state to the end state
    states[states.Count - 1].Transitions = new FsmTransition[] {
      new FsmTransition() {
        ToState = endState.Name,
        FsmEvent = finishEvent
      }
    };

    // add start and end state to overall state list and set to fsm
    states.Add(startState);
    states.Add(endState);
    fsm.States = states.ToArray();

    // force a reset to the fsm
    playMaker.enabled = false;
    playMaker.enabled = true;
  }

  protected Rect BumpPosition(ref Rect rect) {
    rect.y += 80f;
    return rect;
  }
}

« Last Edit: January 19, 2018, 08:35:27 AM by henriqueranj »
Senior Game Programmer
https://ranj.com/portfolio

jeanfabre

  • Administrator
  • Hero Member
  • *****
  • Posts: 15500
  • Official Playmaker Support
Re: Construct an FSM flow in runtime [SOLVED]
« Reply #4 on: January 22, 2018, 02:19:46 AM »
Hi,

 nice nice!!!

 Bye,

 Jean