I left object oriented programming behind a very long time ago in my career, and found that generally writing functions was far less prone to problems. I'd still use objects and instances of things but they were always just data and references, not significant amounts of code.

Now that I'm entering back into object oriented programming to an extent by using Godot, I'm finding myself running into architectural headaches. So I wanted to ask a quick question and receive a little bit of input on how you approach the problem. We're talking super super basic here.

My problem stems from the way you need to keep references to things in your class instances. If you have a class instance that represents a character. And then you have a lof of things associated with that character like a movement controller, or a set of actions they can perform. It seems like that movement controller and those actions need a reference to the character, similarly you probably need to be able to look up the movement controller and those actions for a given character.

So they reference each other?

Like you have a reference to the character stored on every action and you have a reference to every action stored on the character? It just seems like tons of stuff that can fall out of sync. But do I have that right? And if so why does Gdscript punish me for circular references?

I feel like I've fallen out of my element.

What I usually do is try not to call up that much (ideally never). For example, if you have some object, let us say a player, and the player has a backpack. It is okay to have the player call methods on the backpack (say "get_free_slots()", "insert_item(item)") but try to not have the backpack calling methods on the player (this also doesn't physically make sense).

So the objects on the higher level (the highest level being the main or level script) can call their children, but children shouldn't call parents. This helps with the circular dependencies as well, and also makes things more modular. Like in the backpack example, I can create a second character (maybe player two in a multiplayer game) and give her a backpack, and it just works with no changes to the backpack code.

In the cases where objects do need to know about each other (which happens of course), I use signals or returns from other functions. Like if an enemy attacks the player, the enemy should not have the player node hard-coded. The attack script can get the object from the collision, and then send a signal to the collided object (which happens to be the player, but could be a tree, or an NPC, etc.). Then, if the object is able to take damage, it will implement the signal and do the proper response. Or if it doesn't take damage, the signal will be ignored, nothing will happen and there will be no error.

Yes, but.

I mean what if the system for taking something out of your backpack is large enough that is deserves its own file. This is a problem I come across all the time in OOP. You can't keep loading all the code into a single file, just because it belongs there. Like for example what business does the character instance have needing to manage the backpack when it probably has 100 other things it's doing.

OOP is constantly bashing up against things like the single responsibility principle, and everything else. Code just doesn't belong along side data. But, OOP, it sticks around forever.

:)

I'm sorry I'm just frustrated. So, specifically what I'm doing right now is building out some AI for my character. A character has many actions, when an action is running it's receiving process and physics_process method calls. That way the action could be causing the character to do just about anything.

But what I've ended up doing is just passing in all the extra data I need, in order to avoid cyclical references. Every process and physics_process tick is receiving the simulation resource itself as well as the character instance it's acting on and the delta.

This is the absolute cleanest possible solution I am able to come up with, since I can't reference either of those things. If I reference those things then I can't reference the actions from the simulation or the character. And since I'm passing references around like those I'm not really using OOP for anything. Which makes me feel like I'm not going about it the right way.

I do get your point about signals I'm trying to use those as well but they seem limited by only accepting a single parameter in gdscript. And I'm finding I'm ending up passing a signal to a instance that triggers another signal and so on.

Like I understand the principles of object oriented programming, it's the first thing I learned when I went to school like everyone else. But in practice it always turns into this madhouse for me.

You probably want to use a finite state machine (FSM). I've seen this coded elsewhere with each state being a separate script, with inherited methods (like "enter_state()", "update(delta)", "exit_state()"). The FSM machine itself is owned by the character using it, and the character script handles the updates. I haven't implemented this myself this way, but I have seen it done before.

Alright thanks for your help. I'll keep playing with it and see if I can't find a mechanism that makes the code feel a bit better. I think writing and then re writing it is sort of adding fatigue.

7 days later

I think I've gotten a little closer to the answer.

I'm reducing my use of nodes even further. I'm writing my code in carefully crafted and organised basic classes, some of which directly control the nodes in my scene. Scene nodes have a handful of sensors, emit signals, and that's it.

This allows me to avoid building big complicated stupid scripts for nodes.

And when there's a lot of complicated behaviour there's class instances that reduce the complexity of what's happening all the way down to whatever class instance has direct control over the node.

I think this is more or less the best that can be done.

3 years later