From 75d0c41bb5e21bfbf3ee5468238db8bc4c7b7500 Mon Sep 17 00:00:00 2001 From: Matt Low Date: Thu, 26 Dec 2019 03:38:14 +0400 Subject: [PATCH] Ghosts can chase! Refactor position to be a Vector2 for convenience sake. --- core/src/com/me/pacman/entity/Direction.java | 37 ++++++++++ core/src/com/me/pacman/entity/Entity.java | 11 ++- core/src/com/me/pacman/entity/Ghost.java | 43 +++++++++++- .../com/me/pacman/entity/MovableEntity.java | 70 ++++++++++--------- core/src/com/me/pacman/entity/Pacman.java | 6 +- .../entity/ai/BlinkyChaseBehaviour.java | 16 +++++ core/src/com/me/pacman/entity/ai/Target.java | 13 ++++ core/src/com/me/pacman/level/Level.java | 5 ++ core/src/com/me/pacman/state/PlayState.java | 7 +- 9 files changed, 163 insertions(+), 45 deletions(-) create mode 100644 core/src/com/me/pacman/entity/ai/BlinkyChaseBehaviour.java diff --git a/core/src/com/me/pacman/entity/Direction.java b/core/src/com/me/pacman/entity/Direction.java index 70b7bac..c93b637 100644 --- a/core/src/com/me/pacman/entity/Direction.java +++ b/core/src/com/me/pacman/entity/Direction.java @@ -1,8 +1,45 @@ package com.me.pacman.entity; +import com.badlogic.gdx.math.Vector2; + public enum Direction { + UP, DOWN, LEFT, 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); + } + } diff --git a/core/src/com/me/pacman/entity/Entity.java b/core/src/com/me/pacman/entity/Entity.java index 8a85ab5..4a7d1d5 100644 --- a/core/src/com/me/pacman/entity/Entity.java +++ b/core/src/com/me/pacman/entity/Entity.java @@ -2,28 +2,27 @@ package com.me.pacman.entity; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; import com.me.pacman.state.LevelState; public abstract class Entity { public LevelState state; - public float x; - public float y; + public Vector2 pos; public int age; public Entity(LevelState state, float x, float y) { this.state = state; - this.x = x; - this.y = y; + this.pos = new Vector2(x, y); this.age = 0; } 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) { - 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(); diff --git a/core/src/com/me/pacman/entity/Ghost.java b/core/src/com/me/pacman/entity/Ghost.java index 425ee4e..9f4dffe 100644 --- a/core/src/com/me/pacman/entity/Ghost.java +++ b/core/src/com/me/pacman/entity/Ghost.java @@ -2,12 +2,16 @@ package com.me.pacman.entity; import com.badlogic.gdx.graphics.g2d.SpriteBatch; 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.Target; +import com.me.pacman.level.LevelTile; import com.me.pacman.state.PlayState; public class Ghost extends MovableEntity { + public static final Direction[] GHOST_ORDER = { Direction.UP, Direction.LEFT, Direction.DOWN, Direction.RIGHT }; + private PlayState state; public Behaviour currentBehaviour; @@ -23,7 +27,7 @@ public class Ghost extends MovableEntity { public Ghost(PlayState state, float x, float y, Direction direction, int spriteIndex, 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.spriteIndex = spriteIndex; this.chaseBehaviour = chaseBehaviour; @@ -42,7 +46,7 @@ public class Ghost extends MovableEntity { super.render(batch, offsetX, offsetY); // 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 @@ -52,6 +56,41 @@ public class Ghost extends MovableEntity { if (!state.paused && age % 20 == 0) { 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 { diff --git a/core/src/com/me/pacman/entity/MovableEntity.java b/core/src/com/me/pacman/entity/MovableEntity.java index a7856c6..65f97ba 100644 --- a/core/src/com/me/pacman/entity/MovableEntity.java +++ b/core/src/com/me/pacman/entity/MovableEntity.java @@ -1,5 +1,6 @@ package com.me.pacman.entity; +import com.badlogic.gdx.math.Vector2; import com.me.pacman.level.LevelTile; import com.me.pacman.state.LevelState; @@ -35,46 +36,46 @@ public abstract class MovableEntity extends Entity { boolean turned = false; switch (nextDirection) { case UP: - nextTile = state.level.getTile(x, y + 1f); + nextTile = state.level.getTile(pos.x, pos.y + 1f); if (nextTile == null) { if (currDirection == Direction.DOWN) { turned = true; } - } else if (nextTile.isPassable() && Math.abs(x - ((int) x + 0.5f)) <= tolerance) { - x = ((int) x) + 0.5f; + } else if (nextTile.isPassable() && Math.abs(pos.x - ((int) pos.x + 0.5f)) <= tolerance) { + pos.x = ((int) pos.x) + 0.5f; turned = true; } break; case RIGHT: - nextTile = state.level.getTile(x + 1f, y); + nextTile = state.level.getTile(pos.x + 1f, pos.y); if (nextTile == null) { if (currDirection == Direction.LEFT) { turned = true; } - } else if (nextTile.isPassable() && Math.abs(y - ((int) y + 0.5f)) <= tolerance) { - y = ((int) y) + 0.5f; + } else if (nextTile.isPassable() && Math.abs(pos.y - ((int) pos.y + 0.5f)) <= tolerance) { + pos.y = ((int) pos.y) + 0.5f; turned = true; } break; case DOWN: - nextTile = state.level.getTile(x, y - 1f); + nextTile = state.level.getTile(pos.x, pos.y - 1f); if (nextTile == null) { if (currDirection == Direction.UP) { turned = true; } - } else if (nextTile.isPassable() && Math.abs(x - ((int) x + 0.5f)) <= tolerance) { - x = ((int) x) + 0.5f; + } else if (nextTile.isPassable() && Math.abs(pos.x - ((int) pos.x + 0.5f)) <= tolerance) { + pos.x = ((int) pos.x) + 0.5f; turned = true; } break; case LEFT: - nextTile = state.level.getTile(x - 1f, y); + nextTile = state.level.getTile(pos.x - 1f, pos.y); if (nextTile == null) { if (currDirection == Direction.RIGHT) { turned = true; } - } else if (nextTile.isPassable() && Math.abs(y - ((int) y + 0.5f)) <= tolerance) { - y = ((int) y) + 0.5f; + } else if (nextTile.isPassable() && Math.abs(pos.y - ((int) pos.y + 0.5f)) <= tolerance) { + pos.y = ((int) pos.y) + 0.5f; turned = true; } break; @@ -87,50 +88,49 @@ public abstract class MovableEntity extends Entity { float dist = speed * dt; - LevelTile currentTile = state.level.getTile(x, y); + LevelTile currentTile = state.level.getTile(pos.x, pos.y); nextTile = null; - float new_y = y; - float new_x = x; + Vector2 new_pos = new Vector2(pos); switch (currDirection) { case UP: - if (currentTile == null && y > state.level.height + 1) { - new_y = -1f; + if (currentTile == null && pos.y > state.level.height + 1) { + new_pos.y = -1f; canMove = true; } else { - new_y += dist; - nextTile = state.level.getTile(new_x, new_y + 0.5f); + new_pos.y += dist; + nextTile = state.level.getTile(new_pos.x, new_pos.y + 0.5f); canMove = nextTile == null || nextTile.isPassable(); } break; case RIGHT: - if (currentTile == null && x > state.level.width + 1) { - new_x = -1f; + if (currentTile == null && pos.x > state.level.width + 1) { + new_pos.x = -1f; canMove = true; } else { - new_x += dist; - nextTile = state.level.getTile(new_x + 0.5f, new_y); + new_pos.x += dist; + nextTile = state.level.getTile(new_pos.x + 0.5f, new_pos.y); canMove = nextTile == null || nextTile.isPassable(); } break; case DOWN: - if (currentTile == null && y < 0) { - new_y = state.level.height + 1; + if (currentTile == null && pos.y < 0) { + new_pos.y = state.level.height + 1; canMove = true; } else { - new_y -= dist; - nextTile = state.level.getTile(new_x, new_y - 0.5f); + new_pos.y -= dist; + nextTile = state.level.getTile(new_pos.x, new_pos.y - 0.5f); canMove = nextTile == null || nextTile.isPassable(); } break; case LEFT: - if (currentTile == null && x < 0) { - new_x = state.level.width + 1; + if (currentTile == null && pos.x < 0) { + new_pos.x = state.level.width + 1; canMove = true; } else { - new_x -= dist; - nextTile = state.level.getTile(new_x - 0.5f, new_y); + new_pos.x -= dist; + nextTile = state.level.getTile(new_pos.x - 0.5f, new_pos.y); canMove = nextTile == null || nextTile.isPassable(); } break; @@ -138,8 +138,8 @@ public abstract class MovableEntity extends Entity { // if move isn't going to collide with wall, move normally. // otherwise, trim would-be decimal and center entity on tile - x = canMove? new_x : ((int) new_x) + 0.5f; - y = canMove? new_y : ((int) new_y) + 0.5f; + pos.x = canMove? new_pos.x : ((int) new_pos.x) + 0.5f; + pos.y = canMove? new_pos.y : ((int) new_pos.y) + 0.5f; } public void setNextDirection(Direction direction) { @@ -148,4 +148,8 @@ public abstract class MovableEntity extends Entity { } } + public Direction getNextDirection() { + return nextDirection; + } + } diff --git a/core/src/com/me/pacman/entity/Pacman.java b/core/src/com/me/pacman/entity/Pacman.java index e3a413a..e8ed2e0 100644 --- a/core/src/com/me/pacman/entity/Pacman.java +++ b/core/src/com/me/pacman/entity/Pacman.java @@ -54,18 +54,18 @@ public class Pacman extends MovableEntity { deathFrame++; } - LevelTile tile = state.level.getTile(x, y); + LevelTile tile = state.level.getTile(pos.x, pos.y); if (tile == null) { return; } switch (tile) { case PELLET: - state.eatPellet(x, y); + state.eatPellet(pos.x, pos.y); freezeFrames = 1; break; case POWER_PELLET: - state.eatPowerPellet(x, y); + state.eatPowerPellet(pos.x, pos.y); freezeFrames = 3; break; } diff --git a/core/src/com/me/pacman/entity/ai/BlinkyChaseBehaviour.java b/core/src/com/me/pacman/entity/ai/BlinkyChaseBehaviour.java new file mode 100644 index 0000000..f4eab87 --- /dev/null +++ b/core/src/com/me/pacman/entity/ai/BlinkyChaseBehaviour.java @@ -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); + } + +} diff --git a/core/src/com/me/pacman/entity/ai/Target.java b/core/src/com/me/pacman/entity/ai/Target.java index 35b27fc..1bb1465 100644 --- a/core/src/com/me/pacman/entity/ai/Target.java +++ b/core/src/com/me/pacman/entity/ai/Target.java @@ -1,5 +1,7 @@ package com.me.pacman.entity.ai; +import com.badlogic.gdx.math.Vector2; + public class Target { public int x; @@ -15,6 +17,17 @@ public class Target { 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) { return this.x == (int) x && this.y == (int) y; } diff --git a/core/src/com/me/pacman/level/Level.java b/core/src/com/me/pacman/level/Level.java index 79d3d66..fb05b49 100644 --- a/core/src/com/me/pacman/level/Level.java +++ b/core/src/com/me/pacman/level/Level.java @@ -1,6 +1,7 @@ package com.me.pacman.level; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; import com.me.pacman.PacDude; public class Level { @@ -38,6 +39,10 @@ public class Level { 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) { tiles[y][x] = tile; } diff --git a/core/src/com/me/pacman/state/PlayState.java b/core/src/com/me/pacman/state/PlayState.java index 11bd330..a656d40 100644 --- a/core/src/com/me/pacman/state/PlayState.java +++ b/core/src/com/me/pacman/state/PlayState.java @@ -11,6 +11,7 @@ import com.me.pacman.PacDude; import com.me.pacman.entity.Direction; import com.me.pacman.entity.Ghost; import com.me.pacman.entity.Pacman; +import com.me.pacman.entity.ai.BlinkyChaseBehaviour; import com.me.pacman.level.Level; import com.me.pacman.level.LevelTile; @@ -181,7 +182,10 @@ public class PlayState extends LevelState { public void newRound() { round++; level = new Level(game,"level"); + 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); @@ -202,7 +206,8 @@ public class PlayState extends LevelState { pacmanCaught = 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() {