You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Mar 8, 2021. It is now read-only.
In this post I will detail the way I currently handle object management for my games. The main idea for how I handle this is loosely based on the concept of spaces detailed in this video:
The main concept is a Group, which is equivalent to a Space. This Group object will simply have a list of objects and then update and draw them when those functions are called:
This assumes each object has update and draw functions defined. Additionally, there needs to be a way of automatically removing objects that are dead. The way I do this is by checking for a .dead attribute:
Finally, we also need a way to add objects to the group:
functionGroup:add(object)
object.group=selftable.insert(self.objects, object)
end
We also set the object's .group attribute to point to the current group because a lot of the times it's useful to be able to refer from the object's code to the group it belongs to (i.e. when you need to grab nearby objects, for instance).
This simple setup can get you very very far. At a level higher than this Group object we need somewhere to instantiate it. The way I do it by instantiating it in my State objects (similar conceptually to FlxState), which are just normal objects used to hold groups and other objects and to coordinate between them. MainMenu or OptionsMenu or Arena would be examples of these kinds of states. In the picture below is an example with the constructor for a MainMenu class:
Here a group is created for UI objects and they're added to it. Then the update and draw functions (not shown) call the group's update and draw functions as well. I will leave the details of these State-like objects for another article, I just wanted to give you some context of how these groups are used.
For now I'll focus on explaining some more useful functionalities to add to our Group class. The first thing is being able to get objects by their unique identifier. First, objects need to be stored somewhere by their .id attribute, and we'll do this in objects.by_id, where the keys will be ids and the values will be references to objects:
functionGroup:new()
self.objects= {}
self.objects.by_id= {}
endfunctionGroup:add(object)
object.group=selfself.objects.by_id[object.id] =objecttable.insert(self.objects, object)
end
This assumes each object has an unique .id attribute. Now if we have an object's id we can easily get it by just accessing the objects.by_id table:
functionGroup:get_object_by_id(id)
returnself.object.by_id[id]
end
Finally, we also need to take care that when we remove an object due to its .dead attribute, we also remove it from the .by_id table, since if we have multiple references of the same object but we forget to remove some of them, the object will never get garbage collected, which will lead to memory leaks in your game.
Another improvement we can make to Groups is being able to get objects by their class. This might not be as easily achievable in languages other than Lua, but this is how I do it. Similarly to how we did for identifiers, we also create an additional .by_class table, add objects to it, remove objects from it, and create a get_objects_by_class function:
Each object has a .class attribute which holds a reference to its class, and then we use that as keys in new tables which will hold instances of that class. Like with the .by_id table, this is just another way of holding objects for easy retrieval because often times you want to get all objects of a certain class to do some operation.
Finally, the last thing I'll focus on in this post is retrieving objects by location. Often times we want to retrieve objects in a certain area, and one very useful setup is dividing objects by buckets and then querying those buckets. This speeds up queries because instead of going through all objects and doing intersection calculations on all of them, we go through all buckets instead and only add the objects that are within buckets that collide with our area. This is a very simple exchange of memory for speed, which is also what we're doing with the .by_id and .by_class attributes.
First, we'll create the table that represents these buckets:
Here .cells will hold buckets, and .cell_size represents the size (width and height) of each bucket. So this means that the entire world will get divided into chunks of 128x128 units and all objects will be inside their own respective buckets. This recalculation will happen every frame, which seems wasteful but it's less wasteful than the alternative of looping through all objects for all objects that need to query for nearby objects every frame.
So here we're creating a new .cells table every frame then going through every object and adding it to its respective bucket by dividing its position by .cell_size. As an example of one function that might query this .cells table:
This function gets all objects inside the specified rectangle. Instead of going through all objects we go through nearby bucket indexes (defined by cx1, cy1 top-left and cx2, cy2 bottom-right boundaries) instead, and then through the objects inside each bucket, checking to see if the object is colliding with the area we're querying. Also worth noting that this assumes all objects have .w and .h attributes.
This provides a pretty good base for managing objects in our games in a way that's fairly reusable but also simple. I intend on writing more articles exploring more aspects of game programming and in them I will expand on this Group class as necessary, but for now this covers the basics!
The text was updated successfully, but these errors were encountered:
In this post I will detail the way I currently handle object management for my games. The main idea for how I handle this is loosely based on the concept of spaces detailed in this video:
The main concept is a Group, which is equivalent to a Space. This Group object will simply have a list of objects and then update and draw them when those functions are called:
This assumes each object has update and draw functions defined. Additionally, there needs to be a way of automatically removing objects that are dead. The way I do this is by checking for a .dead attribute:
Finally, we also need a way to add objects to the group:
We also set the object's .group attribute to point to the current group because a lot of the times it's useful to be able to refer from the object's code to the group it belongs to (i.e. when you need to grab nearby objects, for instance).
This simple setup can get you very very far. At a level higher than this Group object we need somewhere to instantiate it. The way I do it by instantiating it in my State objects (similar conceptually to FlxState), which are just normal objects used to hold groups and other objects and to coordinate between them. MainMenu or OptionsMenu or Arena would be examples of these kinds of states. In the picture below is an example with the constructor for a MainMenu class:
Here a group is created for UI objects and they're added to it. Then the update and draw functions (not shown) call the group's update and draw functions as well. I will leave the details of these State-like objects for another article, I just wanted to give you some context of how these groups are used.
For now I'll focus on explaining some more useful functionalities to add to our Group class. The first thing is being able to get objects by their unique identifier. First, objects need to be stored somewhere by their .id attribute, and we'll do this in objects.by_id, where the keys will be ids and the values will be references to objects:
This assumes each object has an unique .id attribute. Now if we have an object's id we can easily get it by just accessing the objects.by_id table:
Finally, we also need to take care that when we remove an object due to its .dead attribute, we also remove it from the .by_id table, since if we have multiple references of the same object but we forget to remove some of them, the object will never get garbage collected, which will lead to memory leaks in your game.
Another improvement we can make to Groups is being able to get objects by their class. This might not be as easily achievable in languages other than Lua, but this is how I do it. Similarly to how we did for identifiers, we also create an additional .by_class table, add objects to it, remove objects from it, and create a get_objects_by_class function:
Each object has a .class attribute which holds a reference to its class, and then we use that as keys in new tables which will hold instances of that class. Like with the .by_id table, this is just another way of holding objects for easy retrieval because often times you want to get all objects of a certain class to do some operation.
Finally, the last thing I'll focus on in this post is retrieving objects by location. Often times we want to retrieve objects in a certain area, and one very useful setup is dividing objects by buckets and then querying those buckets. This speeds up queries because instead of going through all objects and doing intersection calculations on all of them, we go through all buckets instead and only add the objects that are within buckets that collide with our area. This is a very simple exchange of memory for speed, which is also what we're doing with the .by_id and .by_class attributes.
First, we'll create the table that represents these buckets:
Here .cells will hold buckets, and .cell_size represents the size (width and height) of each bucket. So this means that the entire world will get divided into chunks of 128x128 units and all objects will be inside their own respective buckets. This recalculation will happen every frame, which seems wasteful but it's less wasteful than the alternative of looping through all objects for all objects that need to query for nearby objects every frame.
So here we're creating a new .cells table every frame then going through every object and adding it to its respective bucket by dividing its position by .cell_size. As an example of one function that might query this .cells table:
This function gets all objects inside the specified rectangle. Instead of going through all objects we go through nearby bucket indexes (defined by cx1, cy1 top-left and cx2, cy2 bottom-right boundaries) instead, and then through the objects inside each bucket, checking to see if the object is colliding with the area we're querying. Also worth noting that this assumes all objects have .w and .h attributes.
This provides a pretty good base for managing objects in our games in a way that's fairly reusable but also simple. I intend on writing more articles exploring more aspects of game programming and in them I will expand on this Group class as necessary, but for now this covers the basics!
The text was updated successfully, but these errors were encountered: