I'm working on a top down 2D shooter in Python with Pygame. Currently, I have different lists for different types of gameobjects. One for enemies, one for items, one for impassable objects, etc... Is this a good approach? Should all gameobjects be stored in the same data structure? I know there may not be a "best" approach, but would like some insight on this topic. Side note: I'm not using any spatial partitioning scheme currently for collision detection, but I may want to implement this in the future.
Should all gameobjects be stored in the same data structure?
In fact, having separate containers for distinct types is often good for efficiency (if you're not just storing a container of pointers) and readability. Most "classic" games had this kind of structure.
Moving objects into a singular data structure is most useful if you're trying to abstract out the concept of an object. There are various reasons you might do this. If you don't have any use cases for this, though, there's no reason to think that it's going to magically make your code better.
A variation of what you have would be to have a singular structure that contains the core common representation of all your objects, but to then also put the specific into their own structures. That combines well with component architectures.
Sean Middleditch – Game Systems Engineer – Join my team!
Only if the objects are similar in data and go through similar logic paths in the code. The less similar they are (in data and usage), the less likely you want them in the same structure.
So basically, it'd vary from game to game. :wink:
Care to post the structs/classes for the three kinds of objects? Do they share a base class? Do they share some variables in common (e.g. collision rects)? Do they share some code in common (e.g. collision testing)?
How much do they have in common? Sometimes you want:
ProccessCollisions(&player, allGameObjects);
Othertimes this is better:
ProccessCollisions(&player, allEnemies, &Enemy::collisionRect, [](Enemy &enemyCollidedWith) { ... });
ProccessCollisions(&player, allWalls, &Wall::collisionRect, [](Wall &wallCollidedWith) { ... });
ProccessCollisions(&player, allItems, &Item::collisionRect, [](Item &itemCollidedWith) { ... });
^ templated func pointer-to-member-var ^ ^ lambda callback
Depending on how much varies or is similar, affects whether you should parameterize your data and logic, or treat them as identical.
A good indication that you should probably not lump everything into a single structure is when you retrieve an object. If find you need to determine the located objects type so you can cast it to allow access member functions only it has. Ugly, extra overhead and brittle :)
There's nothing stopping you from keeping one object in multiple lists (except the complexity of keeping them in sync, of course :P).
So if all your game objects share the same collision functionality (defined by attributes on them like mass/shape, rather than by their concrete type), then you could dump (pointers to) them in a data structure that is used for handing collisions. At the same time you can keep your strongly-typed lists for handling specific functionality.
Cascade Quest - IceFall Games - Dev blog - twitter - youtube
I'm not using any spatial partitioning scheme currently for collision detection, but I may want to implement this in the future.
Depends on the game. Iterating through a linear collection is an extremely fast operation, and tends to be cache friendly. Jumping around objects that are blocked off by spatial positions is not cache friendly.
Spatial partitions can be useful for some things, such as "find me any objects within 3 spaces of here". But until you have many thousand objects you won't see any performance boost over a direct data traversal. Once you do reach that point, you'll want your spatial tree to only reference the other objects rather than be their primary container, so you only need to hop around in memory infrequently.
Spatial partitions can be useful for some things, such as "find me any objects within 3 spaces of here". But until you have many thousand objects you won't see any performance boost over a direct data traversal.
I'll probably only ever have 100 or less collide-able items on screen at once, so I guess it won't be worth.
Would it be appropriate to define some gameobject features based on what list it is in? What I mean is that all objects in my blockers list block movement, so I wouldn't need to add a component/property to the gameobject to indicate it is a blocker, it just needs to be in the appropriate list. Is this a valid approach?
Would it be appropriate to define some gameobject features based on what list it is in? Is this a valid approach?
It can be, depending on your game.
You mentioned lists for enemies, another for game items, another for immovable items. This lets you search whatever collections you are interested in. If you're interested in the map, search the immovable map items and the movable map items. If you are interested in enemies on the map, you can search that. If you are interested in all of them, search all of them.
Given that you already have that arrangement of multiple lists, it seems an alternate approach would be a selective search function. You might have a method that searches both immovable and movable map points. You might have another method, or a different parameter set, that lets you find all visible/discovered items. You might have another method that lets you find both friendly and enemy units.
Assuming you are adhering to SOLID principles, with dependency inversion creating some common interfaces for all the different types of things you need an it for should not be too difficult. As long as your items are true substitutes for each other and you don't waste time finding the actual concrete type, then adding a value that shows what kind of thing it is would be fine. If they're not true substitutes for each other then they probably shouldn't be merged into a single collection.
Would it be appropriate to define some gameobject features based on what list it is in? What I mean is that all objects in my blockers list block movement, so I wouldn't need to add a component/property to the gameobject to indicate it is a blocker, it just needs to be in the appropriate list. Is this a valid approach?
Simply put, anything that works is a valid approach. "valid" mostly means "works in my case".
I think your idea could work, so yeah, it would be valid.
As you may realize, the above definition means pretty much anything is valid. That's good in you don't get much boundaries, and bad in you don't get much boundaries.
The flexibility there makes you can make anything you want, and even quite a few things you are not aware of that can be done too (that holds for all of us, even experienced programmers). On the other hand, lack of boundaries make that you don't get much guidance on what is useful and what is less useful.
You will develop (or in fact, are already developing) your own guides here, by noticing alternative ideas, trying them in a program, asking about them, discarding or embracing them as you find them useful or not. In time, you'll see possible solutions to problems with their bad and good sides, and be able to decide which works best for the job at hand.
To get back to your question, yes it is a valid approach. If you need to distinguish a blocker object from a non-blocker object, putting blockers in a special list is one way to do that. The consequence of that solution is that the code looking for the object must know it's accessing the blockers-list. If that's the case, it is fine (valid :) ).
If you organize your code differently, and eg make a function "give me something at these coordinates", and that function then gives you an object back, you don't know where the function found that object, ie you'd loose the information whether it was found in the blockers-list. In such a case, if you still need to know it's a blocker object, you need another way to identify the object as such. Either the function that gave you the object could say that, or you could get that information from the object itself (which then leads to your idea to add a property "I am a blocker object" to the object). So typically, adding such information to an object itself is useful when you don't know what some code gave you, and you need to "re-invent" that information.
There is a 3rd solution, that you may want to consider (if you have already read about classes and methods).
In both your solutions above, the code that gets the object knows or finds out what type of object it is (by looking in the right list, or by asking the object itself, or so). Then it acts on that information (something along the lines "Hmm, this blocker object is in my path, I can't move further").
The 3rd solution is to ask the object not for its type, but ask it "may I move into you?" with a method of the object. The object then responds with "yes" or "no", for example (boolean True or False). Blocker objects will always respond with "no", while other objects may respond with "yes".
In this setup, you don't even care what type of object you have. You just ask the question, and then act on its answer. The consequence of this solution is however, you must be able to ask that question to any bock you may encounter.
I'll probably only ever have 100 or less collide-able items on screen at once, so I guess it won't be worth.Spatial partitions can be useful for some things, such as "find me any objects within 3 spaces of here". But until you have many thousand objects you won't see any performance boost over a direct data traversal.
Definitely not. "thousands" is a conservative estimate of how many you need before special algorithms start giving enough of a performance benefit to be worth the extra code complexity.
Would it be appropriate to define some gameobject features based on what list it is in? What I mean is that all objects in my blockers list block movement, so I wouldn't need to add a component/property to the gameobject to indicate it is a blocker, it just needs to be in the appropriate list. Is this a valid approach?
Absolutely. The container itself provides meaning to the elements it contains, and that meaning can definitely be used to run one branch of logic instead of another branch.
Objects that contain other objects (whether as member variables or as elements of a container, or as children or parents in a parent-child relationship) provide context and meaning to the objects they contain or reference. It is absolutely 100% valid and commonplace to use the concept of a variable's owner to choose what logic to run.