World map
The world,but not everything in it.
First create a new XNA game project in Visual studio.
Now lets get the world map into the game.
The map format in U4 was a little wierd, but made perfect sense.
The world is 256 by 256 chars, so 64K in size.
Instead of having all 64K of the map in memory at all times they split it into 1K blocks, 32 by 32 chars, organised from top left to bottom right.
WORLD.MAP is included in the resource section below.
Create an array to hold it called originalMap and load the file into it.
originalMap = new byte[256 * 256]; FileStream infile = new FileStream("WORLD.MAP", FileMode.Open, FileAccess.Read); infile.Read(originalMap, 0, 65536); infile.Close();
We want a nice easy map to navigate, so we will have to change the arrangement of the map.
Firstly we have to fiigure out how to get a char for a given x,y .
We work out which 1K block the char is in, then add in the offset within the cell.
So to get a char for a given x,y location we need to do this.
Firstly we have to fiigure out how to get a char for a given x,y .
We work out which 1K block the char is in, then add in the offset within the cell.
So to get a char for a given x,y location we need to do this.
private byte getOriginal(int x, int y) { int cellx = x / 32; int celly = y / 32; int bx = x % 32; int by = y % 32; int offset = (cellx * 1024) + (celly * 8192) + bx + (by * 32); return originalMap[offset]; }
Now this is a bit of a pain, and we don't want all that redundant code in the final version, so we will convert the original map to a two dimensional array straight away.
This is a trivial task now we have the original in memory.
Just create a two dimensional array and loop through all the x and y locations copying the char at x,y into the new array.
Do it in Initialize().
This is a trivial task now we have the original in memory.
Just create a two dimensional array and loop through all the x and y locations copying the char at x,y into the new array.
Do it in Initialize().
newMap = new byte[256, 256]; for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { a = getOriginal(x, y); newMap[x, y] = a; } }
Now we have a map we can index trivially.
The next stage is to get something on the screen.
We are lucky that the charset for U4 is already available. shapes.bmp in the resource section is the file you want.
Add it to your content and load it as a Texture2D called originalChars in your LoadContent() method.
The next stage is to get something on the screen.
We are lucky that the charset for U4 is already available. shapes.bmp in the resource section is the file you want.
Add it to your content and load it as a Texture2D called originalChars in your LoadContent() method.
originalChars = Content.Load('shapes');
Now to draw the map.
Shapes.png is organised as a vertical strip of 16 by 16 pixel chars. Which was very efficent in the old days.
It's terrible for modern graphics hardware, but we'll ignore that for the moment.
To get the pixels for a map char we just need to blit a rectangle from x=0, y=char*16. This is so easy in xna.
I have added a bit of code to setup the window size and the number of chars to display on screen, you can pick that up from the listing at the end, but the draw code is as simple as.
Shapes.png is organised as a vertical strip of 16 by 16 pixel chars. Which was very efficent in the old days.
It's terrible for modern graphics hardware, but we'll ignore that for the moment.
To get the pixels for a map char we just need to blit a rectangle from x=0, y=char*16. This is so easy in xna.
I have added a bit of code to setup the window size and the number of chars to display on screen, you can pick that up from the listing at the end, but the draw code is as simple as.
spriteBatch.Begin(); dy = 0; for (y = 0; y < dispHeight; y++) { py = y + mapY; dx = 0; for ( x = 0; x < dispWidth; x++) { px = x + mapX; a = newMap[px, py]; sy = a * 16; spriteBatch.Draw(originalChars, new Rectangle(dx, dy, 16, 16), new Rectangle(0, sy, 16, 16), Color.White); dx += 16; } dy += 16; } spriteBatch.End();
You will notice that I have added a couple of vars mapX and mapY.
What's the point of having the whole map in if you can't move around it?
So in your Update method you need.
What's the point of having the whole map in if you can't move around it?
So in your Update method you need.
float dx = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X; float dy = GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.Y; mapX += (int)(dx * 2); mapY -= (int)(dy * 2); if (mapX > 255 - dispWidth) mapX = 255 - dispWidth; if (mapX < 0) mapX = 0; if (mapY > 255 - dispHeight) mapY = 255 - dispHeight; if (mapY < 0) mapY = 0;
Run it and you should have something like this.
Now 16 by 16 pixel chars are a bit too small for modern systems, you have to squint to see them.
So we need to update the graphics.
Since I chose to remake U4 though this again is trivial. Other people have done a set of 32 by 32 chars that we can borrow. tileset4.png is included in the resource section.
Rather than just use that instead of the original chars I have added a bit of code so I can swap between the old and new chars.
You can see the details of that in the listing but using tileset4 instead of shapes gives you this.
So we need to update the graphics.
Since I chose to remake U4 though this again is trivial. Other people have done a set of 32 by 32 chars that we can borrow. tileset4.png is included in the resource section.
Rather than just use that instead of the original chars I have added a bit of code so I can swap between the old and new chars.
You can see the details of that in the listing but using tileset4 instead of shapes gives you this.
Better, but we can still improve on it with very little effort. The way the water hits the land without any sort of transition looks a bit nasty. So lets change that. In your favorite graphics editor grab a copy of the grass tile and the shallow water tile (the third and fifth tiles in tileset4 ). What we are going to do is create a set of transition graphics.
Grab the water tile and paste 15 copies of it into a scratch area.
We need a bit pattern to work to, in the old days we called them ocodes. It's basicly a binary number. We treat a water char with land to it's right as having bit 0 set, left as bit 1, down as bit 2, up as bit 3. Four bits = 16 new chars that need to be drawn, except we don't need the case where no bits are set so we actually need 15.
Start with number 1. With this bit pattern that is right. So grab 4 pixels from the right hand edge of the grass tile and paste them into the first water char on the right hand side.
Number 2 is left, so grab 4 pixels from the left hand edge of the grass tile and paste them into the second water char on the left hand side.
Number 3 is left AND right, so, you've guessed it 4 pixels from the left AND 4 pixels from the right.
Continue this way until you have all 15 chars. Now is the time to use your artistic talent. Edit the tiles to smooth out the transition, in my example I have just added a bit of sand, but I'm not a graphic artist. Organise them into a single strip. You should end up with something like this.
Grab the water tile and paste 15 copies of it into a scratch area.
We need a bit pattern to work to, in the old days we called them ocodes. It's basicly a binary number. We treat a water char with land to it's right as having bit 0 set, left as bit 1, down as bit 2, up as bit 3. Four bits = 16 new chars that need to be drawn, except we don't need the case where no bits are set so we actually need 15.
Start with number 1. With this bit pattern that is right. So grab 4 pixels from the right hand edge of the grass tile and paste them into the first water char on the right hand side.
Number 2 is left, so grab 4 pixels from the left hand edge of the grass tile and paste them into the second water char on the left hand side.
Number 3 is left AND right, so, you've guessed it 4 pixels from the left AND 4 pixels from the right.
Continue this way until you have all 15 chars. Now is the time to use your artistic talent. Edit the tiles to smooth out the transition, in my example I have just added a bit of sand, but I'm not a graphic artist. Organise them into a single strip. You should end up with something like this.
So now we need somewhere to put these chars. Luckily this again is easy. tileset4 includes all the chars in the game, including all the characters and monsters. We are going to be handling these differently, so we can overwrite them. Take a copy of tileset4 and paste the strip into it at a convenient location. I took the strip of chars at x=352.
While you have the graphic packages setup use the same techniques to create a set of tiles with just the corners set. I.E. mostly water but with a little bit of land in the top right hand corner, top left hand corner, top right and top left, .... You will see why later.
Save it as modchars.png.
So how do we get these new chars into the game. Well you could use a map editor and do it by hand. You would be mad, but you could.
Instead we will do it in code. When we copy the map into newMap, create a new array modMap and copy it into that as well. Then we just need to look for the correct conditions and add in the new chars.
While you have the graphic packages setup use the same techniques to create a set of tiles with just the corners set. I.E. mostly water but with a little bit of land in the top right hand corner, top left hand corner, top right and top left, .... You will see why later.
Save it as modchars.png.
So how do we get these new chars into the game. Well you could use a map editor and do it by hand. You would be mad, but you could.
Instead we will do it in code. When we copy the map into newMap, create a new array modMap and copy it into that as well. Then we just need to look for the correct conditions and add in the new chars.
// add water to grass transition for (y = 1; y < 255; y++) { for (x=1; x<255; x++) { // is it water if (newMap[x, y] < 3) { a = 0; if (edge(x + 1, y)) a++; if (edge(x - 1, y)) a+=2; if (edge(x , y + 1)) a+=4; if (edge(x , y - 1)) a+=8; if (a != 0) { modMap[x, y] = (byte)(164 + a); } } } }
The function edge just looks to see if the char is a land char or a water char. Test this out. You will now see why we needed the corner chars as well. To add these you simply extend the test to include the diagonals.
There is a problem with this though, look at the rivers and you will see corners missing. This is because we can't have sides and corners set at the same time. We could create a whole new set of tiles to cope with this, but we then have an 8 bit number, so we would need 256 chars. Not for me thank you, so we will cheat.
Create a new array called layer2, the same size as the new map. Take your graphic with just the corners and delete all the water to transparent. Paste this into your modified tileset in a new column.
In the code we test for this condition (sides required and corners) and instead of modifying the map we write the new char into layer2.
We draw layer2 over the top of the map, with transparency enabled. Bingo, problem solved.
Theres quiet a bit of code involved in this so look at the listing for details.
Now you should have a nice beach all the way around the land. Is that it? Hell no! now we do exactly the same for all the transitions we can be bothered with drawing chars for. I have done grass to scrub and grass to forrest.
At the end of all that you should end up with this.
There is a problem with this though, look at the rivers and you will see corners missing. This is because we can't have sides and corners set at the same time. We could create a whole new set of tiles to cope with this, but we then have an 8 bit number, so we would need 256 chars. Not for me thank you, so we will cheat.
Create a new array called layer2, the same size as the new map. Take your graphic with just the corners and delete all the water to transparent. Paste this into your modified tileset in a new column.
In the code we test for this condition (sides required and corners) and instead of modifying the map we write the new char into layer2.
We draw layer2 over the top of the map, with transparency enabled. Bingo, problem solved.
Theres quiet a bit of code involved in this so look at the listing for details.
Now you should have a nice beach all the way around the land. Is that it? Hell no! now we do exactly the same for all the transitions we can be bothered with drawing chars for. I have done grass to scrub and grass to forrest.
At the end of all that you should end up with this.
The code below also shows a list of used chars in the original map, this was just handy for me while writing the code.
Use left thumbstick on your xbox controller to move around the map, and A X B Y to change display mode.
What's next
Modern graphic hardware doesn't like textures that are not power of two square. You should create a 256 by 256 texture with just the chars you are using in and write code to change the map to work with this texture.
You could animate the water.
You could do the same for the other transitions, wood to grass, woods to hills, hills to mountains.
Finally save the map to disk, in a format you like, I will mention this again later, but for now I would like you to think about how you would do it.
What's next
Modern graphic hardware doesn't like textures that are not power of two square. You should create a 256 by 256 texture with just the chars you are using in and write code to change the map to work with this texture.
You could animate the water.
You could do the same for the other transitions, wood to grass, woods to hills, hills to mountains.
Finally save the map to disk, in a format you like, I will mention this again later, but for now I would like you to think about how you would do it.
resources.zip | |
File Size: | 347 kb |
File Type: | zip |
game1.cs | |
File Size: | 15 kb |
File Type: | cs |