When to use RefCounted Vs Resource Vs Node?

Godot Version

4.4

Question

What are good practices to use when creating game logic, for example a Status effect system? For performance purposes.

In the example, Status effect system:

extends Resource
class_name StatusEffectData

# contains all variable data
extends RefCounted
class_name StatusEffects

#contains all status effect logic
extends node
class_name StatusEffectManager

# maintains all status effects on all entity's

Curious what kind of limitations or challenges this might have and potentially easier or more efficient ways of coding. I currently know the basics and would like to expand my understanding of computer.

This article in the documentation seems relevant to your question.

3 Likes

Here’s a class inheritance tree for a quick guide.

  • Object: Needs manual free() call when you don’t need it anymore, else it will be always there, causing a memory leak (took memory and didn’t give them back). You won’t need it in most-most-most scenarios.
    • RefCounted: Counts the references, it will automatically free the resources once the count is zero. Learn about pointer (:warning: advanced content) if you want to know more about the mechanics.
      • Node: Lies in your scene tree, so you can place logics that interact with nodes easily.
      • Resource: Provides features like exporting your own complex class to the editor inspector, and easy save/load functions for the disk.

Edit: I corrected the description for Object and RefCounted thanks to @makaboi.

Is this from a C# perspective, or…? Because this is absolutely not what Object and RefCounted are for in GDScript.

Your example makes no sense.

Start here: Your first 2D game — Godot Engine (stable) documentation in English
And here: Your first 3D game — Godot Engine (stable) documentation in English

Once you’ve built some games, you will be able to ask more informed questions.

This sounds like Ken in the Barbie movie saying his job is “Beach”. What about computer(s) would you like to understand?

Sorry, I messed Object up :rofl:. I heard some people say it, or I possibly misunderstood it. You’re right, the correct one from Object — Godot Engine (stable) documentation in English

To delete an Object instance, call free(). This is necessary for most classes inheriting Object, because they do not manage memory on their own, and will otherwise cause memory leaks when no longer in use. There are a few classes that perform memory management. For example, RefCounted (and by extension Resource) deletes itself when no longer referenced, and Node deletes its children when freed.

It seems like you think, you are some genius dev. If so, please share how you would make status effect system.

… and most people, would be able to understand, " to expand my understanding of computer". Is a metaphor for programming… we are kind of in a Godot forum… it was also kind of the point. Like a cave man. “COmpUTer”

If I want a custom runtime object of some sort I extend either Node or the closest other standard node to my needs.

If I want to save/load the custom object then I extend Resource because it has all that functionality built in and they are relatively easy to manage and modify in the Property Inspector panels.

I have not yet felt the need to extend RefCounted.

Martin

1 Like

Apologies @Chris_v for the snark. I did not understand your tone to be sarcastic in the initial post. Regardless, it was unwarranted and I apologize.

My original comment stands. You have not given enough information for me to provide you with much advice.

  1. What do you consider a status effect system?
  2. What performance issues are you most worried about? Execution speed or RAM usage?
  3. What devices are you targeting? Windows? Mobile Devices? Low-End Mobile Devices?

But here’s some thoughts. RefCounted does not save you much of anything performance-wise. I don’t recommend using it unless you have a very specific need that it solves, or you feel it would help during optimization. In other words, don’t start there.

Resources are good for things you will change a lot during development, or you want the player to change in-game and you want to save those changes to disk.

Godot uses inheritance and composition together. If you can use Nodes, then it’s pretty easy to drop them on and off a character and make things modular.

Ultimately it comes down to your design philosophy. I would not use a SatusEffectManager for example. If I was, I would name it StatusEffects. There was a talk a number of years ago at a C++ conference about how using “Manager” in everything tends to cause us to be lazy in naming things. I have embraced that approach.

However, in this case, that’s not why I wouldn’t use it. It’s because I would create a base StatusEffect node inherited from Node. Then for each status effect I had, I would inherit StatusEffect, so like, StatusEffectBurning would know everything about shaders, damage taken, etc.

Then a Character (Players/NPCs/Enemies) would look through it’s children nodes, find any of type StatusEffect, call their apply() method, passing in a reference to themselves. This would encapsulate areas of responsibility. The Character doesn’t need to know what the individual effects do, and the StatusEffect doesn’t need to care who it is affecting.

If you want to add a new StatusEffect, you just make a new inherited Node.

What I don’t know is if this is where I would end up leaving it. I’ve not yet made a game where I would want to save status effects. I.E. Allow someone to save a game in the middle of a battle or what have you. In that case, I might later refactor my code for saving. Maybe. That would involve changing StatusEffect from inheriting Node to inheriting Resource Then the application of status effects would have to change from a node-based approach, to storing the effects in an array on the Character.

The benefit of this system is that from a RAM perspective, no code will ever get run that doesn’t need to. There will be a lot less conditionals and therefore slightly faster code execution and a smaller memory footprint. The drawback to this system would be that in a bullet hell game, memory would be bloated by these nodes. In which case I’d probably store all statuses in a single int and use bitmasks to pull them out and apply them. Then a StatusEffects autoload might be more performant. The tradeoff being that in my opinion, it’s harder to maintain and less flexible.

4 Likes

Hey, it’s just two people talking. I can see where you may have intended to be funny. But thank you, and I am not too bothered by the snark. Even what I said could have been seen as passive aggressive.

Yes, thank you though! This is the wealth of information I was looking for!

  1. only 2-3 status effects to be on a character at a time, out of 50 different unique effect (ranging from control to dot) with the ability to be on 10-100 different characters at any given time
  • Game characteristics:
    -Turn Based
    -Roguelike
    -Status effects are key to character builds
    -Pick up and put down game functionality. saves states at the start of your turn.
  1. Execution speed for sure. Ram as well for lower end devices
  2. For the sake of the question, it would be Low end Mobile devices. (Maybe this is the wrong thinking, but how could any device be able to run the game)

What would be a brief example where RefCounted would be used to solve a problem?

Totally Agree

This was the original method I was going to use. But then was worried that if it started to get applied to many characters execution speeds would start to die fast. Especially when the enemy turns are fast like half a second or less

1 Like

I’m going out of town for a couple days, so I can give you a longer response when I get back. But based on what you’re describing, I’d try out the node-based approach and go from there. Just spawn 100 “enemies” with a simple Sprite2D using the Godot logo. Then attach a node to all of them that changes the modulation color every _process() frame. Attach a another node that moves them in a random direction every _physics_process(). See how bad it is. You might be ok.

Having said that, afterwards I would look at doing:

class_name StatusEffect extends Resource

var duration: float

func apply(node: Node) ->void:
#maybe a virtual function, maybe something small
#perhaps have a more specific parameter than Node.

Then inherit from that and stick them in an array on each mob. Have all the logic in the effect still. They will just save with your mobs. At most you’ll have to make a save function that calls ResourceSaver.save("path") Have the mobs call them when needed. See where that gets you.

RefCounted is best used for things that inherit RefCounted, but don’t do what you want them to do. I’d recommend looking what inherits it and seeing what there is and where you have a need at that level. TBH, every time I’ve contemplated using RefCounted, someone has talked me out of it.

1 Like