My project its growing at a steady pace and I'm proud to say after 3~4 years programming on and off with GDscript as my very first programming language 90% of the problems that bring me to a halt are no longer related to programming. But that doesn't mean I don't have doubts or that I'm doing things right.

My fundamentals are not terrible but I would consider that I'm still pretty ignorant about godot and programming in general as an example I don’t always know what godot is doing with memory. I've written C++ for a couple of small arduino projects but it's not the same and I was using libraries. Reading other people’s code is still pretty hard for me.

I have some problems where I would like to hear how you would solve them. I wrote down the solutions I know of and my uneducated thoughts about them.

Example 1:
A class that needs a couple of variables from another class.

  • Constant: This is what I mostly use but sometimes I need to change the values.

  • Static variables: Seems like the tidiest option if you need to edit the variable and you only need a couple of them but I don't know how they compare to other options in terms of memory/cpu.

  • Autoload: I know that creating an autoload just for a couple of variables may be a waste but what about adding the variables to an already created autoload?.

  • Signals: You can use signals but making a lambda or a method just to set and get individual variables doesn’t seem right to me.

  • Add the instance of the class as a variable in the script that needed them at runtime.
    Starting off this was what I mostly did. It was required to populate the script variable with the instance before the script required the variable. When would you consider using this method over any of the other choices in this specific circumstance where you only need a couple of variables?.

Example 2:
A class that needs both variables and methods from another class.

  • For this case in particular I prefer to store the instance in a variable to use as needed. What’s the difference to calling the method from an Autoload, an instance stored in a variable and a static function?

  • Before they introduced static variables I thought static functions were only there to prevent the use of public variables which means all variables outside the function since godot doesn’t have private variables.

Example 3:
Storing a functions return value instead of using it directly.

In case I didn’t explain myself clearly:

func foo():
	var dict :Dictionary = get_dictionary()
	var name:String = dict[0]
	var age :int = dict[1]
	var height :float = dict[2]

	use_something(name)
	do_another_thing(age)
	print(height)

Would you do something like this? Checking the type and duplicating the data in memory is kind of a waste but how much of a waste is it? I find myself doing this from time to time to infer the type on dictionaries or just for readability.

Example 4:
What kind of array is better for iteration?

If you have a for loop for example and you can choose between a PackedVector2Array and a simple Array which would be better? I know PackedArrays are more memory efficient but does it have a cost to retrieve the data from them?

  • xyz, Toxe, and Erich_L replied to this.
  • llHate It's great to hear that your project is growing, and it's natural to have questions and doubts as you continue to develop your skills. Let's go through your examples and discuss the various approaches you've mentioned:

    Example 1: A class that needs variables from another class.

    Constants: Constants are suitable when you have values that won't change during runtime. If you need to change these values during runtime, they are not suitable.

    Static Variables: Static variables can work well for this purpose. They provide a way to share data between instances of a class without having to create a new instance every time. They generally have minimal overhead, and their memory usage is low.

    Autoload: Adding variables to an already created autoload is a reasonable option if you want to share data between different parts of your game. Just ensure you don't clutter your autoload with too many variables.

    Signals: Using signals primarily for sharing variables isn't a typical use case. Signals are more suitable for communication between objects or components. They are better for responding to events or changes in state rather than simple variable sharing.

    Add the instance of the class as a variable: This is a common approach when you need to access several variables or methods from another class. It's more flexible than the other options as it allows you to access any part of the class instance. You would use this when you need more than just a few variables or when you want the flexibility to access methods and variables freely.

    Choose the method that fits your specific use case. If you only need a couple of variables and don't expect them to change frequently, static variables can be a clean and efficient solution.

    Example 2: A class that needs variables and methods from another class.

    Storing the instance of the class in a variable is a common and practical approach. Here are some differences between this approach, calling a method from an Autoload, and using a static function:

    Instance Variable: When you store an instance in a variable, you have direct access to its variables and methods. This provides maximum flexibility but might involve some memory overhead for the instance itself.

    Autoload: If you call a method from an Autoload, you are essentially accessing a global function. This approach is suitable when you need to access the method from various parts of your game. It doesn't involve memory overhead like storing an instance, but it's less flexible since you can only access methods, not variables.

    Static Function: Static functions are typically used for utility functions that don't require access to an instance's variables. They are not intended for accessing instance-specific data or methods. Use them when you have a function that doesn't rely on instance-specific data.

    Choose the approach that best fits your needs. If you need both variables and methods from another class and want flexibility, storing the instance in a variable is often a good choice.

    Example 3: Storing a function's return value instead of using it directly.

    In your example, you're storing values from a dictionary into separate variables before using them. While this does incur some additional memory overhead, it can enhance code readability, which is often a good trade-off.

    The memory overhead here depends on the size of the data being copied. For small pieces of data, like strings, integers, or floats, the overhead is usually negligible. Prioritizing code readability can make your code more maintainable and easier to understand, which is important for long-term development.

    Example 4: Choosing between PackedVector2Array and Array for iteration.

    PackedVector2Array: Packed arrays are memory-efficient because they store data in a more compact format. However, accessing data from them can be slower than from a regular array because they need to unpack the data on access. They are beneficial when memory usage is a concern, and you don't need to frequently access the data in a performance-critical manner.

    Array: Regular arrays are less memory-efficient but offer faster access times since the data is stored in a more straightforward format. Use regular arrays when you need fast and frequent access to the data, and memory usage is not a significant concern.

    In practice, the choice between these two types of arrays should be based on your specific performance and memory requirements. If your game has many elements, and memory usage becomes an issue, consider using packed arrays. Otherwise, regular arrays are often sufficient and more straightforward to work with. Always profile your code to identify potential performance bottlenecks and make informed decisions based on the actual needs of your project.

    llHate Not sure what you mean when saying "a script that needs variables/methods from a class". In GDScript each script is a class. Script and class are basically synonyms. So if a script/class needs some variables/methods, it should simply declare/implement them.

      xyz I meant to say from another class it could be a custom class both scripts may extend Node, Object or something but they are different subclasses and have different methods and variables.

      I'll edit the post and try to make it more clear.

      • xyz replied to this.

        llHate If you don't need objects, then the most lightweight and logically clearest approach is to use a completely static class, with static properties and methods. This basically works like a namespace shield around global variables/functions.

        All the other solutions you mentioned are fine too though. An instantiated singleton, either autoloaded or manually created, would create pretty much same footprint as a static class if you inherit from the high upstream in the class hierarchy (i.e. Node in case of autoload, and Object of RefCounted in case of manual instantiation). Engine needs to allocate almost the same amount of storage in both cases, a little bit more for an instantiated object. In practice, this seldom makes a difference.

        Using temp variables is perfectly fine too especially if they make your code more readable. If performance becomes an actual issue detected by the profiler, try to avoid var declarations inside loop bodies with lots of iteration steps.

          I cannot really talk to example 1+2 because it's quite vague and maybe you should provide code examples on what you want to achieve.

          As for example 3:

          llHate Checking the type and duplicating the data in memory is kind of a waste but how much of a waste is it?

          Don't worry about it. When in doubt always profile and measure. And if you don't notice any peformance or memory issues then you are fine.

          Readability should always be your primary concern.

          llHate Would you do something like this?

          Talking about readability: Well, it depends. If I am just doing what you are doing in your example, that is passing a simple return value to another function, then I would probably not store those values in new variables first because that makes the code smaller and probably easier to read.

          Although the problem with that is that you never really know what you just plucked out of your dictionary. In your example you know that it is a name, age and height.

          Slightly better might be to write functions that return those values, like:

          func get_person_name() -> String:
              return get_dictionary().get(0)
          
          func get_person_age() -> int:
              return get_dictionary().get(1)
          
          func get_person_height() -> float:
              return get_dictionary().get(2)
          
          func foo():
              use_something(get_person_name())
              do_another_thing(get_person_age())

          But even better might just be to either skip the dictionary completely or store a class inside it:

          func foo():
              var person = get_dictionary().get(person_index)
              use_something(person.name)
              do_another_thing(person.age)

          Ultimately maybe you should just reconsider why you are storing these values in a dictionary in the first place and get rid of it. That might automatically solve all the issues you have.

            Perhaps worth noting something like this:

            var dict :Dictionary = get_dictionary()

            Is only a reference and doesn't copy the memory unless you call duplicate (rtfm).

            Generally, there's some coding guidelines and practices in the manual and as long as you're coding in a way that you're happy with and (mostly) consistently to achieve the results you want then don't worry too much about it.

              spaceyjase Thanks for remembering me about this I knew about arrays being like this but I completly forgot about Objects and dictionaries there's probably more things that get passed by reference. That solves my doubts about storing instances references on other classes. Is it right to assume this is the same as a pointer in C?

              Toxe My example was pretty bad, my actual case looks like this:

              enum {BIOME_ID, EVENTS, ENTITIES}
              
              var world_data :Dictionary = { 
                chunk_coord1: {Vector2i(0,0): [biome_id, [event1,event2,...],[entity1,entity2] ] }, 
                chunk_coord2: {Vector2i(1,0): [biome_id, [event1,event2,...] ,[...] },
                ...,
              }

              This is a huge dictionary I use all the time so I made an enumerator to get the correct index once I have the cell_data, in the future there will be more indexes. Its readable and I can skip the temporary variable, but I still prefer to store the "main block of information" in a variable to keep the lines short:

              var chunk_cell_data :Array = world_data[chunk_coord][chunk_cell]
              foo_biomes(chunk_cell_data[BIOME_ID])
              foo_events(chunk_cell_data[EVENTS])
              foo_entities(chunk_cell_data[ENTITIES])

              I'm pretty bad with naming and the enumerators can get quite long too so I resort to manually typing the indexes in the functions and storing the values on variables with descriptive names and type like in the first example.

              I would like to use something like world_data[chunk_coord][x][y] because I think it will be faster I don't know if hashing a Vector2i is way more complex than hashing a string or int.
              But also using this method would force me to create a ton of empty arrays at times and I don't know if that is better than having the dictionary.

              xyz
              When you say static class you mean adding static to every variable and func or can you add static to the class directly?
              and how can I create a class that doesn't depend on Object? The smallest I found is RefCount.

              • xyz replied to this.

                llHate When you say static class you mean adding static to every variable and func or can you add static to the class directly?
                and how can I create a class that doesn't depend on Object? The smallest I found is RefCount.

                Yeah, by static class I meant a class in which properties and methods are declared static. For such a class it doesn't really matter what it extends since nothing will be instantiated. As I said, it will work like a namespace:

                class_name Stuff
                extends Object
                
                static func f():
                    pass

                Now without instantiating any objects you can access f() from any other script:

                Stuff.f()

                Same for properties.

                As others have mentioned, often you don't need to worry about performance of small things (creating a temporary variable, accessing a reference, etc). Code readability and maintainability should come first. Once you notice that the performance is worse than it should be (for which you need to develop an intuition), you open up the profiler and look at things in order of impact. And oftentimes it will be architectural issues, rather than small nuances, for example sorting to find the max value (instead of .max() which is O(n)) or recalculating things that can be cached.
                Other things you mentioned are often down to stylistic preference. Some people might claim that this way or that way is "Better™", but it's rarely that black and white. Code should be written for people who will read it, so if you're working alone, it only matters what feels right to you. With that being said, if you want some other perspectives (which is great!), you should try a book/course/talk on design patterns. There is the Game Programming Patterns book, which is widely recommended. Other, not gamedev specific, should also help.

                  LoipesMas I'm not super worried about performance but I have gone through a couple of refactors and migrating the project to godot 4.1.1 and its not a fun experience but making the code more compact readable and modular feels good. I just wanted to make sure I understood some misconceptions caused by my lacking fundamentals.

                  I think I'm starting to develop an intuition about these things but it also makes me grow wary especially functions call I wasn't aware they can be this costly, take this example I had these two functions and the first one called the second one instead of repeating the code of the first one. By the way my ability to name things is terrible...

                  ## Given a list of raw_cells: cell coords based on a chunk with global_position (0,0)
                  ## sorts each cell by their real chunk coords: [br]
                  ## cells_chunk_data= { chunk_coords1:[cell1,cell2,...], chunk_coords2:[cell1,cell2,...]  }
                  func get_cells_chunk_data(raw_cells:Array[Vector2i]) -> Dictionary:
                      var cells_chunk_data :Dictionary
                    
                      for raw_cell in raw_cells:
                      # get_world_cell_chunk_coord(raw_cell)
                          var raw_chunk_coord :Vector2i = Vector2( float(raw_cell.x)/chunk_cell_size.x, float(raw_cell.y)/chunk_cell_size.y ).floor()
                          var chunk_coord     :Vector2i = Vector2i( posmod(raw_chunk_coord.x,world_cell_size.x), posmod(raw_chunk_coord.y,world_cell_size.y))
                          var chunk_cell      :Vector2i = raw_cell - raw_chunk_coord * chunk_cell_size
                  		
                      if cells_chunk_data.has(chunk_coord):
                          cells_chunk_data[chunk_coord].append(chunk_cell)
                      else:
                          var chunk_cells :Array[Vector2i] = [chunk_cell]
                          cells_chunk_data[chunk_coord] = chunk_cells
                  		
                    return cells_chunk_data
                  
                  
                  func get_cell_chunk_data(raw_cell:Vector2i) -> Array[Vector2i]:
                    # Needs float for rounding down and get the correct raw_chunk_coord with negative cell positions.
                    var raw_chunk_coord :Vector2i = Vector2( float(raw_cell.x)/chunk_cell_size.x, float(raw_cell.y)/chunk_cell_size.y ).floor()
                    var chunk_coord     :Vector2i = Vector2i( posmod(raw_chunk_coord.x,world_cell_size.x), posmod(raw_chunk_coord.y,world_cell_size.y))
                    var chunk_cell      :Vector2i = raw_cell - raw_chunk_coord * chunk_cell_size
                    return [ chunk_coord, chunk_cell, raw_chunk_coord ]

                  This was 10x faster than calling the second function inside the first function it went from 6000 microsecs to something like 500 microsecs so now I'm more inclined to do less functions if possible.

                  For me the profiler crashes or freezes the game most of the times even tho its perfectly fine and idle doing nothing so I don't know what's up with that I'm in 4.1.1 Its easier to reproduce when changing the focus from the editor to the game's window.

                  Thanks for the book recommendation I've read about different patterns but I'm going with my gut feeling most of the time. I think that I naturally gravitated towards something similar to ECS without even knowing it. I use resources heavily as data containers that's the big reason I changed to godot 4 resources in godot 3.5 gave me a lot of problems when saving to disk nested resources and with cyclic dependencies I couldn't use duck typing. But I never use subclasses or inner classes.

                  • xyz replied to this.

                    llHate A function call itself is not very expensive. Allocation is though. Function in your above example creates a temporary return array at each call. As I already said, if you want to optimize for speed get rid of any (re)allocations inside loop bodies. This includes declaring vars and creating and appending arrays. It needlessly allocates and/or deletes their storage at each loop step. This can accumulate a lot of wasted time when number of iterations is large. If you know the size of the array beforehand, reserve the storage prior to entering the loop.

                      xyz

                      I tried to apply what you explained. This time it runs 100 times faster, the dictionary at the start is a placeholder. I removed the if statement but I think the dictionary is so small right now that It doesn't make a significant difference.
                      I still have to return on the second function because I use it on its own from time to time.

                      func get_cells_chunk_data(raw_cells:Array[Vector2i]) -> Dictionary:
                      	var cells_chunk_data  :Dictionary = { 
                      		Vector2i(0,0):[] as Array[Vector2i],
                      	 	Vector2i(1,0):[] as Array[Vector2i], 
                      		Vector2i(0,1):[] as Array[Vector2i], 
                      		Vector2i(1,1):[] as Array[Vector2i], 
                      		}
                      	var ichunk_data :Array[Vector2i] = [Vector2i.ZERO,Vector2i.ZERO,Vector2i.ZERO]
                      
                      	for raw_cell in raw_cells:
                      		ichunk_data[2] = (Vector2( float(raw_cell.x)/chunk_cell_size.x, float(raw_cell.y)/chunk_cell_size.y ).floor() ) as Vector2i
                      		ichunk_data[1] = Vector2i( posmod(ichunk_data[2].x,world_cell_size.x), posmod(ichunk_data[2].y,world_cell_size.y))
                      		ichunk_data[0] = raw_cell - ichunk_data[2] * chunk_cell_size
                      		cells_chunk_data[ichunk_data[1]].append(ichunk_data[0])
                      
                      
                      func get_cell_chunk_data(raw_cell:Vector2i, arr :Array[Vector2i] ) -> Array[Vector2i]:
                      	# Need float for rounding down and get the correct raw_chunk_coord with negative cell positions.
                      	# raw_chunk_coord
                      	arr[2] = (Vector2( float(raw_cell.x)/chunk_cell_size.x, float(raw_cell.y)/chunk_cell_size.y ).floor() ) as Vector2i
                      	# chunk_cell
                      	arr[1] = Vector2i( posmod(arr[2].x,world_cell_size.x), posmod(arr[2].y,world_cell_size.y))
                      	# chunk_coord
                      	arr[0] = raw_cell - arr[2] * chunk_cell_size
                      	return arr

                      But this runs 150 times faster, is it because I don't have to do the extra casting in ichunk_data[2]? I don't know if duck typing has the same cost as casting. I thought using a sized array would be more efficient than creating and assigning new variables.

                      func get_cells_chunk_data(raw_cells :Array[Vector2i]) -> Dictionary:
                      	var cells_chunk_data  :Dictionary = { 
                      		Vector2i(0,0):[] as Array[Vector2i],
                      	 	Vector2i(1,0):[] as Array[Vector2i], 
                      		Vector2i(0,1):[] as Array[Vector2i], 
                      		Vector2i(1,1):[] as Array[Vector2i], 
                      		}
                      
                      	for raw_cell in raw_cells:
                      		var raw_chunk_coord :Vector2i = Vector2( float(raw_cell.x)/chunk_cell_size.x, float(raw_cell.y)/chunk_cell_size.y ).floor()
                      		var chunk_coord     :Vector2i = Vector2i( posmod(raw_chunk_coord.x,world_cell_size.x), posmod(raw_chunk_coord.y,world_cell_size.y))
                      		var chunk_cell      :Vector2i = raw_cell - raw_chunk_coord * chunk_cell_size
                      
                      		cells_chunk_data[chunk_coord].append(chunk_cell)
                      		
                      	return cells_chunk_data 
                      • xyz replied to this.

                        llHate You're not using pre-allocated arrays though. Four arrays inside the dictionary are still getting resized each iteration (when calling Array::append()). If speed is priority, it's ok to sacrifice some storage and pre-allocate the array size to maximum possible, even if not used to full capacity.

                        Using Vector type as a dictionary key could also contribute to execution time. Better to use more atomic type or simply an array instead of dict if you can. It too could be pre-allocated.

                        Also, you can replace regular vector arrays with PoolVector2Array (again with pre-allocated size). GDScript's Pool arrays ensure tightly packed contiguous storage. This maximizes CPU cache hits when accessing that memory during iteration. It can considerably boost performance with modern cache-oriented CPUs. This is in general one of the main reasons why it's always advisable to use contiguous arrays over linked-list arrays, even if individual random access insertion/deletion is less expensive with linked lists.

                        And remember to pull all your var declarations out of loop bodies.

                          xyz

                          I tried the PackedVector2Array but they didn't perform as well as Arrays[Vector2i] and also implementing them in my project would be a problem because they throw an error every time I insert a Vector2i and I use them everywhere, sticking to Vector2i whenever possible seems to be the fastest just using Vector2 on a couple more variables makes it run a lot slower.

                          The second version is 10% faster than the version using PackedVector2Array I also removed the float casting making the const chunk_cell_size a Vector2 instead of Vector2i

                          func get_cells_chunk_data_packed_array(raw_cells:Array[Vector2]) -> Dictionary:
                              var cells_chunk_data :Dictionary = { 
                              Vector2i(0,0): PackedVector2Array([]),
                              Vector2i(1,0): PackedVector2Array([]),
                              Vector2i(0,1): PackedVector2Array([]),
                              Vector2i(1,1): PackedVector2Array([]),
                              }
                              
                              for chunk_coords in cells_chunk_data:
                                  cells_chunk_data[chunk_coords].resize(chunk_cell_size.x * chunk_cell_size.y) 
                          
                              var raw_chunk_coords :Vector2
                              var chunk_coords     :Vector2i
                              var chunk_cell       :Vector2
                          
                              for x in raw_cells.size():
                                  raw_chunk_coords = Vector2( raw_cells[x].x/chunk_cell_size.x, raw_cells[x].y/chunk_cell_size.y ).floor()
                          	chunk_coords = Vector2i( posmod(raw_chunk_coords.x,world_cell_size.x), posmod(raw_chunk_coords.y,world_cell_size.y))
                          	chunk_cell = raw_cells[x] - raw_chunk_coords * chunk_cell_size
                          	cells_chunk_data[chunk_coords][x] = chunk_cell
                          		
                              return cells_chunk_data
                          func get_cells_chunk_data_vector2i_array(raw_cells:Array[Vector2i]) -> Dictionary:
                              var cells_chunk_datai  :Dictionary = {
                                  Vector2i(0,0):[] as Array[Vector2i],
                          	Vector2i(1,0):[] as Array[Vector2i], 
                          	Vector2i(0,1):[] as Array[Vector2i], 
                          	Vector2i(1,1):[] as Array[Vector2i], 
                          	}
                          
                              var raw_chunk_coords :Vector2i
                              var chunk_coords :Vector2i
                              var chunk_cell :Vector2i
                          
                              for raw_cell in raw_cells:
                                  raw_chunk_coords = Vector2( raw_cell.x/chunk_cell_size.x, raw_cell.y/chunk_cell_size.y ).floor()
                          	chunk_coords = Vector2( posmod(raw_chunk_coords.x,world_cell_size.x), posmod(raw_chunk_coords.y,world_cell_size.y))
                          	chunk_cell = raw_cell - raw_chunk_coords * ichunk_cell_size
                          	cells_chunk_datai[chunk_coords].append(chunk_cell)
                          		
                              return cells_chunk_data 

                          I even tried removing this section of get_cells_chunk_data_packed_array() into another function and not taking it into consideration for the measuring. It got similar times but still slower on average than the Vector2i array. Vector2i arrays seem to be pretty efficient. We need PackedVector2iArrays!. I'm learning a lot thanks a ton to everyone!.

                          var cells_chunk_data :Dictionary = { 
                          Vector2i(0,0): PackedVector2Array([]),
                          Vector2i(1,0): PackedVector2Array([]),
                          Vector2i(0,1): PackedVector2Array([]),
                          Vector2i(1,1): PackedVector2Array([]),
                          }
                          for chunk_coords in cells_chunk_data:
                              cells_chunk_data[chunk_coords].resize(chunk_cell_size.x * chunk_cell_size.y) 
                          • xyz replied to this.

                            llHate You can use PackedInt32Array. Note that you'll have performance benefits from compacted arrays outside of just this code snippet, assuming you'll additionally iterate over that data at different places in your code.

                              xyz you mean using the PackedInt32Array as an axis container instead of the Vector2i? but this small arrays would be wrapped inside a regular array, I don't know if this is what you're referring to:
                              [ PackedInt32Array(x,y), PackedInt32Array(x,y), PackedInt32Array(x,y) ]
                              I'll try it later.

                              • xyz replied to this.

                                llHate It's great to hear that your project is growing, and it's natural to have questions and doubts as you continue to develop your skills. Let's go through your examples and discuss the various approaches you've mentioned:

                                Example 1: A class that needs variables from another class.

                                Constants: Constants are suitable when you have values that won't change during runtime. If you need to change these values during runtime, they are not suitable.

                                Static Variables: Static variables can work well for this purpose. They provide a way to share data between instances of a class without having to create a new instance every time. They generally have minimal overhead, and their memory usage is low.

                                Autoload: Adding variables to an already created autoload is a reasonable option if you want to share data between different parts of your game. Just ensure you don't clutter your autoload with too many variables.

                                Signals: Using signals primarily for sharing variables isn't a typical use case. Signals are more suitable for communication between objects or components. They are better for responding to events or changes in state rather than simple variable sharing.

                                Add the instance of the class as a variable: This is a common approach when you need to access several variables or methods from another class. It's more flexible than the other options as it allows you to access any part of the class instance. You would use this when you need more than just a few variables or when you want the flexibility to access methods and variables freely.

                                Choose the method that fits your specific use case. If you only need a couple of variables and don't expect them to change frequently, static variables can be a clean and efficient solution.

                                Example 2: A class that needs variables and methods from another class.

                                Storing the instance of the class in a variable is a common and practical approach. Here are some differences between this approach, calling a method from an Autoload, and using a static function:

                                Instance Variable: When you store an instance in a variable, you have direct access to its variables and methods. This provides maximum flexibility but might involve some memory overhead for the instance itself.

                                Autoload: If you call a method from an Autoload, you are essentially accessing a global function. This approach is suitable when you need to access the method from various parts of your game. It doesn't involve memory overhead like storing an instance, but it's less flexible since you can only access methods, not variables.

                                Static Function: Static functions are typically used for utility functions that don't require access to an instance's variables. They are not intended for accessing instance-specific data or methods. Use them when you have a function that doesn't rely on instance-specific data.

                                Choose the approach that best fits your needs. If you need both variables and methods from another class and want flexibility, storing the instance in a variable is often a good choice.

                                Example 3: Storing a function's return value instead of using it directly.

                                In your example, you're storing values from a dictionary into separate variables before using them. While this does incur some additional memory overhead, it can enhance code readability, which is often a good trade-off.

                                The memory overhead here depends on the size of the data being copied. For small pieces of data, like strings, integers, or floats, the overhead is usually negligible. Prioritizing code readability can make your code more maintainable and easier to understand, which is important for long-term development.

                                Example 4: Choosing between PackedVector2Array and Array for iteration.

                                PackedVector2Array: Packed arrays are memory-efficient because they store data in a more compact format. However, accessing data from them can be slower than from a regular array because they need to unpack the data on access. They are beneficial when memory usage is a concern, and you don't need to frequently access the data in a performance-critical manner.

                                Array: Regular arrays are less memory-efficient but offer faster access times since the data is stored in a more straightforward format. Use regular arrays when you need fast and frequent access to the data, and memory usage is not a significant concern.

                                In practice, the choice between these two types of arrays should be based on your specific performance and memory requirements. If your game has many elements, and memory usage becomes an issue, consider using packed arrays. Otherwise, regular arrays are often sufficient and more straightforward to work with. Always profile your code to identify potential performance bottlenecks and make informed decisions based on the actual needs of your project.

                                  llHate I don't know if this is what you're referring to:
                                  [ PackedInt32Array(x,y), PackedInt32Array(x,y), PackedInt32Array(x,y) ]

                                  No I meant one big packed array of ints in which you treat two consecutive ints as x and y components of a vector.

                                    xyz
                                    That's an interesting idea but I think I'll settle with what we got I'm pretty happy thanks for all the feedback.

                                    Erich_L Thanks for the general overview I think I have a better understanding now I'll mark your answer as the best answer because you touch upon everything and it would be the most useful for people in case people find the post asking themselves the same questions.