Search code examples
c#architecturexnaxna-4.0

Game Architecture


I have a question about a XNA game I'm making, but it is also a generic question for future games. I'm making a Pong game and I don't know exactly what to update where, so I'll explain better what I mean. I have a class Game, Paddle and Ball and, for example, I want to verify the collisions between the ball with the screen limits or the paddles, but I come across 2 approaches to do this:

Higher Level Approach - Make paddle and ball properties public and on the Game.Update check for collisions?

Lower Level Approach- I supply every info I need (the screen limits and paddles info) to the ball class (by parameter, or in a Common public static class) and on the Ball.Update I check for collisions?

I guess my question in a more generic way is:

Does an object need to know how to update and draw itself, even having dependencies from higher levels that somehow are supplied to them?

or

Is better to process it at higher levels in Game.Update or Game.Draw or using Managers to simplify code?

I think this is a game logic model question that applies to every game. I don't know if I made my question clear, if not, feel free to ask.


Solution

  • The difficult part of answering your question is that you're asking both: "what should I do now, for Pong" and "what should I do later, on some generic game".


    To make Pong you don't even need Ball and Paddle classes, because they're basically just positions. Just stick something like this in your Game class:

    Vector2 ballPosition, ballVelocity;
    float leftPaddlePosition, rightPaddlePosition;
    

    Then just update and draw them in whatever order suits you in your Game's Update and Draw functions. Easy!


    But, say you want to create multiple balls, and balls have many properties (position, velocity, rotation, colour, etc): You might want to make a Ball class or struct that you can instance (same goes for the paddles). You could even move some functions into that class where they are self-contained (a Draw function is a good example).

    But keep the design concept the same - all of the object-to-object interaction handling (ie: the gameplay) happens in your Game class.

    This is all just fine if you have two or three different gameplay elements (or classes).


    However let's postulate a more complicated game. Let's take the basic pong game, add some pinball elements like mutli-ball and player-controlled flippers. Let's add some elements from Snake, say we have an AI-controlled "snake" as well as some pickup objects that either the balls or the snake can hit. And for good measure let's say the paddles can also shoot lasers like in Space Invaders and the laser bolts do different things depending on what they hit.

    Golly that is a huge mess of interaction! How are we going to cope with it? We can't put it all in Game!

    Simple! We make an interface (or an abstract class or a virtual class) that each "thing" (or "actor") in our game world will derive from. Here is an example:

    interface IActor
    {
        void LoadContent(ContentManager content);
        void UnloadContent();
    
        void Think(float seconds);
        void UpdatePhysics(float seconds);
    
        void Draw(SpriteBatch spriteBatch);
    
        void Touched(IActor by);
    
        Vector2 Position { get; }
        Rectangle BoundingBox { get; }
    }
    

    (This is only an example. There is not "one true actor interface" that will work for every game, you will need to design your own. This is why I don't like DrawableGameComponent.)

    Having a common interface allows Game to just talk about Actors - instead of needing to know about every single type in your game. It is just left to do the things common to every type - collision detection, drawing, updating, loading, unloading, etc.

    Once you're in the actor, you can start worrying about specific types of actor. For example, this might be a method in Paddle:

    void Touched(IActor by)
    {
        if(by is Ball)
             ((Ball)by).BounceOff(this.BoundingBox);
        if(by is Snake)
             ((Snake)by).Kill();
    }
    

    Now, I like to make the Ball bounced by the Paddle, but it is really a matter of taste. You could do it the other way around.

    In the end you should be able to stick all your actors in a big list that you can simply iterate through in Game.

    In practice you might end up having multiple lists of actors of different types for performance or code simplicity reasons. This is ok - but in general try to stick to the principle of Game only knowing about generic actors.

    Actors also may want to query what other actors exist for various reasons. So give each actor a reference to Game, and make the list of actors public on Game (there's no need to be super-strict about public/private when you're writing gameplay code and it's your own internal code.)


    Now, you could even go a step further and have multiple interfaces. For example: one for rendering, one for scripting and AI, one for physics, etc. Then have multiple implementations that can be composed into objects.

    This is described in detail in this article. And I've got a simple example in this answer. This is an appropriate next step if you start finding that your single actor interface is starting to turn into more of a "tree" of abstract classes.