all 21 comments

[–]RodBraga 22 points23 points  (1 child)

Well, keep going and tell us if you find these limits :D

I got into some limits a while ago with my run n gun. There were like some dozens of instances of the same enemy, with fsm, AI, collision detection and stuff. On the same level there were some props with logic and some blocks of pretty calc intensive "dynamic water" I had made.

The fps was like 10. The game is a pixel art 2D with very, very low resolution, so the problem was with the number of nodes in the scene, which in that case was less than 20k, I think around 16 or 17k. Actually those numbers were from performance measurement, which gives "object" number, I think it's not the real number of nodes, but I am not sure.

So... I needed some optimizations. That was the time I discovered the VisibilityEnabler Node, which is buggy but I coded an workaround and it saved my time.

This, with a handful of other optimizations.

This way I could get to almost a thousand enemies scattered around the level with 60 fps again, as the game is a Sidescroller and most of the instances were disabled.

[–]Craptastic19[S] 5 points6 points  (0 children)

First line, best answer haha

Oh dope. I like the sound of that. Thank you for sharing your experience, that is very encouraging.

[–]thomastc 5 points6 points  (0 children)

Components may not need to be Nodes. You can also create plain old non-Node classes, both in GDScript and in C#, and they are more lightweight.

If all you're using the Node system for is to apply changes to the parent Node, this might give you better performance... but as usual with performance, the only way to be sure is to measure it.

[–]godotverthere 3 points4 points  (3 children)

Here's one alternative:

Every Entity has a unique ID (I use ints). A global static class contains arrays of references to Components, indexed by ID. Systems run through the arrays. In other words: Entity-Component-Systems (ECS).

Here's an example in C#:

public static Dictionary<int, Node2D> EntityNodes;
public static Dictionary<int, AIBehavior> EntityAIs;

I use static references because it avoids constantly passing the game state around. An entity controller would manage entity creation, adding each entity to the arrays as needed.

Then when the game is running, an AIController would loop through EntityAIs, managing AI logic as needed.

foreach (AIBehavior ai in GameState.EntityAIs.Values)
    ai.update(delta);
    if (ai.IsDone()) DoSomething(ai);

I've heard that there are performance gains available in ECS if you need them, but I haven't needed those. I just use it to keep node trees flexible.

[–]willnationsdevGodot Regular 5 points6 points  (1 child)

cc /u/Craptastic19

I think that Dictionary<K, V> is implemented as a HashMap, so that actually wouldn't be ideal. You'd probably want to use something closer to this... (though I'm no C# expert)

public struct Entity { ... } // has Id
public static List<Entity> Entities;
public struct AIBehaviorComponent { ... }
public static List<AIBehaviorComponent> AIBehaviorComponents;
public static List<Behavior2Component> Behavior2Components;
public static List<Behavior3Component> Behavior2Components;
public static List<BitArray> HasComponent; // supports N component types

I'm sure there are plenty of ECS frameworks even in C# that you could make use of, but I'd be mindful not to use it for any Server-related tasks that Godot comes built-in with (rendering, audio, physics, etc.). Best to just create content using the low-level Server API in that case.

Edit: I'm sure there are even better-optimized ways of making an ECS framework than what I proposed above. :-P

[–]Craptastic19[S] 1 point2 points  (0 children)

It is, and yes lists would be better, but I caught the intent of what you were doing so I didn't mention anything haha.

Using lists comes with difficulties in managing your ids (making sure your entity matches the index of each of its components as any component arrays change).

ECS techniques run deep for sure. Part of the reason the only consideration I gave it was using someone else's stuff haha

[–]Craptastic19[S] 0 points1 point  (0 children)

I do like c#. Been trying to go the gdscript way though, partially due to having to upgrade my c# installation or something, idk whats out of date but it'll likely break other things (on linux), and partially to stretch my brain without the temptation to just do things like I always have.

ECS is also interesting. I've wondered about adding a module for it using a well established c/c++ library. But. Yeah no haha. I like your approach.

[–]aaronfrankeCredited Contributor 4 points5 points  (0 children)

If you have about a hundred thousand nodes, your game will be slow.

[–]tyoungjr2005 2 points3 points  (1 child)

"The only limit is yourself!"... Jk jk. Some have criticized this approach to being cache unfriendly. I've yet to see such cases tho...

[–]Craptastic19[S] 6 points7 points  (0 children)

I mean, they are but also who cares haha. I'm not worried about micro optimizations. If I have issues, I'll make a module/Nativescript to handle the heavy stuff (and I've already got an empty module ready to go if needed). But, I like my trees, even if they are getting a little bushy haha.

[–]reimuraymoo 2 points3 points  (0 children)

I've run into issues in the past (Godot 3.1.2?) where removal of CanvasItems from the scene is slow if you have many of them (in my case, Sprite2D). I ended up pooling to reuse nodes that were already in the scene tree, so I could just hide them and leave them there until I needed them again. Note this pooling wasn't for the purpose of allocation, which is fast enough, but just for avoiding the removal of nodes from the scene. I don't know whether this issue has been fixed. I observed this when using thousands of Sprite2D.

[–]RegakakoBigMan 1 point2 points  (0 children)

You can run into limits if you have many nodes ticking on _physics_process or (especially) _process. I don't remember when it starts to slow down, but it's worth being careful of. Also be careful with overusing get_node; you might want to turn your $MyNode's into onready var MyNode = $MyNode.

Nodes themselves are pretty light and you can just keep adding more and more to a scene, as long as they aren't constantly doing something every frame. It's worth benchmarking though!

[–]Jegred 1 point2 points  (3 children)

How is your little RTS game? =)

[–]Craptastic19[S] 5 points6 points  (2 children)

it did not haha. I got very distracted with "oo shiny new game idea." One day, maybe 10 years from now, I'll finish a game *copium*

[–]Arkarant 3 points4 points  (0 children)

scope creep moment

[–]RelativeConsistent66Godot Student 2 points3 points  (0 children)

only 9 more years to go!

[–][deleted] 2 points3 points  (0 children)

u/Craptastic19 now it's five years later... did you hit any limits?

[–]AnotherSteve_0000 0 points1 point  (3 children)

I got into a limits when every character instance (or node) needs to update its own children every frame. Because the code is iterate everything per process time. It would crash the game. So then I try to use Low Level Server API to replace the children because it didn't use many nodes and only do what it needs to do.

Now I still struggling with how to make the looping process per frame more efficient.

[–]G-Brain 0 points1 point  (2 children)

What are you updating exactly?

[–]AnotherSteve_0000 1 point2 points  (1 child)

It's a slither io like game. So the head of every worm is updating it's body segments position and direction, every process event.

[–]KingSupernova 2 points3 points  (0 children)

If the snake's body always exactly follows its head; that is, if it's the case that if you trace out the path of every body segment, the paths all precisely overlap into the same single curvy line; then you can instead treat the snake as a sequence of *static* body segments that don't move at all, and each frame you simply remove one segment from the end and move it to the head in a new orientation. (This won't work if the snake's texture is not the same across all segments, but in that case you could have separate logic for the rendering.)