Arrays and tilemaps

Hello!

I’m assessing the flexibility of my code for my Dark&Under Android prototype and I’m second guessing how I’m handling the maps, and most importantly, displaying them in screen.

array

I’m using 2D arrays for my maps. To draw the map on screen (both the minimap and the pseudo 3D map), I look at the position of the player, then look at the rows/columns surrounding it to draw my world.

  1. In figure A, my player is looking north, with a vision with a range of 4 tiles, so I’ll be drawing rows -3,-2,-1 and at position, and looking at their respective surrounding col +1 and -1 to draw the 3D maze on screen

  2. if my player operates a 90deg rotation to the right, I’m thinking at instead looking at a range of 4 columns (-3,-2,-1 and at position) and it’s surrounding rows to draw the 3D maze.

Is that the right way to operate?

My enemies are objects (independent from the array, for now) that are stored with their coordinates expressed in rows/columns relative to the level array. When they move, they refer to that array and update their coordinates inside the object.
While relatively easy to show them in the 3D maze when I’m looking at the array from the North point of view (figure A), it might become very complicated to convert their coordinates when rotating the player: any idea on how I could handle this?

Thanks :slight_smile:

1 Like

Just write a single method that gets cells RELATIVE to direction… so get (-1,-1) will always be the cell to the front left based on how the user is facing… you use that for render-time… the rest of the time you can use the regular orientation.

Thanks! I’m not sure I understand what you mean? Do you want me to post my code so that you can explain further?

Maybe I’m not sure I follow. Is the idea to render the map where “north” is always whichever way the user is facing - ie, how the current game works?

Yes, exactly how the game is working now: right now, my player goes forward, backward and move laterally…so I can rely on my array, as shown in figure A to build my 3D map “as is”…but once the player rotates 90degrees, I need to read the array differently…and that messes up all the other things, like enemies, objects, etc, that rely on array coordinates, while not being part of it.
I know a way around it, but it would make the code very big, so I’m really thinking that there might be a trick that I’m missing.

Except it doesn’t really. The only thing that needs to care about “which way you’re facing” is the map rendering… the rest of the game north should be north, period.

So there MIGHT be some complexity but it’d be all wrapped up in:

// get tile relative to which way the player is facing
// ie: (0, -1) is right in front of the player  
player.getRelativeTileAt(x, y)

Then to render your player map you just have a two dimensional loop that calls getRelativeTileAt(x, y).

So internally getRelativeTile looks at which way you are facing and does a transform of the cordinates provided into rotated coordinates. Should be a case statement with 4 cases, obviously north being the easiest in that you don’t do any transform at all.

Here’s the function that draws the world, inside a Vision class that also loads the proper images, etc

void playerVision (){//draw the walls by checking row and cols ahead of player
    //println(oddStep);
    description = myLevel.myDescription;
    if (oddStep){
     image(visionBack,myVision.originX,myVision.originY,98*multi,70*multi); 
    } else {
     image(visionBack2,myVision.originX,myVision.originY,98*multi,70*multi);
    }
    //scale(1,1);
    //far front wall
    if (direction=="forward"){
      lookForward();
    } else if (direction=="right"){
      lookRight();
    }
  }
  void lookForward(){
   if (row-3>=0){//make sure we don't check row out of bound in the array
      if (myLevel.worldGrid[row-3][col]>0) {
        image(farWallFront,originX,originY,98*multi,70*multi);
      }
    }
    //far left wall
    if (row-2>=0){
      if (myLevel.worldGrid[row-2][col-1]>0) {
        image(farWallLeft,originX,originY,98*multi,70*multi);
      }
    }
    //far right wall
    if (row-2>=0){
      if (myLevel.worldGrid[row-2][col+1]>0) {
        image(farWallRight,originX,originY,98*multi,70*multi);
      }
    }
    //mid front wall
    if (row-2>=0){
      if (myLevel.worldGrid[row-2][col]>0) {
        image(midWallFront,originX,originY,98*multi,70*multi);
      }
    }
    //mid left wall
    if (myLevel.worldGrid[row-1][col-1]>0) {
      image(midWallLeft,originX,originY,98*multi,70*multi);
    }
    //mid right wall
    if (myLevel.worldGrid[row-1][col+1]>0) {
      image(midWallRight,originX,originY,98*multi,70*multi);
    }
    //close front wall
    if (myLevel.worldGrid[row-1][col]>0) {
      image(closeWallFront,originX,originY,98*multi,70*multi);
    }
    //close left wall
    if (myLevel.worldGrid[row][col-1]>0) {
      image(closeWallLeft,originX,originY,98*multi,70*multi);
    }
    //close right wall
    if (myLevel.worldGrid[row][col+1]>0) {
      image(closeWallRight,originX,originY,98*multi,70*multi);
    } 
  }

The question is: right now, this always draw the world as if I was looking north, relatively to the Array… what If I rotate 90degs on the right? I’ve posted my solution in the original question, I’m just checking if there is a better way?

You don’t need “lookDirection” you push that complexity into the getRelativeTile function then there is only “lookingForward” - you’re always looking forward…

# this works regardless of direction
func renderMap()
  for x: -10..10
    for y: -10..10
      drawTile player.getRelativeTile(x, y)

Well technically … you might run off the edge of the map, but hopefully you get the idea. :slight_smile:

Edit: Sorry updated sample to be RELATIVE coordinates. IF a coordinate is “off the map” you can just return “wall” or “mountain” or something you can’t walk thru.

Sample: (actually it’s really like only a 8 line method)

func Player::getRelativeTile(x, y, orient: player.orientation) {
  if orient == north
    getAbsoluteTile(player.x+x,player.y+y) # no transform
  if orient == south
   getRelativeTile(-x,-y, orient: North) # transform the x, y coordinate to northern orientation
  # etc
}

Actually you’re looking at +3, +2 and +1.
Positive to the right, negative to the left.

Yes, that’s more or less what the original does.

Why do you need to convert the enemies’ coordinates?

Their coordinates will be global/absolute coordinates, as will the player’s coordinates.

The direction the player is facing only needs to be factored in when rendering (or possibly if there’s some enemy AI logic that takes it into account).

Rotating 90 degrees is only a matter of changing the sign of the coordinates.
(There’s actually a very good mathematical reason why this is the case, but I won’t bore you with that unless you’re interested.)

Converting is pretty simple:

  • To rotate 0° clockwise newX = oldX; newY = oldY;
  • To rotate 90° clockwise newX = oldY; newY = -oldX;
  • To rotate 180° clockwise newX = -oldX; newY = -oldY;
  • To rotate 270° clockwise (90° anticlockwise) newX = -oldY; newY = oldX;

If you’ve got some kind of Point type then you can easily hide this behind a function that does the rotating for you.

However, be aware that this transformation happens relative to an origin.
For global/absolute coordinates the origin is point (0, 0).
If you want to to rotate around a particular point (e.g. the player’s position) then you have to change the origin.
You do that by subtracting the origin from the coordinates, performing the rotation and then adding the origin back to the result.

I.e.

public static Point rotate90ClockwiseAroundPoint(Point origin, Point point)
{
	int relativeX = (point.getX() - origin.getX());
	int relativeY = (point.getY() - origin.getY());
	
	int transformedX = relativeY;
	int transformedY = -relativeX;
	
	int resultX = (transformedX + origin.getX());
	int resultY = (transformedY + origin.getY());
	
	return new Point(resultX, resultY);
}

(At least I’m pretty sure that’s how it works, I’m a tad rusty.)

Note that I haven’t tested the code at all.
In fact I’ll be surprised if it doesn’t have any syntax errors at least,
it’s been years since I last used Java.

I’m surprised that processing doesn’t have enum support.
This is the kind of thing I was worried about when it comes to memory and porting.

Apparently it kind of does, but you have to use a separate .java file rather than a regular processing file because processing’s preprocessor can’t handle enums.

You could get around this by using some static final variables,
but it probably doesn’t matter that much.
The performance difference would be negligable on phone/tablet hardware.

Thanks! I’m going to try and absorb this and will report back :slight_smile: