Instances ARE State Machines - GMwolf

Discussion in 'Tutorials' started by GMWolf, Nov 11, 2018.

  1. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,160
    GM Version: Any that has instance_change
    Target Platform: ALL
    Links: Youtube Video
    Summary:
    Game maker provides us with a very powerful feature, yet it seems to go underused within the community. This video re-introduces the idea of using instances as state machines.

    Tutorial:
     
  2. hippyman

    hippyman Member

    Joined:
    Jun 20, 2016
    Posts:
    551
    I completely agree with this. I remember when I first read one of those books that Mark Overmars wrote and it did something like this for a platformer tutorial. Definitely an underused function in my opinion.
     
    Bentley likes this.
  3. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    190
    You can correct me if I'm wrong but I think this is way less efficient than simply having scripts for states. Also dealing with passing variables and dealing with cleanup don't make it appealing to me.
     
    Steevo likes this.
  4. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,160
    You don't have to pass variables.
    They are passed by default!
    This isn't the first time this is brought up, perhaps I need to make a second video...

    As for cleanup, just have s cleanup state! Not too bad imo. But yeah, it is a bit more work I guess.

    As for efficiency, no. This is probably the most efficient there is.
    GM already calls events. By having scripts on top, we add a level of indirection.
    But with instance change, all we do is tell GM to call a different set of events. No slow GML in the middle.
     
    Bingdom likes this.
  5. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    3,387
    I am always pleased when someone advocates returning to the real roots of GM and using what's given, instead of needlessly reinventing the wheel and stockpiling code in the Step event.

    As for why this technique fell out of favour in the first place, the main reason was a decade-long bug with continuous event triggers. Consider the following example:

    object0 Space Pressed:
    Code:
    instance_change(object1, true);
    object1 Space Pressed:
    Code:
    instance_change(object0, true);
    In this example, the expected behaviour of pressing Space is toggling between object0 and object1. But in ALL versions of GM from 5.3A through 8.1.141, you can observe an unwanted "double-take" behaviour that would change object0 to object1 and then immediately back again via the same event. The only workaround in this situation is to abandon instance-based states and multiplex the behaviours manually (i.e. the current popular method). By the time the bug was silently addressed sometime during the GMS 1.x timeframe (no repro on 1.4.9999 or 2.2.0.261), anyone who remembered the old technique had forsaken it, and everyone new who naturally discovered the old technique were told off by older users who remembered the problem but weren't aware of its resolution.

    Another reason is that rookies who discover this technique do so naturally by duplicating the object wholesale, then struggle with maintenance as the number of properties and states increases. This could be mitigated with proper inheritance and DRY techniques, but again it was just too easy to throw the baby out with the bathwater and adopt a popular workaround.

    If you want to create a shared initialization or disposal, my take would be to use inheritance instead using the object variables interface. The parent would implement the base Create and Cleanup, while children with specific additions could start with event_inherited() and then add new overriding code within the object editor.
     
    Bentley, Justice and GMWolf like this.
  6. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,160
    Quite interesting but of history!
    I remember using GM7 and 8 but never ran into that particular bug. Probably because I didn't use instance change because other users told me not to.

    I'm not sure that would work out, since the destroy and cleanup events get called when you call instance change.
    Yes, you could set the perf argument to false, but then you would not be able to use the create event for state initialization.
     
  7. NeZvers

    NeZvers Member

    Joined:
    Mar 24, 2018
    Posts:
    190
    Biggest issue I have with this is clogging project resource tree.
    I'm using GM just for half year and found that it tends to build up in resources very easily (even using grouping folders, it turns into folder clutter). So I found my way of managing project in almost singleton approach.

    For those doesn't care for it, instance_change() approach might be amazing way to handle states.
     
  8. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    3,387
    You're always going to be clogging something up the more distinct behaviours you implement. If you use separate objects, it's the resource tree. If you multiplex manually, it's the Step event.

    In a legacy-like interface, yes, having a lot of grouping folders would get in your way. But with the resource search fields in GMS 2 (available at the top level and in several dropdowns across the board), having lots of folders won't necessarily matter as much to you, as long as you remember the resource name. It takes many more folders and nesting levels to overwhelm the GMS 2 IDE than the legacy IDE.

    A lot of the current narrative on GM resource organization is founded on assumptions and historical limitations that no longer hold. With a drastically different IDE ahead of us and the continuous-trigger bug behind us, there is room for re-evaluating the validity of distinct objects as states.
     
  9. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,828
    you can switch states by calling the object event directly instead of using instance_change.
    oAI
    ///create
    //set default state
    state = oWalkLeft;

    //step
    event_perform_object(state, ev_step, ev_step_normal);



    oWalkLeft
    ///step
    x--;
    if (x<0) state = oWalkRight;


    oWalkRight
    ///setp
    x++;
    if (x>room_width) state = oWalkLeft;


    Basically it's the same as doing script_execute() based state machines.
    Someone made a tutorial on that...
     
  10. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,160
    That was me xD

    But why, when we can just use instance change?
     
  11. icuurd12b42

    icuurd12b42 TMC Founder GMC Elder

    Joined:
    Apr 22, 2016
    Posts:
    1,828
    Well, there you go...

    I used instance_change in gms6 to have a huge asteroid field with asteroid of various degrees of colissionability.
    like NotMoving,Moving,OnScreen, like 10 variation, which would progressively make it more interactable (costly) depending on how important it was...
     
  12. jo-thijs

    jo-thijs Member

    Joined:
    Jun 20, 2016
    Posts:
    2,708
    I should have known there would be a thread for this and I should have had my discussion with GMWolf here instead.

    Representing states as objects sometimes definitely make sense.
    Consider a Super Mario Bros clone.
    One could divide Mario up into several states: some for when he is alive and one for when he's dead (in his death animation where he makes a last jump).
    Mario's behavior while in his death animation differs so much from his other states that it is not unlikely a programmer would decide to put its behavior in a separate object.
    We could have a state for when mario enters or leaves a pipe.
    The only thing this state's behavior has in common with a different state, is the walking animation and the power up of Mario.
    It would definitely be reasonable to implement this behavior for pipe traveling in a new object.
    The same for when mario slides off the flag pole and walks to the castle.
    All those states also require some timers and events that would have unnecessarily made the object(s) containing code for other states less overviewable.

    We could further have some states to differentiate Mario's behavior when walking/running/idle, jumping/falling, ducking or swimming.
    The odds of a programmer doing this is a lot less, because those states have a lot of behavior in common.
    Mario's behavior when hitting an enemy or an item or getting hit by an enemy, his way of throwing fireballs, the collision checking with solids, invincibility frames, ...
    are the same over all 4 states and so splitting those states up in several objects could lead to a lot of code repetition or some awkward coding structures if not dealt with carefully.
    There are actually just a couple of events that would be affected and the pieces of code that would be different aren't huge.
    It might be more overviewable here to keep those states in 1 object and either have script-based states for those 4 or even just use some if-statements.

    Now suppose we did split every state up in a separate object.
    We would probably have some base state representing Mario as a whole, being the parent of all the state objects and implementing some of the common behavior.
    We might now want to implement luigi with some gimmicks (for example, multi-coin blocks might throw all their coins out at once, instead of 1 by 1).
    We wouldn't want to reimplement everything we implemented for Mario already however.
    We would probably create a new parent for both Mario and Luigi.
    Now we have an issue however, the states that got implemented as separate objects have a base state representing Mario as parent.
    We would want to reuse those states for Luigi, but how do we manage this through inheritance?
    It becomes difficult to keep the states as objects scheme up at this point.

    We could consider "being Mario" and "being Luigi" to be 2 states of the player, that are independent from the 7 other states we have defined.
    We want to somehow compose multiple state machines in 1 entity.
    The fact that we use instance_change to change states makes it difficult if not impossible to make general compositions of multiple state machines using the states as objects scheme.
    It's relatively easy with a states as scripts scheme.
    Scripts can have other scripts (representing states) as arguments and define their composition in GML.
    Objects could have the scripts as variables and define their composition in the code in their events.

    Having said all of that, it looks like the usefulness of the states as objects scheme depends on how much behavior the several states have in common.
    The states as objects scheme alo has some issues like the composition issue I explained above.
    The best solution is probably a hybrid of both schemes (in a nested way, as described above).

    An other fun observation I made:
    In the states as scripts scheme, people would put common behavior of the states in the object and the behavior that differs over states in scripts.
    In the states as objects scheme, people would more likely do the opposite.
    The latter sounds more appealing in a lot of cases, because scripts are more reusable than events of objects,
    so it'd make sense to put behavior that "occurs a lot" in scripts.
     
    GMWolf likes this.
  13. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,160
    @jo-thijs yes, code organizations can get a little awkward.
    But I think for the Luigi example, you could use object variables to change state transition.

    For example, if Luigi jumps a little differently than mario, we could create a new state for Luigi jumping that inherits from mario jumping, changing the relevant parts.
    Then in the object variables, we could set the jumping state to the luigi-jumping state.


    Actually, object variables make a lot of this rather more interesting.
    I'll have to play around it some more to decide if it's something to adopt long term.
     
    NeZvers likes this.
  14. gmx0

    gmx0 Member

    Joined:
    Jul 10, 2016
    Posts:
    58
    I was never really told that instance_change was bad, and I've been using GM since 6.1. I would say though I do not use it because instance_create then instance_destroy seems cleaner to me (as some have stated with the clean up states).

    The first thing I might apply this to is RTS games that have a lot of unit types and terrain types and more than 2 factions, because RTS units if you want a good detection/targeting system either needs multiple parents or instance changing. For example, if you want to target ground infantry units but can't target units of your own side but another side, so you have to cycle through only units of the enemy's side but then you also have to cycle through all infantry units, and all ground units, and some are tanks instead of infantry, and some are flying infantry. So it would be just easier to cycle through by changing all objects to each category. (Yes, you can use "if statements" but that makes the detection algorithm bloated).

    (Also I realize the above paragraph can be confusing and also possibly inefficient)
     
  15. jo-thijs

    jo-thijs Member

    Joined:
    Jun 20, 2016
    Posts:
    2,708
    I might be misunderstanding what you said, but wouldn't using if-statements be less bloated?
    You just have a variable in the units that indicates which team they belong to
    and you have a boolean variable that indicates whether they are ground units
    and you have a boolean variable that indicates whether they are infantry units.
    You can then loop over every gound infantry unit of the opponent by:
    Code:
    with obj_unit_parent {
        if team != other.team
        && am_ground
        && am_infantry {
            ...
        }
    }
    If you would use instance_change, I suppose you would either:
    - Have duplicate objects with different parents, which would be a bad idea in all ways I can think of.
    - Have the objects temporarily change to the parent object currently being checked for, but then:
    * you need to keep track of the original object index in every unit
    * you need to implement the logic of which object may be transformed to which categories (which will inevitably be less nice than just stating these facts as boolean variables in the create event of the units).
    * you will either need to keep track of which instances have been selected so far and keep looping over those instances only when checking for the next category, or you'll have to work per instance, in which case you'll be using those if-satements anyway, but with just more intermediate and less clear steps inbetween.
    - Have for each unit an object defining its behavior and a bunch of dedicated objects representing a category for that unit specifically (which would have their respective category objects as parent), so that you can change to those objects when checking for said category and then change back. This would have almost the exact same issues as the previous approach.
    - Have an object to represent an instance is selected. You would again have similar issues.

    So, I don't think this will help.

    What GMWolf proposed is a way to implement a state pattern in GameMaker.
    The state pattern is used for instances that change their internal state and have their behavior change as a result of it.
    You don't have units that change their internal state (which categories they belong to), but rather have a static state that is only used to put labels on units.
    This isn't quite what GMWolf's suggestion is meant for.

    Although, @GMWolf, feel free to correct me if I'm wrong here.

    Now @gmx0, without knowing more about what you intend to do specifically, I think using if-statements will work out just fine.
    However, experimenting with different programming approaches can't hurt, so certainly try it out if you feel like it.
     
    GMWolf likes this.
  16. gmx0

    gmx0 Member

    Joined:
    Jul 10, 2016
    Posts:
    58
    The thing is, you need to have a lot of different inheritances especially if there are different rules for all different unit types. For example, a ground unit type will have a Create event with things that are different from an air unit type for variables and such, and an infantry unity and a vehicle unit type will also need different considerations, however, one needs to seamlessly combine ground + infantry, ground + vehicle, air + infantry, air + vehicle. Now multiply that with a lot of unit types. And not just Create events, but a whole host of events concerning movement, selection, AI handling specific unit types for battle composition, and such.

    Having terrain (and general) collisions would also be factored. For example, air units will always be never collided upon, so adding if statements to collision events is more bloated than not having collision events at all.

    With collisions also comes wholly different movement. And many units would share movement "engines", some wheeled, some tracked, etc. Yes, you can set each unit with its own engine by having separate code for each, but if you need to share them, and you can't use inheritance (because inheritance is used by detection unit types first, not terrain unit types) except through event_perform or instance_change.

    So when all such considerations are in place, not just detection, to loop per object type makes more sense than setting booleans by that point. And where instance_change can come in. (Though right now my system uses event_perform a lot instead of instance_change)

    Of course some would suggest to simplify the system, however, I'm aiming for an RTS system that replicates modern RTSes in flexibility.

    As noted by my signature, I have been making RTSes as my specialty for a long time, and using if statements to regularly check for unit types seems more efficient for just detection, until you realize you need units that are more robust and flexible at the same time with a host of other things. There's a reason this genre is one that is not often tackled by game developers, and its because it needs to be a balanced system made up of bloated resource directory (that's the tradeoff), not a haphazard one with bloated code of if statements.

    So your premise is false, because it isn't a static state, or rather, at its simplest form, it is a static state, but when complexity is added, it is now internal states I'm dealing with.

    Simple static state: only relies on detection
    Complex internal state: relies on detection, terrain, movement, AI, etc.

    Already have a working system of objects with multiple inheritances and multiple parents, again, using event_perform. No biggie, I'm used to thinking with multiple inheritances already. You just need to have distinct, non-conflicting roles for each.

    Not really necessary, you just need instance id. Then use data structures to take out non-qualified ones per cycle.

    Already accounted for.

    When you're already dealing with complex RTS detection (range, 3D z isometric coordinates, terrain, fog of war, stealth, cloaking, etc. considerations), splitting it up per object type is my preference.

    So instead of dealing with which side the unit is on, I'd be dealing only with stealth and fog of war in said code. It's arguably much cleaner.

    Many solutions.
    For example: put all close units in a list with detection unit type, cycle through each unit, instance change all units to their terrain unit type, cycle through again, change them back.
    OR
    Have global events where each instance_change is on one step, and then on another step, switching back and forth based on the current mode.

    Again, I understand I'm only transferring the bloat from if statements to a bloated resource tree, as stated. That is preferable.
     
    Last edited: Dec 4, 2018

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice