Jump to content

  • Log In with Google      Sign In   
  • Create Account

#ActualJosh Petrie

Posted 15 November 2012 - 11:13 AM

There are two basic ideas that I'm trying to drive you towards: the idea that a given class or interface should serve a single function ("single responsibility principle") and, to that end, the idea that while object-oriented design begins with the conceptualization of types as real world analogues, it doesn't (and shouldn't end there) -- you can have classes that don't correspond to real-world objects.

Your sprite manager code has some game-specific constructs, such as a specific texture variable for the player and the ball. It also handles game-specific initialization and update logic, such as StartGame and the collision checks (even the fact that it handles collision at all is problematic).

At a high level, I would instead expect to see this:
  • A type called "SpriteManager" should only understand how to manage a list of (generic) sprite objects and send them to the graphics card at render time. While these sprites may include position, width and height as data, that data is not necessarily what is used to perform collision. The idea that collision geometry and render geometry are distinct is a common practice and becomes very important with 3D games or even in 2D games when you have complicated shapes.
  • You should have a distinct entity for representing collision geometry; perhaps a "CollisionShape" interface contains collision bounds information. These collision entities should be managed by a "CollisionWorld" or similar high-level interface that understands the relative positions of all collision entities and can raise events or invoke callbacks when any set of entities enters or leaves collision (or remains in collision across more than one frame, that can be useful as well).

Your top-level "game" class should be the one that contains all the game specific logic and types. The game class can maintain a set of logical game objects -- either using a single common class or, if their functionality differs wildly enough, many classes. Since we're already attempting to generalize quite a lot, I will leave further generalization of the game logic entities to you and, for the purposes of this example, say we have a Ball class and a Player class.

The game creates a ball instance, and asks the sprite manager to create a corresponding sprite. That sprite is stored in the ball object. Similarly, the game asks the collision world to construct a collision entity with the ball's dimensions and initial position in the world. A reference to that entity is also stored in the ball. Similarly for the player.

Now the game loop consists of, approximately: collecting player input, applying those inputs to the player object's collision entity (to move it), then asking the collision world to update. This will cause the collision world to fire any callbacks registered concerning inter-object collision -- the game would have installed a callback to handle ball-ball collision, ball-wall collision, and ball-player collision, possibly. In those callbacks, appropriate action can be taken by the game class -- perhaps to destroy one or both entities, or reflect the path of one to cause a "bounce" effect, et cetera.

Finally, the game looks at every game entity it knows about (balls, player, et cetera) and transfers the appropriate logical properties -- such as position, or damage state, or whatever -- to the object's renderable representation (the sprite in this case). Then your game asks the sprite manager to render all your frames.

With only this first level of generalization and refactoring (and there's much more you can do on top of this), you should have two systems -- the collision handling stuff and the sprite handling stuff -- that are entirely game-agnostic and can be reused and extended without having to couple them to the idea of a breakout (or any other) kind of game. It's only at the highest level of abstraction where your game-specific logic and means of tying these two systems together exist.

#2Josh Petrie

Posted 15 November 2012 - 11:13 AM

There are two basic ideas that I'm trying to drive you towards: the idea that a given class or interface should serve a single function ("single responsibility principle") and, to that end, the idea that while object-oriented design begins with the conceptualization of types as real world analogues, it doesn't (and shouldn't end there) -- you can have classes that don't correspond to real-world objects.

Your sprite manager code has some game-specific constructs, such as a specific texture variable for the player and the ball. It also handles game-specific initialization and update logic, such as StartGame and the collision checks (even the fact that it handles collision at all is problematic).

At a high level, I would instead expect to see this:
A type called "SpriteManager" should only understand how to manage a list of (generic) sprite objects and send them to the graphics card at render time. While these sprites may include position, width and height as data, that data is not necessarily what is used to perform collision. The idea that collision geometry and render geometry are distinct is a common practice and becomes very important with 3D games or even in 2D games when you have complicated shapes.
You should have a distinct entity for representing collision geometry; perhaps a "CollisionShape" interface contains collision bounds information. These collision entities should be managed by a "CollisionWorld" or similar high-level interface that understands the relative positions of all collision entities and can raise events or invoke callbacks when any set of entities enters or leaves collision (or remains in collision across more than one frame, that can be useful as well).

Your top-level "game" class should be the one that contains all the game specific logic and types. The game class can maintain a set of logical game objects -- either using a single common class or, if their functionality differs wildly enough, many classes. Since we're already attempting to generalize quite a lot, I will leave further generalization of the game logic entities to you and, for the purposes of this example, say we have a Ball class and a Player class.

The game creates a ball instance, and asks the sprite manager to create a corresponding sprite. That sprite is stored in the ball object. Similarly, the game asks the collision world to construct a collision entity with the ball's dimensions and initial position in the world. A reference to that entity is also stored in the ball. Similarly for the player.

Now the game loop consists of, approximately: collecting player input, applying those inputs to the player object's collision entity (to move it), then asking the collision world to update. This will cause the collision world to fire any callbacks registered concerning inter-object collision -- the game would have installed a callback to handle ball-ball collision, ball-wall collision, and ball-player collision, possibly. In those callbacks, appropriate action can be taken by the game class -- perhaps to destroy one or both entities, or reflect the path of one to cause a "bounce" effect, et cetera.

Finally, the game looks at every game entity it knows about (balls, player, et cetera) and transfers the appropriate logical properties -- such as position, or damage state, or whatever -- to the object's renderable representation (the sprite in this case). Then your game asks the sprite manager to render all your frames.

With only this first level of generalization and refactoring (and there's much more you can do on top of this), you should have two systems -- the collision handling stuff and the sprite handling stuff -- that are entirely game-agnostic and can be reused and extended without having to couple them to the idea of a breakout (or any other) kind of game. It's only at the highest level of abstraction where your game-specific logic and means of tying these two systems together exist.

#1Josh Petrie

Posted 15 November 2012 - 11:12 AM

There are two basic ideas that I'm trying to drive you towards: the idea that a given class or interface should serve a single function ("single responsibility principle") and, to that end, the idea that while object-oriented design begins with the conceptualization of types as real world analogues, it doesn't (and shouldn't end there) -- you can have classes that don't correspond to real-world objects.
Your sprite manager code has some game-specific constructs, such as a specific texture variable for the player and the ball. It also handles game-specific initialization and update logic, such as StartGame and the collision checks (even the fact that it handles collision at all is problematic).
At a high level, I would instead expect to see this:
A type called "SpriteManager" should only understand how to manage a list of (generic) sprite objects and send them to the graphics card at render time. While these sprites may include position, width and height as data, that data is not necessarily what is used to perform collision. The idea that collision geometry and render geometry are distinct is a common practice and becomes very important with 3D games or even in 2D games when you have complicated shapes.
You should have a distinct entity for representing collision geometry; perhaps a "CollisionShape" interface contains collision bounds information. These collision entities should be managed by a "CollisionWorld" or similar high-level interface that understands the relative positions of all collision entities and can raise events or invoke callbacks when any set of entities enters or leaves collision (or remains in collision across more than one frame, that can be useful as well).
Finally your top-level "game" class should be the one that contains all the game specific logic and types. The game class can maintain a set of logical game objects -- either using a single common class or, if their functionality differs wildly enough, many classes. Since we're already attempting to generalize quite a lot, I will leave further generalization of the game logic entities to you and, for the purposes of this example, say we have a Ball class and a Player class.
The game creates a ball instance, and asks the sprite manager to create a corresponding sprite. That sprite is stored in the ball object. Similarly, the game asks the collision world to construct a collision entity with the ball's dimensions and initial position in the world. A reference to that entity is also stored in the ball. Similarly for the player.
Now the game loop consists of, approximately: collecting player input, applying those inputs to the player object's collision entity (to move it), then asking the collision world to update. This will cause the collision world to fire any callbacks registered concerning inter-object collision -- the game would have installed a callback to handle ball-ball collision, ball-wall collision, and ball-player collision, possibly. In those callbacks, appropriate action can be taken by the game class -- perhaps to destroy one or both entities, or reflect the path of one to cause a "bounce" effect, et cetera.
Finally, the game looks at every game entity it knows about (balls, player, et cetera) and transfers the appropriate logical properties -- such as position, or damage state, or whatever -- to the object's renderable representation (the sprite in this case). Then your game asks the sprite manager to render all your frames.
With only this first level of generalization and refactoring (and there's much more you can do on top of this), you should have two systems -- the collision handling stuff and the sprite handling stuff -- that are entirely game-agnostic and can be reused and extended without having to couple them to the idea of a breakout (or any other) kind of game. It's only at the highest level of abstraction where your game-specific logic and means of tying these two systems together exist.

PARTNERS