Playmaker Forum

PlayMaker Updates & Downloads => Share New Actions => Topic started by: vonchor on September 07, 2011, 11:22:47 AM

Title: Proxy Scripts
Post by: vonchor on September 07, 2011, 11:22:47 AM
I was faced with a need to send an Event to a PlayMaker FSM from a non-PlayMaker Script. So I created something generic that you can drop into a GO (via the inspector).  PlaymakerProxy is simple to use if you just need to send a single event to a FSM. But since you can only have one instance of this attached to a GO, I also wrote PlaymakerProxyObjPass, which uses the object-passing capability of the Unity messaging system to pass a simple dictionary with the FSM name and the event name.  The comments and example embedded in this second script should show how to use it.

Code: [Select]
/*
 * PlaymakerProxy.cs
 *
 * author: Jeff Sasmor. jeff@sasmor.com
 * Copyright 2011 Jeff Sasmor
 *
 * this script can be used to transition from a message sent by a script to a PlayMaker FSM
 * that's attached to a GameObject. It gets all the FSMs attached to this GameObject
 * and sends the named event to the named FSM (defaults to FSM).
 *
 * Use case:  you have a script that wants to send an event to a PlayMaker FSM attached to
 * the same GO or to another one.
 *
 * Add this script to the target GO which has the target FSM.
 *    Set the target to the correct destination. If not set it looks for an FSM on the same GO
 *    Change FSMname if necc
 *    set the correct FSM name
 *
 * Set your messaging fcn to send a message to the PMProxy method in the target GO.
 *    The script CANNOT be sending an object along with the message.
 *
 * The PMProxy method will send an event to the FSM.
 *
 *
 * Shortcoming: only one PMProxy can be attached to a GO.
 * see PlaymakerProxyObjPass.cs for more info and an example.
*/

using UnityEngine;
//using System.Collections;
using HutongGames.PlayMaker;


public class PlaymakerProxy : MonoBehaviour {
public GameObject target = null;  //if not changed, code uses 'this' i.e. the GO the script is attached to
public string FSMname = "FSM";
public string eventName = "A Global Event Name";
public bool debug = false;

//find the fsm and optionally set the object then invoke the FSM Event
void PMProxy() {
PlayMakerFSM the_fsm; //the FSM we want to send an event to
PlayMakerFSM[] components;  //all the FSMs attached to the GO are stored here

if( !target )
            target = this.gameObject;

components = target.GetComponents<PlayMakerFSM>();  //Get all FSMs on this game object

the_fsm = null; //we haven't found one yet

//loop thru all the FSMs and find one with the correct name
foreach(PlayMakerFSM fsm in components) {
if (fsm.FsmName.CompareTo(FSMname) == 0) {
the_fsm = fsm;
break;
}
}

//if we found one
if (the_fsm != null)
the_fsm.Fsm.Event(eventName);   //send the Event to the FSM
else //Foo-bar
Debug.Log( "GameObject: " + target.name +  " doesn't have named FsmComponent: " + FSMname, this);
if (debug)
Debug.Log("Proxy Activated");
}
}


and

Code: [Select]
/*
 * PlaymakerProxyObjPass.cs
 *
 * author: Jeff Sasmor. jeff@sasmor.com
 * Copyright 2011 Jeff Sasmor
 *
 * this script can be used to transition from a message sent by a script to a PlayMaker FSM
 * that's attached to a GameObject. It gets all the FSMs attached to this GameObject
 * and sends the named event to the named FSM (defaults to FSM).
 *
 * Use case:  you have a script that wants to send an event to a PlayMaker FSM attached to
 * the same GO or to another one and you want to be able to steer to a particular FSM/event.
 *
 * Add this script to the target GO which has the target FSM.
 *    Set the target to the correct destination. If not set it looks for an FSM on the same GO
 *    Change FSMname if necc
 *    set the correct GLOBAL event name
 *
 * Set your messaging fcn to send a message to the PMProxy method in the target GO.
 *    The fcn must send along an object.
 *    The object sent is a dictionary (System.Collections.Generic.Dictionary) with K,V:
 *    {fsm: fsmname, event: eventname}
 *
 * for example:  
 *  put this in some script that you want to have "call" an FSM event.
 *  ---> "this.gameObject" may or may not be appropriate.
 *
 * using System.Collections.Specialized;
 *
 *   ListDictionary Dict = new ListDictionary();
 *
 *   Dict.Add("fsm","FSM");  //the normal name for an FSM
 * Dict.Add("event","globEvName"); //the global event sent to the fsm
 *
 *   this.gameObject.SendMessage("PMProxyObjPass",Dict,SendMessageOptions.DontRequireReceiver);
 *
 *
 * The PMProxy method will send an event to the FSM.
 * You might want to change DontRequireReceiver to RequireReciever to throw a message into the
 * debug log if stuff doesn't seem to work.
 *
 * Note that the event HAS to be a GLOBAL event.
 *
 * Shortcoming: only one PMProxy can be attached to a GO.
*/

using UnityEngine;
using System.Collections.Specialized;
using HutongGames.PlayMaker;


public class PlaymakerProxyObjPass : MonoBehaviour {
public GameObject target = null;  //if not changed, code uses 'this' i.e. the GO the script is attached to

//note: FSM name and Event must be passed in via the message. See above
private string FSMname = "FSM";
private string eventName = "xxx";


//find the fsm and optionally set the object then invoke the FSM Event
void PMProxyObjPass(ListDictionary Dict) {
PlayMakerFSM the_fsm; //the FSM we want to send an event to
PlayMakerFSM[] components;  //all the FSMs attached to the GO are stored here


//Are the correct keys present?
if (Dict.Contains("fsm")==false || Dict.Contains("event")==false){
Debug.Log( "GameObject: " + target.name +  " input dictionary missing keys when msg sent to PMProxyObjPass", this);
return;
}

//OK then :-)
FSMname = Dict["fsm"].ToString();
eventName = Dict["event"].ToString();

if( !target )
            target = this.gameObject;

components = target.GetComponents<PlayMakerFSM>();  //Get all FSMs on this game object

the_fsm = null; //we haven't found one yet

//loop thru all the FSMs and find one with the correct name
foreach(PlayMakerFSM fsm in components) {
if (fsm.FsmName.CompareTo(FSMname) == 0) {
the_fsm = fsm;
break;
}
}

//if we found one
if (the_fsm != null)
the_fsm.Fsm.Event(eventName);   //send the Event to the FSM
else //Foo-bar
Debug.Log( "GameObject: " + target.name +  " doesn't have named FsmComponent: " + FSMname, this);
}
}

Title: Re: Proxy Scripts
Post by: Alex Chouls on September 19, 2011, 10:56:24 AM
Excellent! I'll add something like this to the base Playmaker install in a future update - it's very useful!
Title: Re: Proxy Scripts
Post by: vonchor on September 21, 2011, 11:04:29 AM
Made a minor change to the example in the second pc of code:

 this.gameObject.SendMessage("PMProxyObjPass",Dict,SendMessageOptions.DontRequireReceiver);

I had the name of the class instead of the method name in the original example.

I hope it's obvious to people what this can be used for. Right now I'm trying to adapt EZGUI using this scheme.
Title: Re: Proxy Scripts
Post by: vonchor on September 21, 2011, 12:51:28 PM
Spoke too soon on EZGUI as the concept doesn't apply.
Title: Re: Proxy Scripts
Post by: vonchor on September 21, 2011, 01:25:20 PM
Uhh, sorry. It does work. See another post I'm making on this subject
Title: Re: Proxy Scripts
Post by: MaDDoX on September 22, 2011, 03:42:58 PM
Very nice Vonchor! :) I generally try and stay away from scripts having to "talk back" to FSMs, if that's ever needed our general approach here at the team is to simply convert the code to an action. This used to be a hassle in cases where you're trying to pass along a specific data structure not supported by Playmaker, but with the newly added (1.2 beta) object type and actions this became fully feasible. For instance, we now have an action pack to detect and pass along touch-based input actions like swipes and such. It's not generic enough for public release (would require some cleanup and polish first) but it definitely works.

IMO, scripts are really great as what I call "leaves", which makes figurative sense if you think about your code system as a tree. Not saying the Proxy scripts aren't useful, much on the contrary :) The only objection I'd have to this is that it uses a string as the event name, making it refactor-unfriendly - if you change an event name for instance, which's quite common. PlayMaker used to work like that but thankfully Alex changed it to a reference model in the latest "Send Event" versions. Maybe you could adapt your script to pull the list of existing events (after selecting the GO/FSM) and turn it into a drop down selector instead?
Title: Re: Proxy Scripts
Post by: vonchor on September 22, 2011, 04:34:07 PM
the drop-down selector - that's a great idea. But doesn't it run into the same problem, that is, if you refactor  and the name of an event changes then you have to remember to check all the Proxies anyway. Or maybe there's something I'm missing. I've only been using PM for a little while and haven't uncovered all the mysteries. And although I've done OO programming before (ObjC, C++) I'm not that used to C# yet.

The idea for these came up because I'm using a few third-party pkgs like Fingergestures for example and I wanted to avoid having to make large-scale changes to that code so I wouldn't have to keep track of my changes whenever that pkg came out with an update.  I don't think of it as "talking back" so much as translating some inputs (in the case of fingergestures and ezgui) into an event. Mostly just looking for a simple way to integrate disparate things into the PM framework.

In the case of EZGui the proxy seems as if will work pretty well for most things that are purely input like, tap a button and it causes an event. Or move a slider, fire an event, and use the "Get Property" action to get the value of the slider. But a leaf as you call it would be needed to populate a list or do more advanced stuff.

Anyway I'm going to try to add the dropdown selector as soon as I can figger out how to do it :-)
Title: Re: Proxy Scripts
Post by: patch24 on December 09, 2011, 07:37:02 PM
Hey there,
This thread came up in a search I did re: EZGui and FingerGestures specifically.  I had been interested in Playmaker before, but now even more so.  I use uScript and love how it organizes things visually for you.  Really helps with flow of logic for this stuff.  I thought Playmaker would be a great addition to uScript.  They seem to play nice together from what I've read.

Anyways, my most recent issue I'm trying to solve is getting EZGui and FingerGestures working together.  I figured it would make most sense having a central input manager detect all touch screen input, figure out if an EZGui control was touched or finger gestures used, and then dispatch messages to the appropriate library to do something.  I was thinking FingerGestures could be used as the gateway with it's InputManager class perhaps. -?

Is this sort of thing easier to set up logically through Playmaker?  I know this stuff can get kind of tricky to manage.
Thanks for any insight!
-Patrick