playMaker

Author Topic: Custom Actions: Starter Walkthrough  (Read 286 times)

Thore

  • Sr. Member
  • ****
  • Posts: 341
    • View Profile
Custom Actions: Starter Walkthrough
« on: September 29, 2019, 12:40:28 PM »
It's fine to ask for actions :) However it's empowering to know how to adapt your own. Whatever experience or fears you have, it's actually easy and I walk you through the steps. This looks like a lot of text, but I think it's a good Crash Course that shouldn't take you that long!

PlayMaker is in fact a great way to write your own code, because you can focus on a small action at a time and see if it works, and then combine it with all the other actions in the arsenal. The existing actions are also great example code.

What Are PlayMaker Actions?
Let's begin at the very start. The actions you see in the PlayMaker brower are just scripts, like the ones often attached to game objects. There is some extra code, mostly at the top that tells PlayMaker to show these scripts as PlayMaker actions. You typically just copy that part and make obvious changes (to the class name etc).

How to Start?
At the very first, we check the Ecosystem brower if the action we want already exists. The Ecosystem is a (free) add on, we can install from here.

Indeed, the action we want to make already exists, but we're making a slightly simpler version as a test. We want to take a float variable and simply round it up.

The folks at PlayMaker provided a tool (PlayMaker > Tools > Custom Action Wizard) that allows us to start a custom action from scratch. However, we won't be using that for now, and instead start off with an existing, but different action.

Preparations
Find Similar Actions
First, we can make a new empty scene so that it starts faster when playing. Next, attach an FSM to an empty game object. It doesn't need anything else, as we just want to test the action we're making as well as quickly look up other actions.

In this case, we want to make a different math formula, and a good way to start might be Float Abs. We can load it into our FSM, click the gear icon, and select Edit Script.... Be cautious though, we don't ever want to modify any of the existing actions, but taking a peek is fine. Now you know how to do this quickly.

We could now be using the Custom Action Wizard, but we're taking the basic route. We want to duplicate the Float Abs action, and first turn it into our own playground to fool around with. A quick way to find the file is this: in the Action Brower, right-click on the action > Find Script. This will mark the file in the project tab. Alternatively, directly type in 'Float Abs' into the project tab to find it.

Making Our Own Action
Across Unity you can use CTRL+D to duplicate, so let's do this there. Select it and duplicate away! Now we want to rename the file, and let's call our action FloatCeilingExample.cs. In programming, rounding down is often called "floor" and rounding up is often called "ceiling" (or ceil for short). As I wrote above, such an action already exists in the Ecosystem, but we're just using it as an example.

Unity does not care where scripts are located within the asset folder. We can thefore move it around. I recommend you make a new folder, Scripts, and within that, a "My PlayMaker Actions" folder. It keeps it clean and you can back up your actions more easily. Unity will complain about some problem now. Because the contents of the file is of course a duplicate from an existing Float Abs action. Let's bring you up to speed:

Scripting 101
To fix this first, let's open our new action FloatCeilingExample.cs. Here's something that is often omitted by the experts: make sure Visual Studio is installed AND opened up from within Unity! It should come with any recent version of Unity. Older versions have Monodevelop, which also works just fine. You can also check this in Unity > Preferences, and set it under External Tools.

Let's open our new action FloatCeilingExample.cs, but from within Unity by simply double-clicking the file we made. You can also use all sorts of other ways (Find Script, using the gear icon etc) later, but be sure that Visual Studio recognizes the file as Unity's because you need the scripting editor to help you with the syntax and functions. It's also something the Pros do all the time. While code is really like a language you'll eventually need to learn, programmers still constantly look into a dictionary, so to speak to see how a particular line is written.

If all goes according to plan, you should see a lot of code with red underlines, showing you that something is wrong. And indeed, it is: remember, we duplicated another action and the editor now complains that this is duplicated code (even with a different file name).

Namespace
It knows this by looking at the namespace and class right above. Scripts not always have namespaces. They are generally used to allow external assets to work. PlayMaker can then ensure that their script called startup.cs is not in conflict with some other action called startup.cs, say from another asset, by putting their stuff into their own namespace. If you at some point write your own (non-PlayMaker) scripts, you can use namespaces also to keep your functions sorted together. Since we want PlayMaker to recognize our action, it must be in the PlayMaker namespace, so we keep this.

Edit: excellent beginner series, to complement

Class
Generally, you only need to change the class name to fix the errors. And the general rule is that the class name is exactly like the file name, but without the .cs at the end. Thus, in our FloatCeilingExample.cs we need to change the class to our name, and line 9 will now look like this.

Code: [Select]
public class FloatCeilingExample : FsmStateAction
Public means, this class is usable by other scripts and the colon and FsmStateAction means it derives from another scrip called FsmStateAction (i.e. it uses something from that class). Especially when you work from example actions, it should already be correct. Most of the time it's FsmStateAction. If you're curious to see how it works under the hood, you can select FsmStateAction and press F12 to navigate to that script.

Basic Code Tips
With the class changed, it should instantly remove the red squiggles. First lesson: the red marks give you very useful hints, but they don't always tell exactly what is wrong. For example, when you miss a semicolon after a statement (the line breaks are ignored), the code is interpreted as one larger statement and this may cause something else to be marked red. The error is often before something is marked red.

Let's now use the powerful suggestion tool. Go to the line 15, below
public FsmFloat floatVariable;. And type pub  and see it suggests public. Hit tab or enter, and it completes it. Observe how stuff below again turns red, of course. Write Fsm and wait and see the suggestions coming up. With the arrows you can browse through the list. You see all sorts of entries in there. Rather than guessing, you can often consult this to learn the options you have. Sometimes this autocomplete and suggestions can be in the way. If you want to close it or abort it momentarily, press ESC.

Let's delete your public FsmFloat line again. I know, but it was useful. We actually want to create a second Float variable, and later store the result of the calculation into this one, rather than overwriting the FsmFloat floatVariable. For this, I show you another powerful trick: Duplicate again! Highlight the entire block of four lines (11-14) from [RequiredField] to public FsmFloat floatVariable; and press CTRL+D. You probably need to add a line break or two to make it look neat. Now we have a second float variable to use later. At minumum we must change the name. Let's rename the lower (new) one as storeResult.

Save and Compile
And go to Unity. Unity will now compile, which means it checks if everything is alright. There should be no errors (at least not ones we created). Next, use the action browser and be amazed to find our new action in there! ADD it to the FSM and let's see what happened. For now, it's just like FloatAbs with a different name and an additional variable. You can now also delete other actions, so that you only see our new action.

Next part right below...
« Last Edit: November 25, 2019, 03:16:43 AM by Thore »

Thore

  • Sr. Member
  • ****
  • Posts: 341
    • View Profile
Custom Actions: Starter Walkthrough (Part II)
« Reply #1 on: September 29, 2019, 12:41:23 PM »
We have duplicated the FloatAbs.cs, made a new file FloatCeilingExample.cs, have fixed the class name, and added a new float variable, renamed it. We cleared out the other actions in PlayMaker and added FloatCeilingExample. We've first cover a lot of ground to make sense of the code.

Some Conventions to Know
Before we go further. Let's see what also happened. Open up the action we've made (FloatCeilingExample.cs) from within Unity. Visual Studio should pop up or still be open.

Right under the class, there is always a list of variables that are identical to the ones you see in the action in PlayMaker. Compare. The variable name corresponds to the name next to the variable field, but interestingly floatVariable in code is displayed as Float Variable in the action. And storeResult becomes Store Result.

The programming language is called c# (pronounced ”c sharp”), and the convention for variables commonly used is called camelCase. There are other conventions e.g. snake_case, Hungarian notation etc (see Wikipedia). We should stick to that camelCase convention, at least in PlayMaker. Important is also that <space> separates instructions, so you cannot use <space> within a variable name. As you saw, the space is automatically displayed before each uppercase letter in the action.

Another important wrinkle is that variable names are always case-sensitive. foo, Foo, fOo, foO, FoO and FOO are different variables as far as the code is concerned. If you want to rename a variable you'll use in the script, hold CTRL and tap R twice, you can then rename all instances of the variable. Needless to say, you should be careful with this, but you cannot break much when you work in your own file, and saved recently.

Line breaks (i.e. new line) are ignored by the code and are treated as <space>.  So you could theoretically put the whole code into a single line. Thus, line breaks are used to make it look nice.

There's is an exception and that are comments. Everything in the same line after // is seen as a comment ("outcommenting") and ignored by the code. You'll often see this, and it's a good habit to comment non-obvious things.

DO NOT SAVE THE FILE FROM NOW ON.
We're playing around a bit.

The Brackets
I often get confused by the many brackets. So let's help ourselves with it. Go to the very bottom of the script to the last } and write // end namespace after it. The bracket right above it marks the end of the class. So you can write // end class in a similar fashion behind it, then you'll know that these should always be closing the script.

Visual Studio also helps you identify bracket-pairs with the dotted lines. To align things nicely, use TAB or SHIFT+TAB. You can of course indent entire blocks in either direction. Finally, you can also see which pair belongs together by going with the cursor next to it, or clicking on it. It should mark the pair.

As you've noticed already, there are quite a lot of brackets, which provide the structure of everything. So the class is "inside" the namespace. Then there is stuff "inside" the class. The meaning of the brackets is roughly like this:

    {} is like a container for functions/methods.
    () typically follows a function name and is a container for variables, so-called arguments that are passed into it.
    [] are typically for properties in the editor or inspector elements, for requirements, tooltips, to add sections etc. And then there's also...
    "" which is for strings (i.e. words that aren't interpreted as code).[/li]

So first, we are inside the class, which is inside the namespace. Hence, there are the first and last two curly brackets. Then you'll see the list with the variables and below that, finally, where the magic happens. The so-called functions or methods.

Functions?
This action has four on a first glance: Let's count them: 1. Reset(), 2. OnEnter(), 3. OnUpdate(), and 4. DoFloatAbs().

Let's try to understand what's going on there. First off, you'll see that they start uppercase and followed by this (). This means that they do not take in arguments, but instead will simply use the variables that are listed above, or made up variables that are only used inside them. Writing functions with arguments is not too difficult, but you don't really encounter this with PlayMaker actions. Basically, they are  empty hence (). When they return no particular value or result, they are called void, which is a keyword you often see.

Great many functions look like this, and now you can also spot that there are more than these four instances. Let's count them quickly, too.

Within OnEnter(), we see DoFloatAbs() and it's exactly the one that is also written out below. When the code encounters this reference, it looks into the same script and executes the function as specified below. If we were to change this, we needed to change it in all places within the script. Since this exists there, it just runs the code. Let's play around with this.

Copy that line inside the function, with CTRL+C
Code: [Select]
floatVariable.Value = Mathf.Abs(floatVariable.Value);
then mark the DoFloatAbs();inside OnEnter (in line 37), and replace it. This should show no errors. Basically instead of saying "Go find DoFloatAbs and then run it, which means run "floatVariable.Value = Mathf..." we directly say run this line of code. We skip a step.

But you'll see that DoFloatAbs();exists a second time inside OnUpdate, too. If we really wanted to remove DoFloatAbs(); altogether, we would have to plug the line of code there, too, duplicating the logic and that's a bad idea. If we made changes, we would always have to apply the changes to both locations, and that's not ideal. That's why the logic is usually inside its own function and can then be called from various places whenever needed.

The OnEnter version is executed when the state is entered, and the OnUpdate one is called when everyFrame is ticked. Both of them are predefined by PlayMaker or Unity. All you need to remember is that there are different "places" when a function could be executed, which also reveals how often it is executed. Beside these two, there are several more. Of those, you might run into FixedUpdate (typically used for physics) and LateUpdate (afaik for rendering or position updates). There’s also OnExit which executes its stuff when leaving the state. You can thus easily make custom actions for leaving the state (send event when leaving is pretty useful). In regular scripts, OnStart and OnAwake are common.

The IF Statement
Speaking of which: if (!everyFrame) also looks like a function, and it has an argument it uses. Remember, that's the round bracket. For now we chalk it up as a built-in function, which comes as part of c#. If and else often come up, and they work with boolean logic. The ! before a variable means "is false". Without the exclamation mark means "everyFrame is true". These are shorthands. Let's change that quickly to test it.

Edit: complementary video

Replace the part inside the bracket to everyFrame = false, and some green underlining should appear. Something is not going to work, but the error is not screeching the script to a halt. To express a check that something is exactly equal, we need to fix it by writing everyFrame == false. It should also tell you when you hover over it. Consider when you want to express something is equal or bigger, you'll write >= for instance. Likewise variableA = variableB means "set variableA to the value inside VariableB". That's why it complains. We want to compare.

But now you already know the basics of if statements. To ask this AND that, use && and for this OR that, use ||. For example, to check if it's a good day for swimming: if (sun && !clouds) sun is true AND clouds are false. Another example: good day to hike? if (temperature <= 15° celsius || !rain), means it's relatively warm OR not raining. A common check beside this operators is a null check. You can ask if a variable has been set at all by asking if (myGameObject == !null) meaning: there is something stored as myGameObject (it is not null).

Finish()
is another function in there. It’s also PlayMaker specific. When this is executed, the action tells the state that it did the thing, and if all actions in the state say this, the state says it’s finished. This is how the Finish event works, which is then triggered. That’s also the secret behind everyFrame in OnUpdate. You see, when everyFrame is false, Finish gets triggered, and thus it doesn’t execute it after this.

The MathF Function
And finally, there's yet another function in use: You'll see Mathf.Abs(floatVariable.Value). There's apparently a function somewhere which takes an argument, inside the () and it looks for a floatVariable from above.

Let's try something. Go above and find the line using UnityEngine; and delete this. Now scroll down to the Mathf.Abs part again, and see it's now with red errors. This means, now that the using... statement is gone, the script can no longer find the function that is apparently part of the UnityEngine kit.

To be continued below...
« Last Edit: November 25, 2019, 03:18:04 AM by Thore »

Thore

  • Sr. Member
  • ****
  • Posts: 341
    • View Profile
Custom Actions: Starter Walkthrough (Part III)
« Reply #2 on: September 29, 2019, 12:41:46 PM »
Here's where we were. We just fooled around, and didn't save. Throw it away and load the action as it was. Here's how far we got.

1) duplicated FloatAbs
2) renamed the file FloatCeilingExample.cs
3) renamed the class FloatCeilingExample
4) duplicated the variable from 11-14, so you have two float variables (and make it look neat).
5) renamed the second one to storeResult

Okay. I hope a lot makes sense by now.

The autocomplete helper is not everything. Pros and beginners alike always need to search the web, use StackOverflow for example, and importantly, use the actual documentation of Unity (the so-called API documentation). You are now able to generally use it.


Unity already has math functions for floats, hence called Mathf. We saw that it contains one called Abs. This happens in this line:

Code: [Select]
floatVariable.Value = Mathf.Abs(floatVariable.Value);
When you translate this backwards, it says Mathf.Abs returns some result, using floatVariable.Value as an argument (an input). This result is then set into floatVariable.Value, i.e. we overwrite the value. When you translate it from the beginning: floatVariable.Value becomes the result from Mathf.Abs, which takes in floatVariable.Value as an argument.

We see here that = has a subtly different meaning than in math. It means one is equal to the other, but the former is set to the result from the latter. In other words myVariable = 23; means that by executing that line it makes it happen: the myVariable is henceforth equal to 23. But further above in the code, myVariable was not yet 23. So you have to keep an eye on when something is set. That's why variables are usually set far above, and it's often the first thing to update the variables before doing anything else.

Making our own action
Let's do our first change. We want to store the result into our own extra variable. So all we need to do is to replace the floatVariable.Value at the front with storeResult, and add a .Value at the end. Like so:

      
Code: [Select]
storeResult.Value = Mathf.Abs(floatVariable.Value);
Here's a tip: when you delete the floatVariable before the Value and then start typing sto... .Value you should see the suggestion already. Again, it's very powerful and the easiest way to prevent errors. It should show you storeResult as an option. And if you miss the .Value you can type the . and it should also suggest that in the list.

The Semicolons
There are two things we need to cover quickly as well. First, every statement is generally closed with a semicolon. Execute this; Execute that; The line breaks (new lines) do not matter. It knows this from the semicolons. But you'll see this.

The .Value
Okay, this one is PlayMaker specific. Whenever your variable is of the type Fsm..., like FsmFloat, FsmInt, FsmString etcetera above, then the variable below needs to have the .Value attached at the end. You see that everyFrame is simply a bool, and thus doesn't need the .Value. User variables for PlayMaker should generally be FsmVariables. But why is everyFrame not a FsmBool? Because it not something you're supposed to change in runtime. It's not "inside the game" so to speak, but part of how the actions works.

Okay, we made a good change by storing the result to a new variable. SAVE IT. And head to Unity to test it. But we have to do something crucial.

The Reset Part
We've introduced a second float variable, and that means we must reset when we enter the state, so that it does not accidently read an old value. This is done differently for the various variable types, but here we can simply use the other float as an example (i.e. add storeResult = null) into Reset().

Generally, all variables that are declared above should be reset. With that in mind, we now tackle the actual function we wanted to make.

Next part below!
« Last Edit: September 29, 2019, 01:05:35 PM by Thore »

Thore

  • Sr. Member
  • ****
  • Posts: 341
    • View Profile
Custom Actions: Starter Walkthrough (Part IV)
« Reply #3 on: September 29, 2019, 01:04:57 PM »
Our Function
Finally, we're going to make our own function. There are different ways you should try. The fastest is keeping the structure. Select one of the DoFloatAbs() by clicking on it, then hold CTRL and tap R twice. Then simply type away. See how all instances are renamed (this works in Visual Studio and will be different for other tools).

But let's do something else (too). Go below DoFloatAbs(); in OnEnter, make a new line. Then let's write there DoFloatCeiling(); We can name it anything, but it's good practice to name it after what this function actually does. Now the name should be marked red, as that function does not yet exist. Now click into red-marked text, and ALT+RETURN and it should suggest to simply make this function for you, it'll contain "throw new NotImplementedException();" and also adds "using System;" Good that we already know how this works. So there's a function NotImplementedException() and it's stored in "System". But we won't be needing this and want to change it right away. So we can delete the "using System" statement above again, and also remove the throw... line, leaving us with an empty function. Which we can now adapt.

Third, you can also type away DoFloatCeiling() { and then it should autocomplete the missing brackets and arrange it correctly.

These are just some ways. The result we want is similar to FloatAbs, one DoFloatCeiling(); in On Enter, one in On Update, and then at the end of the script,   
Code: [Select]
void DoFloatCeiling()
{
// Our stuff goes here.
}

Take a minute and figure out how rounding up (called Math ceiling/ceil) works.


I'll walk you through the steps.

So first, we just look it up. We would be internet searching "float round up unity" or "float rounding c#" and see what comes up. But we already know that this is done using the math float functions. How about we use the suggestion feature in Visual Studio that was already very useful?

We clear the comment line (delete // Our stuff goes here.) and type in Mathf. and see what is suggested. There is for example ceil and ceilToInt. Select these and read the tooltips right in Visual Script. Now complete this to Mathf.Ceil(). When you put the cursor inside the round brackets, it shows you the arguments it wants. In this case, it just wants a float (called f). We want to apply this to our floatVariable, and because it's an FsmFloat, we must add the .Value. Hence we get:


Code: [Select]
void DoFloatCeiling()
{
Mathf.Ceil(floatVariable.Value)
}

Okay, but you see this is pretty incomplete. First off, don't forget the semicolon at the end. And then it should work already, rounding up the value, but the problem is that it doesn't write it down anywhere. We want the result to be set into storeResult. This is again a FsmFloat, so we need to add the .Value once more. When you know the steps, it only takes half a minute and looks like this.

Code: [Select]
// Example Action to throw away later.

using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
[ActionCategory(ActionCategory.Math)]
[Tooltip("Sets a Float variable to its absolute value.")]
public class FloatCeilingExample : FsmStateAction
{
[RequiredField]
[UIHint(UIHint.Variable)]
        [Tooltip("The Float variable.")]
public FsmFloat floatVariable;

        [RequiredField]
[UIHint(UIHint.Variable)]
        [Tooltip("The Float variable.")]
public FsmFloat storeResult;

        [Tooltip("Repeat every frame. Useful if the Float variable is changing.")]
public bool everyFrame;

public override void Reset()
{
floatVariable = null;
everyFrame = false;
            storeResult = null;
        }

public override void OnEnter()
{
DoFloorCeiling();

if (!everyFrame)
{
    Finish();
}
}

public override void OnUpdate()
{
DoFloorCeiling();
}

void DoFloorCeiling()
{
            storeResult = Mathf.Ceil(floatVariable.Value);
        }
}
}

It's now functional and can be tested. But to really complete it, we would have to at least update the tooltips. It's also possible to use the autonaming feature, but that covers the general gist. When you later make your own actions, test often and slowly build out the functions you want. With that knowledge you could already write your own damage or level up formulas, and don't need to stack it together with countless actions.

I hope that flattened the initial learning curve. Have fun and share your actions!
« Last Edit: September 29, 2019, 01:10:37 PM by Thore »