Tile engine
Ariesnl

I'm writing a tile engine using Open layer

this is what I have thusfar

header

1#ifndef _TILEMAP
2#define _TILEMAP
3 
4#include <vector>
5#include "Engine.h"
6#include "AIdef.h"
7 
8class TTile
9{
10public:
11int bound;
12int index;
13short occupied;
14};
15 
16class TTilemap
17{
18private:
19 
20public:
21int m_nWidth;
22int m_nHeight;
23 
24std::vector<std::vector< TTile * > >cell;
25std::vector< Bitmap *> tile;
26TTilemap();
27~TTilemap();
28void draw(int camx,int camy);
29void load(char * filename);
30
31};
32 
33#endif

CPP

1include <allegro.h>
2#include <OpenLayer.hpp>
3#include <loadpng.h>
4#include <malloc.h>
5#include <string.h>
6#include <iostream>
7#include <fstream>
8#include "Tilemap.h"
9#include "AIdef.h"
10 
11extern TEngine * engine;
12 
13extern int screenmid_x;
14extern int screenmid_y;
15 
16extern Bitmap * im_occ;
17 
18void TTilemap::load(char * filename)
19{
20char szChar[2];
21char c;
22char szTAG[255];
23strcpy(szTAG,"");
24int readingchunk=FC_NONE,Tile_X,Tile_Y;
25int tilepart=TP_NONE;
26Tile_X=0;
27Tile_Y=0;
28std::ifstream mapfile(filename);
29TTile * temptile=NULL;
30std::vector<TTile * > tempvector;
31while (!mapfile.eof())
32 {
33 mapfile.read(szChar,1);
34 szChar[1]='\0';
35 
36 if ((szChar[0]==' ')||(szChar[0]=='\n'))
37 {
38 switch (readingchunk)
39 {
40 case FC_NONE:
41 readingchunk=FC_NONE;
42 break;
43 case FC_DATE:
44 readingchunk=FC_NONE;
45 break;
46 case FC_TILESET:
47 readingchunk=FC_NONE;
48 break;
49 case FC_MAP:
50 readingchunk=FC_NONE;
51 break;
52 case FC_WIDTH:
53 m_nWidth=atoi(szTAG);
54 readingchunk=FC_NONE;
55 break;
56 case FC_HEIGHT:
57 m_nHeight=atoi(szTAG);
58 readingchunk=FC_NONE;
59 break;
60 case FC_TILES:
61 if (tilepart==TP_BOUND)
62 {
63 temptile->bound=atoi(szTAG);
64 tempvector.push_back(temptile);
65 Tile_X++;
66 if (Tile_X==m_nWidth)
67 {
68 cell.push_back(tempvector);
69 Tile_X=0;
70 Tile_Y++;
71 tempvector.clear();
72 }
73 tilepart=TP_NONE;
74 }
75 else if (tilepart==TP_INDEX)
76 {
77 temptile=new TTile;
78 temptile->index=atoi(szTAG);
79 temptile->occupied=OC_NONE;
80 tilepart=TP_NONE;
81 }
82 break;
83 }
84 
85 if (strcmp(szTAG,"DATE:")==0) readingchunk=FC_DATE;
86 if (strcmp(szTAG,"TILESET:")==0) readingchunk=FC_TILESET;
87 if (strcmp(szTAG,"MAP:")==0) readingchunk=FC_MAP;
88 if (strcmp(szTAG,"WIDTH:")==0) readingchunk=FC_WIDTH;
89 if (strcmp(szTAG,"HEIGHT:")==0) readingchunk=FC_HEIGHT;
90 if (strcmp(szTAG,"TILES:")==0) readingchunk=FC_TILES;
91 
92 if (strcmp(szTAG,"I:")==0)
93 {
94 tilepart=TP_INDEX;
95 }
96 
97 if (strcmp(szTAG,"B:")==0)
98 {
99 tilepart=TP_BOUND;
100 }
101 
102 strcpy(szTAG,"");
103 }
104 else
105 {
106 strcat(szTAG,szChar);
107 }
108 }
109mapfile.close();
110}
111 
112 
113TTilemap::TTilemap()
114{
115 
116}
117 
118 
119TTilemap::~TTilemap()
120{
121int n = tile.size();
122for (int i = 0; i < n; i++)
123 {
124 delete tile<i>;
125 }
126 
127}
128 
129void TTilemap::draw(int camx,int camy)
130{
131int DrawX;
132int DrawY;
133int n,startx,endx,starty,endy;
134 
135startx=(camx-400)/TILESIZE;
136if (startx<0) startx=0;
137if (startx>cell.size()) startx=cell.size();
138 
139endx=((camx+400)/TILESIZE)+1;
140if (endx<0) endx=0;
141if (endx>cell.size()) endx=cell.size();
142 
143starty=(camy-300)/TILESIZE;
144if (starty<0) starty=0;
145if (starty>cell[0].size()) starty=cell[0].size();
146 
147endy=((camy+300)/TILESIZE)+1;
148if (endy<0) endy=0;
149if (endy>cell[0].size()) endy=cell[0].size();
150 
151 
152for (int y=starty;y<endy;y++)
153 {
154 for (int x=startx;x<endx;x++)
155 {
156 DrawX=int(screenmid_x+((x*32)-camx));
157 DrawY=int(screenmid_y+((y*32)-camy));
158 n=cell[y][x]->index;
159 if (n!=TI_NONE)
160 {
161 if (n<=tile.size()) tile[n-1]->Blit(DrawX, DrawY);
162 }
163 else
164 {
165 assert(false);
166 }
167 }
168 }
169 
170}

any advice ? speedup tips ?

Kauhiz

It works?

Ariesnl

yes it works ;D

here is some working test from the map with some wacky AI code
use cursorkeys to scroll

Indeterminatus

Warning! The following are mostly stylistic issues, so read them with a grain of salt, don't get religious about it and ignore them at will.

Replace the magic numbers with named constants, like (camx+400), which I guess resembles (camx+SCREEN_W/2).

        if (n!=TI_NONE)
           {
               if (n<=tile.size()) tile[n-1]->Blit(DrawX, DrawY);
           }
        else
           {
             assert(false);
           }
        }

IMHO, that's improper use of assert.

Try to keep the lifespan of a variable as short as possible, meaning: limit its scope by declaring it only where you need it (and not at the top of the method body).

If you don't want camx or camy to be negative, declare them as unsigned int, and you can remove the checks if (camx < 0) etc.
(The same goes for m_nWidth).

Use consistent nomenclature. Sometimes you use Hungarian notation (m_nWidth), and sometimes you don't. I won't argue about which one to choose, just pick one and stick to it.

In your load method, use std::string.

Other than that, possible functional extensions:

  • more layers

  • arbitrary dimensions of tiles

  • zooming in and out

  • ...

Carrus85
if (n!=TI_NONE)
           {
               if (n<=tile.size()) tile[n-1]->Blit(DrawX, DrawY);
           }
        else
           {
             assert(false);
           }
        }

Yes, that would be a bad use of assert, especially considering assert doesn't exist if you compile without debugging enabled (-DNDEBUG on the compiler line), which should be done for all release executables.

This is a better, more appropriate use of assert:

assert(n!=TI_NONE);
if(n<=tile.size()) tile[n-1]->Blit(DrawX, DrawY);

This way, you have the same general effect when assertions are enabled (your program terminates with an assertion failed message). Additionally, you eliminate some unnecessary branches.

Ariesnl

@Indeterminatus You've got a point there, I'll have to neat up my code anyway.
About that hungarian notation.. that's something I just learned.. I'm still getting used to it.

As some of you might ave noticed My sprites (units) can walk in any direction, but it makes AI a bit complicated. I thought it would be nice to combine a tilemap with vector AI ( since till now I only used vector AI) but maybe that is not a verry good idea... On the other hand It's fun if you can steer the player in a 2D game like you can in 3D games ( with mouse and WSAD )

what brings me to another subject.. aiming and shooting.. My NPC's really aim and fire a bullet but if you have only 8 directions... should you use a hit chance and just animate or ..?

Steve++

Everything that Indeterminatus said.

If you want to improve your coding style, I thoroughly suggest reading Code Complete. I've read the second edition. You don't have to agree with every recommendation in the book, but it gets you thinking about writing readable and maintainable code.

As for Hungarian notation, I use it myself in C++. The only real problem is that it's not enforced (unless you know of an IDE that enforces it) so the compiler isn't going to complain if you don't use it properly or consistently. Also, Hungarian notated code isn't very readable to people that don't use the notation, but it is actually more readable to people that use the notation than 'regular' code. Another thing, you can extend Hungarian notation in any way you see fit. When I started playing with 3D, I used the 'mat' prefix for matrices. For example, m_matRotate would be a matrix that does some kind of rotation that belongs to an object. pmatRotate could be a matrix pointer passed in as a parameter. Enough about Hungarian notation though.

You also need to clean up your indenting style. For one thing, it's inconsistent. I see that you're placing your opening braces on a new line. That's a good start (in my opinion). You shouldn't indent the opening brace, because then it doesn't look like the code inside the braces belongs to its control structure. Also in some places, you're not indenting the code once it's inside the braces. Not good. Also, use one tab character per indent level instead of spaces. If your IDE is set to replace tabs with spaces, turn that setting off. ALWAYS use braces for if/else/while/do-while/for/etc. For one thing, it saves confusion (some of that confusion is caused by poor indenting) and it allows you to add more statements to the body if and when you need to (which happens quite often). What IDE are you using by the way?

Here's some code of yours, formatted nicely (take note of the added 'else's):

1void TTilemap::draw(int camx, int camy)
2{
3 int DrawX;
4 int DrawY;
5 int n, startx, endx, starty, endy;
6 startx = (camx - 400) / TILESIZE;
7 if (startx < 0)
8 {
9 startx = 0;
10 }
11 else if (startx > cell.size())
12 {
13 startx = cell.size();
14 }
15 endx = ((camx + 400) / TILESIZE) + 1;
16 if (endx < 0)
17 {
18 endx = 0;
19 }
20 else if (endx > cell.size())
21 {
22 endx = cell.size();
23 }
24 starty = (camy - 300) / TILESIZE;
25 if (starty < 0)
26 {
27 starty = 0;
28 }
29 else if (starty > cell[0].size())
30 {
31 starty = cell[0].size();
32 }
33 
34 endy = ((camy + 300) / TILESIZE) + 1;
35 if (endy < 0)
36 {
37 endy = 0;
38 }
39 else if (endy > cell[0].size())
40 {
41 endy = cell[0].size();
42 }
43 
44 for (int y = starty; y < endy; y++)
45 {
46 for (int x = startx; x < endx; x++)
47 {
48 DrawX = int(screenmid_x + ((x * 32) - camx));
49 DrawY = int(screenmid_y + ((y * 32) - camy));
50 n = cell[y][x]->index;
51 if (n != TI_NONE)
52 {
53 assert(n <= tile.size())
54 tile[n-1]->Blit(DrawX, DrawY);
55 }
56 }
57 }
58}

You could also do this to improve readability/maintainability/etc:

1inline int limitRange(int var, int lower, int upper)
2{
3 if (var < lower)
4 {
5 return lower;
6 }
7 else if (var > upper)
8 {
9 return upper;
10 }
11 return var;
12}
13 
14void TTilemap::draw(int camx, int camy)
15{
16 int DrawX;
17 int DrawY;
18 int n, startx, endx, starty, endy;
19 startx = limitRange((camx - 400) / TILESIZE, 0, cell.size());
20 endx = limitRange((camx + 400) / TILESIZE + 1, 0, cell.size());
21 starty = limitRange((camy - 300) / TILESIZE, 0, cell[0].size());
22 endy = limitRange((camy + 300) / TILESIZE + 1, 0, cell[0].size());
23 
24 for (int y = starty; y < endy; y++)
25 {
26 for (int x = startx; x < endx; x++)
27 {
28 DrawX = int(screenmid_x + ((x * 32) - camx));
29 DrawY = int(screenmid_y + ((y * 32) - camy));
30 n = cell[y][x]->index;
31 if (n != TI_NONE)
32 {
33 assert(n <= tile.size())
34 tile[n-1]->Blit(DrawX, DrawY);
35 }
36 }
37 }
38}

Richard Phipps

Personally I dislike the large amount of spaces (or spaces per tab) for that indentation style. I thought 2 characters in was supposed to be the optimum.

Ariesnl

Thanks for your advice .

I use CodeBlocks, I also have MSVC but that one is not ANSI C++

there is for example no way to do this in MSVC:

1bool Comp(const TSprite *v1, const TSprite *v2)
2{
3return (v1->z < v2->z);
4}
5 
6void TEngine::draw()
7{
8 items.sort(Comp);
9 std::list<TSprite *>::iterator p=items.begin();
10 while (p!=items.end())
11 {
12 TSprite*hulp=*p;
13 hulp->draw(x,y);
14 p++;
15 }
16
17}

and for some reason it does this wrong:

1 
2 
3for (int i=0;i<10;i++)
4{
5 // i should be declared here
6 
7}
8 
9 // NOT here
10 
11/*
12Try this im MSVC you'll get duplicate definition of i
13*/
14for (int i=0;i<10;i++)
15{
16
17 
18}

Steve++
Quote:

Personally I dislike the large amount of spaces (or spaces per tab) for that indentation style. I thought 2 characters in was supposed to be the optimum.

Allegro.cc forums use 8 spaces to represent a tab, even in the code areas. I used the TAB character there. I prefer four spaces to represent a tab, which is what most coders seem to use (and that's what MSVC and many other IDEs default to).

Ariesnl, are you using MSVC 6? It is pre-ANSI (although it tried to implement some ANSI features). You can get MSVC 2005 Express Edition free of charge. It's downloadable from MSDN.

The thing I liked about MSVC 6 is that it played the Microsoft Sound when you selected Help/About. But that was only on Win 95/98, before this XP orchestra crap.

Jonny Cook
Quote:

arbitrary dimensions of tiles

Is that even possible to accomplish with a tile engine?

Indeterminatus

Well, I was thinking more along the lines of "don't limit a tile's size to 32x32".

It is possible, yes, you probably need an offset vector to place a tile on the map.

Example: The default tilesize is 32x32 pixels, and the grid is laid out accordingly (so that two neighbouring tiles, both 32x32 pixels match perfectly without overlap or gap). One tile's gfx is 16x16. If you don't want to scale it to 32x32 (which could wreak ugly artifacts), you can center it on a grid's cell by translating the tile by the vector (8, 8).

That most likely won't make any sense for the bottom layer (or you could end up having gaps between tiles), but it could be applicable for higher layers. Not telling that's the way to go, though, just pointing out it's possible, even though that's not what I meant originally.

edit: Rewrote most of my post to clarify point.

Tobias Dammers
Quote:

Example: The default tilesize is 32x32 pixels, and the grid is laid out accordingly (so that two neighbouring tiles, both 32x32 pixels match perfectly without overlap or gap). One tile's gfx is 16x16. If you don't want to scale it to 32x32 (which could wreak ugly artifacts), you can center it on a grid's cell by translating the tile by the vector (8, 8).

For the game logic, the tile is still 32x32. You won't be able to put 16 8x8-tiles in the space of one 32x32-tile using a conventional tile grid.

Thread #587038. Printed from Allegro.cc