Game Architecture

2019-01-16 09:18发布

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.

4条回答
可以哭但决不认输i
2楼-- · 2019-01-16 09:50

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.

查看更多
该账号已被封号
3楼-- · 2019-01-16 09:58

I agree with what Andrew said. I am just learning XNA as well and in my classes, for example your ball class. I'd have an Update(gametime) method and a Draw() method in it at the least. Usually an Initialize(), Load() as well. Then from the main game class I will call those methods from their respective cousins. This was before I learned about GameComponent. Here is a good article about if you should use that. http://www.nuclex.org/blog/gamedev/100-to-gamecomponent-or-not-to-gamecomponent

查看更多
一夜七次
4楼-- · 2019-01-16 09:59

You could see your ball and paddle as a component of your game, and XNA gives you the base class GameComponent that has an Update(GameTime gameTime) method you may override to do the logic. Additionally, there is also the DrawableGameComponent class, which comes with its own Draw method to override. Every GameComponent class has also a Game property which holds the game object that created them. There you may add some Services that your component can use to obtain information by itself.

Which approach you want to make, either have a "master" object that handles every interaction, or provide the information to the components and have them react themselves, is entirely up to you. The latter method is preferred in larger project. Also, that would be the object-oriented way to handle things, to give every entity its own Update and Draw methods.

查看更多
男人必须洒脱
5楼-- · 2019-01-16 10:03

You could also opt to start thinking about how different components of the game need to talk to each other.

Ball and Paddle, both are objects in the game and in this case, Renderable, Movable objects. The Paddle has the following criteria

It can only move up and down

  1. The paddle is fixed to one side of the screen, or to the bottom
  2. The Paddle might be controlled by the user (1 vs Computer or 1 vs 1)
  3. The paddle can be rendered
  4. The paddle can only be moved to the bottom or the top of the screen, it cannot pass it's boundaries

The ball has the following criteria

It cannot leave the boundaries of the screen

  1. It can be rendered
  2. Depending on where it gets hit on the paddle, you can control it indirectly (Some simple physics)
  3. If it goes behind the paddle the round is finished
  4. When the game is started, the ball is generally attached to the Paddle of the person who lost.

Identifying the common criteria you can extract an interface

public interface IRenderableGameObject
{
   Vector3 Position { get; set; }
   Color Color { get; set; }
   float Speed { get; set; }
   float Angle { get; set; }
}

You also have some GamePhysics

public interface IPhysics
{
    bool HasHitBoundaries(Window window, Ball ball);
    bool HasHit(Paddle paddle, Ball ball);
    float CalculateNewAngle(Paddle paddleThatWasHit, Ball ball);
}

Then there is some game logic

public interface IGameLogic
{
   bool HasLostRound(...);
   bool HasLostGame(...);
}

This is not all the logic, but it should give you an idea of what to look for, because you are building a set of Libraries and functions that you can use to determine what is going to happen and what can happen and how you need to act when those things happen.

Also, looking at this you can refine and refactor this so that it's a better design.

Know your domain and write your ideas down. Failing to plan is planning to fail

查看更多
登录 后发表回答