Hello Guest, please login or register.
Did you miss your activation email?
Login with username, password and session length.

Pages: [1] 2   Go Down

Author Topic: [INFINITUS] 004 - Creating a map class  (Read 3183 times)

0 Members and 1 Guest are viewing this topic.
[INFINITUS] 004 - Creating a map class
« on: March 22, 2008, 11:32:24 am »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Hello and welcome to my set of zelda-game-development tutorials. Hopefully in the next tutorials I will teach you how to create a basic zelda game in C# that is extensible enough for you to be able to use it, and the knowledge you have learned, to produce your own zelda games or similar RPG type games.



Ok first things first a little clean up of the last tutorials code. If you haven't already make sure to remove the testing code (the code that loaded and image and created a thousand actors) we used in the last tutorial.

Now that that is out of the way, what are we going to do in this tutorial I hear you ask? Well we are going to add a tilemap framework to our engine. A tilemap, as most of you will know, is essentially just a pattern of tiles rendered in a grid, using different sections (termed 'frames) of the same image (called a tileset, or chipset depending on your background) to give the user the illusion of an environment. The advantage of doing this over, say, having a large image for each map is both lower CPU/GPU usage and less memory consumption (a full size map image will take up many, many megabytes of vram). The map system we will be creating will also have an inbuilt camera system so we can easily move the map, and everything on it, around with only a few lines of code.

So where do we start then? Well the first thing to do, as with most entities in our game, is to create a new source file! Call this one 'Map.cs', and paste the following code into it (this is roughly the same template code we will be using for all source files so memorize it :P);

Code: [Select]
using System;
using System.IO;
using System.Drawing;
using System.Collections;

using Zelda.Graphics;
using Zelda.Runtime;
using Zelda.Input;

namespace Zelda
{

    public struct Tile
    {
        public int Frame;
        public bool Solid;
    }

    public class Map
    {

    }

}


Owh what do we have here then? As well as the obvious class 'Map' we also have a struct!? Our first in this project, this struct is called Tile and will store basic properties about each individual tile in our map, specifically the image frame we will use to render it and its solidity. If your not sure what a struct is then here is a quick explanation; structs are basically user-defined primitive data types (line int, long, short, .etc), the main advantage to using structs is that rather than passing references between different functions in your code (like you do with classes) you actually pass a new version each time (just like you do with int, long, short and so forth) which can be changed without effecting the one passed. I realise that may not be the best explanation ever and I advise you to read up on some websites about them if your not sure. Either way its not that important for the moment, all you will need to know is that you will never have to use the New operator when using this struct as a data type.

So now we have an empty map class and a complete tile class, lets start filling in the map class shall we? First lets declare a few variables that we are going to need to store information about our map;

Code: [Select]
        public Graphics.Image Tileset = null;
        public int LayerCount = 5;
        public int MapWidth = 16;
        public int MapHeight = 16;
        public Tile[,,] TileData = new Tile[5, 16, 16];
        public PointF CameraPosition = new PointF(0, 0);

Most of these do pretty obvious jobs but I'll give you a quick rundown anyhow;

  • Tileset: This is the image used to render each tile on our map. If you feel brave enough after this tutorial you could even try making it so each map can have multiple tilesets.
  • LayerCount: This obviously stores the number of layers (each layer is essentially a new grid of tiles on our map, the only difference between them is what height they are rendered at. For example layer 5 could be above the player and layer 1 below him. This allows you to do some need overlapping tricks like having the top of a statue go over your player and the bottom under!) in our map.
  • MapWidth: As the name says, this variable stores the width of our map (in tiles).
  • MapHeight: As the name says, this variable stores the heght of our map (in tiles).
  • TileData: Owwh, now this one is the intresting one. Its a 3 dimensional array array of tiles, the first dimension indexs the layer, the second the x-axis and the third the y-axis. If your not sure, my advise is to read up on arrays, it can be a tricky subject but its easy to understand once you get your head round it :P.
  • CameraPosition: The position of our camera on the map! Pretty simple eh? Its stored in the form of a Point, which is just a struct that contains an X and a Y axis.

Well now we have the variables sorted out we need to write the functions that will use these don't we! Well first things first lets add all the function declarations into the class, and we can slowly fill them in during this tutorial;

Code: [Select]
        public void Render()
        {
        }

        public void Update()
        {
        }

        public bool RectangleCollide(Rectangle rect)
        {
        }

        public void Load(string url)
        {
        }

        public void Save(string url)
        {
        }


What most of these do is pretty obvious, render renders the map, update updates the map, RectangleCollide returns true if the given rectangle collides with any solid tiles on the map, and the load and save ones obvious load and save maps to and from files on your computer.

So lets start with the most intresting, and probably easiest function to do (though the one that sounds most complex) shall we? Lets start with Render!

Ok so how is render going to work then? Well basically what its going to do is decide what rectangle of tiles is visible to the camera; as we don't want to render tiles outside the camera as it would result in quite severe slowdown on large maps; Then after we have the rectangle calculated we will loop through each layer and render all the tiles in that rectangle, for each layer we will also render all the actors that are on the layer (so that each layer will actually produce depth, with actors above and below certain layers, rather than just having all actors rendered on top of the map).

So how do we start this then? Well lets first calculate the rectangle shall we?

Code: [Select]
            int tileWidth = Tileset.FrameSize.Width;
            int tileHeight = Tileset.FrameSize.Height;

            int renderStartAreaX = (int)(CameraPosition.X / tileWidth) - 1;
            int renderStartAreaY = (int)(CameraPosition.Y / tileHeight) - 1;
            int renderEndAreaX = renderStartAreaX + (GraphicsManager.GraphicsDimensions.X / tileWidth) + 2;
            int renderEndAreaY = renderStartAreaY + (GraphicsManager.GraphicsDimensions.Y / tileHeight) + 2;
Well that wasn't so hard was it? Its a lot less code than I'm sure some of you would have imagined. Lets break it down though and explain what it does.

The first two lines simply declare variables that store the width and height of each tile in the map, theses are worked out by getting the frame size from the tileset image. If you wish you can just directly access the tileset frame size throughout the code, but I personally believe declaring an extra couple of variables makes the code far more readable.

Now how about the next 2 lines? Well these work out which tile is visible in the top-left corner of the screen. The lines after that work out which tile is visible in the bottom-left corner of the screen, this is just worked out by adding the tile coordinates of the top-left tile to the amount of tiles that are required to fill the screen.

Pretty simple isn't it? The main problem with this code at the moment is that if the camera goes outside the map then the code will give you the coordinates of tiles outside of the map, which is obviously not what we want. So what we need to do is cap these coordinates to the map width and height (and 0 so they don't go negative). We can do that with the following code;

Code: [Select]
            renderStartAreaX = Math.Min(MapWidth - 1, Math.Max(0, renderStartAreaX));
            renderStartAreaY = Math.Min(MapHeight - 1, Math.Max(0, renderStartAreaY));
            renderEndAreaX = Math.Min(MapWidth - 1, Math.Max(0, renderEndAreaX));
            renderEndAreaY = Math.Min(MapHeight - 1, Math.Max(0, renderEndAreaY));

Math.Min just resturns the lowest of two given numbers and Math.Max just returns the highest. Combined together they can be used to cap a number between to numbers.

Right now that we know what to render its time to start actually rendering! The first thing we need to do is set the graphics offset (the graphics offset just offsets the coordinates all drawing commands by the given amount) to the camera position so that everything is drawn relative to the camera position. We can do this using the following code;

Code: [Select]
GraphicsManager.Offset = new PointF(-CameraPosition.X, -CameraPosition.Y);

Now you may be wondering why we negatate it, well just think about it, if your camera is at 40,40 on the map then the whole map is going to have to go backwards by -40,-40 isn't it, otherwise the perspective will be wrong? Just try and picture it in your head.

Now then what we need to do is loop through every layer in the map and then every tile within the rectangle we are going to render, you can do this using the following simple for loop block;

Code: [Select]
            for (int layer = 0; layer < LayerCount; layer++)
            {
                for (int x = renderStartAreaX; x <= renderEndAreaX; x++)
                {
                    for (int y = renderStartAreaY; y <= renderEndAreaY; y++)
                    {



                    }
                }
            }

That was easy wasen't it? Now the only thing left is to extract the tile from the tile data array, determine the position of the tile on the screen and render it. This is actually a suprisingly simple process, you can do it like this;

Code: [Select]
                        Tile tile = TileData[layer, x, y];
                        float renderX = x * tileWidth;
                        float renderY = y * tileHeight;
                        if (tile.Frame >= 0 && tile.Frame < Tileset.FrameCount)
                           GraphicsManager.RenderImage(Tileset, renderX, renderY, 0, tile.Frame);

Simple eh? The first line just extract the tile from the data array using the coordinates of the for loops. The two lines following that work out where to render the tile, this is fairly easy to work out its just the coordinates of the tile multiplied by the tile sizes. And last thing of all the last two lines render the tile to the screen! The only thing you may wonder about these lines is why we are using an if block? Well this is just to check that tile's frame index is above 0 and below the amount of tiles in the image, simple eh?

Now theres just two little things we need to do before we are done with this function. Firstly we need to render all the actors that are on each layer. How do you do this you may ask? Well if you remember from the last tutorial we had a piece of code in the main loop that looked similar to this;

Code: [Select]
                foreach (Actor actor in Actor.ActorList)
                {
                    actor.Render();
                }

Well what we have to do is essentially the same except for two things, first we need to make sure we only render actors on the layer we are currently rendering, and second we need to clear the depth buffer (if your not sure what this is don't worry, its not that important) so that all actors are drawn on top of the currently rendering layer. We can do all of this in the following code that is slightly modified from the above code;

Code: [Select]
                GraphicsManager.ClearDepthBuffer();
                foreach (Actor actor in Actor.ActorList)
                {
                    if (actor.Layer == layer) actor.Render();
                }
                GraphicsManager.ClearDepthBuffer();

Pretty dam simple isn't it? All we've slapped in is a if block to check the actor we are rendering is on the correct layer and we've called the clear buffer function a couple of times. Now that we have the code where do we put it in our code, well we put it at the end of our layer for loop;

Code: [Select]
            for (int layer = 0; layer < LayerCount; layer++)
            {
                for (int x = renderStartAreaX; x <= renderEndAreaX; x++)
                {
                    for (int y = renderStartAreaY; y <= renderEndAreaY; y++)
                    {



                    }
                }

                // <--- Your actor rendering code goes here!

            }

Finished that? Yay! Thats the main rendering function almooooost done, all we need to add is one last line of code right at the end of the function to reset the graphics offset that we changed (otherwise all things rendered after will be offset, such as the fps display, which we don't want :S).

Code: [Select]
            GraphicsManager.Offset = new PointF(0, 0);

Woooohooo! That was a long bit of coding wasn't it? Well you can now render a tilemap, pretty dam cool eh? Suprising simple wasn't it? Unfortunatly we still have another 4 more functions to go! Fortunatly these are quite easy to code, and don't require to much effort.

So lets do the update code shall we? First things first, what exactly will the update code do? Well its going to update all the actors in the game and restrict the camera movement so that the camera has to stay in the map boundries!

We can do all of this we the following simplistic code;

Code: [Select]
            foreach (Actor actor in Actor.ActorList)
            {
                actor.Update();
            }

            int tileWidth = Tileset.FrameSize.Width;
            int tileHeight = Tileset.FrameSize.Height;
            CameraPosition.X = Math.Max(0, Math.Min((MapWidth * tileWidth) - GraphicsManager.GraphicsDimensions.X, CameraPosition.X));
            CameraPosition.Y = Math.Max(0, Math.Min((MapHeight * tileHeight) - GraphicsManager.GraphicsDimensions.Y, CameraPosition.Y));

The first 4 lines you should be familiar with, it the same code we used in the last tutorial to update the actors in the main loop. As such I shouldn't need to explain it.  Now the next 4 lines just use those handy Min and Max commands again to stop the camera going outside the maps boundries, see if you can work out how this one works yourself, its really quite simple, break it down into smaller chunks of code if you need to work out how it works :D.

What you want more? Tough thats the entire update code :D. However if you want to add more to it, think about writing some code to animate tiles! I'll leave this bit of coding as a challenge to you ;).

Now onto the next function, RectangleCollide! This is probably the most intresting function and the most used, it is what we will be using for more or less our entire collision detection code later on. As the name implies the function will return true if the given rectangle collides with any solid tiles on the map.

So how are we going to do this? Well rather than just checking every single tile to see if it overlaps with the rectangle we work out which tiles are around the rectangle and check to see if they collide with it. We work out what tiles to render in a similar manner to how we worked out what tiles are in the camera view and should be rendered. We can do that with the following code;

Code: [Select]
            int tileWidth = Tileset.FrameSize.Width;
            int tileHeight = Tileset.FrameSize.Height;
            int horizontalTileCount = (rect.X / tileWidth);
            int verticalTileCount = (rect.Y / tileHeight);

            int startAreaX = horizontalTileCount - ((rect.Width / tileWidth) + 1);
            int startAreaY = verticalTileCount - ((rect.Height / tileHeight) + 1);
            int finishAreaX = horizontalTileCount + ((rect.Width / tileWidth) + 1) * 2;
            int finishAreaY = verticalTileCount + ((rect.Height / tileHeight) + 1) * 2;

As with the rendering code the first 4 lines just store a couple of handy values to make the code more readable. Now the next 4 lines are the most intresting, they work almost the same as the code in the rendering function except rather than using the camera view we use the rectangle. It looks slightly messey but it works very well :P. If your not sure how it works try slapping a DrawRectangle command in the code so you can actually see what tiles it is rendering.

As with the rendering calculations the problem occurs where we try and references non-existant tiles when the rectangle goes outside the map boundry, we can fix this in exactly the same way we did this in the rendering function;

Code: [Select]
            startAreaX = Math.Min(MapWidth - 1, Math.Max(0, startAreaX));
            startAreaY = Math.Min(MapHeight - 1, Math.Max(0, startAreaY));
            finishAreaX = Math.Min(MapWidth - 1, Math.Max(0, finishAreaX));
            finishAreaY = Math.Min(MapHeight - 1, Math.Max(0, finishAreaY));

Very handy those min and max commands are aren't they :P. Saves using a bunch of if blocks.

Now that we have the tiles that we need to check for collisions, its time to actually check for any collisions between them and the rectangle. So lets iterate through them in the same way we did in the rendering function shall we?

Code: [Select]
           for (int layer = 0; layer < LayerCount; layer++)
            {
                for (int x = startAreaX; x <= finishAreaX; x++)
                {
                    for (int y = startAreaY; y <= finishAreaY; y++)
                    {

                    }
                }
            }

Done that? Right now we need to work out the screen coordinates of the tile, again exactly the same code is used as the rendering function

Code: [Select]
                        int tileX = (x * tileWidth);
                        int tileY = (y * tileHeight);

Dum de dum dum, now its time for the hardest part! Its time to actually check if the tile overlaps the rectangle or vis-versa.

Code: [Select]
                        if (TileData[layer, x, y].Solid == true && Runtime.MathMethods.RectanglesOverlap(rect.X, rect.Y, rect.Width, rect.Height, tileX, tileY, tileWidth, tileHeight))
                        {
                            return true;
                        }

Actually I'm lieing, this is a very simple bit of code, it mearly checks first to see if the tile is solid and then to see if any of the corners of the collision rectangle are inside the tile, if they are then by the definition of a rectangle a collision has occured, if not then one hasen't. Fortunatly for you, I've even been nice enough to wrap that rectangle collision code and place it in the runtime dll, so you don't even need to see any of the maths behind it!

Cool eh? You now have a fully working collision checking algorithem! I can tell you that this will come in handy later on :P.
« Last Edit: December 15, 2008, 08:04:46 pm by Minalien »
Logged
Re: 004 - Creating a map class
« Reply #1 on: March 25, 2008, 05:05:30 pm »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Now just two functions left! Saving and loading the maps from a file! I'm actually going to be a !@#$% here and not explain these functions to you but just give you the code! The reason being that these functions use a lot of somewhat complex concepts that are irrelivent to this walkthrough, however if you can get to grips with the rest of this article then you should be able to understand how these functions work. These functions are also completely commented in the source code download which may help you to understand them!

Code: [Select]
        public void Load(string url)
        {
            if (File.Exists(url) == false) return;
           
            FileStream stream = new FileStream(url, FileMode.Open, FileAccess.Read);
            StreamReader reader = new StreamReader(stream);

            if (reader.ReadLine().Trim() != "ZELDA MAP") return;

            while (reader.EndOfStream == false)
            {
                string line = reader.ReadLine().Trim();

                if (line != "")
                {
                    string[] parameters = line.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);

                    switch (parameters[0].ToUpper())
                    {
                        case "WIDTH":
                            MapWidth = int.Parse(parameters[1]);
                            TileData = new Tile[LayerCount, MapWidth, MapHeight];
                            break;

                        case "HEIGHT":
                            MapHeight = int.Parse(parameters[1]);
                            TileData = new Tile[LayerCount, MapWidth, MapHeight];
                            break;

                        case "LAYERS":
                            LayerCount = int.Parse(parameters[1]);
                            TileData = new Tile[LayerCount, MapWidth, MapHeight];
                            break;

                        case "TILESET":

                            string originalPath = Environment.CurrentDirectory;
                            Environment.CurrentDirectory = Path.GetDirectoryName(url);
                            Tileset = GraphicsManager.LoadImage(parameters[5], int.Parse(parameters[1]), int.Parse(parameters[2]), int.Parse(parameters[3]), int.Parse(parameters[4]));
                            Environment.CurrentDirectory = originalPath;

                            break;

                        case "LAYER":

                            int index = int.Parse(parameters[1]);
                            for (int row = 0; row < MapHeight; row++)
                            {
                                string[] frames = reader.ReadLine().Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                                for (int x = 0; x < MapWidth; x++)
                                {
                                    if (frames[x].Trim().EndsWith("+"))
                                    {
                                        TileData[index, x, row].Solid = true;
                                        frames[x] = frames[x].Substring(0, frames[x].Length - 1);
                                    }
                                    else
                                        TileData[index, x, row].Solid = false;
                                    TileData[index,x,row].Frame = int.Parse(frames[x].Trim());
                                }
                            }

                            break;
                    }

                }
            }
            reader.Close();
        }

        public void Save(string url)
        {
            if (File.Exists(url) == false) return;

            FileStream stream = new FileStream(url, FileMode.Create, FileAccess.Write);
            StreamWriter writer = new StreamWriter(stream);

            writer.WriteLine("ZELDA LINE");
            writer.WriteLine("WIDTH " + MapWidth);
            writer.WriteLine("HEIGHT " + MapHeight);
            writer.WriteLine("LAYERS " + LayerCount);

            for (int i = 0; i < LayerCount; i++)
            {
                writer.WriteLine("LAYER " + i);

                for (int y = 0; y < MapHeight; y++)
                {
                    string rowData = "\t";
                    for (int x = 0; x < MapWidth; x++)
                    {
                        rowData += TileData[i, x, y].Frame;
                        if (TileData[i, x, y].Solid == true) rowData += "+";
                        if (x < MapWidth - 1) rowData += ",";
                    }
                    writer.WriteLine(rowData);
                }
            }
            writer.Close();
        }

However I am going to explain the format of the map files that they load and save. Here is an example file that can be saved and loaded by the above functions.

Code: [Select]
ZELDA MAP
TILESET 16 16 1 1 tileset.png
LAYERS 2
WIDTH 16
HEIGHT 16
LAYER 0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
LAYER 1
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,2+,2+,0,2+,2+,0,0,0,2+,2+,2+,2+,0,0
0,0,2+,2+,0,2+,2+,0,0,0,2+,2+,2+,2+,0,0
0,0,2+,2+,0,2+,2+,0,0,0,0,2+,2+,0,0,0
0,0,2+,2+,0,2+,2+,0,0,0,0,2+,2+,0,0,0
0,0,2+,2+,2+,2+,2+,0,0,0,0,2+,2+,0,0,0
0,0,2+,2+,2+,2+,2+,0,0,0,0,2+,2+,0,0,0
0,0,2+,2+,0,2+,2+,0,0,0,0,2+,2+,0,0,0
0,0,2+,2+,0,2+,2+,0,0,0,0,2+,2+,0,0,0
0,0,2+,2+,0,2+,2+,0,0,0,2+,2+,2+,2+,0,0
0,0,2+,2+,0,2+,2+,0,0,0,2+,2+,2+,2+,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

Right so lets explain the file format shall we?

Well the files always start with a line containing nothng but "ZELDA MAP" in capitals. This must never be modified and must be in all map files. This basically just tells the engine that this is a zelda map file and can be loaded by the engine, this is used to make sur ewe don't try loading invalid or corrupt files.

The next line defines the tileset image that should be loaded and used to render the given map, its syntax is very simple, it always starts with the word TILESET and is followed by five parameters, the first two are the size of each tile contained in the image, the next two are the spacing between each tile and the last is the path to the actual image.

The 3 lines following that define the dimensions of the map, specifically its width, height and how many layers it contains.

The next lines are the most complicated. They define the properties of every tile on a given layer. A layer block always starts with the word LAYER followed by the layer index, after this line there is a line for each row in the map, each line consists of the frames used to render each tile in this row, seperated by commas, if a plus sign follows the frame index then the tile is considered to be solid. There must be enough layer blocks for each layer in the map!

And thats it really! I designed the format to be as simple as possible for you to modify until we get round to writing a map editor. Try making a few and loading them (there are some tilesets and sample files in the source code download at the bottom)!

Well then, thats the entire map class done! The only problem is we are not putting it to use. So lets modify our Zelda.cs file shall we so that we can actually use it!

First thing we need to do is add a variable to store the current map of our game! So add this to the top of our zelda class;

Code: [Select]
        public static Map CurrentMap = null;

Now how about we create a map and load it from a file? So lets place the following code after the CreateWindow function shall we?

Code: [Select]
            CurrentMap = new Map();
            CurrentMap.Load("Assets\\start.map");

So what does this do then? Well the first line create a new map object, the second then calls the Load function of the map so that it loads the map data from the Assets\Start.map file (an example map is contained in the source code download if you don't want to make your own)! That was easy wasn't it? unfortunatly we haven't added any code yet to render or update the map so all we are going to see is a blank screen when we start the game.

So to rectify this lets add some code. Lets replace the following code;

Code: [Select]
                foreach (Actor actor in Actor.ActorList)
                {
                    actor.Update();
                }

With this new code. The reason we are replacing it is because our actors are now updated in our map class and as such we no longer need this code in the main loop;

Code: [Select]
                CurrentMap.Update();

*Whistles* Almost done. Now just replace the actor rendering code from the last tutorial with this new code;

Code: [Select]
                foreach (Actor actor in Actor.ActorList)
                {
                    actor.Render();
                }

to

Code: [Select]
                CurrentMap.Render();

Now just need to do one more quick thing before we can run, lets change the window resolution to 256x224 (the SNES resolution of LTTP) so that it better fits the map. You can do this simply by changing the CreateWindow function call so that it reads;

Code: [Select]
            GraphicsManager.CreateWindow("Zelda Game", 256,224, false);

Wow!!!! We finally have a working map class that is correctly rendered and updated on the screen! Pretty dam cool isn't it? However its not very interactive is it? So lets add some code to move the camera around the map using the arrow keys. Add the following code before the CurrentMap.Update() line;

Code: [Select]
                if (InputManager.KeyDown(System.Windows.Forms.Keys.Left))   CurrentMap.CameraPosition.X -= 1;
                if (InputManager.KeyDown(System.Windows.Forms.Keys.Right))  CurrentMap.CameraPosition.X += 1;
                if (InputManager.KeyDown(System.Windows.Forms.Keys.Up))     CurrentMap.CameraPosition.Y -= 1;
                if (InputManager.KeyDown(System.Windows.Forms.Keys.Down))   CurrentMap.CameraPosition.Y += 1;

This code is pretty simple, it just increased or decreases the camera position if any of the given arrow keys are currently pressed down.

And how about our collision testing code? Lets check that works shall we! Add the following code just before we render the FPS display;

Code: [Select]
                if (CurrentMap.RectangleCollide(new Rectangle(InputManager.MouseX() + (int)CurrentMap.CameraPosition.X, InputManager.MouseY() + (int)CurrentMap.CameraPosition.Y, 1, 1)))
                    GraphicsManager.RenderText("Mouse is over solid tile!", 10, 25, 0, null);
This will just check if the current position of the mouse on the map is over a solid tile, if it is we render a notification to the screen!

Now RUN!! Kick ass isn't it? You now have a pretty neat map framework working, took a long article to do it though didn't it? And it was only a couple of hundred lines of code. Hope you enjoy that tutorial, as with all the other tutorials fully commented source code is available to download below. Make sure to check out the next article! Where we are going to get into some real zelda programming and add link to the map!





Completed Source Code: http://www.zfgc.com/infinitus/tutorials/004/complete.rar
« Last Edit: March 30, 2008, 12:57:45 pm by Infinitus »
Logged
Re: 004 - Creating a map class
« Reply #2 on: March 25, 2008, 05:47:21 pm »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Thank god, that was a long tutorial >.<. Probably should split it up a bit.

@Theforeshadower: Think the last one was long! Check this out!
Logged
Re: 004 - Creating a map class
« Reply #3 on: March 27, 2008, 11:52:17 am »
  • *
  • Reputation: +0/-0
  • Offline Offline
  • Posts: 2245
A few things to mention,
Source code url ends with .zip instead of .rar (Broken Link)
It would be nice if you explicitly mention in there somewhere (like as a reminder or something) that when using the example map code that they will need a tileset.png file and it will need to be placed in the same directory as the map file. (I didn't actually pick up on this until I attempted to run the code and it crashed.)

Quote
Wow!!!! We finally have a working map class that is correctly rendered and updated on the screen! Pretty dam cool isn't it? However its not very interactive is it? So lets add some code to move the camera around the map using the arrow keys. Add the following code about the CurrentMap.Update() line;
I think this would be better written differently, "about" isn't really specific, especially since by placing it below that function will cause the camera moving code to go over the width of the map by 1 pixel.

Next thing, there are a few parts I feel that could be better written, mainly the parts that require you to add/subtract/replace code.  As things get more complicated, as where you're dealing with multiple classes, each using similar function names (ie Update(), Render()), it gets confusing as to where you're actually supposed to be putting/replacing/removing these pieces of code

eg. When I actually got up to this part, I ended up copying this code and placing it in the Render() function of the Actor Class, before later realising I had placed it in the wrong area because my code wouldn't compile.
*Whistles* Almost done. Now just replace the actor rendering code from the last tutorial with this new code;

Code: [Select]
                foreach (Actor actor in Actor.ActorList)
                {
                    actor.Render();
                }

to

Code: [Select]
                CurrentMap.Render();


Logged
Re: 004 - Creating a map class
« Reply #4 on: March 27, 2008, 12:01:42 pm »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Quote
Source code url ends with .zip instead of .rar (Broken Link)
Check.

Quote
I think this would be better written differently, "about" isn't really specific, especially since by placing it below that function will cause the camera moving code to go over the width of the map by 1 pixel.
Sorry that was badly phrased. Not what I meant.

Quote
Next thing, there are a few parts I feel that could be better written, mainly the parts that require you to add/subtract/replace code.  As things get more complicated, as where you're dealing with multiple classes, each using similar function names (ie Update(), Render()), it gets confusing as to where you're actually supposed to be putting/replacing/removing these pieces of code
I agree. I'm not much of a writer :). If you feel your could word it better, by all means have a go.

Quote
When I actually got up to this part, I ended up copying this code and placing it in the Render() function of the Actor Class, before later realising I had placed it in the wrong area because my code wouldn't compile.
Yup probably, its a little ambigious isn't it.
Logged
Re: 004 - Creating a map class
« Reply #5 on: March 27, 2008, 12:14:06 pm »
  • *
  • Reputation: +0/-0
  • Offline Offline
  • Posts: 2245
I'm not really one for writing either, english was my worst subject D:

anyway.. I guess you'll be writing that next tutorial now, AM I RITE :O?
Logged
Re: 004 - Creating a map class
« Reply #6 on: March 27, 2008, 12:28:35 pm »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Quote
anyway.. I guess you'll be writing that next tutorial now, AM I RITE :O?

I will probably do so at the weekend. All the code is already done for it. I just have college work I need to get done first ;).
Logged
Re: 004 - Creating a map class
« Reply #7 on: March 30, 2008, 05:14:27 am »
  • *
  • Reputation: +1/-0
  • Offline Offline
  • Gender: Male
  • Posts: 4588
I actually got lost a few times because you were a little messy on guidance at some parts. Like where within a few for loops some code was meant to go exactly. I ended up getting frustrated and just downloading the source.
Logged
the a o d c
Re: 004 - Creating a map class
« Reply #8 on: March 30, 2008, 12:40:52 pm »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
I actually got lost a few times because you were a little messy on guidance at some parts. Like where within a few for loops some code was meant to go exactly. I ended up getting frustrated and just downloading the source.
Yup, I'm not the best at writing, and trying to explain large quantites of code in an easy way (I could explain it easier, but I think I may confuse people who are newer to programming, eh.) is somewhat difficult. If you feel like pointing out what places you got lost and how you think it should be updated, please do say.
Logged
Re: 004 - Creating a map class
« Reply #9 on: March 31, 2008, 09:14:07 am »
  • *
  • Reputation: +1/-0
  • Offline Offline
  • Gender: Male
  • Posts: 4588
A few books start with say a template (like a new namespace you create a lot) and add a piece of code to it, but show the whole file, with the new code bolded, so users see what we're looking at, what we're typing in, and where. Though that'd fill the character limit here faster it's easier.
Logged
the a o d c
Re: 004 - Creating a map class
« Reply #10 on: March 31, 2008, 09:23:40 am »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Yeh but I want actual explanation,  I don't want the tutorials to just end up as huge blocks of code with a line of writing between each one >.<
Logged

gm112

Re: 004 - Creating a map class
« Reply #11 on: March 31, 2008, 09:49:35 am »
Question.. you know how in RPG Maker, you would have the ability to add certain events at certain locations? Would you just set something in the map file telling the map engine what event is where and whatnot?
Logged
Re: 004 - Creating a map class
« Reply #12 on: March 31, 2008, 09:50:59 am »
  • *
  • Reputation: +1/-0
  • Offline Offline
  • Gender: Male
  • Posts: 4588
Question.. you know how in RPG Maker, you would have the ability to add certain events at certain locations? Would you just set something in the map file telling the map engine what event is where and whatnot?
I'd imagine it'd be sprung by either object collision or a check if link is at a certain part of the map.
Logged
the a o d c
Re: 004 - Creating a map class
« Reply #13 on: March 31, 2008, 10:10:46 am »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 3374
Question.. you know how in RPG Maker, you would have the ability to add certain events at certain locations? Would you just set something in the map file telling the map engine what event is where and whatnot?
Well I suppose when we get smart enough we can add that in ourselves :).
Logged
Quote from: Jason
Your community is a bunch of stuck up turds.
Re: 004 - Creating a map class
« Reply #14 on: March 31, 2008, 10:22:23 am »
  • *
  • Reputation: +1/-0
  • Offline Offline
  • Gender: Male
  • Posts: 4588
I guess you could use the current Map instance's RectangleCollide with the Link instance, since he DOES also have a publically (bad infin) accessible property Rectangle "CollisionRectangle". Or something.
Logged
the a o d c

gm112

Re: 004 - Creating a map class
« Reply #15 on: March 31, 2008, 10:41:27 am »
Question.. you know how in RPG Maker, you would have the ability to add certain events at certain locations? Would you just set something in the map file telling the map engine what event is where and whatnot?
Well I suppose when we get smart enough we can add that in ourselves :).
Eh... thanks for the help, smart guy. I was asking because I'm trying to learn more here. I've never been good with tile maps, and if I did know more about these, then I would've contributed. Anyways.. great thanks to Mammy for explaining everything.
Quote
Mamoruanime[] says:
set an object ID that calls to an event reference ID

Mamoruanime[] says:
pretty easy really, but you place that  object on the map for collision

Pretty simple, eh? For collision detection, you just assign an x,y cordinate for the object ID and then have an event handler do the work. I do not know C# well enough to type an accurate example, so I'll try my BEST at making psuedo code that explains this accurately.

Code: [Select]
//Here we'll just fetch the object ID, xy cordinates, and figure out which EventID this is...

case "OBJECTID";

         parse(argument0)                                                    = objID; //Fetches object ID
         parse(argument1)                                                    = objX; // Object X
         parse(argument2)                                                    = objy; // Object Y

      
         
Code: [Select]
//Simple example on how to fetch eventID
Int FetchEventID(ID)

Select ID //Scan ID

         case 1:
                  
             DrawText 0,0,"Object Collision at "+objX+","+objy+"!!!!!"; //If the EventID == 1, then print detection at the top left hand corner of the screen..


        case 2:

             TeleportCharacter("\\maps\\town01.map",pDest); //If the EventID == 2, Teleport player to map "town01.map" and return previous destination for later use.

       break;//Continue on even if neither case happens.
end select //End procedure.

I know that's a generic representation on how to do it, but it's the best I can come up with. Especially after being up for a day on a school night >__<.. damn insonmia.. Anyways, happy now, Darklight?

EDIT: By the way, I know that the psuedo code does teach a very HORRIBLE method of working this out, I choose it anyways because it's a simple explanation and nothing too complicated.

EDIT 2: Oh, and darklight, I didn't mean to seem like angry or anything at the end >_<.. Also, I made a mistake in the second code quote rofl.
« Last Edit: March 31, 2008, 11:10:30 am by gm112 »
Logged
Re: 004 - Creating a map class
« Reply #16 on: March 31, 2008, 11:13:55 am »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
You will see soon enough, I'll be expanding the map file format in a few tutorials time to do add objects.
Logged

gm112

Re: 004 - Creating a map class
« Reply #17 on: March 31, 2008, 11:22:48 am »
You will see soon enough, I'll be expanding the map file format in a few tutorials time to do add objects.
Just out of curiosity Tim, how close was I with my own explanation?
Logged
Re: 004 - Creating a map class
« Reply #18 on: March 31, 2008, 11:26:26 am »
  • *
  • Reputation: +3/-0
  • Offline Offline
  • Gender: Male
  • Posts: 6629
Events are going to be handled in a scripting language, so its going to be done in a totally different way.
Logged

gm112

Re: 004 - Creating a map class
« Reply #19 on: March 31, 2008, 11:35:47 am »
Events are going to be handled in a scripting language, so its going to be done in a totally different way.
Ooohh! Interesting! Yeah, the method I used was very rough and would be EXTREMELY !@#$% if used in a real RPG.
Logged
Pages: [1] 2   Go Up

 


Contact Us | Legal | Advertise Here
2013 © ZFGC, All Rights Reserved



Page created in 0.261 seconds with 74 queries.

anything