component design with enemies

Started by
19 comments, last by rpiller 10 years, 10 months ago

I'm playing around with the component design. This is for just testing/trying out so nothing too serious. My components have events as outputs and functions as inputs. I want to stay very strict to decoupling so in no situation is a component to know about ANY other component inside itself. Instead the games created with this design would have a central place that does all the hooking up of events to functions between all components to create the actual flow of the game.

An easy example would be having a Health component attached to a player game object that has an event called OnHealthUpdated. Then having a HUD component that has a function called HealthUpdated. Create the 2 objects, attach their components and hookup the players OnHealthUpdated to the huds HealthUpdated() function and now everytime the players health components is changed it'll fire this event which will tell the hud component to update the display of health with the value that was passed to the event & function.

That's easy and there are many components like this. However when I get into enemy/player interaction things get a little more fuzzier in my head. My enemy component can do a bounding box check to see what models are all this area. However, to keep to the decoupling I can't within the result query for "player" or anything like that. Remember this is more research of ways than, I need to make a game right now with this.

So I'm looking for ideas around enemy/player interaction that is decoupled in their components. Ideally events need to be fired and functions need to be called from those events. The enemy has to somehow know the player from only events/functions linked.

Each game object is just a generic class that stores a list of components. For some reason I'm hesitant to pass around GameObject pointers to events/function. Not sure if I have to get over that or what. :) In my example above I'm passing around a simple health value (int) and it seems less coupled than passing a GameObject*, but since GameObject* is so generic maybe it's ok? Maybe I should track ID's (long) for each GameObject and that's what gets passed around instead? Then when I create enemy components I can just pass it the player's ID so they know what they are looking for?

Any ideas around this?

Advertisement

All you're really doing in this exercise is removing a perfectly reasonable coupling (between related components) and introducing a more abstract and less informative coupling (between components and this GameObject).

Remember that coupling is not inherently evil. Coupling between unrelated subsystems is what you want to avoid. If it makes sense for several components to know how to interact with each other, let them do so.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

In the past, I have used a FactionInfoComponent to provide entity identification. This component would respond to information requests, and would hold such information as faction (player, enemy, neutral, etc..) team (Red Team, Blue Team, etc...) and possibly even name. If you send a QueryFaction event to an object and receive no reply, that means it is not an object for which faction info is relevant, and it can be discarded from consideration in your area check. All other objects will return their appropriate faction info, and you can further cull your area list based on the contents of this result list. Doing it this way, you can provide more useful information than just "is this object the player or an enemy", and still keep to your decoupling abstraction.

To further emphasize ApochPiQ's point, it can be a mistake to try to take the decoupling too far. There are cases where a certain amount of coupling makes sense. For instance, say you have something like a CombatAnimationsController component, which listens for certain events (StartMeleeAttack, StartSpellCast, etc...) and maps those events to animations to play in the AnimatedModel component. In this case, it makes sense to assume that if the entity has a CombatAnimationsController then it also has an AnimatedModel, and so you can have the controller call the entity to obtain a direct reference to the AnimatedModel (and log an error if one isn't found) rather than going through the convolutions of handling the interaction by way of the message system. Similarly with the various components that might be involved in combat, such as Health, Buffs and Debuffs, etc...

Remember that coupling is not inherently evil. Coupling between unrelated subsystems is what you want to avoid. If it makes sense for several components to know how to interact with each other, let them do so.

I agree if I was making a game today and needed to get it done I would do this. For research though I want to explore a more strict representation of component design for some other benefits it adds that I list below.

For instance, say you have something like a CombatAnimationsController component, which listens for certain events (StartMeleeAttack, StartSpellCast, etc...) and maps those events to animations to play in the AnimatedModel component. In this case, it makes sense to assume that if the entity has a CombatAnimationsController then it also has an AnimatedModel, and so you can have the controller call the entity to obtain a direct reference to the AnimatedModel (and log an error if one isn't found) rather than going through the convolutions of handling the interaction by way of the message system. Similarly with the various components that might be involved in combat, such as Health, Buffs and Debuffs, etc...

A big part of my wanting strict decoupling is to make developing components easier and less error prone (note developing components not hooking components up as that'll be more tedious/work). Working in a completely self contained component is about as easy as you can get it. Not having any other dependent components helps a ton in separating out tasks in a group setting. Unit testing becomes so much easier to make sure the components do what they are supposed to do. I see these as huge pluses.

For research purposes I'm really forcing myself to be ignorant to the fact that hooking these messages up might be tedious, complicated, or slow performance. For right now anyway that's how I'm thinking about it.

A "goal" of mine would be to design, on paper, all the components needed for a very basic (to start with anyway) game. Then ask the community to have 1 person take 1 component and code/test it. These components would be rather small and specific and have no dependencies so it shouldn't take someone long to do this. Then once I have all the components I (game design master) would assemble them together to make the game. I really would love to see the result of such an experiement. This would be parallelism at it's best, and puts more responsibility on the designer than the coders. The designer really needs to know all interactions for everything. Maybe this is more of a technical designer vs game designer, but this would really flesh out the game ahead of time. Not saying changes won't be needed but that's another benefit. Completely isolated components will be far easier/less error prone to change (that's the idea anyway).

Your example I think fits decoupling pretty well actually (meaning doesn't seem like it poses any technical issues like the AI example I gave). Tedious? yes, but imagine you had a flowgraph or GUI for hooking these up. Designers would love that.


GameObject* zombie = new GameObject();
 
zombie->AddComponent(new CombatAnimationsController("StartMeleeAttack", "StartSpellCast"));
zombie->AddComponent(new AnimatedModel("melee_attack", "spell_cast"));
 
// not functioning code, but OnStateChanged would pass the state name to the function ChangeAnimation
zombie->GetComponent("CombatAnimationsController")->OnStateChanged.Bind(zombie->GetComponent("AnimatedModel"), AnimatedModel::ChangeAnimation);
 
// as a not so good but to show the point, the order of states in COmbatAnimationsController vs animation names in AnimatedModel should match so when OnStateChanged is triggered and passes 2 (for example) to AnimatedModel, it'll play the "spell_cast" animation because it was defined second. You can get more involved with start, middle, end animations and such, and make the relationship outside of the components themselves, but this just shows a basic example of how you might do that

?

So to the original question if you were faced with the challenge of making decoupled components that are hooked up via events in 1 central init place, how can you see AI interacting with each other and the player working? That's sort of the sticky point right now for me. I'm sure it can be done, but need to keep playing around with different ways but wanted to see if anyone wanted to help take the challenge here. :)

How would an AI "see" the player and then "attack" the player without knowing anything about the components hooked up to the player from within itself. It only knows about the player and it's components in the Init() method.

?

You're more than welcome to carry out this experiment if you really want the first-hand experience, but I can already tell you from my experience that it won't work.

Software does not exist in a vacuum. Neither does any well-designed engineered product. You don't design a car by asking a dozen different manufacturers to invent parts and then try and smash them together. (For humorous contrast, consider some space projects that have attempted this - and witness the inordinate amounts of pain they incurred by doing so. The infamous Mars rover crash due to Imperial/SI unit mixups is a classic example.)

Good design does not come from parallelism, and farming off the "grunt work" of doing component implementation is not a good way to scale a project. You will personally rapidly become a bottleneck to production, in the best case; in the worst, you will spend so much time trying to fix the hodgepodge of implementations you get back that you'll never make forward progress, and the whole thing will fall apart.

Now, if you really want to know this for yourself, by all means do the experiment - just be prepared for it to go badly. I'd much rather you learn this the hard way than just take my word for it and turn it into yet another religious belief - software development has too much of that as it is :-)

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

@ApochPiQ I can for sure see the issues you listed happening. I have no idea of the details of how others have implemented something like this but sometimes small changes or a different approach to a similar thing can yeild different results. I'm optimisitc, right now anyway :)

I'll have to think of a small enough game to try this in yet big enough to get a good test. Then find people who care enough to try to implement the components :)

Still looking for decopled AI interactions. If they even exist I guess :)

I'll have to think of a small enough game to try this in yet big enough to get a good test. Then find people who care enough to try to implement the components smile.png

I've worked on open-source projects before, and have tried to start a few myself that failed to gain traction. It takes quite a bit to get folks fired up about a project enough that they contribute anything meaningful, and getting people onboard usually requires that you do quite a lot of work upfront to convince them that it's actually viable, and not just somebody's pipe dream. So just showing up with a bunch of design notes on components to be written, without anything else, is a good way to become familiar with the sound of crickets in an empty room.

@FLeBlanc Yeah, I've been through all that before too :). Good times huh. I actually found the lack of a design was the biggest problem. They were asking people to stick around and just "play" around with the design. People lose interest real fast when that's the case :).

What I'm hoping to do is ask for 1 person to work on 1 well defined component which should take maybe no more than 1-4 hours of their time. That's it. I don't want them to stick around and wait for anything. 1-4 hours of a persons total time on the project. If my game has 20 components I would be looking for maybe 15-19 people (I would pick up the other components). That's the idea anyway. If that many would even want to give it a shot would be in question. I think if I use Unity I should have a decent pool to pick from. You don't even have to be that advanced to make some of the basic components, so all knowledge levels could be represented. We'll see how it plays out :).

I'm not really interested in forming a team really, just getting 20 or so people to make 1 component each. So it's up to me to make the entire design of how all components interact and their basic functionality up front. Then hand these out and hope I got it right the first time :)

So it's up to me to make the entire design of how all components interact and their basic functionality up front. Then hand these out and hope I got it right the first time smile.png

This right here is the kicker, though. Because you probably won't get it right, not on the first try. Software design is complex, and typically requires a lot of iteration. There are always things you didn't account for in the initial design, things that you at first thought would be fun or otherwise contribute to the overall experience but upon practical realization turned out to be duds, things that occur to you later that can contribute greatly, things that you just flat-out overlooked, and so on. I remember being taught the waterfall model (which is basically what you are proposing) in college because back in those dark ages people just didn't know any better, but modern software engineering has moved far beyond that flawed model. Processes now are much more iterative in nature. So rather than being able to bring on coders for 4 hours at a time, you'll need to bring them back to iterate on their code as your own framework changes. Even in the most loosely coupled systems, there are dependencies. There is just no getting around it. So when those dependencies change, code needs to be refactored. That means, the first prototype will probably be a long ways from finished product, and you can't just have fire-and-forget coders doing the grunt work.

This right here is the kicker, though. Because you probably won't get it right, not on the first try. Software design is complex, and typically requires a lot of iteration. There are always things you didn't account for in the initial design, things that you at first thought would be fun or otherwise contribute to the overall experience but upon practical realization turned out to be duds, things that occur to you later that can contribute greatly, things that you just flat-out overlooked, and so on.

I agree. If this was a full blown game and I wanted a team to stick with me there would be iterations and evolving components. For my initial research here it'll be a very small game so the design won't be all that hard. Any iterations I need I might just do myself to prove out the idea, but hoping those will be small. I'm probably going to do a clone with this research project so "fun" factors are already created for me smile.png. This method isn't really about NOT having iterations. I just choose to limit that for the research project to prove out the method itself. If this works then I would do it again with a medium size game where I would need people to stick around and iterate the code.

I agree with everything you guys are saying, but I'm looking at this research as doing things a different way. Seeing if this way can work and can be efficient in building indie teams around this idea because at it's core for me that's what it's about. Trying to make a game efficiently for indie people who aren't getting paid or lose interest quickly. Can we do this differently to help that problem. I think this method can help. Just have to prove it out.

So many people come and go on indie projects that the code ends up so messed up and often people are scared to make changes for fear of breaking many other things. I believe "total" isolation of components can help ease that problem/fear. It's easier to tell a person to go into this component and fix it's insides (not it's interface) when it's isolated. I think this can help indie project management.

This topic is closed to new replies.

Advertisement