This is totally NOT what I'm creating... *shifty eyes* |
For my purposes, preparing a game to handle an AI boils down to the old dilemma of whether to use inheritance or composition for game objects. For now, the only objects in the game that have any kind of self-control are Ships. I'm not going into all the details on how the Ship class is setup, but I'd like them to be pooled objects. Before I started messing with the bulk of the solution discussed below, the only ship present was an instance of a derived class; PlayerShip. This worked fine for debugging up to this point, but here's why I no longer like using a derived class to exert control over an object:
- Using instances of both the base class and its derived class (or instances of two different derived classes) screws up the existing object pooling. If you have 2 ships in your pool, the first is a PlayerShip and the second is an BotShip, you're gonna need to either re-instantiate when you need a new object (which defeats the whole purpose of object pooling), do a lot of fancy casting to make it work (which is just a headache), or re-design ObjectPools entirely to store and fetch multiple types of objects within the same pool (which, let's face it, is too much work)
- The game shouldn't care whether it's updating a PlayerShip or a BotShip! You don't want a human player or an AI player treated any different when calling the Update method, for instance. If you do, it's very easy to give abilities to one or the other, or force yourself to repeat logic to prevent that from happening. If a ship needs to move 200 meters starboard, it doesn't matter who issued the order (Captain Picard or the Borg queen), the game logic should move the ship the same way.
abstract class Entity { // Ship's Input variables: protected Vector2 desiredFocalDirection; // The direction the pilot wants the ship to face protected Vector2 desiredEngineFireDirection; // The direction the helmsman wants the ship to move protected bool activatePrimaryEquipment; // Should you attempt to activate any onboard equipment? // Other properties omitted... you get the idea. #region Public Properties public Vector2 DesiredFocalDirection { get { return desiredFocalDirection; } } public Vector2 DesiredEngineFireDirection { get { return desiredEngineFireDirection; } } public bool ActivatePrimaryEquipment { get { return activatePrimaryEquipment; } } #endregion public Entity(){ } public abstract void LoadContent(ContentManager content); // Load any resources derived entities need (ex: screen output information for the player to make decisions, or an AI personality) public abstract void UnloadContent(); // Clean up! public abstract void Update(GameTime gameTime) // Update your ShipInputVariables! public virtual void Initialize(Ship givenShip) { shipControlled = givenShip; shipControlled.SetControllingEntity(this); } }
These Entities are maintained by something outside the physics or gameplay calculations, and the "Ship's Input Variables" are set in derived classes: PlayerEntity and BotEntity. In PlayerEntity, they are set by the controller/keyboard input, and in BotEntity: by the AI logic. Now, we just have to give each ship an Entity (via a SetControllingEntity method), from which it reads it's input variables during it's Update phase. To help clarify, have a much-simplified UML diagram:
So that's it, composition wins out over inheritance... this time. What's even better? We have a clean separation of responsibility! LocalSpace handles all the physics engine calculations and collision detection, the PlayerEntity handles translating input device states into intention, and the BotEntity handles translating AI decision-making into intention. Heck, throw an EntityManagement class in there storing a collection (or ObjectPool, anyone?) of Entities to manage adding/removing players: when a player leaves, swap his ship's ControllingEntity with a BotEntity (that's been learning his gameplay style... again, I'm not creating SkyNet), and the action can continue when your friend gets called home for dinner! Feeling like making things more awesome? Create a new class derived from Entity: SquadronEntity, that contains a list of BotEntities, and can override decisions made by the individual bots for a coordinated battle strategy!
... this is how the Technological Singularity will come about, isn't it?...