可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm designing a simple game, which uses Java 2D and Newtonian physics. Currently my main "game loop" looks something like:
do {
for (GameEntity entity : entities) {
entity.update(gameContext);
}
for (Drawable drawable : drawables) {
drawable.draw(graphics2d);
}
} while (gameRunning);
When an entity is instructed to update itself it will adjust its velocity and position based on the current forces applied to it. However, I need entities to exhibit other behaviour; e.g. if a "bad guy" is shot by a player the entity should be destroyed and removed from the game world.
My question: What is the best way to achieve this in an object oriented manner? All examples I've seen so far incorporate the game loop into a God class called something like Game
, which performs the steps: detect collisions, check-if-bad-guy-killed, check-if-player-killed, repaint, etc and encapsulates all the game state (lives remaining, etc). In other words, it's very procedural and all the logic is in the Game class. Can anyone recommend a better approach?
Here are the options I've thought of so far:
- Pass a
GameContext
to each entity from which the entity can remove itself if required or update the game state (e.g. to "not running" if the player is killed).
- Register each
GameEntity
as a listener to the central Game
class and take an event oriented approach; e.g. a collision would result in a CollisionEvent
being fired to the two participants in the collision.
回答1:
I have worked closely with two commercial game engines and they follow a similar pattern:
Objects represent components or aspects of a game entity (like physical, renderable, whatever), rather than the whole entity. For each type of component there is a giant list of components, one for each entity instance that has the component.
The 'game entity' type itself is just a unique ID. Each giant list of components has a map to look up the component (if any exists) that corresponds to an entity ID.
If a component requires an update it is called by a service or system object. Each service is updated directly from the game loop. Alternatively, you could call services from a scheduler object which determines update order from a dependency graph.
Here are the advantages of this approach:
You can freely combine functionality
without writing new classes for every
combination or using complex inheritance
trees.
There is almost no functionality that
you can assume about all game
entities that you could put in a game
entity base class (what does a light
have in common with a race car or a
sky-box?)
The ID-to-component look-ups might seem
expensive, but services are doing
most of the intensive work by
iterating through all the components
of a particular type. In these cases,
it works better to store all the data
you need in a single tidy list.
回答2:
In one particular engine that I worked on, we decoupled the logic from the graphic representation and then had objects that would send messages for what they wanted to do. We did this so that we could have games exist on a local machine or networked and they were indistinguishable from each other from a code standpoint. (Command pattern)
We also had the actual physics modeling done in a separate object that could be changed on the fly. This let us easily mess with gravity, etc.
We made heavy use of the event driven code (listener pattern), and lots and lots of timers.
For example, we had a base class for an object that was intersectable that could listened to the collision event. We subclassed it into a health box. On collision, if it was hit by a player entity, it sent a Command to the collider that it should gain health, sent a message to broadcast a sound to all that could hear it, deactivated collisions, activated the animation to remove the graphics from the scene graph, and set a timer to reinstantiate itself later. It sounds complicated, but it really wasn't.
If I recall (and it's been 12 years), we had the abstract notion of scenes, so a game was a sequence of scenes. When a scene was finished, an event was fired that would typically send a command take down the current scene and start another one.
回答3:
I disagree that because you have a main Game class all the logic has to happen in that class.
Over-simplification here mimicking your example just to make my point:
mainloop:
moveEntities()
resolveCollisions() [objects may "disappear"/explode here]
drawEntities() [drawing before or after cleanEntitites() ain't an issue, a dead entity won't draw itself]
cleanDeadEntities()
Now you have a Bubble class:
Bubble implements Drawable {
handle( Needle needle ) {
if ( needle collide with us ) {
exploded = true;
}
}
draw (...) {
if (!exploded) {
draw();
}
}
}
So, sure, there's a main loop that takes care of passing the messages around between the entities but the logic related to the collision between a Bubble and a Needle is definitely not in the main Game class.
I'm pretty sure that even in your case all the logic related to the movement is not happening in the main class.
So I disagree with your statement, that you written in bold, that "all the logic happens in the main class".
This is simply not correct.
As for good design: if you can easily provide another "view" of your game (like, say, a mini-map) and if you can easily code a "frame-for-frame perfect replayer", then your design is probably not that bad (that is: by recording only the inputs and the time at which they happened, you should be able to recreate the game exactly as it was played. That's how Age of Empires, Warcraft 3, etc. make their replay: it's only the user inputs and the time at which they happened that is recorded [that's also why the replay files are typically so tiny]).
回答4:
I write my own engines (raw & dirty), but a pre-built engine that has a decent OO model, is Ogre. I'd recommend taking a look at it (it's object model / API). The node assignment is a tad funky, but it makes perfect sense the more you look at it. It's also extremely well documented with a ton of working game examples.
I've learned a couple tricks from it myself.
回答5:
I'm only posting this as an answer because I'm not able to comment on other posts yet.
As an addendum to Evan Rogers excellent answer you might be interested in these articles:
http://www.gamasutra.com/blogs/MeganFox/20101208/6590/Game_Engines_101_The_EntityComponent_Model.php
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
回答6:
This game was an experiment in keeping the model and view separate. It uses the observer pattern to notify the view(s) of changes in the game's state, but events would perhaps offer a richer context. Originally, the model was driven by keyboard input, but the separation made it easy to add timer-driven animation.
Addendum: You need to keep the game's model separate, but you can re-factor that model into as many classes as required.