From 3fe14e6e5d54a70b7537d5fdcbbd6e74b56a7984 Mon Sep 17 00:00:00 2001 From: Matt Low Date: Sat, 28 Dec 2019 23:26:58 +0400 Subject: [PATCH] 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 --- core/src/com/me/pacman/entity/Blinky.java | 28 +++++ core/src/com/me/pacman/entity/Clyde.java | 34 ++++++ core/src/com/me/pacman/entity/Ghost.java | 24 +++-- core/src/com/me/pacman/entity/Inky.java | 35 ++++++ core/src/com/me/pacman/entity/Pacman.java | 2 - core/src/com/me/pacman/entity/Pinky.java | 27 +++++ .../pacman/entity/ai/ClydeChaseBehaviour.java | 5 +- core/src/com/me/pacman/state/PlayState.java | 100 ++++++++++++++---- 8 files changed, 220 insertions(+), 35 deletions(-) create mode 100644 core/src/com/me/pacman/entity/Blinky.java create mode 100644 core/src/com/me/pacman/entity/Clyde.java create mode 100644 core/src/com/me/pacman/entity/Inky.java create mode 100644 core/src/com/me/pacman/entity/Pinky.java diff --git a/core/src/com/me/pacman/entity/Blinky.java b/core/src/com/me/pacman/entity/Blinky.java new file mode 100644 index 0000000..2f02ccd --- /dev/null +++ b/core/src/com/me/pacman/entity/Blinky.java @@ -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); + } + +} diff --git a/core/src/com/me/pacman/entity/Clyde.java b/core/src/com/me/pacman/entity/Clyde.java new file mode 100644 index 0000000..5fcd8c7 --- /dev/null +++ b/core/src/com/me/pacman/entity/Clyde.java @@ -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); + } + + +} diff --git a/core/src/com/me/pacman/entity/Ghost.java b/core/src/com/me/pacman/entity/Ghost.java index d82d927..a6ae854 100644 --- a/core/src/com/me/pacman/entity/Ghost.java +++ b/core/src/com/me/pacman/entity/Ghost.java @@ -3,6 +3,8 @@ 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.Direction; +import com.me.pacman.entity.MovableEntity; import com.me.pacman.entity.ai.Behaviour; import com.me.pacman.entity.ai.ReturnToBase; import com.me.pacman.entity.ai.Target; @@ -25,7 +27,7 @@ public class Ghost extends MovableEntity { private int spriteIndex; private int counter = 0; - private PlayState state; + protected PlayState state; public Behaviour currentBehaviour; @@ -36,9 +38,11 @@ public class Ghost extends MovableEntity { public boolean inHouse; 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) { - 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.spriteIndex = spriteIndex; this.chaseBehaviour = chaseBehaviour; @@ -89,13 +93,11 @@ public class Ghost extends MovableEntity { } if (currentPath instanceof EnterGhostHousePath) { - currentPath = new ExitGhostHousePath(pos); - currentBehaviour = null; - speed = GHOST_SPEED; + inHouse = true; } else if (currentPath instanceof ExitGhostHousePath) { - currentBehaviour = chaseBehaviour; - currentPath = null; + speed = GHOST_SPEED; } + currentPath = null; } if (inHouse) { @@ -179,6 +181,12 @@ public class Ghost extends MovableEntity { setNextDirection(nextDirection); } + public void leaveHouse() { + currentPath = new ExitGhostHousePath(pos); + inHouse = false; + currentBehaviour = state.chase? chaseBehaviour : scatterBehaviour; + } + private class FrightenedBehaviour extends Behaviour { private Target target; diff --git a/core/src/com/me/pacman/entity/Inky.java b/core/src/com/me/pacman/entity/Inky.java new file mode 100644 index 0000000..c4ea906 --- /dev/null +++ b/core/src/com/me/pacman/entity/Inky.java @@ -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); + } + + +} diff --git a/core/src/com/me/pacman/entity/Pacman.java b/core/src/com/me/pacman/entity/Pacman.java index c647fed..889ac57 100644 --- a/core/src/com/me/pacman/entity/Pacman.java +++ b/core/src/com/me/pacman/entity/Pacman.java @@ -1,8 +1,6 @@ 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.level.LevelTile; import com.me.pacman.state.PlayState; diff --git a/core/src/com/me/pacman/entity/Pinky.java b/core/src/com/me/pacman/entity/Pinky.java new file mode 100644 index 0000000..ed2aecf --- /dev/null +++ b/core/src/com/me/pacman/entity/Pinky.java @@ -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); + } + +} diff --git a/core/src/com/me/pacman/entity/ai/ClydeChaseBehaviour.java b/core/src/com/me/pacman/entity/ai/ClydeChaseBehaviour.java index e2ee587..a08d9c0 100644 --- a/core/src/com/me/pacman/entity/ai/ClydeChaseBehaviour.java +++ b/core/src/com/me/pacman/entity/ai/ClydeChaseBehaviour.java @@ -1,6 +1,7 @@ package com.me.pacman.entity.ai; import com.badlogic.gdx.math.Vector2; +import com.me.pacman.entity.Clyde; import com.me.pacman.state.PlayState; public class ClydeChaseBehaviour extends Behaviour { @@ -12,7 +13,7 @@ public class ClydeChaseBehaviour extends Behaviour { @Override public Target getTarget() { Vector2 pacmanTile = state.pacman.getTileVector(); - Vector2 clydeTile = state.ghosts[2].getTileVector(); - return pacmanTile.dst2(clydeTile) >= 8*8? new Target(pacmanTile) : PlayState.CLYDE_SCATTER_TARGET; + Vector2 clydeTile = state.ghosts[3].getTileVector(); + return pacmanTile.dst2(clydeTile) >= 8*8? new Target(pacmanTile) : Clyde.SCATTER_TARGET; } } diff --git a/core/src/com/me/pacman/state/PlayState.java b/core/src/com/me/pacman/state/PlayState.java index 7cbd9c3..957d5bd 100644 --- a/core/src/com/me/pacman/state/PlayState.java +++ b/core/src/com/me/pacman/state/PlayState.java @@ -7,11 +7,10 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; 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.*; +import com.me.pacman.entity.*; +import com.me.pacman.entity.ai.ReturnToBase; import com.me.pacman.level.Level; import com.me.pacman.level.LevelTile; @@ -19,10 +18,19 @@ import java.util.Random; 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 Target PINKY_SCATTER_TARGET = new Target(2, PacDude.LEVEL_HEIGHT / 8); - public static final Target INKY_SCATTER_TARGET = new Target(PacDude.LEVEL_WIDTH / 8, -1); - public static final Target CLYDE_SCATTER_TARGET = new Target(1, -1); + public static final Vector2[] GHOST_SPAWN_POINTS = { + new Vector2(14f, 19.5f), + new Vector2(14f, 16.5f), + 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 BitmapFont font; @@ -35,6 +43,10 @@ public class PlayState extends LevelState { private int pelletCount; private int pelletEatenCount; + + public int pelletsEatenSinceDeath; + public boolean pelletsEatenSinceDeathCounterEnabled; + private int score; private int lives; private int round; @@ -46,6 +58,7 @@ public class PlayState extends LevelState { private float deathTimer; private float pointsTimer; public float frightTimer; + public float secondsSinceLastDot; private float chaseTimer; private float scatterTimer; @@ -91,7 +104,7 @@ public class PlayState extends LevelState { pacman.render(game.batch, 0, 16); } - if (pacman.alive) { + if (pacman.alive && newGameTimer <= 0) { for (Ghost ghost : ghosts) { if (ghost == null || (pointsTimer > 0 && ghost == lastGhostCaptured)) { 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) { for (Ghost ghost : ghosts) { if (ghost == null) { @@ -231,6 +258,7 @@ public class PlayState extends LevelState { pacmanCaught = false; ghostsCaught = 0; lastGhostCaptured = null; + secondsSinceLastDot = 0; if (chase) { chaseTimer = 20f; @@ -239,7 +267,6 @@ public class PlayState extends LevelState { } random = new Random(897198256012865L); - ghosts = new Ghost[4]; pacman = new Pacman(this, false); } @@ -261,11 +288,11 @@ public class PlayState extends LevelState { level = new Level(game,"level"); pelletCount = level.getTileCount(LevelTile.PELLET); pelletCount += level.getTileCount(LevelTile.POWER_PELLET); + pelletsEatenSinceDeath = 0; + pelletsEatenSinceDeathCounterEnabled = false; initializeField(); - if (newGameTimer <= 0) { - spawnGhosts(); - } + spawnGhosts(); chase = false; scatterCount = 1; @@ -283,18 +310,15 @@ public class PlayState extends LevelState { public void setupGame() { lives--; initializeField(); - spawnGhosts(); + resetGhosts(); } - public void spawnGhosts() { - ghosts[0] = new Ghost(this, 14, 19.5f, Direction.LEFT, 0, new BlinkyChaseBehaviour(this), new StaticTargetBehaviour(this, BLINKY_SCATTER_TARGET), false); - ghosts[1] = new Ghost(this, 14f, 16.5f, Direction.DOWN, 1, new PinkyChaseBehaviour(this), new StaticTargetBehaviour(this, PINKY_SCATTER_TARGET), true); - ghosts[2] = new Ghost(this, 12f, 16.5f, Direction.UP, 2, new InkyChaseBehaviour(this), new StaticTargetBehaviour(this, INKY_SCATTER_TARGET), true); - ghosts[3] = new Ghost(this, 16f, 16.5f, Direction.UP, 3, new ClydeChaseBehaviour(this), new StaticTargetBehaviour(this, CLYDE_SCATTER_TARGET), true); - - for (int i = 0; i < 4; i++) { - ghosts[i].currentBehaviour = ghosts[i].scatterBehaviour; - } + private void spawnGhosts() { + ghosts = new Ghost[4]; + ghosts[0] = new Blinky(this, new Vector2(GHOST_SPAWN_POINTS[0]), GHOST_SPAWN_DIRS[0]); + ghosts[1] = new Pinky(this, new Vector2(GHOST_SPAWN_POINTS[1]), GHOST_SPAWN_DIRS[1]); + 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]); } public void startGame() { @@ -302,6 +326,17 @@ public class PlayState extends LevelState { 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) { level.setTile(x, y, LevelTile.EMPTY); if (pelletEatenCount % 2 == 0) { @@ -312,6 +347,23 @@ public class PlayState extends LevelState { pelletEatenCount++; 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) { newRound(); } @@ -341,6 +393,8 @@ public class PlayState extends LevelState { deathTimer = 1f; pacmanCaught = true; pacman.moving = false; + pelletsEatenSinceDeath = 0; + pelletsEatenSinceDeathCounterEnabled = true; } private void ghostCaught(Ghost ghost) {