Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Valgor

Member Since 07 Oct 2012
Offline Last Active Nov 03 2012 01:10 PM

Topics I've Started

Looking for a code/desgin review of continous map implemention

28 October 2012 - 07:22 PM

Hello, incoming huge post! I am creating a 2D RPG where the entire world is open and continous. That is, there are no instances (for now). I am looking for any and all criticism over my design and code. I also hope this will help future developers. I will explain my ideas and walk through the design. At the bottom you can find my entire implemention.

The world is divided up into tiles where each tile is one image. Each tile is named "X_Y" where x and y are the coordinates with respect to the entire world. Suppose our playable character starts in 3_3, and the tile the character is actively in is called the center tile. The character renders to the center of the screen always (think Diablo), so we keep track of the map tile's (x,y) position and also the character's (x,y) position relative to the map. The difference in distance between the two is called the xOffSet and yOffSet.

There are two main steps in the Update method. (1) Checks to see if we need to load nearby tile and (2) see if we are no longer in the center tile.

(1) The center tile has an interboundry rectangle, so that if this boundary is crossed, we must load a nearby tile to render. Suppose we are in 3_3 moving east towards the edge so the 4_3 must be loaded. The code to check for this is here:

[source lang="csharp"]if (Math.Abs(mapPosition.X) > interBoundaryWidth){ if (!bEast) { Load(EAST, 1, 0); bEast = true; }}else if (bEast){ Unload(EAST); bEast = false;}[/source]

bEast is a boolean that says if the map to the east of the center tile is loaded. We see if our map's position has crossed the inter boundary. If so, we check if the eastern tile is loaded already so that we only load it once. We call the Load function telling it the direction relative to the center tile and that the coordinates going to added are 1 in the x direction and 0 in the y. Since we are in 3_3, this will load 4_3. bEast is set to true so we dont load it again. Backing up to the beginning, suppose the map's position is still within the interboundry. Then, if the Eastern map is loaded, we want to unload it and set the boolean to false. This step is needed if we traveled east, passed the interboundary, loaded 4_3, but then turned back west far enough that we don't need to render 4_3 anymore.

Similar sections of code are used for all possible directions including the need to load NE, NW, SE, and SW maps.

(2) This section sees if our character has moved out of the center tile and into another. Continuing with the east example, suppose our character moves from being in 3_3 into 4_3.

[source lang="csharp"]if (Math.Abs(mapPosition.X) + (GraphicsDeviceManager.DefaultBackBufferWidth / 2) > tileWidth){ foreach (MapTile bt in mapTiles) { if (bt.direction == CENTER) bt.direction = WEST; else if (bt.direction == NORTH) bt.direction = NW; else if (bt.direction == SOUTH) bt.direction = SW; } foreach (MapTile bt in mapTiles) { if (bt.direction == EAST) { bt.direction = CENTER; mapName = bt.name; } else if (bt.direction == NE) bt.direction = NORTH; else if (bt.direction == SE) bt.direction = SOUTH; }worldManager.setCharacterPosition(new Vector2(characterPosition.X + tileWidth, characterPosition.Y));[/source]

The first if statement sees if we have crossed outside of the map tile's width and thus into a new map tile. If we have, we want to update all the tiles to reflect the direction on the new center tile. Consider this crude drawing that maps each possible tile into the new direction. Note that only the N/NE or S/SE sections could possibly be loaded at the same time since the north and south tiles cannot be loaded at the same time because the maps are bigger than our screen. "C" is for "Center" which is where our character is located.

--------- -----------
| N | NE | | NW | N |
---------- ------------
| C | E | ---> | W | C |
---------- ------------
|S | SE | | SW | S |
---------- ------------

So our center tile becomes the west tile, and the west tile becomes the center tile, and so on for the other tiles. The order of changing the directions is important, which is why I have two foreach loops. Lastly, we update the character's position to that it starts on the far left side of the new center tile.

Finally, the rendering is based on the tile's direction which can be seen in my code below. If you have read this far, please leave a comment! I'm open to improvements and hopefully would like to help others tackling this problem. Thanks for reading!

[source lang="csharp"]namespace Arkenstone{ // The Map class contains and manages up to four total MapTiles. class Map { String mapName; // This will be the name of the map the playable character is in. List<MapTile> mapTiles; // Contains up to 4 loaded tiles. int mapListSize = 3; // Size limit for backgroundTiles. float xOffSet = (GraphicsDeviceManager.DefaultBackBufferWidth - 48) / 2; // = 376, 48 = mainCharacter's spirte width float yOffSet = (GraphicsDeviceManager.DefaultBackBufferHeight - 72) / 2; // = 264, 72 = mainCharacter's sprite height int tileHeight; // All map tiles are the same height. int tileWidth; // All map tiles are the same width. int interBoundaryWidth; // Inside width boundary of a map tile used to know when to load other maps. int interBoundaryHeight; // Inside height boundary of a map tile used to know when to load other maps. bool bEast, bWest, bNorth, bSouth; // Directional booleans set to true when a map is loaded. bool bNE, bSE, bSW, bNW; // Directional booleans set to true when a map is loaded. // Directional strings. private const string CENTER = "CENTER"; private const string NORTH = "NORTH"; private const string SOUTH = "SOUTH"; private const string EAST = "EAST"; private const string WEST = "WEST"; private const string NE = "NE"; private const string SE = "SE"; private const string NW = "NW"; private const string SW = "SW"; Vector2 characterPosition; // Character's position relative to the map. That is, with the offsets. ContentManager contentManager; // Keep a copy of the content manager since it is used so often. Vector2 mapPosition; //top x,y for the map tile used for drawing. public Map() { // Only have 4 maps total, ever. mapTiles = new List<MapTile>(mapListSize); // Create new tiles. MapTile mapTile1 = new MapTile(); MapTile mapTile2 = new MapTile(); MapTile mapTile3 = new MapTile(); MapTile mapTile4 = new MapTile(); // Fill the list. mapTiles.Add(mapTile1); mapTiles.Add(mapTile2); mapTiles.Add(mapTile3); mapTiles.Add(mapTile4); } // Loads map the first map "x_y" public void LoadContent(int x, int y, ContentManager contentManager) { try { this.contentManager = contentManager; mapName = (x).ToString() + "_" + (y).ToString(); mapTiles[0].LoadContent(this.contentManager, mapName, CENTER); tileHeight = mapTiles[0].Source.Height; tileWidth = mapTiles[0].Source.Width; interBoundaryHeight = mapTiles[0].innerBoundary.Height; interBoundaryWidth = mapTiles[0].innerBoundary.Width; // When the character is first loaded, no other map tiles are in view. bWest = bNorth = bEast = bSouth = false; bNE = bSE = bSW = bNW = false; } catch (Exception e) { Console.WriteLine(mapName + " does not exist."); } } public void Update(WorldManager worldManager) { // Get the character's position relitive to the map. characterPosition = worldManager.mainCharacter.mapPosition; // Set the top x,y for the map mapPosition = new Vector2(-xOffSet + characterPosition.X, -yOffSet + characterPosition.Y); // If we are outside the inter boundaries of the center tile, load the next tile so that it can be rendered. #region Map loading // East if (Math.Abs(mapPosition.X) > interBoundaryWidth) { if (!bEast) { Load(EAST, 1, 0); bEast = true; } } else if (bEast) { Unload(EAST); bEast = false; } // South if (Math.Abs(mapPosition.Y) > interBoundaryHeight) { if (!bSouth) { Load(SOUTH, 0, 1); bSouth = true; } } else if (bSouth) { Unload(SOUTH); bSouth = false; } // West if (mapPosition.X > 0) { if (!bWest) { Load(WEST, -1, 0); bWest = true; } } else if (bWest) { Unload(WEST); bWest = false; } // North if (mapPosition.Y > 0) { if (!bNorth) { Load(NORTH, 0, -1); bNorth = true; } } else if (bNorth) { Unload(NORTH); bNorth = false; } // NE if (mapPosition.Y > 0 && Math.Abs(mapPosition.X) > interBoundaryWidth) { if (!bNE) { Load(NE, 1, -1); bNE = true; } } else if (bNE) { Unload(NE); bNE = false; } // NW if (mapPosition.Y > 0 && mapPosition.X > 0) { if (!bNW) { Load(NW, -1, -1); bNW = true; } } else if (bNW) { Unload(NW); bNW = false; } // SE if (Math.Abs(mapPosition.Y) > interBoundaryHeight && Math.Abs(mapPosition.X) > interBoundaryWidth) { if (!bSE) { Load(SE, 1, 1); bSE = true; } } else if (bSE) { Unload(SE); bSE = false; } // SW if (Math.Abs(mapPosition.Y) > interBoundaryHeight && mapPosition.X > 0) { if (!bSW) { Load(SW, -1, 1); bSW = true; } } else if (bSW) { Unload(SW); bSW = false; } #endregion // This region checks if the character has moved out of the center tile, and updates accordingly so we are back in the center tile. #region Moved In Positions // Moved to the west square if (mapPosition.X > (GraphicsDeviceManager.DefaultBackBufferWidth / 2)) { // Moving from (x,y) to (x-1, y) foreach (MapTile bt in mapTiles) { if (bt.direction == CENTER) bt.direction = EAST; else if (bt.direction == NORTH) bt.direction = NE; else if (bt.direction == SOUTH) bt.direction = SE; } foreach (MapTile bt in mapTiles) { if (bt.direction == WEST) { bt.direction = CENTER; mapName = bt.name; // Updates the name of the map our character is in. } else if (bt.direction == NW) bt.direction = NORTH; else if (bt.direction == SW) bt.direction = SOUTH; } // Update the character's position. worldManager.setCharacterPosition(new Vector2 (characterPosition.X - tileWidth, characterPosition.Y)); } // Moved to the east tile. else if (Math.Abs(mapPosition.X) + (GraphicsDeviceManager.DefaultBackBufferWidth / 2) > tileWidth) { foreach (MapTile bt in mapTiles) { if (bt.direction == CENTER) bt.direction = WEST; else if (bt.direction == NORTH) bt.direction = NW; else if (bt.direction == SOUTH) bt.direction = SW; } foreach (MapTile bt in mapTiles) { if (bt.direction == EAST) { bt.direction = CENTER; mapName = bt.name; } else if (bt.direction == NE) bt.direction = NORTH; else if (bt.direction == SE) bt.direction = SOUTH; } worldManager.setCharacterPosition(new Vector2(characterPosition.X + tileWidth, characterPosition.Y)); } // Moved to the north tile else if (mapPosition.Y > (GraphicsDeviceManager.DefaultBackBufferHeight / 2)) { foreach (MapTile bt in mapTiles) { if (bt.direction == CENTER) bt.direction = SOUTH; else if (bt.direction == WEST) bt.direction = SW; else if (bt.direction == EAST) bt.direction = SE; } foreach (MapTile bt in mapTiles) { if (bt.direction == NORTH) { bt.direction = CENTER; mapName = bt.name; } else if (bt.direction == NE) bt.direction = EAST; else if (bt.direction == NW) bt.direction = WEST; } worldManager.setCharacterPosition(new Vector2(characterPosition.X, characterPosition.Y - tileHeight)); } // Moved to the south tile. else if (Math.Abs(mapPosition.Y) + (GraphicsDeviceManager.DefaultBackBufferHeight / 2) > tileHeight) { foreach (MapTile bt in mapTiles) { if (bt.direction == CENTER) bt.direction = NORTH; else if (bt.direction == WEST) bt.direction = NW; else if (bt.direction == EAST) bt.direction = NE; } foreach (MapTile bt in mapTiles) { if (bt.direction == SOUTH) { bt.direction = CENTER; mapName = bt.name; } else if (bt.direction == SE) bt.direction = EAST; else if (bt.direction == SW) bt.direction = WEST; } worldManager.setCharacterPosition(new Vector2(characterPosition.X, characterPosition.Y + tileHeight)); } #endregion } // Loads a BackgroundTile named "a_b". public void Load(string direction, int a, int b) { // Search for an empty slot in the list to load a map tile. foreach (MapTile backgroundTile in mapTiles) { // This if statement is for safety so we don't accidently try to load a tile that is already loaded. if (backgroundTile.direction == direction) { // Map is already loaded. break; } if (!backgroundTile.isLoaded) { // Deconstruct the name to get the grid number String[] coordinates = mapName.Split('_'); int x = Convert.ToInt32(coordinates[0]); int y = Convert.ToInt32(coordinates[1]); // Example: we are in "4_5" and moving east into the view of the next eastern square. // We then want to load map "5_5". The a and b parameters have the required offset. // That is, a=1 and b=0. string newMapName = (x + a).ToString() + "_" + (y + b).ToString(); backgroundTile.LoadContent(this.contentManager, newMapName, direction); break; } } } // Unloads the BackgroundTile in the given direction. public void Unload(string direction) { foreach (MapTile backgroundTile in mapTiles) { if (backgroundTile.direction == direction) { backgroundTile.UnloadContent(contentManager); break; } } } public void Draw(SpriteBatch spriteBatch, WorldManager worldManager) { // Character's position does not change between the world map's update and the world map's draw, but // keeping this for safety in case I change the order in the future. Could be deleted later. characterPosition = worldManager.mainCharacter.mapPosition; // Update top x,y for the map mapPosition = new Vector2(-xOffSet + characterPosition.X, -yOffSet + characterPosition.Y); // Draws squares to the screen foreach (MapTile backgroundTile in mapTiles) { if (backgroundTile.isLoaded) { // Draw the tiles based on their directional position. switch (backgroundTile.direction) { case CENTER: backgroundTile.Draw(spriteBatch, mapPosition); break; case EAST: backgroundTile.Draw(spriteBatch, new Vector2(Math.Abs(tileWidth) - Math.Abs(mapPosition.X), mapPosition.Y)); break; case WEST: backgroundTile.Draw(spriteBatch, new Vector2(-Math.Abs(tileWidth) + Math.Abs(mapPosition.X), mapPosition.Y)); break; case NORTH: backgroundTile.Draw(spriteBatch, new Vector2(mapPosition.X, -Math.Abs(tileHeight) + Math.Abs(mapPosition.Y))); break; case SOUTH: backgroundTile.Draw(spriteBatch, new Vector2(mapPosition.X, Math.Abs(tileHeight) - Math.Abs(mapPosition.Y))); break; case NW: backgroundTile.Draw(spriteBatch, new Vector2(-Math.Abs(tileWidth) + Math.Abs(mapPosition.X), -Math.Abs(tileHeight) + Math.Abs(mapPosition.Y))); break; case NE: backgroundTile.Draw(spriteBatch, new Vector2(Math.Abs(tileWidth) - Math.Abs(mapPosition.X), -Math.Abs(tileHeight) + Math.Abs(mapPosition.Y))); break; case SE: backgroundTile.Draw(spriteBatch, new Vector2(Math.Abs(tileWidth) - Math.Abs(mapPosition.X), Math.Abs(tileHeight) - Math.Abs(mapPosition.Y))); break; case SW: backgroundTile.Draw(spriteBatch, new Vector2(-Math.Abs(tileWidth) + Math.Abs(mapPosition.X), Math.Abs(tileHeight) - Math.Abs(mapPosition.Y))); break; } } } } }}[/source]

2D RPG Example Games

20 October 2012 - 07:38 AM

Hello,

Does anyone know of any 2D RPG games that are free or open sourced?

I'm looking for examples of how other games of this genre handle various tasks and organization.

Thanks!

2D Animation Scripting

08 October 2012 - 05:32 PM

I posted this in the graphics section and recieved no replies, so maybe I'll have better luck here!

I've been working on a 2D RPG in XNA. While browsing tutorials and videos, I've heard them mention doing animation in a seperate script file. Since tutorials are always brief and never indepth, the following questions persist:

1) Are scripting languages faster than C# for animations? Is that why we would want to use them?

2) Is this only for time/movie like animations and not animations that require input (such as pressing up to animate my sprite to move up compared to a cut scene that plays regardless of input).

3) Good, possible, and best scripting lanuage to use for animation?

4) Any other reason to use scripts in a game?

Thanks!

2D Animation Scripting

07 October 2012 - 08:40 AM

I've been working on a 2D RPG in XNA. While browsing tutorials and videos, I've heard them mention doing animation in a seperate script file. Since tutorials are always brief and never indepth, the following questions persist:

1) Are scripting languages faster than C# for animations? Is that why we would want to use them?

2) Is this only for time/movie like animations and not animations that require input (such as pressing up to animate my sprite to move up compared to a cut scene that plays regardless of input).

3) Good, possible, and best scripting lanuage to use for animation?

Thanks!

PARTNERS