Temple of The Roguelike Forums
Development => Programming => Topic started by: joeclark77 on March 30, 2013, 02:25:23 AM
-
I'm trying to be a good object-oriented programmer, but I'm self taught and maybe some of you with more book-learnin' have a better grasp of the principles. One thing that has me stumped is this question: is it OK, I mean according to good practice, to have an object contain a reference to the thing that holds it? It seems like that would violate some key design principle, but I'm not sure how to do it better.
Here's the specific case:
I have a map object that keeps track of all the Critters (monsters/npcs) and Things (inanimate objects) by their locations.
Critter objects each have an inventory (a python list containing references to Thing objects they're carrying).
This is what I think I'm doing wrong:
Critter objects also have references back to the map that contains them (and to the location that is their index).
Each Thing object has a reference either to the map and location, or to the critter (if carried).
But sometimes you want to look at the map and see what is present in a given location; other times you want to start from the npc or object and find out where it is located. What would be the proper object oriented way to do this? I know that this is a pedantic and unnecessary question, but learning a thing or two about programming is indeed why I'm making a roguelike, so, I'm asking anyway!
-
No, I think the question is a good one. Here's my take on it.
It's best to minimize how spread out your data is across your code, and keep things general. So for example, I would not have references pointing backward (a Critter knowing which map it's in), only forward. A map has a list of levels, a level has a list of things, each thing has a list of their things, etcetera. You can see that this is very general -- everything is a thing, and they can contain other things.
Then for the cases where you want to look at the map and see what's where, or look at a thing and see where it is, I would write the appropriate methods/functions.
It's true that if you want to ask a thing where it is, you need to do a lookup in a more roundabout way than simply querying a property on the thing (though the complexity is well hidden by the method/function anyway). But I would argue that most of the time this will not be a performance critical part of your code. If it is, you can always optimize. Ultimately I think this approach has benefits for OO concepts like reusability, encapsulation and minimizing inter-dependencies.
-
I sometimes do that, but it ends up seeming very hacky. It's probably better just to run a check for when you need to know what contains the object rather than updating object.
I've used it with bullet properties. The bullet has properties based on who shot it, and you also need to know who shot it for the message system. "Yer shot by the Pirate" or "The Captain shoots you." With multiple shooters you start needing a reference variable attached to the bullet. Of course this would have been alleviated by not even making a bullet, and simulating the hit, but I actually checked line of sight and trajectory by running the projectile through every intervening square...anyway...:-)
Maybe that's not similar, maybe it is, what I ended up doing was doing referencing back up the chain.
-
For my 7dRL I tried having the map have an array of map cells and also a list of creatures on the map. Each creature also knows it's own location, but only as a point not in reference to the actual map. This allows me to easy run through the list of creatures and do distance calculations without referring to the map cells and then find the cell with the creature I'm working with without having to iterate the map.
Note that Map has ArrayList<Creature> and MapCell[][], MapCell has Creature, and Creature has Point. No circular dependance, no OO principle breaking. The only thing to watch out for is to update the creature's Point when you move it to a new map cell, which is no big deal.
-
I think the array of map cells can be thought as a container of objects, but I think it's not useful in that task, because you need to iterate through the entire map to search for objects. I guess it's better use a list for objects owned by level class and then use map array as some kind of temporary "copy" of object handles (pointers). You can then access objects from that handle and even use map in things like collision checking, but still retain the ownership of object in the level class. I think the ownership is the important issue, because it's pedantic if only one host can own the object and take care of its creation and destruction.
-
I'm using Python dictionaries (other languages call them "hashes") to locate things on my map. There are three so far:
terrains = [an array containing a reference to one terraintype per map tile]
things = a dictionary, indexed by tile number, pointing to a list (stack) of items at that tile
critters = as 'things' but for critters
A dictionary is good for a "sparse array". For example, if my map has 1000 tiles, the list of terrains will be 1000 items long. But if there's only one "critter" (i.e the player) and he's at tile 345, the 'critters' dictionary will look like this:
{345:<Critter object>}
So you don't have to iterate over 1000 map cells to figure out what your critters are doing.
I'm also going to make use of "tags" for things, critters, etc. So a torch or lantern might have the tag [illuminating]. There will also be a dictionary indexed by tag, each entry containing pointers to the objects with that tag. So if I want to run a function on my light sources, I don't have to iterate between the thousands of inanimate objects in the game, just iterate through the [illuminating] set.
The trick is, if I get a pointer to a torch in this way, how do I then get its location (or its carrier's location) so I can implement its effect? Iterating around all map cells and all critters would eliminate the efficiency gains. Maybe my hacky way of having the objects point back to their containers is actually going to be the least problematic.
By the way, I'm trying to do a real-time simulation game (kinda like dwarf fortress but not) which is part of why I'm pushing for efficiency.
-
I guess I would just ask you this: do you know that iterating over a 1000 objects vs. 2 is a real efficiency gain?
Why not mock up a list of 1000 objects and a list of two and then time the iteration?
That said, I don't think many games would iterate over every map cell to deal with a thing. On the other hand, most games would not subdivide their things into many data structures either.
-
I guess I would just ask you this: do you know that iterating over a 1000 objects vs. 2 is a real efficiency gain?
Why not mock up a list of 1000 objects and a list of two and then time the iteration?
That said, I don't think many games would iterate over every map cell to deal with a thing. On the other hand, most games would not subdivide their things into many data structures either.
No, I don't know if it's a huge difference. I'm only deducing it. Hard to test, at this point, because my "things" don't really do anything yet. But, if I'm going to have a huge map with thousands of things, where most are just sitting around but a few of them are performing actions, I don't want to try and iterate (30 times per second?) over the thousands of things and test each one's thing-type before running a function.
I *have* observed a similar finding in my code that draws the map to screen. The initial screen-draw requires iterating over every tile and rendering it as a sprite. Subsequent draws grab a list of "changed tile" indexes and re-render only those tiles (rather than iterating over every tile and re-rendering or checking whether it needs to be re-rendered). This is fantastically faster.
-
The trick is, if I get a pointer to a torch in this way, how do I then get its location (or its carrier's location) so I can implement its effect?
The item or monster should have a location stored in it. I think you are doing things over-complicated way. Let's say a monster is carrying a lit torch. The torch location doesn't have to be updated, because the monster is carrying it. The torch needs only know it's lit. When you draw the monster you check if it has a torch in its light slot (faster than looking through the inventory) and then update the light map from monster's location. I think it's easiest to model things like they are: when monster picks up an item it's stored in monster's inventory/tool slot and the monster becomes the owner of the item. The item doesn't have to be anywhere else or referenced in any other place.
-
I do:
Every tile on the map has a reference to the map, a reference to the creature inhabiting the tile and a reference to the item on the floor of the tile. Each creature references the tile it is on. You can get the map a creature is on through creature->tile->map.
So everything is accessible from everything directly. The only danger is things falling out of sync, but as long as you have methods like creature.moveto(tile) which guarantee everything is fixed and only go through those methods everything is fine.
-
Object-oriented design and video games aren't really the most logical fit in a conceptual sense. Strong type systems and inflexible hierarchies tend to get in the way of your ability to express what you want. You find yourself going back and fixing objects that result in cascading changes in how everything is done. This contradicts the idea of what a video game really is-- Nested tables of data, a collection of looping systems, and a UI.
There are bigger questions here though if you go the OO route. I always got buggered out because I could never really decide where methods should be and what objects should have access to what data. For ease of implementation, letting everything reference everything is a good way to get OO-principles from blocking your progress. This is bad OO, but I'm starting to hate OO to the point of trolling anyone who mentions it.
But yea- you want to be able to access WorldInfo from every actor in that world. All Actors (including wall tiles) should inherit from an actor base class with events like onBumped, onTick, and other junk that everyone overwrites so that everything can communicate with each other in a hunky-dory way. That's how it's done in industry.
Edit: To add- abstractly, all Actors can have a location and an owning Actor. GetLocation() would just return the location of the Owner, if it has one, otherwise it can return it's own location (which would should be set whenever the player drops/throws the item). Alternatively- the Owning Actor can have a set of ownedObjects. Each time the Actor moves, you can iterate through each object and update the position of those objects.
Are you programming in Python? Python has a lot of slick solutions to do this- though Lua, IMO, is infinitely more well-suited... though I wouldn't use traditional hierarchical OOP at all in either case.
-
Having an object contain a pointer to it's parent object is pretty common (especially in GUI programming). I actually don't use it a lot in roguelikes because most of the time I'm going in the opposite direction, from parent to child, and don't need to know (or I already know) who the parent is. The only thing that it adds is that you have to make sure to keep the parent of each object up to date, but that's pretty simple. If it works for you, use it.
I don't really buy into the concept anymore that anything in programming is better or worse than anything else. If it works, and it works well, and it isn't slow, then it's good. I'm sure everyone else disagrees with that, but it works well for me. The difference between theory and practice with programming is that practice is all about implementing something that works as quickly as possible (or in as small an amount of time as possible). It's much more important that something is implemented and works than that it is implemented "perfectly" or "correctly". The user doesn't know the difference between the two. All the user sees is whether or not something is implemented at all.
If you spend too much time trying to figure out the "perfect" way to do something you're likely to end up over engineering the whole thing, and spending way more time on the problem than it really needed. Nothing needs to be perfect, it just needs to work. In the professional programing world no one talks about perfect solutions, they talk about deadlines and the amount of time it takes to implement things.
-
I don't really buy into the concept anymore that anything in programming is better or worse than anything else. If it works, and it works well, and it isn't slow, then it's good. I'm sure everyone else disagrees with that, but it works well for me. The difference between theory and practice with programming is that practice is all about implementing something that works as quickly as possible (or in as small an amount of time as possible). It's much more important that something is implemented and works than that it is implemented "perfectly" or "correctly". The user doesn't know the difference between the two. All the user sees is whether or not something is implemented at all.
I'm not disagreeing, but of course a sloppy yet fast implementation today can easily slow you down later on ;)
-
I don't really buy into the concept anymore that anything in programming is better or worse than anything else. If it works, and it works well, and it isn't slow, then it's good. I'm sure everyone else disagrees with that, but it works well for me. The difference between theory and practice with programming is that practice is all about implementing something that works as quickly as possible (or in as small an amount of time as possible). It's much more important that something is implemented and works than that it is implemented "perfectly" or "correctly". The user doesn't know the difference between the two. All the user sees is whether or not something is implemented at all.
I'm not disagreeing, but of course a sloppy yet fast implementation today can easily slow you down later on ;)
That is definitely true for OOP, not necessarily true for other paradigms.
-
It's much more important that something is implemented and works than that it is implemented "perfectly" or "correctly".
People who say that don't get the idea. The principles of OOP were not invented because they are "correct". During the years I have learned to use some of the OOP techniques while I feel there is less need for some others. What I see frequently are programmers who use everything without any good reason. In C++ it's usually overusing templates and inheritance. It's important to really understand OOP and how you can use it in a practical way. I think the difficulty of OOP is one of the reasons why strict OOP languages are not that good in programming real programs and why C++ is so popular, because it's multi-paradigm and you can break strict rules which is sometimes better and faster solution.
-
It's much more important that something is implemented and works than that it is implemented "perfectly" or "correctly".
People who say that don't get the idea. The principles of OOP were not invented because they are "correct". During the years I have learned to use some of the OOP techniques while I feel there is less need for some others. What I see frequently are programmers who use everything without any good reason. In C++ it's usually overusing templates and inheritance. It's important to really understand OOP and how you can use it in a practical way. I think the difficulty of OOP is one of the reasons why strict OOP languages are not that good in programming real programs and why C++ is so popular, because it's multi-paradigm and you can break strict rules which is sometimes better and faster solution.
The only problem with OOP is that you cannot necessarily predict the scope of your program and all of it's use-cases without some degree of software engineering. For games, that's not simple without a clear design goals and game rules, which is hard for even AAA firms. What you say about C++ is true when it comes to breaking OOP when you need to, but it's equally obtuse and obstructive when you start talking about its utterly retarded type system and all of the "features."
Lua embedded in C will do everything that C++ does without obstruction and without compromise to speed. Algorithms in C, Data management in Lua-- it's really beautiful.
This hilarious little tidbit from Linus Torvalds on C++ is a worthwhile read.
http://thread.gmane.org/gmane.comp.version-control.git/57643/focus=57918
-
Lua embedded in C will do everything that C++ does without obstruction and without compromise to speed. Algorithms in C, Data management in Lua-- it's really beautiful.
Uh oh.. I don't like the way this is going. Like I said, you need to forget your prejudice about C++/OOP. Only then you can understand why it is better than C.
This hilarious little tidbit from Linus Torvalds on C++ is a worthwhile read.
http://thread.gmane.org/gmane.comp.version-control.git/57643/focus=57918
Linus is a wrong person to talk about programming. He is just a lucky guy who invented Linux, but didn't really do any of the hard work that made Linux what it is today. Now he is a lazy millionaire and speaking all sorts of rubbish about programming, Windows etc. I wouldn't listen to that guy.
-
Lua embedded in C will do everything that C++ does without obstruction and without compromise to speed. Algorithms in C, Data management in Lua-- it's really beautiful.
Uh oh.. I don't like the way this is going. Like I said, you need to forget your prejudice about C++/OOP. Only then you can understand why it is better than C.
This hilarious little tidbit from Linus Torvalds on C++ is a worthwhile read.
http://thread.gmane.org/gmane.comp.version-control.git/57643/focus=57918
Linus is a wrong person to talk about programming. He is just a lucky guy who invented Linux, but didn't really do any of the hard work that made Linux what it is today. Now he is a lazy millionaire and speaking all sorts of rubbish about programming, Windows etc. I wouldn't listen to that guy.
It's more useful, but we can't qualitatively say better.
Linus is just funny ^_^.
-
Re: Critters referencing which level/map they belong to: I ended up doing exactly this in Squirm (http://code.google.com/p/squirm/), leading to really horrible memory leaks that I discovered years later, and which made me just give up on the game completely. Related, (maybe you already know this, but it can't be said enough, especially about languages with garbage collection, I think): Try not to directly reference instances like items, critters, level layouts, more than you have to. One way that's certainly better, is to keep eg. a dictionary for your critters, where every being can be looked up with a key. That way, if you remove a critter from that dictionary, you can be sure there remain no references to it hidden behind the rose-pot.
Re: Linus. I'm a crusty Linux fanatic myself, but have to agree with Krice on this one ;). That guy just acts like an utter clown so often, even if I'm sure his accomplishments have demanded a lot of talent, ingenuity and hard work.
As always,
Minotauros
-
It's more useful, but we can't qualitatively say better.
It is better:
http://www.stroustrup.com/bs_faq.html#difference
-
Ha! Come on you guys. Which language is better? Which style? Object orientated or nay?
The discussion becomes too abstract and laughable.
-
The discussion becomes too abstract and laughable.
What is laughable is the way a personal view is more important than logical and analytic approach to programming.
-
The discussion becomes too abstract and laughable.
What is laughable is the way a personal view is more important than logical and analytic approach to programming.
Strousstrop arguing that the language he designed is better than the language it was designed with is an amusing way to suggest that it is objectively better.
You can achieve all the power with significantly more elegance with a combination of C and Lua- Lua being programmed in C is a subset of C so... it's really all just a varied expression of C. The fact that C++ attempts to be a super-set of C automatically works against the idea of compatibility, which results in new specifications to address every issue that results from this. If Strouss just created a new language altogether, without trying to build an object oriented paradigm on top of C, he would have something much more elegant and beautiful.
I've recently become incredibly interested in Language design and started a blog discussing a thing or two. http://requerent.blogspot.com/. Although I don't talk about the C vs C++ argument, I do cover some other general issues that we all experience with most programming languages today.
-
You can achieve all the power with significantly more elegance with a combination of C and Lua
I think this discussion is over. Grow up and learn more about programming. That's all I can say.
-
I appreciate all the feedback, guys!
I think the take-away here is that programming a roguelike is different than normal programming if, like me, you're trying to learn something while you do it. This game I'm writing in Python. The next one I might try doing in Java. And I might try using different libraries to do the same things. I know that striving for "perfect" OOP shouldn't be an obstacle to a working game, but I'm glad to have had the chance to think about it with you all anyway. (Now I can put that on my resume...)
-
You can achieve all the power with significantly more elegance with a combination of C and Lua
I think this discussion is over. Grow up and learn more about programming. That's all I can say.
Or I just won't reply with an ad hominem.
-
I appreciate all the feedback, guys!
I think the take-away here is that programming a roguelike is different than normal programming if, like me, you're trying to learn something while you do it. This game I'm writing in Python. The next one I might try doing in Java. And I might try using different libraries to do the same things. I know that striving for "perfect" OOP shouldn't be an obstacle to a working game, but I'm glad to have had the chance to think about it with you all anyway. (Now I can put that on my resume...)
If you want to build up a game making resume having your own FINISHED projects on it is great. I was reading an article the other day about it. It's hard to break into the game industry without a solid resume, and starting your own projects early and often is the key to building that resume. One of the keys, at least.
Here is that article, if you're interested:
http://codesuppository.blogspot.ca/2013/04/so-your-teenager-tells-you-they-want-to.html
-
You can achieve all the power with significantly more elegance with a combination of C and Lua- Lua being programmed in C is a subset of C so... it's really all just a varied expression of C.
By that logic, we should all be programming in assembly, because C is merely a subset of assembly... ::)
-
You can achieve all the power with significantly more elegance with a combination of C and Lua- Lua being programmed in C is a subset of C so... it's really all just a varied expression of C.
By that logic, we should all be programming in assembly, because C is merely a subset of assembly... ::)
Pathetic n00bs, all! Real programmers write the 0s and 1s directly.
As always,
Minotauros
-
You can achieve all the power with significantly more elegance with a combination of C and Lua- Lua being programmed in C is a subset of C so... it's really all just a varied expression of C.
By that logic, we should all be programming in assembly, because C is merely a subset of assembly... ::)
By what logic? OR a lack of reading skills? I hope it's the latter, as inferences like that would make life very difficult.
-
Pathetic n00bs, all! Real programmers write the 0s and 1s directly.
As always,
Minotauros
No, no, real programmers program with copper, silicon, and a soldering iron.
-
Tubes. I use tubes.
-
No, I think the question is a good one. Here's my take on it.
It's best to minimize how spread out your data is across your code, and keep things general. So for example, I would not have references pointing backward (a Critter knowing which map it's in), only forward. A map has a list of levels, a level has a list of things, each thing has a list of their things, etcetera. You can see that this is very general -- everything is a thing, and they can contain other things.
Then for the cases where you want to look at the map and see what's where, or look at a thing and see where it is, I would write the appropriate methods/functions.
It's true that if you want to ask a thing where it is, you need to do a lookup in a more roundabout way than simply querying a property on the thing (though the complexity is well hidden by the method/function anyway). But I would argue that most of the time this will not be a performance critical part of your code. If it is, you can always optimize. Ultimately I think this approach has benefits for OO concepts like reusability, encapsulation and minimizing inter-dependencies.
Hmm. I'm curious: if the critter object doesn't know its map, then what do you do with something as basic as this: movement. Suppose your "Critter" object has a "move()" method, which, well, makes the critter move. Seems reasonable, no? But then you have to check for walls, etc. (you don't want your critter to move through the walls (unless it's a ghost or something)!) which means you need access to the map. Plus, if the map contains a reference to the critter attached to the tile it's standing on, that needs updating. So what should one do? What should that call to "move()" do?
Also, where should the "critters" be stored, anyway? Currently, in my program I have them "owned" by the map, since they're on the map and most would be associated with a map, anyway (when you save out a map, you have to save out all critters on it). How should it be?
-
Hmm. I'm curious: if the critter object doesn't know its map, then what do you do with something as basic as this: movement. Suppose your "Critter" object has a "move()" method, which, well, makes the critter move. Seems reasonable, no? But then you have to check for walls, etc. (you don't want your critter to move through the walls (unless it's a ghost or something)!) which means you need access to the map. Plus, if the map contains a reference to the critter attached to the tile it's standing on, that needs updating. So what should one do? What should that call to "move()" do?
It just updates X and Y of the critter, I suppose. To actually move the critter on the map, you'd have to call something like map.MoveCritter (which would, in turn, call critter.Move). Makes sense to me.
-
Hmm. I'm curious: if the critter object doesn't know its map, then what do you do with something as basic as this: movement. Suppose your "Critter" object has a "move()" method, which, well, makes the critter move. Seems reasonable, no? But then you have to check for walls, etc. (you don't want your critter to move through the walls (unless it's a ghost or something)!) which means you need access to the map. Plus, if the map contains a reference to the critter attached to the tile it's standing on, that needs updating. So what should one do? What should that call to "move()" do?
...
In that particular design the move() method should be in the map rather than the critter because the map is the only object with the required context to actually perform the operation. The only way the critter could really know how to move is by accessing the map anyway (by a cached map reference in the critter or the map being passed as an argument to an update() method).
This doesn't occur to a lot of people because standard teaching of OOP is just hideously bad. The focus on the intuitive real world concept of objects just leads to tears in the end. Intuitively it feels like the move() method should be on the critter because critters move but that's not how you should decide where methods live.
Because a critter can't stand in the same cell as other critters you need more contextual information about how to perform the action (i.e. where the other critters are). Also a move can fail for reasons beyond a critter being in the way, e.g. lava, walls. The only place where all the data is at hand is the map object, so move() should go there.
-
Hmm. I'm curious: if the critter object doesn't know its map, then what do you do with something as basic as this: movement. Suppose your "Critter" object has a "move()" method, which, well, makes the critter move. Seems reasonable, no? But then you have to check for walls, etc. (you don't want your critter to move through the walls (unless it's a ghost or something)!) which means you need access to the map. Plus, if the map contains a reference to the critter attached to the tile it's standing on, that needs updating. So what should one do? What should that call to "move()" do?
...
In that particular design the move() method should be in the map rather than the critter because the map is the only object with the required context to actually perform the operation. The only way the critter could really know how to move is by accessing the map anyway (by a cached map reference in the critter or the map being passed as an argument to an update() method).
This doesn't occur to a lot of people because standard teaching of OOP is just hideously bad. The focus on the intuitive real world concept of objects just leads to tears in the end. Intuitively it feels like the move() method should be on the critter because critters move but that's not how you should decide where methods live.
Because a critter can't stand in the same cell as other critters you need more contextual information about how to perform the action (i.e. where the other critters are). Also a move can fail for reasons beyond a critter being in the way, e.g. lava, walls. The only place where all the data is at hand is the map object, so move() should go there.
So what's a better intuition to use, then?
-
The only place where all the data is at hand is the map object, so move() should go there.
Have you actually implemented movement like that? It doesn't sound logical. Game objects move, not the map. Objects move on the map and it ok to get data from the map to objects so they can see what kind of tile is there etc.
-
So what's a better intuition to use, then?
All other things being equal you put the method where the relevant objects are in scope. So in the running example of moving a critter, the relevant objects are the other critters and the cells you could be moving over or through. The place they are owned is the map so put the method there.
Like all coding guidelines there's pros and cons. One of the cons is that you can end up with very fat classes with lots of methods and variables if you aren't careful.
Have you actually implemented movement like that? It doesn't sound logical. Game objects move, not the map. Objects move on the map and it ok to get data from the map to objects so they can see what kind of tile is there etc.
Yes, ever since I was taught this and many other things by a grey beard programmer many moons ago I've done this kind of thing on several projects. It does seem to make the code more robust but it does cause some gasps of disbelief from other programmers. I've also been lazy and not followed this guideline and I end up regretting on but small and trivial programs.
I like to think the reasons I've given for putting the method in the map are pretty logical. In fact I would say it's more logical than the natural language noun and verb intuitions commonly used. If you've ever looked into relational database normalisation the ideas have some overlap.
A move() method on the map doesn't mean the map is moving but that the map is moving things (which it owns). Also because moves can conceivably fail (wall or critter in the way) it's more of a request than an imperative command. So a more descriptive (but less pretty) method signature would be tryMoveCritter(critter, x, y) : bool.
-
I eventually settled on that paradigm (map has the move() method) for my current game, and regret not using it before.
-
My two pennies:
Objects are worthless in isolation, while it is good to avoid spurious uplinks to containing data structures, an entity (creature, npc, etc...) is almost just a lump of data unless it interacts with the map, items, other creatures, whatever.
Don't be afraid to put uplinks to containing objects. If your items need to know what race of creature is wielding them, there are many fancy design ways to figure it out, but if(_holder->race == RACE_ELF) is probably the easiest and least error prone.
I have entities with move as a member function, and it basically delegates to the map to do the physical moving, but then the entity can override the virtual move function to do something before or after the move, while if you have a monolithic move function for the map, handling pre-post ops on a type by type basis gets nuts.
BTW, C and Lua is a great combo. My engine is C++ and Lua, and I'm thrilled about it.
PS> There is no 'right' way, only wrong ways, and tradeoffs.
-
If you ask me, objects should control themselves and their own variables. Every time I've ever implemented a function outside of an object that directly controls or affects an object I've always regretted it. That means move() inside the object, along with anything else that affects the object similarly being inside the object. Of course, it's a matter of taste, but it follows one of the most common principles of object oriented design.
Move especially makes sense for being inside an object because as Krice said, objects move, the map does not. Further, let's say you have a function in an object that wishes to move the object. If the object doesn't contain it's own move function, then it has to ask the map to move it. That's like asking the map to rename an object from a function inside the object being renamed... Once you start having functions like that, objects start to become C style structs which defeats the purpose of using objects in the first place.
I also disagree with the idea that there are wrong ways. Better ways, absolutely, but not wrong ways. It's only wrong if it doesn't work.
-
... It's only wrong if it doesn't work.
Agreed, I'm a pragmatist, but today at work, a bunch of people definitely did the WRONG DANG THING! It's my job to clean up the poopy code they wrote the WRONG DANG WAY!
Uuuhhhh.... *sigh of disgust* if you have to maintain code, then there is definitely a wrong way, but in my own codebase, I've been satisfied several times by terrible code held together by tape and staples.
-
I've used both the "monsters move themselves" and the "map moves everything" approaches. I prefer the map having control because it has the proper context to make decisions.
If this bothers you then consider the game to be a board game and the map is a player that has control over all of the enemy figures.
-
I have entities with move as a member function, and it basically delegates to the map to do the physical moving, but then the entity can override the virtual move function to do something before or after the move, while if you have a monolithic move function for the map, handling pre-post ops on a type by type basis gets nuts.
I don't think having 'move' owned by the map is wrong per se but I think you can make a stronger argument for something like eclectocrat's design here.
-
This is really a good reminder on how people can fuck up everything. Even simplest things. Then they call it the right way to do things.
-
I have entities with move as a member function, and it basically delegates to the map to do the physical moving, but then the entity can override the virtual move function to do something before or after the move, while if you have a monolithic move function for the map, handling pre-post ops on a type by type basis gets nuts.
I don't think having 'move' owned by the map is wrong per se but I think you can make a stronger argument for something like eclectocrat's design here.
The sky won't fall down whichever way you do it, unless the codebase or the game gets a lot more complicated.
First off why do you need lots of different pre and post steps for movement? I would be quite worried if that happened because it means you derive a new critter class just to get a new kind of movement. If your game does have a lot of very different forms of movement you should be abstracting that out of the critter anyway, e.g. into a hierarchy of movement objects. (I'm assuming that like in most games critters are not uniquely determined by their style of movement.)
If you don't factor it out and you have another method/behaviour on the critter that can vary, an attack() method say, then you could be for a right pain in arse. You only need 3 types of movement and 3 types of attack to lead to 9 different classes you could have to write. Of course you could make a faustian pact and use multiple inheritance but possibly reduce the amount of code you write at the cost of you sanity.
It would be easier to factor both movement and attacking out into their own objects or hierarchies (thereby turning a N*M problem into an N+M problem). This also has the nice side effect of being far easier to make data driven (so new critter types can be added without writing code).
The oft quoted slogan of 'favour composition over inheritance' essentially means that the ideal number of critter classes is one. It's also motivated by very similar arguments to those presented above.
So if you have a small numbers of methods/verbs you won't really feel the pain but I've worked on RTSs where the number of combinations got quite large and the 'critter move' solution writ large led to loads of issues. Once we changed to the compositional system (and put 'move in the map') for the subsequent game it all went a lot more smoothly.
-
A components system does the same work here as a virtual move method on the thing, the point is you specialize the behavior on the thing and not in the map's move function.
-
A components system does the same work here as a virtual move method on the thing, the point is you specialize the behavior on the thing and not in the map's move function.
No one's saying you should specialise in the map move method based on critter type. The point is that there's either only one move method on the critter or it would most likely be abstracted out and therefore not be 'in' the critter.
You're going to need a move method on the map anyway (unless the critter is allowed to access private state of the map) and the critters (or one of it components or otherwise associated objects) will end up having to call it.
-
I think using the word 'Map' is kind of naive. What you're talking about are game states.
- Critter polls game state to determine which of its behaviors is ideal.
- Critter informs game state of its behavior, game state provides available moves and heuristic weights.
- Critter tells state where it wants to move and state updates to a new state.
Doesn't matter where 'move' is located, so long as information is passed and evaluated accordingly.
I think this gets silly though. I prefer that neither my entities nor my states have any methods at all- they're really just tables of data that get worked on by different systems. A system is just a procedure that reads and updates the state based upon a specific subset of salient properties among entities. A system can look at the game state and the state of an entity and evaluate which state it should be in without any silly messaging schemes.
-
I think using the word 'Map' is kind of naive.
Have you looked at the source for many video games? Try Crawl, Nethack, Angband, Doom, Wolfenstein, or Quake some time.
-
I have entities with move as a member function, and it basically delegates to the map to do the physical moving, but then the entity can override the virtual move function to do something before or after the move, while if you have a monolithic move function for the map, handling pre-post ops on a type by type basis gets nuts.
I don't think having 'move' owned by the map is wrong per se but I think you can make a stronger argument for something like eclectocrat's design here.
The sky won't fall down whichever way you do it, unless the codebase or the game gets a lot more complicated.
First off why do you need lots of different pre and post steps for movement? ...
Sorry I kind of stopped taking it seriously there, questions such as these raise major red flags for me. This is why I never ask python questions on stack overflow. The whole "why would anyone ever want to...?" thing is just anathema to making positive progress, imho. No offence dude, the rest of your post contained good engineering points.
Slightly OT, I remember discussing boost::shared_ptr when it was being migrated to tr1 and they were not sure what to do with the thread safety/locking template parameter that came after the primary pointer type. The apparent problem was that the combinations of template parameters would create a combinatorial explosion of complexity that would make it very hard to assign pointer to each-other in safe ways. My question was why even force them to be compatible? Why not just say that any pointer can only be compatible with other pointers with the same thread-safety/locking policy? I never really got an answer to that. I usually like to avoid complexity, because the problem space will provide enough without you adding in heaps of your own.
Anyways, I agree with the sky not falling regardless of which method you ultimately use. In my experience, the surest way to have clean code is to review regularly and take the time to care after corner cases. If you do that, then either way is just fine... (unless you are writing nuclear reactor controllers, cmon this is a roguelike board!)
-
...
First off why do you need lots of different pre and post steps for movement? ...
Sorry I kind of stopped taking it seriously there, questions such as these raise major red flags for me. This is why I never ask python questions on stack overflow. The whole "why would anyone ever want to...?" thing is just anathema to making positive progress, imho. No offence dude, the rest of your post contained good engineering points.
Looking back over the post that sentence does seem more abrasive than I'd like in hindsight. It would be wrong to back and edit it but if I could I'd probably write something like "What kind of things are you doing in the pre and post operations?"
I am genuinely interested in the possible use-cases of lots of different pre and post operations to movement though. It's possible that it could be something not covered by the text succeeding the offending sentence.
-
I think using the word 'Map' is kind of naive.
Have you looked at the source for many video games? Try Crawl, Nethack, Angband, Doom, Wolfenstein, or Quake some time.
Old games used naive nomenclature. So what? Are we talking about topology, game states, the systems that manages the relationships between entities, or just an arrangement of entities? A monolithic 'Map' object for doing all of those things is naive.
-
requerent: by game-state do you mean something roughly like the 'model' in something like MVC? For example:
struct GameState {
Entity entities[...];
Terrain cells[...][...];
Gas gasLayer[...][...]; // For area of effect spells or gases like in Brogue.
};
-
A game state is just a discrete representation of all salient data within the game. It's very useful to have these as separate data structures so that you can easily generate trees and search them, save them, or hash and mask them for networking.
requerent: by game-state do you mean something roughly like the 'model' in something like MVC? For example:
struct GameState {
Entity entities[...];
Terrain cells[...][...];
Gas gasLayer[...][...]; // For area of effect spells or gases like in Brogue.
};
That's incomplete. You'll need the Tick number and you need the seed in there to accurately replicate the state in Brogue. We also need to know whose turn it is- since we may be using states to search the state space to analyze what the best moves for a group of enemies may be.
It is an MVC model so long as each entity has a controller.
-
Aye I've seen that in Quake3 (much cleaner in concept than implementation) and pretty much all games written in functional languages. Must admit it is a lot nicer to do the networking for that kind of structure.
-
Aye I've seen that in Quake3 (much cleaner in concept than implementation) and pretty much all games written in functional languages. Must admit it is a lot nicer to do the networking for that kind of structure.
Many of the modern game engines use a Controller-Pawn relationship to make a distinction between the AI/Player and how they interact with the state/model. This is conceptually simple to understand, but it's usually implemented within a complex hierarchy of entangling Objects, none of which are necessary to the logic in the game with the exception of a few with native hooks (typically for cameras and player input-- and in a general way collision detection, though not necessarily resolution).
That's why I like Component-based Entity-Systems. Your state is a database. Each key is an entity (or a state of an entity) that points to a table of components. Each component is a specialized set of data to be worked on by Systems, which are just state-less procedures that analyze a subset of the components and updates the data accordingly. Saving games, state space search, and networking is then super-trivial. Adding components or states dynamically to entities is trivial, as is creating new systems and components to expand the scope of your game. Easy to test and debug also.
Lua is especially well-engineered for this sort of design scheme (tables and local variable speed-ups are great), though you can accomplish it in any language.
-
It is an MVC model so long as each entity has a controller.
That's one thing I've never understood about MVC. If there's a one-to-one mapping between models and controllers, why even make the distinction between the two? Why should I have to call a controller method to create a new monster or whatever when I can just call a constructor? OK, if there's a lot of logic and parameters involved, then it might be best to use a factory, but isn't it just premature optimization to mandate that all object initialization be done via a controller? And what of regular methods? Why can't Attack(Creature target) be a method of the Creature class, instead of of the CreatureController class? Doesn't moving everything into a controller actually *break* the founding principle of OOP that data and the methods to manipulate it should be kept together?
-
requerent: to be honest the games companies (I'm looking at you Epic) that have the complicated hierarchies for their entities are stuck with mid-90s ideas of how to use OO and design game engines.
While I like component systems I wouldn't go as far as to make all components methodless structs. Then again I was first exposed to a design similar to modern day component systems many years ago by a Smalltalk coder who considered it a good OO design and I agree.
The OOP that the component and 'Data Oriented Programing' guys are attacking is over two decades old, it's just a shame it's what is still being taught to undergrads.
ekolis MVC is a minefield, there are so many subtle variants and architecture astronauts that have muddied the waters over the years. The irony is that is was created by Smalltalk coders to handle GUI apps but they hardly use it any more, they use libraries based on Morphic written for the Self language which is quite radically different.
The original idea was to separate rendering code (the View) from the application's 'database' (the Model or the game-state as requerent mentioned above). The Controller is the main point of contention that causes so many different variants of the pattern to exist (MVP, MVVM and so on).
From a game programming PoV the Controller is worth abstracting out because entities can be 'controlled' by multiple things, a player with an input device, an AI, a networked player or AI on another machine or even totally scripted in a cut-scene. If they all use the same interface to change the Model your life is a lot simpler. If they all have a similar interface themselves, they can be interchanged freely. Component systems achieve the same results by slightly different means.
The guideline (that has become dogma) that controllers should be the only place where new model objects are created is due to the intuition that the input to the system as a whole comes through the Controllers. For some MVC designs this is fine but for others it is pointless or counter-productive.
I'm not sure why Attack() should be a method on the Controller, do you have a link to who would have said that?
-
The OOP that the component and 'Data Oriented Programing' guys are attacking is over two decades old, it's just a shame it's what is still being taught to undergrads.
Please elucidate. I need schooling >_<. A game is really just a database with a UI. Any OOP we do just feels like fluff. You can, of course, program DOP-style in OOP paradigm, but is it not just extra work? If it's all you know- sure, go for it- but are there any strict advantages?
I'm not sure why Attack() should be a method on the Controller, do you have a link to who would have said that?
It's pretty standard nomenclature. Controller.Verb() queues a call to Actor.doVerb(). That's how Unreal does it anyway.
-
Just as a matter of separation of concerns it's quite handy to have a commandVerb and a doVerb for the reasons naughty mentioned.
Remember that the original (Smalltalk) OOP really had nothing to do with most of the conventions that were tacked on top of it later by OOP evangelists. It was basically about messaging (between 'objects'). Pretty much decoupling the parts of your program. Whenever people take an idea and start interpreting it and expounding it there's bound to be a bunch of crap to deal with :).
-
requerent: to be honest the games companies (I'm looking at you Epic) that have the complicated hierarchies for their entities are stuck with mid-90s ideas of how to use OO and design game engines.
While I like component systems I wouldn't go as far as to make all components methodless structs. Then again I was first exposed to a design similar to modern day component systems many years ago by a Smalltalk coder who considered it a good OO design and I agree.
The OOP that the component and 'Data Oriented Programing' guys are attacking is over two decades old, it's just a shame it's what is still being taught to undergrads.
So what's a modern, 2013 way of doing it?
-
First off why do you need lots of different pre and post steps for movement? I would be quite worried if that happened because it means you derive a new critter class just to get a new kind of movement. If your game does have a lot of very different forms of movement you should be abstracting that out of the critter anyway, e.g. into a hierarchy of movement objects. (I'm assuming that like in most games critters are not uniquely determined by their style of movement.)
If you don't factor it out and you have another method/behaviour on the critter that can vary, an attack() method say, then you could be for a right pain in arse. You only need 3 types of movement and 3 types of attack to lead to 9 different classes you could have to write. Of course you could make a faustian pact and use multiple inheritance but possibly reduce the amount of code you write at the cost of you sanity.
It would be easier to factor both movement and attacking out into their own objects or hierarchies (thereby turning a N*M problem into an N+M problem). This also has the nice side effect of being far easier to make data driven (so new critter types can be added without writing code).
What would these "movement" classes do, exactly? How do they interact with the map?
-
The OOP that the component and 'Data Oriented Programing' guys are attacking is over two decades old, it's just a shame it's what is still being taught to undergrads.
Please elucidate. I need schooling >_<. A game is really just a database with a UI. Any OOP we do just feels like fluff. You can, of course, program DOP-style in OOP paradigm, but is it not just extra work? If it's all you know- sure, go for it- but are there any strict advantages?
The 'database with a UI' concept is great way of looking at it and a very fruitful perspective to take.
It's also a more 'OO' perspective than a hierachy of classes with draw(), attack() and move() methods.
MVC is pretty much the same idea but it has unfortunately collected loads of crud of the years that
hide the simple idea underlying it.
The early component systems were written by OO programmers trying to fix the problems with code bases
that massively abused inheritance. Inheritance was still used in some places like for controllers or as
a way to have different rendering backends but far less than in previous designs.
If you take the database analogy, OO is useful in maintaining constraints and invariants on the database. For example
you don't want an entity's health to get higher than the max health stat so instead of manipulating
health directly you use methods or functions to ensure that doesn't happen.
From the DOP standpoint (which is really a component system with a focus on low-level performance) you
can encapsulate the details of an AoS to SoA transform in a class or module. Clients of your class don't
even need to know you made the change. Also the basic tutorials on DOP don't show the full picture on the
SoA situation. You really want to align the values in the arrays so that all cache misses happen at the same
time. This very fiddly work but causes far less issues if hidden behind an interface.
I'm not sure why Attack() should be a method on the Controller, do you have a link to who would have said that?
It's pretty standard nomenclature. Controller.Verb() queues a call to Actor.doVerb(). That's how Unreal does it anyway.
Unreal is a nice enough engine with great tools but it's full of 90s throwback ideas. The controller should be calling Attack(), not having Attack() called on it. This is all IMHO of course.
-
So what's a modern, 2013 way of doing it?
There's two ways to parse that:
What's popular or considered 'best practice' in the games industry at the moment?
Component systems are all the rage and with good reason. There's quite a bit of variety in how they're
being implemented but the basic idea of having one type of 'entity' that can have multiple components
which define how it behaves. A rough analogy is that the programmer makes lego blocks and the designer
builds entities from them.
At the high-performance and AAA end of the industry you have 'double buffered' entities (a read-only public
version and a writeable, private version) to help get the most out of multi-core and the graphics hardware.
These designs are heavily concerned with performance issues and Data-Oriented Programming is a major buzzword.
At the MMO end of the industry component systems have been influenced by the need to interface with
Relational Databases (which are quite naturally 'component' based in a lot of ways).
At the indie/mobile end, Unity is component based (but not super strict about it) and is making a very
big impact.
It's worth noting that this isn't new at all. The first devs that actually publicised they were using a
component based system were probably Gas Powered Games who used it on Dungeon Siege (released 2002).
The company I worked for at the time had been experimenting with them around 2000 and quite a few
other companies had done it for a few years before that.
It's also not a panacea, I worked at a well known game studio that decided to use a component system
and they made a total hash of it. They had 100s of different types of components and singleton
manager objects for everything.
I'm leaving out a lot of details for brevity but the web is full of component system articles,
libraries, talks and slides. Some of them are just as dogmatic as the OOP guys used to be though.
What does this grumpy 'get off my OO lawn' programmer called naughty think is the modern way?
Component systems done well are a lot better in many ways than the inheritance hierarchy of entities
that was common in the 90s. For a small game it could be overkill though.
However you can arrive at roughly the same ideas from several angles. More emphasis on being data-driven
leads you to something like components. Performance concerns on modern hardware lead you to components.
Wariness or fear of inheritance leads you to components. Understanding all those OOP slogans and applying
them leads you to components.
-
If you take the database analogy, OO is useful in maintaining constraints and invariants on the database. For example you don't want an entity's health to get higher than the max health stat so instead of manipulating health directly you use methods or functions to ensure that doesn't happen.
That should just be a part of the System that works with those components.
From the DOP standpoint (which is really a component system with a focus on low-level performance) you
can encapsulate the details of an AoS to SoA transform in a class or module. Clients of your class don't
even need to know you made the change. Also the basic tutorials on DOP don't show the full picture on the
SoA situation. You really want to align the values in the arrays so that all cache misses happen at the same
time. This very fiddly work but causes far less issues if hidden behind an interface.
I'm a little confused about this. There is nothing inherent about DOP that makes AoS to SoA problematic. I mean, the whole idea of thinking of an application as a database is to optimally organize your data- in some minor cases AoS can perform better, but there is nothing preventing you from organizing everything in an SoA manner.
A System is a state-less procedure that evaluates and modifies the game-state/Model. However, each System only works with an explicit subset of Components- or more specifically values within subsets of components. This means that your data should be organized by components or the data within components, not by entities. You can further invert the relationship between keys and data so that everything is "perfectly" SoA if you want. Or only what would perform better that way.
I'm not sure why Attack() should be a method on the Controller, do you have a link to who would have said that?
It's pretty standard nomenclature. Controller.Verb() queues a call to Actor.doVerb(). That's how Unreal does it anyway.
Unreal is a nice enough engine with great tools but it's full of 90s throwback ideas. The controller should be calling Attack(), not having Attack() called on it. This is all IMHO of course.
[/quote]
That's what is happening. Controller.Attack() is bound to an input key which queues up the Actor.doAttack().
I will defend Epic for a moment- in that your entire game can be made up of 4 classes if you wish (camera, playerinput, actor, gameinfo). It isn't necessarily a complex minefield of inheritance. It actually uses some component-based systems, but the events are managed by classes, which I dislike- especially when working on a large team.
-
So what's a modern, 2013 way of doing it?
There's two ways to parse that:
What's popular or considered 'best practice' in the games industry at the moment?
Component systems are all the rage and with good reason. There's quite a bit of variety in how they're
being implemented but the basic idea of having one type of 'entity' that can have multiple components
which define how it behaves. A rough analogy is that the programmer makes lego blocks and the designer
builds entities from them.
(more about component systems -- snipped for brevity)
Mmm. I already programmed a program with a more "traditional" entity "hierarchy" with different subtypes of entities (though I'm just beginning on it, so there isn't too much code for the entity system) instead of components. Should I toss it out and go for components?
-
Mmm. I already programmed a program with a more "traditional" entity "hierarchy" with different subtypes of entities (though I'm just beginning on it, so there isn't too much code for the entity system) instead of components. Should I toss it out and go for components?
Without knowing your exact circumstances it's hard to say. If it's a short term project then I wouldn't bother unless you really want to try to implement a component system to see what the fuss is about.
Throwing code away is also something I wouldn't encourage at all. It would be best to convert over in stages unless you're very close to the beginning.
I suppose the pros and cons are:
Pros
- You'll be trying something new which can be fun.
- If you're making a large or complex game there will be a benefit.
- If you're curious about how component systems work, the best way is to try it.
Cons
- Until you get a feel for how a component system works you will be less productive and probably a bit confused. This can kill motivation especially for hobby projects.
- It's still possible to go wrong with component systems. One of the more well known game companies I worked at made an absolute mess of it.
- The benefits of component systems are most felt in very complex games or games that are pushing the hardware to the limits. So if you have a simple enough game it could all seem a bit useless.
-
I think strict inheritance/class hierarchy and "all components" are both examples of one directional thinking. People can only understand one paradigm and they follow it strictly. What I know about components is that they are simply data type classes (user defined data types), so it's perfectly ok to use them in other classes. You can also use inheritance at the same time.
-
That should just be a part of the System that works with those components.
I can understand that standpoint for examples that require mutation of state (and you're following the rule that only the corresponding system can mutate a particular component's state) but for objects that have involved immutable queries (e.g. a graph with a some complex breadth first based algorithm) you may as well just put the method on it.
I'm a little confused about this. There is nothing inherent about DOP that makes AoS to SoA problematic. I mean, the whole idea of thinking of an application as a database is to optimally organize your data- in some minor cases AoS can perform better, but there is nothing preventing you from organizing everything in an SoA manner.
Sorry I was being a bit vague. If you do an AoS to SoA change all clients need to have their code changed. Use a few inlined getters and they don't. This has stung me in the past when micro-optimising a particle system update. The most memory efficient structure wasn't a pure SoA, e.g.
struct Particle {
vec3 position;
float turbulence;
float timer;
float saturation;
}
Particle AoS[NUM_PARTICLES];
struct SoAParticles {
vec3 positions[NUM_PARTICLES];
float turbulences[NUM_PARTICLES];
float timers[NUM_PARTICLES];
float saturations[NUM_PARTICLES];
};
// Cache-miss Optimised version.
struct OptSoAParticles {
vec3 positions[NUM_PARTICLES];
struct {
float turbulence;
float timer;
float saturation;
} p[NUM_PARTICLES];
};
The actual code was more complicated but it caused issues because other systems were reading some particle data. It would have been easy to hide the changes for most (read-only) clients if it had been hidden a bit.
A System is a state-less procedure that evaluates and modifies the game-state/Model. However, each System only works with an explicit subset of Components- or more specifically values within subsets of components. This means that your data should be organized by components or the data within components, not by entities. You can further invert the relationship between keys and data so that everything is "perfectly" SoA if you want. Or only what would perform better that way.
Are you using the form of component system where entities are purely a unique id and you have no struct/object? Also do you follow the each component can only be written to by one system? There's quite a bit of variation in these kind of details.
Putting an Attack() method on a controller just leaks the abstraction. If there's user input involved in should be handled in the LocalPlayerController or some such. Then again it's been a while since I was using UE2.5 so I could have got my wires crossed.
-
That should just be a part of the System that works with those components.
I can understand that standpoint for examples that require mutation of state (and you're following the rule that only the corresponding system can mutate a particular component's state) but for objects that have involved immutable queries (e.g. a graph with a some complex breadth first based algorithm) you may as well just put the method on it.
I'm a little confused about this. There is nothing inherent about DOP that makes AoS to SoA problematic. I mean, the whole idea of thinking of an application as a database is to optimally organize your data- in some minor cases AoS can perform better, but there is nothing preventing you from organizing everything in an SoA manner.
Sorry I was being a bit vague. If you do an AoS to SoA change all clients need to have their code changed. Use a few inlined getters and they don't. This has stung me in the past when micro-optimising a particle system update. The most memory efficient structure wasn't a pure SoA, e.g.
As long as components are only communicating with each other through a System, you're technically fine. I have mixed feelings on hybrid approaches though. I feel like components should only be storing data in a manner that is salient for their respective systems. You don't violate this by slapping methods into them, but why would you need to? That method is really just a sub-system of the systems using that component-type. If you're always going to be using those getters, why aren't you storing data in that way to begin with?
struct Particle {
vec3 position;
float turbulence;
float timer;
float saturation;
}
Particle AoS[NUM_PARTICLES];
struct SoAParticles {
vec3 positions[NUM_PARTICLES];
float turbulences[NUM_PARTICLES];
float timers[NUM_PARTICLES];
float saturations[NUM_PARTICLES];
};
// Cache-miss Optimised version.
struct OptSoAParticles {
vec3 positions[NUM_PARTICLES];
struct {
float turbulence;
float timer;
float saturation;
} p[NUM_PARTICLES];
};
The actual code was more complicated but it caused issues because other systems were reading some particle data. It would have been easy to hide the changes for most (read-only) clients if it had been hidden a bit.
That's kind of just an issue with using objects and messaging systems to begin with. I don't dispute that such methodologies are powerful and succinct- but in a component system you should just yield to another system to ensure data-consistency. The relationship of systems in a loop can be implemented as an FSM, through which we can easily ensure that data is accessed in a consistent manner. If we get a read request from a client, that request is only going to be processed in its own system, after changes are written to the model.
A System is a state-less procedure that evaluates and modifies the game-state/Model. However, each System only works with an explicit subset of Components- or more specifically values within subsets of components. This means that your data should be organized by components or the data within components, not by entities. You can further invert the relationship between keys and data so that everything is "perfectly" SoA if you want. Or only what would perform better that way.
Are you using the form of component system where entities are purely a unique id and you have no struct/object? Also do you follow the each component can only be written to by one system? There's quite a bit of variation in these kind of details.
Yes. I may have used the word 'pure' earlier, but if I didn't i meant to >_<.
edit: Though it can depend on how you store your data. If you want to split up the x,y,z components of your position because you are working with constraining planes and some systems only modify a micro-component, then you may want to have multiple system modifying data-- but I think this is usually a design oversight more than a necessity.
Putting an Attack() method on a controller just leaks the abstraction. If there's user input involved in should be handled in the LocalPlayerController or some such. Then again it's been a while since I was using UE2.5 so I could have got my wires crossed.
I guess I wasn't being clear. That's how it works. Model inputs and feedback are handled by the Controller that modifies the model as an Actor. Any Controller is hypothetically valid, whether its AI or a Player, so both would be using Controller.Attack() to call Actor.doAttack().
There are two reasons for this-- one, we want to queue actions up so that they happen in a way that is acceptable to the Actor or consistent with the game loop, and two we may want to change the Controller's state and modify what method of Actor Attack() calls.
States in Unreal aren't driven by data, but allow you to change the functionality of any given method. IE. If we're dead and don't have a pawn, we wan't to override Attack() so that it spawns a new pawn for us.
-
@requerent: So what exactly is the kind of design approach you're suggesting? You have a big pile of data called "game state" with no member functions that various objects manipulate using their routines, and which has no routines associated to it itself? Also, you say stuff like "It's pretty standard nomenclature. Controller.Verb() queues a call to Actor.doVerb(). That's how Unreal does it anyway.". What's an "actor" in this setup? Is "Controller" a single monolithic object or are there many "Controller"s? Where's this queue? Who has access to it? Etc.
-
And I'm still not sure on just which approach of the several presented here I should use.
-
@requerent: So what exactly is the kind of design approach you're suggesting? You have a big pile of data called "game state" with no member functions that various objects manipulate using their routines, and which has no routines associated to it itself? Also, you say stuff like "It's pretty standard nomenclature. Controller.Verb() queues a call to Actor.doVerb(). That's how Unreal does it anyway.". What's an "actor" in this setup? Is "Controller" a single monolithic object or are there many "Controller"s? Where's this queue? Who has access to it? Etc.
Ah- Naughty and I are going down a route of discussion that, honestly, doesn't remotely answer your question- but let me try and break some of it down.
Unreal Tournament 3, or the Unreal Development Kit, is a big 3d game engine that can do lots and lots of wonderful things. It uses a rather traditional OOP hierarchy, where all game objects (not data objects) extend from a base Actor class. Actors are any entity, static or dynamic (walls and monsters), that occupy or interact with the game space, or model. The 'state' of the game is a discrete configuration of the 'model,' which is the aggregation of data that describes the interactions/relationships of entities within the game (ie their current stats, positions, etc). When we talk about a 'game state' we're talking about a measurably distinct change to the model of the game. In a real-time game, like Unreal, the game state updates on every 'tick,' or game-loop. In effect, each 'tick,' or 'frame' is a state.
I wasn't being as specific as I should have been- Unreal uses a Controller-Pawn relationship, whereby both the Controller and Pawn inherit from Actor. The Controller is the decision maker and the Pawn interacts with the model. A Controller represent the player and handles input and the camera, but can also represent an AI that asks the Pawn what the model looks like from its point of view and then makes decisions for it. There is typically one controller per pawn- but for an RTS game, you may use Pawns to represent each unit and a single controller commanding them all. The 'queue' I'm describing isn't necessarily a universal action queue, but relative to how each Pawn.doAction() should be called. Controller.Action() is called on key press, whereas the controller then calls Pawn.doAction() where it is most logically appropriate. This can be as simple as setting a boolean 'performJumpOnNextTick,' so that the velocity of a jump is applied at the beginning of a tick, giving the Pawn an opportunity to manipulate it in some way before the physics engine updates the state, or so that the action can be replicated across a server.
Is this a useful architecture for a Roguelike? Yes. Pretty much every game can be thought of in terms of MVC, regardless of whether you use OOP or a Component system. A component system would hook a controller component into an entity, whereas OOP would represent the controller as an Object. I don't know any reference material off-hand, but the question is-- do you want to make Games or do you want to make Games and learn how to make games? If the former, do what you already know howt to do, otherwise, try something a little uncomfortable.
It also depends on what sort of features you want.
OOP vs DOP and Component Systems
Unreal, a heavily OOP game engine, uses components to unite all Actors in the game that share a property. The most mundane example are Collision Components. If an actor has a collision component, then it will be evaluated by the collision system. Unreal uses an event handler, where it calls something like 'CollisionComponent.Owner.onBump(actor bumpedActor,vector momentum, vector hitLocation)' whenever two collision components are interacting. There are other features built-in to the engine that will take care of displacement and overlapping issues, but that all depends on other values set in each Actor. In this way, Actors are performing all of the logic relative to their implementation, whereas the System isn't really doing anything except evaluating relational components and calling events on the Actors. Unfortunately, this tends to lead to a Monolithic Entity Superclass that has all of the possible events available to be overridden. Regardless, you are invariably using components even if you aren't using a component system- IE, the location of an entity is a logically discrete component of that entity that gets worked on at some point in the game-loop via the engine. Explicitly defining component-system relationships helps keep our logic succinct.
A 'pure' component system (DOP) looks at it from the other way around. Part of the value of using components have to do with meta-features like saving the game state, sending concise network messages, and some other things mentioned previously. This is where the 'database w/UI' idea comes in. Instead of having classes and objects relate to one another, we just have a Database and routines acting upon it in a Finite State Machine of Systems. This makes a lot of things easier- including Procedural Generation of bizarre items and monsters. Maybe a monster is equippable but can't be picked up, and there is a spell or ability that equips something from a distance. So very trivial to implement in DOP, but potentially very quirky in OOP.
The main problem I have with OOP is that you cannot predict all of the features you will want to implement. With DOP, it never matters.
If you're familiar with OOP already, then you should try a hybrid. It depends on what your goals are and what your experience is. I don't know of any good examples off-hand, but pseudo-code is available if you want.
Disregarding your experience/goals, Yea- I recommend a Database with no member methods/functions, with stateless routines acting on said database as an FSM of Systems.
-
Disregarding your experience/goals, Yea- I recommend a Database with no member methods/functions, with stateless routines acting on said database as an FSM of Systems.
The trouble is, with this particular project, to switch to such a radically different conceptualization would pretty much require throwing away all work done so far and starting over from scratch -- and I don't have any experience with the kind of architecture you describe. I suppose I could try it as part of a new project, though. What did you mean when you said "try something a little uncomfortable"? Did you mean "try something new so as to learn how to do it", and the "uncomfortable" bit is because you'd be starting without any previous experience with that new something?
-
Disregarding your experience/goals, Yea- I recommend a Database with no member methods/functions, with stateless routines acting on said database as an FSM of Systems.
The trouble is, with this particular project, to switch to such a radically different conceptualization would pretty much require throwing away all work done so far and starting over from scratch -- and I don't have any experience with the kind of architecture you describe. I suppose I could try it as part of a new project, though. What did you mean when you said "try something a little uncomfortable"? Did you mean "try something new so as to learn how to do it", and the "uncomfortable" bit is because you'd be starting without any previous experience with that new something?
I'd save it for a pet project if you're curious. It's my personal recommendation, but it won't be what is best for you (most people don't program this way, so there aren't many good references for it). You might try encapsulating a few of the ideas into what you already have-- breaking the logic of your game into component-systems is a very good idea whether you are OOP or DOP.
-
Disregarding your experience/goals, Yea- I recommend a Database with no member methods/functions, with stateless routines acting on said database as an FSM of Systems.
The trouble is, with this particular project, to switch to such a radically different conceptualization would pretty much require throwing away all work done so far and starting over from scratch -- and I don't have any experience with the kind of architecture you describe. I suppose I could try it as part of a new project, though. What did you mean when you said "try something a little uncomfortable"? Did you mean "try something new so as to learn how to do it", and the "uncomfortable" bit is because you'd be starting without any previous experience with that new something?
I'd save it for a pet project if you're curious. It's my personal recommendation, but it won't be what is best for you (most people don't program this way, so there aren't many good references for it). You might try encapsulating a few of the ideas into what you already have-- breaking the logic of your game into component-systems is a very good idea whether you are OOP or DOP.
Sounds good. Thanks! :)
-
Hmm. I'm curious: if the critter object doesn't know its map, then what do you do with something as basic as this: movement. Suppose your "Critter" object has a "move()" method, which, well, makes the critter move. Seems reasonable, no? But then you have to check for walls, etc. (you don't want your critter to move through the walls (unless it's a ghost or something)!) which means you need access to the map. Plus, if the map contains a reference to the critter attached to the tile it's standing on, that needs updating. So what should one do? What should that call to "move()" do?
Also, where should the "critters" be stored, anyway? Currently, in my program I have them "owned" by the map, since they're on the map and most would be associated with a map, anyway (when you save out a map, you have to save out all critters on it). How should it be?
See, this is why I've become so disappointed with OOP over the past several years. This question comes up so often from so many different people that I simply consider it a failing of the paradigm.
The way I reckon it, it's because people try to find real-world analogs to their program architecture, where there really shouldn't be. For instance:
It seems obvious to you that a critter should have a move method. I say it's not obvious at all. If you don't want your critter object to have a reference to your world, then the world should have a move(Entity e) method that moves critters and other objects around.
It may not be intuitive to think that way in real-world terms, but it is much cleaner OO. You ask your world to move everything that needs to be moved, just like you ask your main game loop to update everything that needs updated.
Honestly there is not a language in existence that has a proper scope system, in my opinion. Almost all the problems I have with programming are caused by where to keep data and the juggling required to get those data to the proper functions. It's not a technical limitation; it's a conceptual one. I know I could throw any old system together and force it to work. The trick is whether it is intuitive to think about or not.
Games are about the most state-based programs you can write, which only compounds the problem.
-
It may not be intuitive to think that way in real-world terms, but it is much cleaner OO. You ask your world to move everything that needs to be moved, just like you ask your main game loop to update everything that needs updated.
In OOP, an object should only modify its own data members. However, the Critter will typically be composed in the Map (Assuming that the Map is your state managing object). The Critter also needs to tell the map where to move, and the map needs to tell the Critter where it can move, and the Critter needs to look at the map to decide where to move.
No matter where you put the move method, there is an exchange of information that obfuscates the idea of what is actually managing the move.
Honestly there is not a language in existence that has a proper scope system, in my opinion. Almost all the problems I have with programming are caused by where to keep data and the juggling required to get those data to the proper functions. It's not a technical limitation; it's a conceptual one. I know I could throw any old system together and force it to work. The trick is whether it is intuitive to think about or not.
Lua works well for this IMO.
An excellent approach to making a game is to make it so that game data is acted upon only by pure functions without the use of methods.
-
I bet that if everyone puts their functions wherever they want, we'll all end up happy.
like you ask your main game loop to update everything that needs updated.
I don't have my main game loop update everything. I ask the objects to update themselves. And I don't even do it in the main game loop. The map does it.
-
I bet that if everyone puts their functions wherever they want, we'll all end up happy.
like you ask your main game loop to update everything that needs updated.
I don't have my main game loop update everything. I ask the objects to update themselves. And I don't even do it in the main game loop. The map does it.
It's a cascading hierarchy of updates.
-
I bet that if everyone puts their functions wherever they want, we'll all end up happy.
like you ask your main game loop to update everything that needs updated.
I don't have my main game loop update everything. I ask the objects to update themselves. And I don't even do it in the main game loop. The map does it.
It's a cascading hierarchy of updates.
All programming is, really. Main is called, and then main calls things, and those things call things, and those things call things, and then one day all of those return and you end up back at main and main returns. It's inescapable. Even in a multithreaded program, main initiates the threads, and the threads one day return before main returns. The whole of programming is a cascading hierarchy of updates starting at the program entry point.
-
Hmm. I'm curious: if the critter object doesn't know its map, then what do you do with something as basic as this: movement. Suppose your "Critter" object has a "move()" method, which, well, makes the critter move. Seems reasonable, no? But then you have to check for walls, etc. (you don't want your critter to move through the walls (unless it's a ghost or something)!) which means you need access to the map. Plus, if the map contains a reference to the critter attached to the tile it's standing on, that needs updating. So what should one do? What should that call to "move()" do?
...
In that particular design the move() method should be in the map rather than the critter because the map is the only object with the required context to actually perform the operation. The only way the critter could really know how to move is by accessing the map anyway (by a cached map reference in the critter or the map being passed as an argument to an update() method).
This doesn't occur to a lot of people because standard teaching of OOP is just hideously bad. The focus on the intuitive real world concept of objects just leads to tears in the end. Intuitively it feels like the move() method should be on the critter because critters move but that's not how you should decide where methods live.
Because a critter can't stand in the same cell as other critters you need more contextual information about how to perform the action (i.e. where the other critters are). Also a move can fail for reasons beyond a critter being in the way, e.g. lava, walls. The only place where all the data is at hand is the map object, so move() should go there.
I'm resurrecting this thread, because I want to come back to this point as I've noticed something else here: doors require access to the map too, to open and close, since they need to check if they're blocked. So now it seems the map would acquire an open/close method for doors as well. It seems the map is gaining more and more responsibilities to make up for the lack of a reverse reference... violating the one-responsibility principle. Isn't that bad?
-
There are two ways the critter can know about the map (well, three, but let's exclude global variables for the moment). It can have a reference to the map, or you can pass it the map. You seem to think the first way is the only way to do it, but there's nothing wrong with the second, and IMO it's a better idea than the reference.
-
There are two ways the critter can know about the map (well, three, but let's exclude global variables for the moment). It can have a reference to the map, or you can pass it the map. You seem to think the first way is the only way to do it, but there's nothing wrong with the second, and IMO it's a better idea than the reference.
Actually, I had considered such a possibility. But I wasn't sure how to make it work here. When a door is opened/closed, it is passed an event from the event queue, which it handles to open/close. Not sure whether it'd be a good idea to pass a map in the event object. I suppose it'd be OK, though, but one has to be sure not to send the wrong map. I guess that would be my concern, that such would be bug-prone.
-
Never mind. I decided to implement it anyway. You can't expect to make something "impossible" to break if it isn't used correctly...
-
Well I don't know how you've written your event system, but I can see a potential problem. If event 1 modifies the map but event 2 has an old copy of the map, it might be working with old data. What does your event loop look like?
-
Well I don't know how you've written your event system, but I can see a potential problem. If event 1 modifies the map but event 2 has an old copy of the map, it might be working with old data. What does your event loop look like?
There's a priority queue, ordered by logical time. The most recent event is fetched, handled, and then the time remaining on the other events is decreased, and the new most-recent event is handled next, and so forth. There is only one map in use at any time (that is, the map the player is on), and I was just thinking of passing a reference/pointer to the map. Copying the map over and over sounds like it'd make for huge overheads anyways.
-
It's extremely important to avoid any kind of copying unless it's practical.