Tile engine HELP! (2D arrays and such)

Started by Scarab, Sun 13/09/2009 09:23:39

Previous topic - Next topic

Scarab

Hokay,
I have learned how to use the DrawingSurface functions to draw out all my tiles (the code being messy, but it works.(I have to SetTile each tile individually)), however, I dont know how to make a GetTile function that will work out what tile the player character is on.

My plan for the movement is essentially to check wether or not the tiles above, below, left and right of the character are walkable every time the player moves into the centre of the tile, and use character[].changeroom to occupy the tiles if they are not walkable.

I will probably work out how to walk one tile at a time on my own eventually, but it would be really good if someone could point me in the right direction. (I remember seeing it in a thread a while ago, but I have been unable to track it down thus far.)

I was looking at the code Here, but I haven't been able to make it work..:(

I'm eager to do all the hard yards myself here, but I've hit a roadblock, and just if anyone with a bit more coding experience than me (that is to say, coding experience>0) could give me a leg up here that would be great.
As always, any help is appreciated.
Peace 8)
Scar

RickJ

First of all there are two ways of creating the equivalent of a 2D array.  The first was is to use a struct in the manner shown below.   This method would not seem appropriate in your case because you will likely need more than one value for each tile and struct is not nestable.
Code: ags

struct tile {
   int value[20];
}
tile TileArray[10];

int value = Tile[5].Value[7];


The other way to go is to simulate a 2D array using a 1D array.  For example an array consisting of 10 columns and 20 rows would require storage space for 20*10=100 elements.   So you make a 1D array to accomodate all the elements.  You can then make a function that converts a 2D index to the equivalent 1D index.

The other thing to do is to create a struct containing all the variables required for each tile.   This definition then becomes just like another data type like int or float.  So you can make an array of these and access them using a 2D index.
Code: ags

// Create a custom data type to hold necessary tile info
struct tile {
   String Name;
   bool Walkable;
   int Type;
   :
   int Whatever;
};

// Create a 2D  array[DIMX, DIMY] 
#define DIMX 10
#define DIMY 20
tile TileArray[200];  // create the array space, size = DIMX * DIMY

// This function converts 2D index to 1D index
function  dd(dimx, dimy) {
   return (dimy*DIMX)+dimx;
}

// To access the array you must put your code within 
// a function, usually an event handler.
function some_function() {
   // Check if tile 5,7 is walkable
   if (TileArray[dd(5,7)].Walkable==true) {
      Display("Tile 5,7 is wakabkle");
   }
   else {
      Display("Tile 5,7 is NOT wakabkle");
   }
}

// Of course you can make custom functions 
// to access the tile data also
function TileIsWalkable(int dimx, int dimy) {
   return TileArray[dd(5,7)].Walkable;
}

// And you would use it something like this ...
function some_function() {
   // Check if tile 5,7 is walkable
   if (TileIsWalkable(5,7)==true) {
      Display("Tile 5,7 is wakabkle");
   }
   else {
      Display("Tile 5,7 is NOT wakabkle");
   }
}


The only other thing you may want to consider is to put all of this in a module once you understand what we are doing in the examples above.  You can find some documents explaining how to create a module and templates for the module header and script files from the link below:

http://www.adventuregamestudio.co.uk/yabb/index.php?topic=34048.msg443220#msg443220

Scarab

Ah I think I understand how to use this, but what would I need to do to find out which tile the player character was on? I thought about dividing the player.x and player.y by 16 (the tile dimensions) but then I would be left with a decimal. Is there a way to floor the number returned from (player.x)/16? Becaue the only way I see this tile engine being able to work is if I can run some sort of GetTile function on the player co-ordinates either in the repeatedly execute or every time he reaches the stationary point of the tile. Then I can run the above functions on the surrounding tiles.

Scar

Khris

#3
Edit: No need to, just store the result in an int.
int a = 20/16;  -> a==1
int a = 31/16;  -> a==1
int a = 32/16;  -> a==2


I was actually wondering about a cool way to implement walkable areas the other day, and this is what I came up with:

Each tile is broken down into a 4x4 grid. So each WA tile consists of 16 small squares that are walkable or not.
Conveniently, to store 16 on or off values, you need 16bit a.k.a a single int.
Now we can use arrangements like this, e.g.:
     ,--------[][]
     |[][][][]|
     |[][]    |
     |[]      | corner
     |[]      |
    []--------´
    []
Each tile has a default value, e.g. ground tiles use 0, i.e. the whole tile is walkable. Then, during the map buildup, ground tiles next to walls get assigned a different value, e.g. to reflect this WA tile:
     ,--------.
     |[][]    |
     |[][]    |
wall |[][]    | ground
     |[][]    |
     '--------´
Implementing free walking (as opposed to moving the player from tile center to tile center) is a piece of cake then. As soon as a direction keypress is detected, the engine moves along the direction, checking tile's WAs until it hits one of the blocked little squares. Then it retraces one step and sends the player non-blocking to the resulting coords.
Of course one could use 9 small squares instead of 16; it depends on the width and height of the character sprite in relation to the tiles.

Scarab

I have been trying to impliment a basic form of the movement code

Code: ags

 if  (IsKeyPressed(372) == 1)
 {
   player.WalkStraight(player.x, player.y-16);
  }
 else if (IsKeyPressed(377) == 1)
 {
   player.WalkStraight(player.x+16, player.y);
   }
 else if (IsKeyPressed(380) == 1)
 {
   player.WalkStraight(player.x, player.y+16);
   }
 else if (IsKeyPressed(375) == 1)
 {
   player.WalkStraight(player.x-16, player.y);
   }
 }


and it started to get out of sinc with the tiles, so I added the following to the room's repeatedly execute:

Code: ags


if ((player.Moving == false)) //&& (player.x/16 != 1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20))
//checks to see if the player is moving, and if not, checks to see if it is on a co-ordinate point
//not sure yet if this will give the right co-ordinates, but that can be tweaked after the code is working 
{
player.Move((((FloatToInt(player.x/16)*16), player.y);
 }


This causes the problem "Runtime error: unexpected eof", in room1.asc, line -10
is there anyone who knows what this means?

the manual says:
EOF property
(Formerly known as FileIsEOF, which is now obsolete)

readonly bool File.EOF

Checks whether the specified file has had all its data read. This is only useful with files opened for reading. It returns 1 if the entire contents of the file has now been read, or 0 if not.


I dont see how it would be causing that though.
HELP!
peace


Scar


@ Khris:
Thats a nice idea with the walkable areas, but I personally like the feel of moving from tile to tile, as free motion with tiles can cause annoying mobility problems like when you are only just blocked by an obsticle, whereas with tile-by-tile motion, its always obvious whats in your path.
however, if youre using a mouse for motion then the picture changes. A good pathfinder removes that problem and in that case I think it would look smoother than a tile-by-tile pathfinder.

Khris

Let's start from the beginning:
I assume that the walkable area covers ground from x = 0 to 319.
Like I said in my previous post, storing the result of a division in an int ( = a whole number) will crop decimals. It doesn't even get rounded.
Thus, dividing player.x by 16 will always result in a whole number in the range of 0 to 19.

First of all, the code is in theory supposed to look like this:
Code: ags
  if (!player.Moving && (player.x/16 == 0 || player.x/16 == 1 || ...  || player.x/16 == 19)) {


(You got an eof error because the compiler couldn't parse your script properly due to the faulty syntax.)

Here's a much shorter version:
Code: ags
  int x = player.x/16;
  if (!player.Moving && x >= 0 && x <= 19) {


However, as I explained, the latter two conditions will always be true. (IF 0 <= player.x <= 319)
What you want to do is check if the division's remainder is equal to zero. If it is, player.x is a multiple of 16.

You can get the remainder using the mod operator:
Code: ags
  if (player.x % 16 == 0) Display("player.x is a multiple of 16");


Enough theory for now, but before I can help you with code: isn't the character supposed to stand on multiples of 16 + 8, as in 8, 24, 40, 56, ... so he's centered on the tile?

Scarab

Quote from: Khris on Sun 13/09/2009 23:04:15
Enough theory for now, but before I can help you with code: isn't the character supposed to stand on multiples of 16 + 8, as in 8, 24, 40, 56, ... so he's centered on the tile?

well, yes, but when learning how to code new things, I generally try to get something functional first, and then tweak it later.

So to change your code to return true for the half tile offset you could just change it to

Code: ags
  if (player.x % 16 == 8) {


right?

unless Im wrong there, i'll probably be able to get a fair few of the major functions working tonight.

scar

SMF spam blocked by CleanTalk