- 1. Introduction
- 2. Smooth movement
- 3. Collision Detection
- 4. Centering the player
- 5. Turning the player
- 6. Rotating the maze around the player
- 7. Summary

`double`

instance variables to represent the position of the player in maze coordinates; in other words, `px = 3.9, py = 1.4`

means that the player is near the middle right of the cell (3, 1) - on the right, because the x value is 3.9, very close to the next cell at 4, and in the middle vertically, because 1.4 is about halfway between 1 and 2.These two variables will be the "official" record of the player's position; the

`player`

object will simply be moved to match `px`

and `py`

whenever they are changed. To do this, I wrote a method that uses `px`

and `py`

to set the position of the `player`

:

private void positionPlayer() {

player.moveTo(ORIGIN_X + (px - .3) * WALL_LENGTH, ORIGIN_Y + (py - .3) * WALL_LENGTH);

}

The key control is very straightforward; we will simply add methods to respond to key release events as well as key press events:

private double vx, vy;

private void press(int direction) {

vx += dx(direction);

vy += dy(direction);

}

private void release(int direction) {

vx -= dx(direction);

vy -= dy(direction);

}

public void onUpPress() { press(UP); }

public void onUpRelease() { release(UP); }

Moving the player while the key is held down is also not terribly complicated. We will make our class extends ActiveObject, and then write it a

`run()`

method that loops endlessly, adding whatever the current `vx`

and `vy`

are to `px`

and `py`

, then calling that `positionPlayer()`

method we just wrote, then pausing for a while.

public void run() {

while(true) {

px += .04 * vx;

py += .04 * vy;

positionPlayer();

pause(20);

}

}

When I add the velocity, I multiply it by .04 to make the speed more reasonable. Pausing just 20 milliseconds means the player moves roughly 50 times per second, which means that each second it moves 50*.04 = 2 cells.

It turns out that collision detection requires two steps: first, we will detect when the player is trying to move through the middle of a wall; then, we will detect when the player has collided with the corner of a wall.

Remember that we said before that the

`px`

and `py`

indicates where the player is within its cell of the maze. So, if the player's circle has a radius of .3 of a cell, and a wall is about .1 of a cell thick, then, any time `px`

is less than .4, the player is touching the left side of the cell, and any time `py`

is greater than |.6|, the player is touching the right side.In order to simply

private void checkWalls() {

int x = (int)px, y = (int)py;

double cx = px - x, cy = py - y;

double rcx = 1 - cx, rcy = 1 - cy;

if(cx < WALL_D && !isOpen(x, y, LEFT)) {

px += (WALL_D - cx);

} else if(rcx < WALL_D && !isOpen(x, y, RIGHT)) {

px -= (WALL_D - rcx);

}

if(cy < WALL_D && !isOpen(x, y, UP)) {

py += (WALL_D - cy);

} else if(rcy < WALL_D && !isOpen(x, y, DOWN)) {

py -= (WALL_D - rcy);

}

}

Here,

`cx`

and `rcx`

are the distance from the left and right sides of whatever cell the player is in, and `WALL_D`

is a constant equal to .4.Once we have detected collision, the next step is to "fix" the collision by moving the player back out of the wall. That is what is going on inside of each of the

`if`

statements. It should be clear that, for example, if we hit a wall because `cx`

is less than `WALL_D`

, then, to get out of the wall, we want to move the player to the point where `cx`

is just equal to `WALL_D`

. That means that moving the player to the right by a distance of `WALL_D - cx`

, by adding that much to `px`

.So, suppose, for example, that the player is at (3.2, 4.3). That means that it is in the cell (3, 4), .2 in from the left wall and .3 in from the top wall. The distance from the top left corner is then √(.2

Once I know that a collision happened, the next step is to "fix" it. We will move the player stright out from the corner until its distance is .4. The current distance from the center is .36, and we want it to be .4, so we have to move the player out another .04, or 1/9 of its current distance. It should be obvious how to do that:

3.2 + (1/9)(.2) = 3.222

4.3 + (1/9)(.3) = 4.333

To write code to do this, I recognize that I first divided

`WALL_D / d`

to find out that the desired distance is 1.111 times what it is currently, then subtracted 1 to find that I want to add .111 of the current offset, then multiplied the current offset from the corner by that and added it to the player's position.The other corners can be handled in the same way, simply taking into account which direction each would push the player in:

private void checkCorners() {

int x = (int)px, y = (int)py;

double cx = px - x, cy = py - y;

double rcx = 1 - cx, rcy = 1 - cy;

double d;

if((d = Math.sqrt(cx * cx + cy * cy)) < WALL_D) {

// Hit top left corner

px += (WALL_D / d - 1) * cx;

py += (WALL_D / d - 1) * cy;

} else if((d = Math.sqrt(rcx * rcx + cy * cy)) < WALL_D) {

// Hit top right corner

px -= (WALL_D / d - 1) * rcx;

py += (WALL_D / d - 1) * cy;

} else if((d = Math.sqrt(cx * cx + rcy * rcy)) < WALL_D) {

// Hit bottom left corner

px += (WALL_D / d - 1) * cx;

py -= (WALL_D / d - 1) * rcy;

} else if((d = Math.sqrt(rcx * rcx + rcy * rcy)) < WALL_D) {

// Hit top left corner

px -= (WALL_D / d - 1) * rcx;

py -= (WALL_D / d - 1) * rcy;

}

}

Now that we have written methods to detect and correct collisions with both walls and corners, we can add collision detection to our program by simply calling both of these methods after we have added the velocity to the player position, but before we call

`positionPlayer()`

.`Corner`

- First, I need to find the coordinates of its corners in the maze (its "world" position)
- Next, I convert that into coordinates that say where it is relative to the player (its "transformed" position)
- Finally, I will have to use those coordinates to calculate where that corner is on the screen (its "screen" position)

Sine every corner now needs six numbers in order to describe its position in multiple different coordinate systems, it makes sense to create a

`Corner`

class that will wrap up all this information into just one object. This class can also handle the calculations for deriving the transformed and screen coordinates from the world coordinates. I will define this class `Maze`

class so that it will have access to useful instance variables like `px`

and `py`

, and constants like `WALL_LENGTH`

:

// Inside my Maze class:

private class Corner {

private double wx, wy, tx, ty, sx, sy;

public Corner(double wx, double wy) {

this.wx = wx;

this.wy = wy;

tx = wx - px;

ty = wy - py;

sx = WALL_LENGTH * tx;

sy = WALL_LENGTH * ty;

}

}

You might notice that I didn't add

`ORIGIN_X`

and `ORIGIN_Y`

to the screen coordinates. This will mean that (0, 0) screen coordinates for a `Corner`

is the middle of the screen, where the player is. As the game gets more complicated, it will be useful not to have to think about `ORIGIN_X`

ad `ORIGIN_Y`

every time we look at screen coordinates. The only place we will need to know where the screen origin is is in our `drawLine()`

method, which we can update to take two `Corner`

s as parameters:

private void drawLine(Corner start, Corner end) {

Line line = new Line(ORIGIN_X + start.sx, ORIGIN_Y + start.sy,

ORIGIN_X + end.sx, ORIGIN_Y + end.sy, canvas);

line.setStroke(STROKE);

}

`Corner`

private void drawMaze() {

canvas.clear();

player.addToCanvas(canvas);

for(int x = 0; x < hwalls.length; x++) {

for(int y = 0; y < hwalls[x].length; y++) {

if(!hwalls[x][y]) {

drawLine(new Corner(x, y), new Corner(x + 1, y));

}

}

}

for(int x = 0; x < vwalls.length; x++) {

for(int y = 0; y < vwalls[x].length; y++) {

if(!vwalls[x][y]) {

drawLine(new Corner(x, y), new Corner(x, y + 1));

}

}

}

}

I have also, of course, changed how I call

`drawLine()`

. Formerly, I would have passed along the four coordinates as numbers:

drawLine(x, y, x + 1, y);

Now, I wrap up those coordinates in

`Corner`

objects and pass them along instead:

drawLine(new Corner(x, y), new Corner(x + 1, y));

Notice that I clear the canvas at the start of the

`drawMaze()`

method. Otherwise, we would keep piling up new walls on top of the old ones, and end up with a big mess.The last step is to rewrite my

`run()`

method so that it calls `drawMaze()`

after every time that it moves the player. We will also tell the canvas to repaint only when we are done drawing the maze, so that the canvas never gets drawn with only half the walls in place.Clearly, in order to do this we have to introduce another instance variable representing the

`angle`

the player is facing in. We will also replace `vx`

and `vy`

with an `angularVelocity`

and a forward `speed`

:

public void onUpPress() { speed++; }

public void onLeftPress() { angularVelocity++; }

public void onDownPress() { speed--; }

public void onRightPress() { angularVelocity--; }

public void onUpRelease() { speed--; }

public void onLeftRelease() { angularVelocity--; }

public void onDownRelease() { speed++; }

public void onRightRelease() { angularVelocity++; }

Now, our big problem is to figure out how to convert a

`speed`

and `angle`

into a `vx`

and a `vy`

. (If you've done any trigonometry, you probably can guess where we're going with this.) Think about what will happen to the player's `vx`

as the player starts out facing up, and turns to the left:- At first,
`vx`

is zero, because the player is moving only in the y direction. - As the player's direction gets more toward the left,
`vx`

gradually becomes more negative. - Next, as the player's direction gets closer to downward,
`vx`

goes back to zero. - Finally,
`vx`

becomes more and more positive as the player turns toward the right.

`vx`

goes `0 -> -1 -> 0 -> 1`

as the player turns. If we think through the same process for `vy`

, we will find that it goes `-1 -> 0 -> 1 -> 0`

.There are two mathematical functions that are specially designed for converting an angle into an

`x`

or `y`

distance. These functions are called `Math.sin(angle)`

and the blue curve shows the value of `Math.cos(angle)`

as `angle`

goes from 0 to 2π:Any time you want to map an angle to coordinates, you should look at the pattern the coordinates take as the angle changes, and try to map it to one of these two curves. The values we found for

`vx`

were the negative of what we see for the `vy`

were the negative of the

double vx = -speed * Math.sin(angle);

double vy = -speed * Math.cos(angle);

Getting this to work will just be a matter of changing how

`Corner`

calculates the "transformed" coordinates. A corner that is directly in front of the player at a distance of 2 spaces ought to have "transformed" coordinates of (0, -2). If the player is at (4, 5), this might mean that he is looking `UP`

at a corner at (4, 3), or looking `LEFT`

at a corner at (2, 5), or looking `DOWN`

at a corner at (4, 7), or looking `RIGHT`

at a corner at (6, 5).For clarity, let's calls the

`x`

and `y`

distance to a corner from the player `dx`

and `dy`

:

double dx = wx - px;

double dy = wy - py;

In the image above, the blue dot has just a

`dx`

and no `dy`

; the green has just a `dy`

and no `dx`

. So, we can use these two dots to determine how `dx`

and `dy`

contribute to the transformed position of a corner.We see that the blue dot's contribution to

`tx`

rotates in a pattern that looks like `Math.cos(angle)`

. In other words, since the blue dot is `dx`

:

tx = dx * Math.cos(angle);

However, the green dot,

`dy`

, can also contribute to the `tx`

, so we need to add that in as well:

tx = dx * Math.cos(angle) - dy * Math.sin(angle);

We can do the same thing for the ways that the two dots,

`dx`

and `dy`

, contribute to the `ty`

:

tx = dx * Math.sin(angle) + dy * Math.cos(angle);

These two equations allow us to simply update how

`Corner`

calculates `tx`

and `ty`

in order to cause the maze to rotate around the player:

public Corner(double wx, double wy) {

this.wx = wx;

this.wy = wy;

double dx = wx - px;

double dy = wy - py;

tx = dx * Math.cos(angle) - dy * Math.sin(angle);

ty = dx * Math.sin(angle) + dy * Math.cos(angle);

sx = WALL_LENGTH * tx;

sy = WALL_LENGTH * ty;

}

The important thing to read through and remember here is the process that we went through to figure out how to use

`x`

and `y`

distances. Notice how we first reasoned about how an object ought to appear to move as the player rotates, then extracted from the the pattern of what something in each direction contributes to the `x`

and `y`

as the angle rotates, then matched that pattern to one of the four "waves" `sin(a)`

, `cos(a)`

, |-sin(a)|, or |-cos(a)|.In the next section, we will make our maze draw itself in 3D. As it turns out, this is very easy to do: we will only have to change a few lines of code to have the screen position appear on a "floor" around the player in a three dimensional world.