How to decouple the animation from the object's logic state?

Started by
7 comments, last by Jh62 7 years, 1 month ago

Hello all,

I'm currently making a 2D game using libgdx. I have only one GameObject class that is used to instantiate every object in the game, it is kind of like entity component system, but components have variables as well as functions in them, so it's not 100% ECS.

My PlayerStateBehaviour class works okay but it is getting too fat because I handle all state changes as well as animation logic in there. That's why I want to decouple some stuff just for clarity of code. But of course the components and connected so they have to communicate in some way. I've read a lot of articles online and there are two widely used ways for small games. One is to just hold a pointer to the other component I need - I can put it as an argument to the constructor. I tried that way but I think it sucks because I can't figure out a way to sort all the dependencies automatically and the other reason is that I need multiple constructors for every different combination that I want. (am I missing something here?)

The other way is messaging. The simplest pattern that is already in java is Observer and this is what I decided to use.
1. I made every single behaviour/component an Observer as well as an Observable. (Is this ok?)

2.My idea is that I want every component to be able to exist on its own, without being dependent on another component. (I know messaging is still a sort of coupling, but at least every object can exist on its own.)

So what I want to do is just limit the PlayerStateComponent to transitioning between different states and sending messages about every state change, nothing else. And then the AnimationBehaviour will catch the message (STATE_CHANGE_ATTACK or something like that) and change the proper animation. (is it cool?)

3. Should I handle every single thing only by messages, or is it okay to sometimes hold pointers to other components, I really like the components to be directly referring to each other but the problem is that I can't automate the process because I don't know how to sort the dependencies....

4. How exactly should I automate the process of finding observers. Should add every component as an observer to everyone else, or just add observer when it's actually needed? The first one is easy to code, the second one, I can't come up with a way of doing it, so I chose the first way.

Can you give me some ideas, or at least tell me if I'm on the right track?

Advertisement

Just access the components directly. It works for Unity. It works for Unreal Engine 4. It'll work for you.

I can't figure out a way to sort all the dependencies automatically and the other reason is that I need multiple constructors for every different combination that I want. (am I missing something here?)

What you're missing is that connections don't have to be set up at construction time. Link things up after everything is constructed in another initialisation method.

Unity simplified example:


class ComponentA : MonoBehavior
{
    private ComponentB componentB;
    void Start()
    {
        componentB = GetComponent<ComponentB>();
    }
}

UE4 simplified example:


UCLASS()
class UComponentA : public UActorComponent
{
private:
    UComponentB* componentB;

    virtual void InitializeComponent() override
    {
        Super::InitializeComponent();
        componentB = GetOwner()->FindComponentByClass<ComponentB>();
    }
}

Thanks for the explanation, Kylotan.

But what happens when I want some component to have multiple different configurations, depending on how many of its required components currently exist in the game object?

q2: So you say that Unity doesn't use messaging, and it's enough to handle all communication by just keeping pointers?

what happens when I want some component to have multiple different configurations, depending on how many of its required components currently exist in the game object?

What do you mean, what happens? What's the actual problem?

So you say that Unity doesn't use messaging, and it's enough to handle all communication by just keeping pointers?

Unity provides messages if you want them. Most people just cache references to the components they need. If that's not safe (e.g. the component is on another object and that object may not exist forever) then messaging is an option there. But so is just looking for the object, and then asking it for the component.

If you really want to live in the Magical Kingdom of Minimal Coupling then you can have whatever owns the components hook up a ton of observers or whatever, meaning you'll have perfectly decoupled code, but will spend much time writing and maintaining all the boilerplate that turns a naive observer system into one that is actually useful.

Kylotan, I looked at one GDC presentation and there was mentioned that writing if-statements like if( someComponent != null ) in a component is wrong because it makes code not generic. And if i hold a reference to other components, I always need to check if it's not null, in case it gets destroyed. ( enemy following player - this will crash when player is dead). How do i handle that?

Kylotan, I looked at one GDC presentation and there was mentioned that writing if-statements like if( someComponent != null ) in a component is wrong because it makes code not generic.

Great. Implement a message bus and communicate with the component that way. And settle in for hours of extra work when messages don't go where you expect, when the system is awkward when you want a return value from another component instantly, when messages arrive in a different order to what you expected, when you struggle to manage generic messages for every possible payload that is convenient for what you need, when you get messages but can't work out where they came from, when your system enters a livelock state because something is waiting for a message response that never arrives, etc.

The GDC presentation is explaining how to solve problems with a highly-coupled system. Do you have those problems yet? Would you rather avoid them, knowing you're making everything more complex in return?

And if i hold a reference to other components, I always need to check if it's not null, in case it gets destroyed

Well, no. If something else destroys the component your reference won't magically become null. Your program will just crash. But I already talked about how to handle this above.

Okay, got it. Thank you again.

2.My idea is that I want every component to be able to exist on its own, without being dependent on another component. (I know messaging is still a sort of coupling, but at least every object can exist on its own.) So what I want to do is just limit the PlayerStateComponent to transitioning between different states and sending messages about every state change, nothing else. And then the AnimationBehaviour will catch the message (STATE_CHANGE_ATTACK or something like that) and change the proper animation. (is it cool?)

Well, nothing well happen in your game if every bit of code "exists on its own".

My general philosophy is to minimize dependencies, but keep them explicit (a messaging system, while appropriate in some circumstances, makes dependencies less explicit).

In your above example, is it really appropriate for AnimationBehavior to know about player states? And likewise, as you mentioned, you don't want PlayerStateComponent knowing about AnimationBehavior. What you should realize is that fundamentally, you have some logic that depends on both these things. One way to address cross-dependencies like this is to have some code outside both these components that depends on both these (one-way dependencies).

So, you could have a PlayerAnimationComponent that has pointers to AnimationBehavior and PlayerStateComponents. And its responsibility is to monitor the state of PlayerStateComponent and choose the right animation, and then update AnimationBehavior. You then even find that PlayerStateComponent and AnimationBehavior become generic enough that they can be used for enemies and other game objects.

What about a map that links classes to renderers? That way you can loop through all the entities and get the renderer for that particular type of entity and call Render.getRenderer(e); and then renderer.render(e). The renderer can know how to draw that particular unit with all it's different components and all.

I used this in a game i made and it worked very well.

This topic is closed to new replies.

Advertisement