Ghosts can chase!

Refactor position to be a Vector2 for convenience sake.
This commit is contained in:
Matt Low 2019-12-26 03:38:14 +04:00
parent d10148ace1
commit 75d0c41bb5
9 changed files with 163 additions and 45 deletions

View File

@ -1,8 +1,45 @@
package com.me.pacman.entity; package com.me.pacman.entity;
import com.badlogic.gdx.math.Vector2;
public enum Direction { public enum Direction {
UP, UP,
DOWN, DOWN,
LEFT, LEFT,
RIGHT, RIGHT,
;
public boolean isOpposite(Direction dir) {
switch (this) {
case UP:
return dir == DOWN;
case DOWN:
return dir == UP;
case LEFT:
return dir == RIGHT;
case RIGHT:
return dir == LEFT;
}
return false;
}
public Vector2 getVector(float scale) {
switch (this) {
case UP:
return new Vector2(0f, 1f * scale);
case DOWN:
return new Vector2(0f, -1f * scale);
case LEFT:
return new Vector2(-1 * scale, 0f);
case RIGHT:
return new Vector2(1 * scale, 0f);
}
return null;
}
public Vector2 getVector() {
return this.getVector(1f);
}
} }

View File

@ -2,28 +2,27 @@ package com.me.pacman.entity;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.state.LevelState; import com.me.pacman.state.LevelState;
public abstract class Entity { public abstract class Entity {
public LevelState state; public LevelState state;
public float x; public Vector2 pos;
public float y;
public int age; public int age;
public Entity(LevelState state, float x, float y) { public Entity(LevelState state, float x, float y) {
this.state = state; this.state = state;
this.x = x; this.pos = new Vector2(x, y);
this.y = y;
this.age = 0; this.age = 0;
} }
public void render(SpriteBatch batch, int offsetX, int offsetY) { public void render(SpriteBatch batch, int offsetX, int offsetY) {
batch.draw(getSprite(), (int) (x * 8) + (offsetX - 8), (y * 8) + (offsetY - 8)); batch.draw(getSprite(), (int) (pos.x * 8) + (offsetX - 8), (pos.y * 8) + (offsetY - 8));
} }
public boolean onSameTile(Entity other) { public boolean onSameTile(Entity other) {
return (int) x == (int) other.x && (int) y == (int) other.y; return (int) pos.x == (int) other.pos.x && (int) pos.y == (int) other.pos.y;
} }
public abstract TextureRegion getSprite(); public abstract TextureRegion getSprite();

View File

@ -2,12 +2,16 @@ package com.me.pacman.entity;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.entity.ai.Behaviour; import com.me.pacman.entity.ai.Behaviour;
import com.me.pacman.entity.ai.Target; import com.me.pacman.entity.ai.Target;
import com.me.pacman.level.LevelTile;
import com.me.pacman.state.PlayState; import com.me.pacman.state.PlayState;
public class Ghost extends MovableEntity { public class Ghost extends MovableEntity {
public static final Direction[] GHOST_ORDER = { Direction.UP, Direction.LEFT, Direction.DOWN, Direction.RIGHT };
private PlayState state; private PlayState state;
public Behaviour currentBehaviour; public Behaviour currentBehaviour;
@ -23,7 +27,7 @@ public class Ghost extends MovableEntity {
public Ghost(PlayState state, float x, float y, Direction direction, int spriteIndex, public Ghost(PlayState state, float x, float y, Direction direction, int spriteIndex,
Behaviour chaseBehaviour, Behaviour freightBehaviour, Behaviour scatterBehaviour) { Behaviour chaseBehaviour, Behaviour freightBehaviour, Behaviour scatterBehaviour) {
super(state, x, y, 7.33f, true, direction, 0.1f); super(state, x, y, 7.03f, true, direction, 0.1f);
this.state = state; this.state = state;
this.spriteIndex = spriteIndex; this.spriteIndex = spriteIndex;
this.chaseBehaviour = chaseBehaviour; this.chaseBehaviour = chaseBehaviour;
@ -42,7 +46,7 @@ public class Ghost extends MovableEntity {
super.render(batch, offsetX, offsetY); super.render(batch, offsetX, offsetY);
// draw eyes so the ghost can see // draw eyes so the ghost can see
batch.draw(sprite[1][currDirection.ordinal()], (int) (x * 8) + (offsetX - 8), (y * 8) + (offsetY - 8)); batch.draw(sprite[1][currDirection.ordinal()], (int) (pos.x * 8) + (offsetX - 8), (pos.y * 8) + (offsetY - 8));
} }
@Override @Override
@ -52,6 +56,41 @@ public class Ghost extends MovableEntity {
if (!state.paused && age % 20 == 0) { if (!state.paused && age % 20 == 0) {
counter++; counter++;
} }
if (currentBehaviour == null) {
return;
}
if (getNextDirection() != null) {
return;
}
Target target = currentBehaviour.getTarget();
Vector2 ahead = new Vector2((int) pos.x, (int) pos.y).add(currDirection.getVector());
Direction bestDirection = null;
float shortest = Float.MAX_VALUE;
for (Direction dir : GHOST_ORDER) {
if (dir.isOpposite(currDirection)) {
continue;
}
Vector2 adjacent = dir.getVector().add(ahead);
LevelTile nextTile = state.level.getTile(adjacent);
if (nextTile != null && nextTile.isPassable()) {
// compute distance to target
float d = target.distance_sqr(adjacent);
if (d < shortest) {
shortest = d;
bestDirection = dir;
}
}
}
if (bestDirection != null) {
setNextDirection(bestDirection);
}
} }
private class ReturnToBase extends Behaviour { private class ReturnToBase extends Behaviour {

View File

@ -1,5 +1,6 @@
package com.me.pacman.entity; package com.me.pacman.entity;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.level.LevelTile; import com.me.pacman.level.LevelTile;
import com.me.pacman.state.LevelState; import com.me.pacman.state.LevelState;
@ -35,46 +36,46 @@ public abstract class MovableEntity extends Entity {
boolean turned = false; boolean turned = false;
switch (nextDirection) { switch (nextDirection) {
case UP: case UP:
nextTile = state.level.getTile(x, y + 1f); nextTile = state.level.getTile(pos.x, pos.y + 1f);
if (nextTile == null) { if (nextTile == null) {
if (currDirection == Direction.DOWN) { if (currDirection == Direction.DOWN) {
turned = true; turned = true;
} }
} else if (nextTile.isPassable() && Math.abs(x - ((int) x + 0.5f)) <= tolerance) { } else if (nextTile.isPassable() && Math.abs(pos.x - ((int) pos.x + 0.5f)) <= tolerance) {
x = ((int) x) + 0.5f; pos.x = ((int) pos.x) + 0.5f;
turned = true; turned = true;
} }
break; break;
case RIGHT: case RIGHT:
nextTile = state.level.getTile(x + 1f, y); nextTile = state.level.getTile(pos.x + 1f, pos.y);
if (nextTile == null) { if (nextTile == null) {
if (currDirection == Direction.LEFT) { if (currDirection == Direction.LEFT) {
turned = true; turned = true;
} }
} else if (nextTile.isPassable() && Math.abs(y - ((int) y + 0.5f)) <= tolerance) { } else if (nextTile.isPassable() && Math.abs(pos.y - ((int) pos.y + 0.5f)) <= tolerance) {
y = ((int) y) + 0.5f; pos.y = ((int) pos.y) + 0.5f;
turned = true; turned = true;
} }
break; break;
case DOWN: case DOWN:
nextTile = state.level.getTile(x, y - 1f); nextTile = state.level.getTile(pos.x, pos.y - 1f);
if (nextTile == null) { if (nextTile == null) {
if (currDirection == Direction.UP) { if (currDirection == Direction.UP) {
turned = true; turned = true;
} }
} else if (nextTile.isPassable() && Math.abs(x - ((int) x + 0.5f)) <= tolerance) { } else if (nextTile.isPassable() && Math.abs(pos.x - ((int) pos.x + 0.5f)) <= tolerance) {
x = ((int) x) + 0.5f; pos.x = ((int) pos.x) + 0.5f;
turned = true; turned = true;
} }
break; break;
case LEFT: case LEFT:
nextTile = state.level.getTile(x - 1f, y); nextTile = state.level.getTile(pos.x - 1f, pos.y);
if (nextTile == null) { if (nextTile == null) {
if (currDirection == Direction.RIGHT) { if (currDirection == Direction.RIGHT) {
turned = true; turned = true;
} }
} else if (nextTile.isPassable() && Math.abs(y - ((int) y + 0.5f)) <= tolerance) { } else if (nextTile.isPassable() && Math.abs(pos.y - ((int) pos.y + 0.5f)) <= tolerance) {
y = ((int) y) + 0.5f; pos.y = ((int) pos.y) + 0.5f;
turned = true; turned = true;
} }
break; break;
@ -87,50 +88,49 @@ public abstract class MovableEntity extends Entity {
float dist = speed * dt; float dist = speed * dt;
LevelTile currentTile = state.level.getTile(x, y); LevelTile currentTile = state.level.getTile(pos.x, pos.y);
nextTile = null; nextTile = null;
float new_y = y; Vector2 new_pos = new Vector2(pos);
float new_x = x;
switch (currDirection) { switch (currDirection) {
case UP: case UP:
if (currentTile == null && y > state.level.height + 1) { if (currentTile == null && pos.y > state.level.height + 1) {
new_y = -1f; new_pos.y = -1f;
canMove = true; canMove = true;
} else { } else {
new_y += dist; new_pos.y += dist;
nextTile = state.level.getTile(new_x, new_y + 0.5f); nextTile = state.level.getTile(new_pos.x, new_pos.y + 0.5f);
canMove = nextTile == null || nextTile.isPassable(); canMove = nextTile == null || nextTile.isPassable();
} }
break; break;
case RIGHT: case RIGHT:
if (currentTile == null && x > state.level.width + 1) { if (currentTile == null && pos.x > state.level.width + 1) {
new_x = -1f; new_pos.x = -1f;
canMove = true; canMove = true;
} else { } else {
new_x += dist; new_pos.x += dist;
nextTile = state.level.getTile(new_x + 0.5f, new_y); nextTile = state.level.getTile(new_pos.x + 0.5f, new_pos.y);
canMove = nextTile == null || nextTile.isPassable(); canMove = nextTile == null || nextTile.isPassable();
} }
break; break;
case DOWN: case DOWN:
if (currentTile == null && y < 0) { if (currentTile == null && pos.y < 0) {
new_y = state.level.height + 1; new_pos.y = state.level.height + 1;
canMove = true; canMove = true;
} else { } else {
new_y -= dist; new_pos.y -= dist;
nextTile = state.level.getTile(new_x, new_y - 0.5f); nextTile = state.level.getTile(new_pos.x, new_pos.y - 0.5f);
canMove = nextTile == null || nextTile.isPassable(); canMove = nextTile == null || nextTile.isPassable();
} }
break; break;
case LEFT: case LEFT:
if (currentTile == null && x < 0) { if (currentTile == null && pos.x < 0) {
new_x = state.level.width + 1; new_pos.x = state.level.width + 1;
canMove = true; canMove = true;
} else { } else {
new_x -= dist; new_pos.x -= dist;
nextTile = state.level.getTile(new_x - 0.5f, new_y); nextTile = state.level.getTile(new_pos.x - 0.5f, new_pos.y);
canMove = nextTile == null || nextTile.isPassable(); canMove = nextTile == null || nextTile.isPassable();
} }
break; break;
@ -138,8 +138,8 @@ public abstract class MovableEntity extends Entity {
// if move isn't going to collide with wall, move normally. // if move isn't going to collide with wall, move normally.
// otherwise, trim would-be decimal and center entity on tile // otherwise, trim would-be decimal and center entity on tile
x = canMove? new_x : ((int) new_x) + 0.5f; pos.x = canMove? new_pos.x : ((int) new_pos.x) + 0.5f;
y = canMove? new_y : ((int) new_y) + 0.5f; pos.y = canMove? new_pos.y : ((int) new_pos.y) + 0.5f;
} }
public void setNextDirection(Direction direction) { public void setNextDirection(Direction direction) {
@ -148,4 +148,8 @@ public abstract class MovableEntity extends Entity {
} }
} }
public Direction getNextDirection() {
return nextDirection;
}
} }

View File

@ -54,18 +54,18 @@ public class Pacman extends MovableEntity {
deathFrame++; deathFrame++;
} }
LevelTile tile = state.level.getTile(x, y); LevelTile tile = state.level.getTile(pos.x, pos.y);
if (tile == null) { if (tile == null) {
return; return;
} }
switch (tile) { switch (tile) {
case PELLET: case PELLET:
state.eatPellet(x, y); state.eatPellet(pos.x, pos.y);
freezeFrames = 1; freezeFrames = 1;
break; break;
case POWER_PELLET: case POWER_PELLET:
state.eatPowerPellet(x, y); state.eatPowerPellet(pos.x, pos.y);
freezeFrames = 3; freezeFrames = 3;
break; break;
} }

View File

@ -0,0 +1,16 @@
package com.me.pacman.entity.ai;
import com.me.pacman.state.PlayState;
public class BlinkyChaseBehaviour extends Behaviour {
public BlinkyChaseBehaviour(PlayState state) {
super(state);
}
@Override
public Target getTarget() {
return new Target(state.pacman.pos);
}
}

View File

@ -1,5 +1,7 @@
package com.me.pacman.entity.ai; package com.me.pacman.entity.ai;
import com.badlogic.gdx.math.Vector2;
public class Target { public class Target {
public int x; public int x;
@ -15,6 +17,17 @@ public class Target {
this.y = (int) y; this.y = (int) y;
} }
public Target(Vector2 vector) {
this(vector.x, vector.y);
}
public float distance_sqr(Vector2 vec) {
float d_x = x - vec.x;
float d_y = y - vec.y;
// a^2 + b^2 = c^2. thanks pythagoras!
return d_x * d_x + d_y * d_y;
}
public boolean targetReached(float x, float y) { public boolean targetReached(float x, float y) {
return this.x == (int) x && this.y == (int) y; return this.x == (int) x && this.y == (int) y;
} }

View File

@ -1,6 +1,7 @@
package com.me.pacman.level; package com.me.pacman.level;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.PacDude; import com.me.pacman.PacDude;
public class Level { public class Level {
@ -38,6 +39,10 @@ public class Level {
return getTile((int) x, (int) y); return getTile((int) x, (int) y);
} }
public LevelTile getTile(Vector2 vec) {
return getTile(vec.x, vec.y);
}
public void setTile(int x, int y, LevelTile tile) { public void setTile(int x, int y, LevelTile tile) {
tiles[y][x] = tile; tiles[y][x] = tile;
} }

View File

@ -11,6 +11,7 @@ import com.me.pacman.PacDude;
import com.me.pacman.entity.Direction; import com.me.pacman.entity.Direction;
import com.me.pacman.entity.Ghost; import com.me.pacman.entity.Ghost;
import com.me.pacman.entity.Pacman; import com.me.pacman.entity.Pacman;
import com.me.pacman.entity.ai.BlinkyChaseBehaviour;
import com.me.pacman.level.Level; import com.me.pacman.level.Level;
import com.me.pacman.level.LevelTile; import com.me.pacman.level.LevelTile;
@ -181,7 +182,10 @@ public class PlayState extends LevelState {
public void newRound() { public void newRound() {
round++; round++;
level = new Level(game,"level"); level = new Level(game,"level");
pacman = new Pacman(this, false); pacman = new Pacman(this, false);
ghosts[0] = new Ghost(this, 14, 19.5f, Direction.LEFT, 0, new BlinkyChaseBehaviour(this), null, null);
ghosts[0].currentBehaviour = ghosts[0].chaseBehaviour;
game.assets.siren.stop(sirenId); game.assets.siren.stop(sirenId);
@ -202,7 +206,8 @@ public class PlayState extends LevelState {
pacmanCaught = false; pacmanCaught = false;
pacman = new Pacman(this, false); pacman = new Pacman(this, false);
ghosts[0] = new Ghost(this, 14, 19.5f, Direction.LEFT, 0, null, null, null); ghosts[0] = new Ghost(this, 14, 19.5f, Direction.LEFT, 0, new BlinkyChaseBehaviour(this), null, null);
ghosts[0].currentBehaviour = ghosts[0].chaseBehaviour;
} }
public void startGame() { public void startGame() {