The ghost AI in Procedural Pac-Man 3D uses A pathfinding* to navigate the procedurally-generated mazes intelligently. The system features multiple behavior states and dynamic difficulty scaling that adapts to player progression.
// From MyGame.js - moveAI() methodmoveAI() { for (var i = 1; i < this.characters.length; i++) { var character = this.characters[i]; // Only compute new path if current path is complete if (character.path == null && (character.behaviour != "freeze" && character.behaviour != "home")) { // Get ghost's current position let pos = new THREE.Vector2( character.getPosition().x / MyConstant.BOX_SIZE, character.getPosition().z / MyConstant.BOX_SIZE ); let dir = new THREE.Vector2(character.dirX, character.dirZ); pos = character.adjustedPosition(pos, dir); // Deep copy maze data for pathfinding var ghostMaze = [...this.maze.mazeData]; ghostMaze.forEach((row, rowIndex) => ghostMaze[rowIndex] = [...row]); // Prevent backtracking in chase mode if (character.behaviour == "chase") { ghostMaze[pos.y - dir.y][pos.x - dir.x] = 0; // Block previous cell } // Create A* graph from maze var graph = new Graph(ghostMaze); var start = graph.grid[pos.y][pos.x]; // Determine target based on behavior var end; if (character.behaviour == "chase") { // Chase Pac-Man's current position var pPos = new THREE.Vector2( this.characters[0].getPosition().x / MyConstant.BOX_SIZE, this.characters[0].getPosition().z / MyConstant.BOX_SIZE ); var pDir = new THREE.Vector2( this.characters[0].dirX, this.characters[0].dirZ ); pPos = this.characters[0].adjustedPosition(pPos, pDir); end = graph.grid[pPos.y][pPos.x]; } else if (character.behaviour == "scape") { // Flee to random position var random = this.maze.getRandomValidPosition(); end = graph.grid[random.x][random.y]; } else if (character.behaviour == "return") { // Return to spawn position end = graph.grid[ this.charactersSpawnPosition[i].z ][ this.charactersSpawnPosition[i].x ]; } // Compute path using A* var result = astar.search(graph, start, end); if (result.length == 0) { result = null; } else { // Optional: visualize path for debugging if (MyConstant.SHOW_PATH) { for (let path of result) { var pos_check = path.x * MyConstant.MAZE_WIDTH + path.y; this.maze.children[pos_check].square.material = character.material; } } } character.path = result; } }}
scare() { this.startInvencibleAnimation(); // Begin countdown this.changeColor(MyMaterial.BLUE); // Turn blue this.behaviour = "scape"; // Note: "scape" is a typo for "escape" this.path = null; // Flee in new direction}
Ghosts don’t all leave the spawn area simultaneously:
// From MyGame.js - startLeaveBoxAnimation() methodstartLeaveBoxAnimation() { var val = 2; // Start with ghost index 2 (first ghost is already out) var duration = 5000 - this.level * 100; if (duration < 3000) duration = 3000; // Minimum 3 seconds var that = this; this.leaveBoxAnimation = new TWEEN.Tween(origin) .duration(duration) .onRepeat(function() { that.characters[val].changeBehaviour("chase"); val = val + 1; }) .repeat(3) // 4 ghosts total .start();}
The AI behavior system combines A* pathfinding, state machines, and dynamic difficulty scaling to create challenging, adaptive ghost opponents. The procedural maze generation ensures that AI strategies must adapt to new layouts each game, preventing memorization and maintaining long-term challenge.