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.
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.
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
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?
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.
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.
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
}
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;
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.