Separation of concerns in game architecture

Started by
6 comments, last by AntP 10 years, 10 months ago

I'm looking at writing a simple iOS game. I'm a .NET developer by trade but have only ever dabbled in writing games before. The game I'm writing is a relatively straightforward tile-based puzzle game. Since I have little experience of videogame architectures, I've done some Googling around for similar open source games and tutorials, to find out what the common design patterns are. I am looking, in particular, for the way the relationship between the physical, visual entities and their abstract, logical counterparts is managed. What I tend to see is something like this (pseudocode):


class Tile
{
    public int PosX;
    public int PosY;
    public string Value;
    public SpriteClass Sprite;
}

Now, when I see this, my architectural brain tells me that something smells bad. Tile is, in this instance, a logical representation of the individual Tile on a board. Its X and Y coordinates are graph coordinates, rather than physical screen coordinates. Its "physical" counterpart is its Sprite property.

I can't help but feel that Sprite should not - in fact - be managed by Tile at all, but that Tile should be exposed to some kind of independent manager that interprets the Tile object's position and creates the appropriate Sprite object, removing this tight coupling and making the Tile and the Sprite completely ignorant of each other.

Are my concerns unfounded? Is the above common practice in well-architected games? If not, can anyone provide any insight, tutorials or examples of a better way of managing the relationship between the physical and the logical?

Advertisement

It sounds reasonable to want to remove the direct dependence on sprite in this case but, on the other hand, there is a strong argument to be made that the spite "is" the tile in combination with the other data, it all goes together. Personally, in order to remove dependence on the sprite class itself, I would just represent the data as an id but leave the concept of the tile grouping of data together. So, when you move tiles around, do things with tiles, etc all the data is in one place, but the graphical side is only interpreted at the point of render which de-references the sprite id into the concrete class of sprite.

In general though, for something so simple and assuming standard C# reference counting, I'm not sure the effort is really worth it. It really depends if it bothers you that much. :)

If tile and sprite should be one thing it might become bothersome to animate a SpriteTile to move from one location to another smoothly (in case this would ever happen).

Anyway, this are very specific implementation details which would require a more specific description of the game.

I think this is generally handled by storing the tilemap as an array or 2D (or 3D) array of id numbers, then storing the tile images in a single texture in order of their id numbers. This allows the tilemap to be rendered efficiently from a single texture. You just iterate through each tile in the map and render that block from the texture into place. If you need to store passability data or other metadata you can just use a second array of packed data, such as a byte array where each byte is a bitmask indicating passability directions.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

Thanks for the responses, all. I've decided to go ahead and use logical "Board" and "Tile" classes that are independent of their visual representations and hold the gamestate in an abstracted layer in its entirety.

This does lead me to another question, though.

Suppose my game has a Bejeweled-like mechanism in which a tile can be destroyed and all of those above it must drop. Then, again, in a manner similar to Bewjeled (or even Tetris), the tiles surrounding the "moved" tiles are checked for some match-3-esque 'success' condition and, based on this, points are scored and flashed on-screen at the point at which they were scored (probably with a random offset) and the cycle then repeats - checking success conditions, destroying tiles, dropping them and so on until no success condition is found.

I am having trouble envisaging how to implement this with the MVC/observer pattern, i.e.:

  1. Detect input to destroy tile.
  2. Gamestate is updated - tile is removed and all tiles are shifted down.
  3. Gamestate notifies view of state change for each tile.
  4. View "slides" each tile down (including the new tile at the top).

Now, the problem I'm having is that - after the tiles are moved - the gamestate should also be checking if there are any success conditions and repeat the point-scoring process. But how do I make sure the view "keeps up" with these changes? I can't make the gamestate wait for the view because that would make the model dependent upon the view. Do I just let the gamestate steam on ahead, notifying the view as it goes and then queue updates up in the view? But this seems like - because the gamestate and view are out of sync - it will cause problems where a second "move" is taken before the tiles have finished dropping and breaking for the first one (as I'd like the player to be able to complete moves elsewhere on the board while the "chain" of moves is still occurring for their first move).

Alternatively, do I simply perform one iteration of point checking with the gamestate and then let it exist in an invalid state until the controller says, "hey, the view's done, check your points again?"

Thanks.

IMO the position of a tile is part of the model, not part of the view.

Of course, there are several ways of doing things, but the somewhat usual one is to run a game loop like this:

(1) capture input

(2) interpret input as actions

(3) apply that actions to the world state

(4) update the world state due to elapsed time (animation and simulated physics)

(5) render view

(6) end if the game state says so, otherwise go on with (1)

So, you wouldn't update the view on each single tile change but at a whole after all tiles (and perhaps other renderables) have been updated. The position of tiles would be controlled by animation or simulated physics. The view would do nothing more than displaying the world state.

If the end-of-game condition should be detected but animation should conclude before ending the game, an intermediate game state can be used. I.e. the win/loose detection leads to the intermediate state. Input handling is disabled already while inside that state. The state is left as soon as the model comes to rest. Something like that.

I think AntP is suggesting that the tile's position on the game board is part of the model, but the sliding/dropping animation might just be considered part of the graphical representation of the underlying game state - a transitioning animation between two game states. A different view could be plugged in which doesn't animate tiles at all. Consider chess: the game state can be captured simply by piece positions on the board, and a "move" event only needs to include the moving piece and the position it is moving to. There is some merit to having this abstract representation of the game's state unpolluted by any other information.

One solution is to make the tile's intermediate (sliding/dropping) position part of the model, as haegarr suggests. You might consider the fact that this makes the model less abstract/"pure" a disadvantage. However, I do believe that this is closer to the traditional MVC pattern. If you choose this approach, I suppose you might store only a "game board position" for each tile in the model, converting these to actual screen/world positions in the view (allowing changing tile size/spacing etc without modifying the model).

Another solution is to provide a mechanism for the view to signal that it is "busy" performing some animation in reaction to an event / change in game state. The model can then be informed of this, and wait for the view to finish before advancing the game. I have implemented something like this before, and it seems to work quite well in turn-based games (which might include your game if gameplay is effectively frozen as tiles are cascading). The disadvantage here is that there is now an "impure" connection between your model and view...

I think AntP is suggesting that the tile's position on the game board is part of the model, but the sliding/dropping animation might just be considered part of the graphical representation of the underlying game state - a transitioning animation between two game states.

Precisely this. What matters to the gamestate are the board coordinates of each tile; the model should not be concerned with how the state - or changes in state - are represented, so the physical screen coordinates are the responsibility of the view (the view being a logical abstraction of the actual rendered information). In theory, I should be able to plug in a completely different view without the gamestate model knowing any different.

It seems that it might not be possible (or - at least - conceptually straightforward) to separate these concerns completely in this case. Your first suggestion is something I was beginning to conclude myself, in that this not being a strictly turn-based game might mean that it is insufficient to store simple grid coordinates for each tile. For example, move 2 can be initiated while move 1 is still "in progress." Supposing move 1 causes a large "cascade" of tiles dropping, scoring, breaking, dropping... for multiple iterations, a second move initiated by the player long before move 1 is complete might actually affect the outcome of move 1 if some tile that would be destroyed later in move 1's tile cascade is prematurely removed by an earlier iteration of move 2's cascade.

With this in mind, controlling the transition speed in the view would mean that a theoretically read-only representation of the gamestate could actually directly influence the way a set of moves plays out. This suggests that a coordinate-based gamestate model is insufficiently dynamic and that I do - in fact - need to make real-time positioning a part of my model. It seems that my current domain model is limited to a turn-based incarnation of what I want, where moves are completed one at a time and I could simply prevent the view from accepting further input until it has finished animating the previous move.

Thanks - this has provided real food for thought!

This topic is closed to new replies.

Advertisement