views:

1330

answers:

12

I recently started working on a small game for my own amusement, using Microsoft XNA and C#. My question is in regards to designing a game object and the objects that inherit it. I'm going to define a game object as something that can be rendered on screen. So for this, I decided to make a base class which all other objects that will need to be rendered will inherit, called GameObject. The code below is the class I made:

class GameObject
{
    private Model model = null;
    private float scale = 1f;
    private Vector3 position = Vector3.Zero;
    private Vector3 rotation = Vector3.Zero;
    private Vector3 velocity = Vector3.Zero;
    private bool alive = false;
    protected ContentManager content;

    #region Constructors
    public GameObject(ContentManager content, string modelResource)
    {
        this.content = content;
        model = content.Load<Model>(modelResource);
    }
    public GameObject(ContentManager content, string modelResource, bool alive)
        : this(content, modelResource)
    {
        this.alive = alive;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale)
        : this(content, modelResource, alive)
    {
        this.scale = scale;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position)
        : this(content, modelResource, alive, scale)
    {
        this.position = position;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation)
        : this(content, modelResource, alive, scale, position)
    {
        this.rotation = rotation;
    }
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity)
        : this(content, modelResource, alive, scale, position, rotation)
    {
        this.velocity = velocity;
    }
    #endregion
}

I've left out extra methods that do things such as rotate, move, and draw the object. Now if I wanted to create another object, like a ship, I'd create a Ship class, which would inherit GameObject. Sample code below:

class Ship : GameObject
{
    private int num_missiles = 20; // the number of missiles this ship can have alive at any given time
    private Missile[] missiles;
    private float max_missile_distance = 3000f; // max distance a missile can be from the ship before it dies

    #region Constructors
    public Ship(ContentManager content, string modelResource)
        : base(content, modelResource)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource , bool alive)
        : base(content, modelResource, alive)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale)
        : base(content, modelResource, alive, scale)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position)
        : base(content, modelResource, alive, scale, position)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation)
        : base(content, modelResource, alive, scale, position, rotation)
    {
        InitShip();
    }
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity)
        : base(content, modelResource, alive, scale, position, rotation, velocity)
    {
        InitShip();
    }
    #endregion
}

Again, I've left out any extra Ship-specific methods, like firing a missile. Do you think that this sort of design is good or should it be improved somehow or changed completely? It seems like the constructors for child classes is messy, but maybe that's the only way to do it. I've never done anything like this and am wondering if I'm way off track.

+3  A: 

This design doesn't separate the UI parts of an object and the behavior parts of an object, the "model" and "view" so to speak. This is not necessarily a bad thing, but you might find the following refactorings difficult if you continue with this design:

  • Re-skinning the game by changing all the art assets
  • Changing the behavior of many different in-game objects, for example the rules that determine whether or not an object is alive
  • Changing to a different sprite engine.

But if you are OK with these tradeoffs and this design makes sense to you, I see nothing egregiously wrong with it.

Ryan
+6  A: 

Personally I find the sheer number of constructors you have a little offputting, but that's your choice, nothing fundamentally wrong with it :)

With regards to the general strategy of deriving game objects from a common base, that's a pretty common way to do things. Standard, even. I've tended to start using something far more akin to MVC, with incredibly lightweight "model" game objects that contain only data.

Other common approaches I've seen in XNA include / things you may want to consider:

  • Implementation of an IRenderable interface rather than doing any render code in the base or derived classes. For instance, you may want game objects that are never rendered - waypoints or somesuch.
  • You could make your base class abstract, you're not likely to want to ever instantiate a GameObject.

Again though, minor points. Your design is fine.

NM
@NM: +1, here, here less constructors more properties!
sixlettervariables
+1  A: 

Depending on how often the constructors would be called, you could consider making all the extra parameters nullable and just passing null to them when they don't have values. Messier creating the objects, fewer constructors

Davy8
+1  A: 

I went down this path when first creating games in XNA, but next time I would do more of a model/view type of approach. The Model objects would be what you deal with in your Update loop, and your Views would be used in your Draw loop.

I got in trouble with not separating the model from the view when I wanted to use my sprite object to handle both 3D and 2D objects. It got real messy real quick.

Bill Reiss
+3  A: 

I am very interested in your storing method for rotation in a vector. How does this work? If you store the angles of X,Y,Z-axis (so called Euler-angles), you should rethink this idea, if you want to create a 3D game, as you will meet shortly after your first rendering the evil GIMBAL LOCK

Personally I do not like so much constructors there, as you can provide 1 constructor and set all not needed params to null. in this way your interface stays cleaner.

Peter Parker
I wasn't aware of gimbal lock. I'm storing angles X,Y,Z in a vector, like you said. I looked at your link and tried recreating it by having X=90,Y=0,Z=0 (this flipped the model up). I then increased Y and it rotated as expected. But increasing Z made it appear as though it were rotating around Y.
Kobol
You'll want a representation that's a little more stable. I know that Second Life uses Quaternions for rotation operations to avoid these sorts of problems.
Mark Bessey
+1  A: 

The people here talking about separating Model from View are correct. To put it in more game developer oriented terms, you should have separate classes for GameObject and RenderObject. Think back to your main game loop:

// main loop
while (true) {
    ProcessInput();  // handle input events
    UpdateGameWorld();  // update game objects
    RenderFrame();  // each render object draws itself
}

You want the process of updating your game world and rendering your current frame to be as separate as possible.

Parappa
In my example of a Ship object, would it inherit GameObject and have a member variable of RenderObject type? Or would you have a RenderObject object available to the main game loop, which could take a GameObject and render it? Or none of the above?
Kobol
+2  A: 

I also heavily recommend looking into a component based design using composition rather than inheritance. The gist of the idea is to model object based on their underlying properties, and allow the properties to do all of the work for you.

In your case, a GameObject may have properties like being able to move, being renderable, or having some state. These seperate functions (or behaviors) can be modeled as objects and composed in the GameObject to build the functionality you desire.

Honestly though, for a small project you're working on I would focus on making the game functional rather than worry about details like coupling and abstraction.

Ron Warholic
+1  A: 

Alternatively you can make a struct or helper class to hold all the data and have the many constructors in the struct, that way you only have to have tons constructors in 1 object rather than in every class that inherits from GameObject

Davy8
+4  A: 

Not only is the number of constructors a potential issue (especially having to re-define them for each derived class) - but the number of parameters to the constructor can become an issue. Even if you make the optional parameters nullable, it becomes difficult to read and maintain the code later.

If I write:

new Ship(content, "resource", true, null, null, null, null);

What does the second NULL do?

It makes code more readable (but more verbose) to use a structure to hold your parameters if the parameter list goes beyond four or five parameters:

GameObjectParams params(content, "resource");
params.IsAlive = true;
new Ship(params);

There are a lot of ways this can be done.

Jeff B
+1  A: 

First game? Start simple. Your base GameObject (which in your case is more appropriately called DrawableGameObject [note the DrawableGameComponent class in XNA]) should only contain the information that belongs to all GameObjects. Your alive and velocity variables don't really belong. As others have mentioned, you probably don't want to ever be mixing your drawing and update logic.

Velocity should be in a Mobile class, which should handle the update logic to change the position of the GameObject. You may also want to consider keeping track of acceleration, and having that steadily increase as the player holds a button down, and steadily decrease after the player releases the button (instead of using a constant acceleration)... but that's more a game design decision than a class organization decision.

What "alive" means can be very different based on context. In a simple space shooter, something that isn't alive should probably be destroyed (possibly replaced with a few chunks of space debris?). If it's a player that respawns, you're going to want to make sure the player is separate from the ship. The ship doesn't respawn, the ship is destroyed and the player gets a new ship (recycling the old ship in memory instead of deleting it and creating a new one will use fewer cycles - you can simply either move the ship into no-man's-land or pull it out of your game scene entirely and put it back after respawn).

Another thing that you should be wary of... Do not let your players look directly up or down on your vertical axis, or you'll run into, as Peter Parker mentioned, Gimbal Lock issues (which you do not want to mess with if you're doing game development for fun!). Better yet... start with 2D (or at the very least, limit movement to only 2 dimensions)! It's a lot easier when you're new to game development.

I'd suggest following the XNA Creators Club Getting Started Guides before doing any more programming, however. You should also consider looking for some other more general game development guides to get a few alternative suggestions.

Illandril
+3  A: 

Since you are asking a design-related question, I will try to give some hints on that, rather than on the lots of code you posted, because others have already started on that.

Games lend themselves very much to the Model-View-Controller pattern. You are explicitly talking about the display part, but think about it: you have vectors in there, etc., and that usually is part of what you are modelling. Think of the game world and the objects in there as the models. They have a position, they might have velocity and direction. That's the model part of MVC.

I noticed your wish to have the ship fire. You will probably want the mechanic to make the ship fire in a controller class, something that takes keyboard input, for example. And this controller class will send messages to the model-part. A sound and easy think you will probably want is to have some fireAction()-method on the model. On your base-model, that might be an overridable (virtual, abstract) method. On the ship it might add a "bullet" to the game world.

Seeing how I wrote about behaviour of your model so much already, here's another thing that has helped me plenty: use the strategy pattern to make behaviour of classes exchangeable. The strategy pattern is also great if you think AI and want behaviour to change somewhen in the future. You might not know it now, but in a month you might want ballistic missiles as the default missiles and you can easily change that later if you had used the strategy pattenr.

So eventually you might want to really make something like a display class. It will have primitives like you named: rotate, translate, and so on. And you want to derive from it to add more functionality or change functionality. But think about it, once you have more than a ship, a special kind of ship, another special kind of ship, you'll run into derivation hell and you will replicate a lot of code. Again, use the strategy pattern. And remember to keep it simple. What does a Displayble class need to have?

  • Means to know its position relative to the screen. It can, thanks to the model of its in-world object and something like the gameworld's model!
  • It must know of a bitmap to display for its model and its dimensions.
  • It must know if anything should prevent it from drawing, if that is not handled by the drawing engine (i.e. another object occluding it).
mstrobl
A: 

Thanks to everyone that left an answer. They were all very helpful. There seems to be a general consensus that changing it around to use an MVC pattern would be best. I'm going to look further into exactly how to do that. I'll also be removing most of the constructors and will have just one constructor, because all of the arguments after modelResource aren't necessary to create the object, and they can all be changed later through method calls.

Kobol
It might not be exactly what you are looking for, but I have written a very short and small base for a jump'n run game once to illustrate use of (some) design patterns. It's C++ and uses OpenGL.If you're interested please post means to contact you.
mstrobl
Yea, I'd be interested in looking at that. You can email [email protected]. I know C++ and am familiar with OpenGL, so that will help. Thanks!
Kobol