Looking for a code/desgin review of continous map implemention

Started by
14 comments, last by slayemin 11 years, 5 months ago

Hold on, I'm not quite sure I understand how you're describing your maps...

Is it a tile based system, where you have a 100x100 grid of tiles, all rendered on the screen at once?
Or is it a system where each map grid is an area which fills up the entire screen (like old school Zelda) and you move in the cardinal directions to go to the next area?


Not like Zelda. The world is, say, a 100x100 grid of tiles. If you are in tile, say, 5_5 and within a boundry inside of that tile, then that is the only tile loaded. If you pass that inter boundry then neigboring tile are then loaded. Only a maximum of four tiles will ever be loaded. So if you are in 5_5 and move the northeast corner of that tile, then the following maps get loaded: 6_5 (east tile), 5_4 (north tile), and 6_4 (NE tile), and 5_5 is still loaded. Once you move into another tile and move within it's inter boundry, you can unload the older tiles, leaving only the current tile you are in in memory.
Advertisement

Hello Valgor

I am working on the same thing as you, except I call my "tiles" "cells" instead, because the word "tile" is reserved for little blue and brown bricks that make up the world. What I do is I keep a 3x3 2d array. array[1][1] is the center cell, and the others are the neighbor cells. Every time the player changes to a new cell, all the surrounding 8 cells are loaded too.


Using the word "cell" does sound like a better idea! I started out with the idea you have, using the surrounding 8 cells, but I thought there would be too much over head updating and moving content around the data structure that holds the current world (in your case, an array). So if you move to the east, then you have to load three new cells and move 6 cells in your arrays. I wanted to smiplfy this, so I am assuming I will be using larger maps and will only need to update at max 4 cells ever.
Okay, I think I understand.

I didn't see this mentioned in your post or in your code, so I'll bring it up. If the player is on the edge of the map, how do you handle it? If you have a grid of 100x100 tiles, the view is always centered on the player, and the player is located at (0,0) (top left corner), what happens? Do you keep the player centered or does the player move to the top left corner of the screen?

(x).ToString() + "_" + (y).ToString();
You don't need to surround your variables with parentheses.

I think your whole approach is flawed, and I'm not saying that to be mean. Here is what I'd change:
1. Use a camera.
2. Store your tiles in a 2D array.
3. Use a quad tree for collision detection.
4. Use "layers" in your map (terrain, static terrain objects, creatures & items, etc).
5. Break your map class up into more objects.

Use a camera:
What you want to do is to create a camera viewing rectangle. If map objects are within the viewing rectangle of the camera, then they are rendered. If you treat your view as a viewing rectangle, you can do a lot more with it (panning, zooming in and out, centering on an object, tweening between points, etc). Rather than rendering everything relative to the character position, you render everything relative to the center position of the camera and, for the most part, keep the camera centered on the player. This makes it much easier to handle the map-edge cases. You could also do interesting features, where if your character is moving in a particular direction very quickly, you can adjust the veiwing area of the camera to show more of the world space in front of the character so that they have more time to react.

Store your tiles in a 2D array:
You can either store the tile objects directly, or just store an index in a 2D array. How you store your tiles should be independent of how you're moving on the tiles and viewing the tiles. Your character movement can be simplified into an X & Y position, and handled with requests to the map manager. Character tells the map "I am at this position. I want to move left one space", and the map manager reads the map array and says "Okay, that's a legal move! you can set your position there." or "Nope, that can't be done. There's a tree there!" or "Nope, you're on the edge of the map and you can't go left any further!"

Use a quad tree for collision detection:
Once I understood the concept, it didn't take me more than an hour or two to get a quad tree datastructure up and running. The idea is to put your spatial objects into the smallest box which will contain them, and keep dividing the box into fourths until its empty. Then, when you're doing collision detection, you only need to compare the objects in the largest box against all of its child cells. This changes your collision checks from O(N*N) to O(log N) (best case). The tiles which are being viewed would be all the tiles which collide with the camera viewing rectangle (don't necessarily need a quad tree for camera rect, you can also do a rect.Contains() call against all tiles in the game model which is more computationally expensive but works)

Use layers in your map:
Terrain is the first, bottom layer. This tends to make for boring maps because they're plain. So, create another layer and add trees, flowers, rocks, and any other terrain objects. These static terrain objects are non-moving, so this simplifies collision detection a bit (you're not checking if a tree is colliding with another tree every frame). Then, you'll also have another layer for moving objects (monsters, player characters, bullets, pick ups, etc). These objects may move every frame, so they can collide with other objects in their layer or objects in the static objects layer. By layering your map, you can be a bit more flexible. A tree can sit on a dirt tile or a grass tile, instead of creating a tree-dirt tile and tree-grass tile.

Break your map class up into more objects:
You're currently trying to do too much "stuff" in one class. Break it up a bit. You've currently got a class which contains map metadata, tile information, character information, character movement logic, screen resolution logic, and resource loading logic all in ONE class. This makes your class design VERY rigid and all of your object interactions are hardcoded. This makes your class have 430 lines of code. You want all of your objects to be as decoupled as possible and interacting with each other via class method calls. I like to use the model view controller design pattern. The model doesn't know about screen resolutions, it only cares about maintaining the game state and game logic, and updating it. The view doesn't care about the game logic, it only cares about correctly rendering the game state to the current rendering settings (resolution, anti-aliasing, shadows, etc). The controller doesn't care about rendering very much (except for getting its controls rendered, such as buttons and drop down menus), it only cares about interacting with the game model.
Some people like to break it out even further and add a data layer to handle data read/writes to and from a file or database. I generally include that in my game model objects.
So, I'd create at least the following classes:
-Map
-Tile
-Character
-Camera
-Player (inherits from character)
-Monster (inherits from character)
The map class would contain one or more 2D arrays of tiles, any meta data about the map (like map name, map size, etc), and methods for loading and saving to disk.
A tile class would contain tile specific information, such as the tile texture, tile dimensions, tile type, movement point costs, etc.
The character class would be an abstract game actor class which contains generic data about a character (hitpoints, name, position, dimensions, etc)
A player class would mostly have UI and controller specific methods
The monster class would be an extension of the character class, maybe with some added AI scripts to control it and do monster specific logic.
I like to keep a global class of loaded art resources (and call it a texture database, or TextureDB for short). Whenever I create a new object, I set its texture to point to one of those stored art assets. By doing this, I can centralize all of my resource loading into one object. Then, all I have to worry about is making sure that the TextureDB has all of the correct art assets loaded up. I can also load art assets into the TextureDB by reading a file for a list of needed art resources (art manifest), so if I load a game level, the level data would contain a list of all the art resources it uses. The textureDB would load those assets into memory, and off we go! :D

Warning: general hand waving to follow:

As for your rendering code, all you should be doing in the draw function is calling each object and telling it to render itself according to its current state. So, if you have a map object, your render code should be this:
Map.draw(spriteBatch);

Since the map contains a list of tiles which will know how to render themselves, it'll do the following:

//you'd also do a foreach for each layer, so you render terrain first, followed by static objects layer from top to bottom, followed by moving objects layer, etc.
foreach(Tile t in TileList)
{
t.draw(spriteBatch, position);
}


To move a character on a map, you'd make a request call to the map manager, something like this:

if(KeyboardInput == MoveLeft)
MyCharacter.Position = Map.MoveTo(MyCharacter.Position, new Vector2(-1,0));


And the map manager would get the request and handle it something like this:

public Vector2 MoveTo(Vector2 Pos, Vector2 Dir)
{
Vector2 RequestedTile = new Vector2(Pos.X + Dir.X, Pos.Y + Dir.Y);

if(Tile[RequestedTile.X][RequestedTile.Y].isPassable() and not Tile[RequestedTile.X][RequestedTile.Y].isOccupied() and blah blah blah conditions)
{
return new Vector(RequestedTile);
}
else
{
return Pos;
}
}
Thank you so much for taking the time to respond in so much detail! There is much to think on.

A player will never reach the edge of the world ( tile 0_0 at position 0,0) because the world will be one large island, or blocked by impassable mountains.

Using a camera rectrangle - I like this idea. I've thought about it before but figure that might be something to mess with later. QuadTree research and implemention was next in line after getting a continous map working.

When you say to store the tiles in a 2d array and/or have a TextureDB class, I just want to be clear you aren't suggesting we load all maps and textures into the memory? I'm loading maps from the harddrive as they are needed. I would toy with the idea of a buffer, but if the entire map is huge?

Thank you for the break down of my class and suggestions to the over all design!

When you say to store the tiles in a 2d array and/or have a TextureDB class, I just want to be clear you aren't suggesting we load all maps and textures into the memory? I'm loading maps from the harddrive as they are needed. I would toy with the idea of a buffer, but if the entire map is huge?


I think tile is the word commonly used for the blocks that make up the terrain, be it a rock, wall, tree, etc. So most likely slayermin refers to the blocks, not the room/cell that contains your player and the blocks.


but I thought there would be too much over head updating and moving content around the data structure that holds the current world


I understand. If that is your worry, how about this:
each cell ( I'll continue to call them cells, to keep my posts consistent) keeps track of a rectangle (x, y, w, h). The rectangle's x and y will be the position of the cell's upper left corner. w and h will be the cell's width and height. This rectangle represents the cell's area.

Another rectangle, I call it the area_of_load, will have a smaller area than a cell's rectangle.
Every frame, this area_of_load rectangle will be updated to have the same center as the player's center.
Next, check if the cell's area rectangle contains the area_of_load. If it does, no loading of adjacent cells is required. If it doesn't, then figure out which cell needs to be loaded, and load it.

Thats my initial approach, but I went with load all surrounding cells in the end for simplicity. Tell me what you think of this ;o

[quote name='Valgor' timestamp='1351723950' post='4995976']
When you say to store the tiles in a 2d array and/or have a TextureDB class, I just want to be clear you aren't suggesting we load all maps and textures into the memory? I'm loading maps from the harddrive as they are needed. I would toy with the idea of a buffer, but if the entire map is huge?

I think tile is the word commonly used for the blocks that make up the terrain, be it a rock, wall, tree, etc. So most likely slayermin refers to the blocks, not the room/cell that contains your player and the blocks.
[/quote]
Yes, this is right.

Initially, store each tile block texture in memory. If you're concerned about memory usage, you can store a list of the assets you need in a file manifest which is added to a level file. But, parsing that is extra work and may be totally unnecessary if your textures don't consume much memory (computers come with gigs of ram these days). I tend to wait until it becomes a problem before I start trying to solve it. It may not be the most efficient with computer resources, but it is the most efficient with my time.

Let the map in memory be an array of integers, where each integer corresponds to a map tile ID, and the array indices are the X & Y coordinates.

This topic is closed to new replies.

Advertisement