State Machine : Module Scripts and Animator Replication issues

Hi there,

I’ve recently started to learn Roblox development. (note that I have a lot of technical experience with a few other engines/editors: unreal, unity, proprietary)

I’m currently working on building a state machine module. Ultimately I’d like to have my state machine be configured by a JSON table. This would allow me to build my state machine logic inside a node editor (like yed graph editor) and have it export the data in JSON format.

But before all that goodness, I’ve come across a few issues that I need some help with.

Module Scripts
I understand that using require will return the same table, effectively turning modules into namespaces for global variables. I don’t really understand how this works with using module scripts to hold a class. From what I understand this makes every module class a singleton.

I found on the forum that one way to get around this issue is to use module:Clone():

local SMM = require(game.ReplicatedStorage.StateMachineModule:Clone())

Am I correct to assume that this would give me a separate “instance” of my module?

Also, in order to have multiple classes in the same module I resorted to the following:

local StateMachineModule = {};
StateMachineModule.Conduit = {} -- define the transition rules between states
StateMachineModule.State = {}
StateMachineModule.StateMachine = {}

local Conduit = StateMachineModule.Conduit;
Conduit.__index = Conduit;
-- class implementation

local State = StateMachineModule.State;
State.__index = State;
-- class implementation

local StateMachine = StateMachineModule.StateMachine;
StateMachine.__index = StateMachine;
-- class implementation

return StateMachineModule

After that,in my local script I can do:

local SMM = require(game.ReplicatedStorage.StateMachineModule:Clone())
local locomotion_stama = SMM.StateMachine.new(animator);

Is this a good way to approach having multiple classes in the same Module Script? Also, does it work as intended with the above mentioned module:Clone() solution?

Animator Replication
I’ve overridden the default StarterCharacter and also added a custom Animate script.
My state machine class works as intended for the local machine. It correctly sets what animation to play based on the rules I defined. The problem is that animationTrack:Play() doesn’t seem to replicate correctly.

I’m only testing 2 animations for now. An idle and a walk. The local player characters all correctly run the idle or walk animation on their respective machines. But the remote characters all run the walking animations.
If everything replicated correctly, this shouldn’t be the case.

As per the documentation, I’ve created the Animator instance on the server and it does indeed get replicated to the clients, each character getting an Animator object as a child of their Humanoid objects.
I also use WaitForChild(“Animator”) before using LoadAnimation. I do this in my Animate LocalScript. That’s everything the documentation has to say about this.

All of the characters run animations, which means that something at least gets replicated.
My question is, should I also create a mechanism to call AnimationTrack:Play() on the server?
Would this solve the issue? Or am I missing something else?

I’d very much appreciate it if anyone could point me in the right direction.

Thanks,
Bleeck.

Yes, basically everywhere you require() a certain module you will get the same value that the module returns. The way we create instances of classes is by having a function in the module which creates a new table with the class properties and metatable, and then returns it. The convention is for this creator function to be called ‘new’.

When you create an instance of your class it should look like this:

    local MyClass = require(Path.To.Class.Module)
    local MyClassInstance = MyClass.new(1, 2, 3) -- Initial setup values are passed into the .new() function.
    MyClassInstance:DoSomething() -- Calling a method of the class. (Notice this uses : while .new() doesn't)
    
    local MyClassInstance2 = MyClass.new(5, 2, 8) -- Second instance of MyClass.
    MyClassInstance2:DoSomethingElse(true, false)

You can read more about how object orientation + inheritance in Lua works here and here.

As for your animation replication issue I’m not really sure. I hope I half helped you.

I see. I’ve seen that it’s common to do return MyModule.new inside module scripts and thought that was the way to do it. That’s basically the problem right? If I return MyModule.new from inside the module script, that’s what’s actually giving me the same “new” reference. Because module scripts “cache” their output.

Thanks for the help. As for the animation stuff, I’ll most likely end up manually handling replication within the state machine script.

In studio, you don’t have tileable and non tileable textures. You have textures and decals. If I can get past this, then I guess mathematicians will just have to deal with it :smile:

Maybe what you saw was return MyModule.new() and not return MyModule.new. This is usually what people do when their class is going to be a singleton, they return a single instance of the class. If it’s not going to be a singleton you just return MyModule and create instances with MyModule.new() in your code. This is just a convention though.

I’ve also noticed a bug where if you export your animation as Looped and then set AnimationTrack.Looped = false as the client. The server would have strange behaviors such as repeating it and never playing it again even if you :Stop() and :Play().

So if you are using custom animations, I recommend double checking to see if they are set as looped before updating them.