Questions about animation

Ok, I saw your example and I think I got what you are trying to do. I think maybe it would be better to use something like the strategy design pattern or even the state design pattern, which would allow you to simply replace the trait logically at runtime among classes that share the same methods (because they are subclassing or they implement some interface). This would allow you to avoid coupling your code to how armory handles traits internally, which could change and then your code would need to be refactored to work again.

3 Likes

You should have a common super class (or better: an interface if you know what that is) for both move/rotate traits that defines a function like execute() that both sub classes override. You then have a variable of the super class type in the trait or object that should execute one of those traits. This variable holds one of the sub class objects (often called strategies, I’m describing the so-called “Strategy pattern”) so if the execution starts, the execute() method of whatever object is in that variable will be called.

This works because variables of super class types can always hold obejcts from sub classes (this is called polymorphism). If you need to execute functions that only certain sub classes have, you first need to cast them to their original type.

I’m not sure how well that was explained, so here is a small example:

// You probably even don't need traits here
class SuperClass extends iron.Trait {
    public function execute() {}
}

class MovingStrategy extends SuperClass {
    // Override with custom functionality
    public override function execute() { doMove(); }
}

class RotateStrategy extends SuperClass {
    // Override with custom functionality
    public override function execute() { doRotate(); }

    public function someSpecialFunction() {}
}

class Main {
    var strategy: SuperClass;

    function main() {
        // This will execute whatever is stored in `strategy`
        strategy.execute();

        // If you need to execute some function that only one sub class has, you
        // first need to cast it back to its original type:
        if (Std.is(strategy, RotateStrategy)) {
            cast(strategy, RotateStrategy).someSpecialFunction();
        }
    }
}

Because we’re talking about design patterns like the strategy pattern: those patterns really help to make code understandable because programmers often recognize them and instantly know what’s going on in the code (like speaking the same language). However they should be used with care because it’s easy to make trivial things very complicated (by overusing patterns or overgeneralizing problems), a good/horrible example is this (satiric) project: GitHub - EnterpriseQualityCoding/FizzBuzzEnterpriseEdition: FizzBuzz Enterprise Edition is a no-nonsense implementation of FizzBuzz made by serious businessmen for serious business purposes.


The callbacks (onInit etc.) are stored here and later called here for initialization for example or here for trait updates.

3 Likes

@timodriaan thanks for the example it is awesome but i don’t know how to execute it :confused:

I made it “run” in someway but i don’t know how to change the strategy. When someone have time, please take a look in what i do and tell me what i am missing https://drive.google.com/file/d/1Ndvoo6Yzl_y1vyaM7R_wLcO2EXZnuN_U/view?usp=sharing

Sorry this is very complicated to understand for me but at the same time this looks very interesting to learn Haxe, i hope i can break this wall as soon :hammer:

3 Likes

Hi, what I posted was no actual implementation (and not very Armory related apart from using a trait), the main() function in your file is never called and Main is not a trait, it was just meant as an example.

Instead, what I would do (there are always many other ways of course) is to have one trait as the main class. The actual strategies and the super class should not be traits (as long as you don’t need to use them as such on other objects for example). Then, in the main class you just implement the update function that calls the strategies.

When the strategy should be changed (whenever that is, might be in the init function or on some condition in the update function), you create a new Move or Rotate object and store that in the strategy pattern. This way, the correct strategy is always executed.


Actually it could make sense to also make the strategies static (that doesn’t work well with overridden methods) or to use singletons (yet another design pattern), but that makes it more complicated again and is probably just overkill. With that you wouldn’t need to create new instances of your strategies whenever you change the strategy which is potentially faster and lets you reuse more memory.

3 Likes

Alright, i think the strategy pattern is not sufficient to create the state machine, probably i will need to use other patterns like observer and factory. So i switched to a simpler thing that is a input mapping and openned another topic. After i complete it i come back to the state machine, thanks for the amazing explanation!

3 Likes

Just an aside to cement what “a state machine” is. The terms “finite state machine” and “FSM” are synonymous. The idea is that the FSM harbors an internal variable, of course called state, and periodically it gets called with some input. Traditionally, these two variables are used to find (X,Y) coordinates in a table which contains two values each: action and new_state. Each time it is called, the FSM looks up the entry, calls the action-handler if any, then sets the specified new value of state. (Often doing so in a way that allows the action-handler to override the new-state value that gets set.) The machine is initialized with a state called initial and when it has finished whatever it was last doing it usually enters a stop-state called idle.

Real-world FSMs are almost always a class descending from a trivial root-parent (or another FSM), and they commonly use switch statements instead of constant-tables to implement their decision-making. They’re a trivially easy way to implement a lot of complicated decision logic, very easy to change or extend and likewise easy to diagram, in a very small space. You can write the guts of the thing in about a dozen lines of code.

4 Likes

It is not that hard to make it work as I thought, the most difficulty is to create it in a way it can be extended to Blender UI and nodes, test the WIP here and feel free to make suggestions!

3 Likes