Implement ghost house exiting logic (wtf, pacman designers?)

- Fixed bug in Clyde's chase behaviour (was using Inky's instead of
his own position)
- Create a subclass of Ghost for each ghost's unique house leaving logic
- Now spawn ghosts only once
This commit is contained in:
Matt Low 2019-12-28 23:26:58 +04:00
parent dd9bdac33d
commit 3fe14e6e5d
8 changed files with 220 additions and 35 deletions

View File

@ -0,0 +1,28 @@
package com.me.pacman.entity;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.PacDude;
import com.me.pacman.entity.ai.BlinkyChaseBehaviour;
import com.me.pacman.entity.ai.StaticTargetBehaviour;
import com.me.pacman.entity.ai.Target;
import com.me.pacman.state.PlayState;
public class Blinky extends Ghost {
public static final Target SCATTER_TARGET = new Target((PacDude.LEVEL_WIDTH / 8) - 2, PacDude.LEVEL_HEIGHT / 8);
public Blinky(PlayState state, Vector2 pos, Direction direction) {
super(state, pos, direction, 0, new BlinkyChaseBehaviour(state), new StaticTargetBehaviour(state, SCATTER_TARGET), false);
}
@Override
public void update(float dt) {
if (inHouse) {
// Immediately leave house
leaveHouse();
}
super.update(dt);
}
}

View File

@ -0,0 +1,34 @@
package com.me.pacman.entity;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.entity.ai.ClydeChaseBehaviour;
import com.me.pacman.entity.ai.StaticTargetBehaviour;
import com.me.pacman.entity.ai.Target;
import com.me.pacman.state.PlayState;
public class Clyde extends Ghost {
public static final Target SCATTER_TARGET = new Target(1, -1);
public static final int DOT_LIMIT = 60;
public Clyde(PlayState state, Vector2 pos, Direction direction) {
super(state, pos, direction, 3, new ClydeChaseBehaviour(state), new StaticTargetBehaviour(state, SCATTER_TARGET), true);
}
@Override
public void update(float dt) {
if (inHouse) {
if (state.pelletsEatenSinceDeathCounterEnabled) {
if (state.pelletsEatenSinceDeath >= 32) {
state.pelletsEatenSinceDeathCounterEnabled = false;
}
} else if (dotCounter >= DOT_LIMIT) {
leaveHouse();
}
}
super.update(dt);
}
}

View File

@ -3,6 +3,8 @@ 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.badlogic.gdx.math.Vector2;
import com.me.pacman.entity.Direction;
import com.me.pacman.entity.MovableEntity;
import com.me.pacman.entity.ai.Behaviour; import com.me.pacman.entity.ai.Behaviour;
import com.me.pacman.entity.ai.ReturnToBase; import com.me.pacman.entity.ai.ReturnToBase;
import com.me.pacman.entity.ai.Target; import com.me.pacman.entity.ai.Target;
@ -25,7 +27,7 @@ public class Ghost extends MovableEntity {
private int spriteIndex; private int spriteIndex;
private int counter = 0; private int counter = 0;
private PlayState state; protected PlayState state;
public Behaviour currentBehaviour; public Behaviour currentBehaviour;
@ -36,9 +38,11 @@ public class Ghost extends MovableEntity {
public boolean inHouse; public boolean inHouse;
public boolean caught; public boolean caught;
public Ghost(PlayState state, float x, float y, Direction direction, int spriteIndex, public int dotCounter;
public Ghost(PlayState state, Vector2 pos, Direction direction, int spriteIndex,
Behaviour chaseBehaviour, Behaviour scatterBehaviour, boolean inHouse) { Behaviour chaseBehaviour, Behaviour scatterBehaviour, boolean inHouse) {
super(state, x, y, GHOST_SPEED, true, direction, 0.1f); super(state, pos.x, pos.y, GHOST_SPEED, true, direction, 0.1f);
this.state = state; this.state = state;
this.spriteIndex = spriteIndex; this.spriteIndex = spriteIndex;
this.chaseBehaviour = chaseBehaviour; this.chaseBehaviour = chaseBehaviour;
@ -89,13 +93,11 @@ public class Ghost extends MovableEntity {
} }
if (currentPath instanceof EnterGhostHousePath) { if (currentPath instanceof EnterGhostHousePath) {
currentPath = new ExitGhostHousePath(pos); inHouse = true;
currentBehaviour = null;
speed = GHOST_SPEED;
} else if (currentPath instanceof ExitGhostHousePath) { } else if (currentPath instanceof ExitGhostHousePath) {
currentBehaviour = chaseBehaviour; speed = GHOST_SPEED;
currentPath = null;
} }
currentPath = null;
} }
if (inHouse) { if (inHouse) {
@ -179,6 +181,12 @@ public class Ghost extends MovableEntity {
setNextDirection(nextDirection); setNextDirection(nextDirection);
} }
public void leaveHouse() {
currentPath = new ExitGhostHousePath(pos);
inHouse = false;
currentBehaviour = state.chase? chaseBehaviour : scatterBehaviour;
}
private class FrightenedBehaviour extends Behaviour { private class FrightenedBehaviour extends Behaviour {
private Target target; private Target target;

View File

@ -0,0 +1,35 @@
package com.me.pacman.entity;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.PacDude;
import com.me.pacman.entity.ai.InkyChaseBehaviour;
import com.me.pacman.entity.ai.StaticTargetBehaviour;
import com.me.pacman.entity.ai.Target;
import com.me.pacman.state.PlayState;
public class Inky extends Ghost {
public static final Target SCATTER_TARGET = new Target(PacDude.LEVEL_WIDTH / 8, -1);
public static final int DOT_LIMIT = 30;
public Inky(PlayState state, Vector2 pos, Direction direction) {
super(state, pos, direction, 2, new InkyChaseBehaviour(state), new StaticTargetBehaviour(state, SCATTER_TARGET), true);
}
@Override
public void update(float dt) {
if (inHouse) {
if (state.pelletsEatenSinceDeathCounterEnabled) {
if (state.pelletsEatenSinceDeath >= 17) {
leaveHouse();
}
} else if (dotCounter >= DOT_LIMIT) {
leaveHouse();
}
}
super.update(dt);
}
}

View File

@ -1,8 +1,6 @@
package com.me.pacman.entity; package com.me.pacman.entity;
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.level.LevelTile; import com.me.pacman.level.LevelTile;
import com.me.pacman.state.PlayState; import com.me.pacman.state.PlayState;

View File

@ -0,0 +1,27 @@
package com.me.pacman.entity;
import com.badlogic.gdx.math.Vector2;
import com.me.pacman.PacDude;
import com.me.pacman.entity.ai.PinkyChaseBehaviour;
import com.me.pacman.entity.ai.StaticTargetBehaviour;
import com.me.pacman.entity.ai.Target;
import com.me.pacman.state.PlayState;
public class Pinky extends Ghost {
public static final Target SCATTER_TARGET = new Target(2, PacDude.LEVEL_HEIGHT / 8);
public Pinky(PlayState state, Vector2 pos, Direction direction) {
super(state, pos, direction, 1, new PinkyChaseBehaviour(state), new StaticTargetBehaviour(state, SCATTER_TARGET), true);
}
@Override
public void update(float dt) {
if (inHouse && (!state.pelletsEatenSinceDeathCounterEnabled || state.pelletsEatenSinceDeath >= 7)) {
leaveHouse();
}
super.update(dt);
}
}

View File

@ -1,6 +1,7 @@
package com.me.pacman.entity.ai; package com.me.pacman.entity.ai;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.me.pacman.entity.Clyde;
import com.me.pacman.state.PlayState; import com.me.pacman.state.PlayState;
public class ClydeChaseBehaviour extends Behaviour { public class ClydeChaseBehaviour extends Behaviour {
@ -12,7 +13,7 @@ public class ClydeChaseBehaviour extends Behaviour {
@Override @Override
public Target getTarget() { public Target getTarget() {
Vector2 pacmanTile = state.pacman.getTileVector(); Vector2 pacmanTile = state.pacman.getTileVector();
Vector2 clydeTile = state.ghosts[2].getTileVector(); Vector2 clydeTile = state.ghosts[3].getTileVector();
return pacmanTile.dst2(clydeTile) >= 8*8? new Target(pacmanTile) : PlayState.CLYDE_SCATTER_TARGET; return pacmanTile.dst2(clydeTile) >= 8*8? new Target(pacmanTile) : Clyde.SCATTER_TARGET;
} }
} }

View File

@ -7,11 +7,10 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont;
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;
import com.me.pacman.entity.Direction; import com.me.pacman.entity.*;
import com.me.pacman.entity.Ghost; import com.me.pacman.entity.ai.ReturnToBase;
import com.me.pacman.entity.Pacman;
import com.me.pacman.entity.ai.*;
import com.me.pacman.level.Level; import com.me.pacman.level.Level;
import com.me.pacman.level.LevelTile; import com.me.pacman.level.LevelTile;
@ -19,10 +18,19 @@ import java.util.Random;
public class PlayState extends LevelState { public class PlayState extends LevelState {
public static final Target BLINKY_SCATTER_TARGET = new Target((PacDude.LEVEL_WIDTH / 8) - 2, PacDude.LEVEL_HEIGHT / 8); public static final Vector2[] GHOST_SPAWN_POINTS = {
public static final Target PINKY_SCATTER_TARGET = new Target(2, PacDude.LEVEL_HEIGHT / 8); new Vector2(14f, 19.5f),
public static final Target INKY_SCATTER_TARGET = new Target(PacDude.LEVEL_WIDTH / 8, -1); new Vector2(14f, 16.5f),
public static final Target CLYDE_SCATTER_TARGET = new Target(1, -1); new Vector2(12f, 16.5f),
new Vector2(16f, 16.5f),
};
public static final Direction[] GHOST_SPAWN_DIRS = {
Direction.LEFT,
Direction.UP,
Direction.DOWN,
Direction.DOWN,
};
private Texture levelBackground; private Texture levelBackground;
private BitmapFont font; private BitmapFont font;
@ -35,6 +43,10 @@ public class PlayState extends LevelState {
private int pelletCount; private int pelletCount;
private int pelletEatenCount; private int pelletEatenCount;
public int pelletsEatenSinceDeath;
public boolean pelletsEatenSinceDeathCounterEnabled;
private int score; private int score;
private int lives; private int lives;
private int round; private int round;
@ -46,6 +58,7 @@ public class PlayState extends LevelState {
private float deathTimer; private float deathTimer;
private float pointsTimer; private float pointsTimer;
public float frightTimer; public float frightTimer;
public float secondsSinceLastDot;
private float chaseTimer; private float chaseTimer;
private float scatterTimer; private float scatterTimer;
@ -91,7 +104,7 @@ public class PlayState extends LevelState {
pacman.render(game.batch, 0, 16); pacman.render(game.batch, 0, 16);
} }
if (pacman.alive) { if (pacman.alive && newGameTimer <= 0) {
for (Ghost ghost : ghosts) { for (Ghost ghost : ghosts) {
if (ghost == null || (pointsTimer > 0 && ghost == lastGhostCaptured)) { if (ghost == null || (pointsTimer > 0 && ghost == lastGhostCaptured)) {
continue; continue;
@ -187,6 +200,20 @@ public class PlayState extends LevelState {
} }
} }
secondsSinceLastDot += dt;
if (secondsSinceLastDot >= 4) {
// It's been 4 seconds since pacman last ate a dot, he's tryin' to avoid ghosts coming out!
// We'll get him...
for (int i = 1; i < 4; i++) {
Ghost ghost = ghosts[i];
if (ghost.inHouse) {
ghost.leaveHouse();
secondsSinceLastDot = 0;
break;
}
}
}
if (!pacmanCaught) { if (!pacmanCaught) {
for (Ghost ghost : ghosts) { for (Ghost ghost : ghosts) {
if (ghost == null) { if (ghost == null) {
@ -231,6 +258,7 @@ public class PlayState extends LevelState {
pacmanCaught = false; pacmanCaught = false;
ghostsCaught = 0; ghostsCaught = 0;
lastGhostCaptured = null; lastGhostCaptured = null;
secondsSinceLastDot = 0;
if (chase) { if (chase) {
chaseTimer = 20f; chaseTimer = 20f;
@ -239,7 +267,6 @@ public class PlayState extends LevelState {
} }
random = new Random(897198256012865L); random = new Random(897198256012865L);
ghosts = new Ghost[4];
pacman = new Pacman(this, false); pacman = new Pacman(this, false);
} }
@ -261,11 +288,11 @@ public class PlayState extends LevelState {
level = new Level(game,"level"); level = new Level(game,"level");
pelletCount = level.getTileCount(LevelTile.PELLET); pelletCount = level.getTileCount(LevelTile.PELLET);
pelletCount += level.getTileCount(LevelTile.POWER_PELLET); pelletCount += level.getTileCount(LevelTile.POWER_PELLET);
pelletsEatenSinceDeath = 0;
pelletsEatenSinceDeathCounterEnabled = false;
initializeField(); initializeField();
if (newGameTimer <= 0) { spawnGhosts();
spawnGhosts();
}
chase = false; chase = false;
scatterCount = 1; scatterCount = 1;
@ -283,18 +310,15 @@ public class PlayState extends LevelState {
public void setupGame() { public void setupGame() {
lives--; lives--;
initializeField(); initializeField();
spawnGhosts(); resetGhosts();
} }
public void spawnGhosts() { private void spawnGhosts() {
ghosts[0] = new Ghost(this, 14, 19.5f, Direction.LEFT, 0, new BlinkyChaseBehaviour(this), new StaticTargetBehaviour(this, BLINKY_SCATTER_TARGET), false); ghosts = new Ghost[4];
ghosts[1] = new Ghost(this, 14f, 16.5f, Direction.DOWN, 1, new PinkyChaseBehaviour(this), new StaticTargetBehaviour(this, PINKY_SCATTER_TARGET), true); ghosts[0] = new Blinky(this, new Vector2(GHOST_SPAWN_POINTS[0]), GHOST_SPAWN_DIRS[0]);
ghosts[2] = new Ghost(this, 12f, 16.5f, Direction.UP, 2, new InkyChaseBehaviour(this), new StaticTargetBehaviour(this, INKY_SCATTER_TARGET), true); ghosts[1] = new Pinky(this, new Vector2(GHOST_SPAWN_POINTS[1]), GHOST_SPAWN_DIRS[1]);
ghosts[3] = new Ghost(this, 16f, 16.5f, Direction.UP, 3, new ClydeChaseBehaviour(this), new StaticTargetBehaviour(this, CLYDE_SCATTER_TARGET), true); ghosts[2] = new Inky(this, new Vector2(GHOST_SPAWN_POINTS[2]), GHOST_SPAWN_DIRS[2]);
ghosts[3] = new Clyde(this, new Vector2(GHOST_SPAWN_POINTS[3]), GHOST_SPAWN_DIRS[3]);
for (int i = 0; i < 4; i++) {
ghosts[i].currentBehaviour = ghosts[i].scatterBehaviour;
}
} }
public void startGame() { public void startGame() {
@ -302,6 +326,17 @@ public class PlayState extends LevelState {
sirenId = game.assets.siren.loop(1.0f); sirenId = game.assets.siren.loop(1.0f);
} }
public void resetGhosts() {
for (int i = 0; i < 4; i++) {
if (ghosts[i] == null) continue;
ghosts[i].pos = new Vector2(GHOST_SPAWN_POINTS[i]);
ghosts[i].currDirection = GHOST_SPAWN_DIRS[i];
ghosts[i].currentBehaviour = chase? ghosts[i].chaseBehaviour : ghosts[i].scatterBehaviour;
ghosts[i].currentPath = null;
ghosts[i].inHouse = i > 0;
}
}
private void pelletEaten(float x, float y) { private void pelletEaten(float x, float y) {
level.setTile(x, y, LevelTile.EMPTY); level.setTile(x, y, LevelTile.EMPTY);
if (pelletEatenCount % 2 == 0) { if (pelletEatenCount % 2 == 0) {
@ -312,6 +347,23 @@ public class PlayState extends LevelState {
pelletEatenCount++; pelletEatenCount++;
pelletCount--; pelletCount--;
if (pelletsEatenSinceDeathCounterEnabled) {
// Increase global dot counter when enabled
pelletsEatenSinceDeath++;
} else {
// else increment appropriate ghost's individual counter
for (Ghost ghost : ghosts) {
// Increment the dot counter of the first encountered ghost in the house
// Ghosts are ordered Blinky, Pinky, Inky, Clyde. Blinky's inHouse is always false,
// Increase the dotCounter of only one ghost: the first of the above list still in the house.
if (!ghost.inHouse) continue;
ghost.dotCounter++;
break;
}
}
secondsSinceLastDot = 0;
if (pelletCount == 0) { if (pelletCount == 0) {
newRound(); newRound();
} }
@ -341,6 +393,8 @@ public class PlayState extends LevelState {
deathTimer = 1f; deathTimer = 1f;
pacmanCaught = true; pacmanCaught = true;
pacman.moving = false; pacman.moving = false;
pelletsEatenSinceDeath = 0;
pelletsEatenSinceDeathCounterEnabled = true;
} }
private void ghostCaught(Ghost ghost) { private void ghostCaught(Ghost ghost) {