#define RECORDING_DEMO namespace Marro.PacManUdon { using System; using UdonSharp; using UnityEngine; using VRC.SDKBase; using VRC.Udon; using VRC.SDK3.Components; using VRC.Udon.Common.Interfaces; using VRC.SDK3.Data; public class GameManager : UdonSharpBehaviour { [Header("Static game components")] [SerializeField] private Maze[] mazes; [SerializeField] private PacMan pacMan; [SerializeField] private GhostManager ghostManager; [SerializeField] private BonusFruit bonusFruit; [SerializeField] private PelletManager pelletManager; [SerializeField] public StatusDisplay statusDisplay; // This one is public so other scripts can write to the debug display [SerializeField] private PelletManager attractScreen; [SerializeField] private GameObject pressStartButtonScreen; [SerializeField] private PlayerInput playerInput; [SerializeField] private Animator demo; [SerializeField] private SoundManager soundManager; [SerializeField] private GameObject recorder; [Header("Game settings")] [SerializeField] private int startingExtraLives = 3; [SerializeField] private int scoreToExtraLife = 10000; [Tooltip("Override amount of pellets needed to clear stage, set to -1 to disable.")] [SerializeField] private int pelletCountOverride = -1; private Maze maze; private VRCObjectPool pelletPool; private Animator mazeSpriteAnimator; private int pelletCountTotal; private int pelletCountRemaining; private GameObject[] attractScreenElements; [UdonSynced, FieldChangeCallback(nameof(GameState))] private PacManGameState gameState; [UdonSynced, FieldChangeCallback(nameof(Score))] private int score; [UdonSynced, FieldChangeCallback(nameof(Level))] private int level; [UdonSynced, FieldChangeCallback(nameof(HighScore))] private int highScore; [UdonSynced, FieldChangeCallback(nameof(ExtraLives))] private int extraLives; public void Start() { maze = mazes[0]; pelletPool = maze.pelletContainer.GetComponent(); mazeSpriteAnimator = maze.mazeSprite.GetComponent(); attractScreenElements = new GameObject[attractScreen.transform.childCount]; for (int i = 0; i < attractScreenElements.Length; i++) { attractScreenElements[i] = attractScreen.transform.GetChild(i).gameObject; } ghostManager.Initialize(maze.ghostTargets, pacMan, this); pacMan.Initialize(playerInput, pelletPool, this); bonusFruit.Initialize(); pelletManager.Initialize(pelletPool); statusDisplay.Initialize(); playerInput.Initialize(this); soundManager.Initialize(); SetScore(0); SetHighScore(0); SetLevel(0); StartAttractMode(); } public void FixedUpdate() { TimeSequenceUpdate(Time.deltaTime); } public void JoystickGrabbed() { if (gameState == PacManGameState.AttractMode || gameState == PacManGameState.AttractModeDemo) StartTimeSequence(PacManTimeSequence.WaitForStart); } public void JoystickReleased() { if (gameState == PacManGameState.WaitForStart) StartTimeSequence(PacManTimeSequence.WaitForStartTimeout); } public void StartGameButtonPressed() { Debug.Log($"{gameObject} Start Game Button was pressed!"); TakeOwnership(); StartTimeSequence(PacManTimeSequence.StartNewGame); } public void SkipLevelButtonPressed() { if (Networking.IsOwner(gameObject)) { Debug.Log($"{gameObject} Skip level button pressed!"); StartTimeSequence(PacManTimeSequence.BoardClear); TimeSequenceSkipToNextStep(); } } public void StartDemoButtonPressed() { if (Networking.IsOwner(gameObject)) { Debug.Log($"{gameObject} Start demo button pressed!"); StartTimeSequence(PacManTimeSequence.AttractScreenDemo); } } private void StartAttractMode() { #if RECORDING_DEMO // recorder.gameObject.SetActive(true); StartTimeSequence(PacManTimeSequence.AttractScreenIntroduction); #else SetGameState(PacManGameState.AttractMode); HideEverything(); demo.gameObject.SetActive(true); #endif } private void InitializeNewGame() { Debug.Log($"{gameObject} Started new game!"); SetScore(0); SetExtraLives(startingExtraLives); SetLevel(1); } private void InitializeLevel() { Debug.Log($"{gameObject} New level started!"); if (Networking.IsOwner(gameObject)) { pelletCountTotal = pelletPool.Pool.Length; pelletCountRemaining = pelletCountTotal; ghostManager.SetPelletsRemaining(pelletCountRemaining); ghostManager.NewLevel(); pelletManager.RestoreAllPellets(); if (pelletCountOverride > 0) { pelletCountRemaining = pelletCountOverride; } } mazeSpriteAnimator.SetBool("Blinking", false); } private void RestartLevel() { Debug.Log($"{gameObject} (Re)started level!"); // SetInGameComponentVisibility(true); ghostManager.Reset(); pacMan.Reset(); bonusFruit.Despawn(); soundManager.Reset(); pelletManager.SetPowerPelletsBlink(false); } public void GotPellet(bool addScore = true) { pelletCountRemaining--; if (addScore) AddScore(10); ghostManager.PelletConsumed(); soundManager.PlayPelletSound(); soundManager.UpdatePelletCount(pelletCountRemaining); int pelletsConsumed = pelletCountTotal - pelletCountRemaining; if (pelletCountRemaining <= 0) { StartTimeSequence(PacManTimeSequence.BoardClear); } else if (pelletsConsumed == 70 || pelletsConsumed == 170) { bonusFruit.Spawn(); } } public void GotPowerPellet() { if (gameState == PacManGameState.AttractMode) { TimeSequenceSkipToNextStep(); return; } GotPellet(addScore: false); AddScore(50); ghostManager.SetPowerPellet(true); pacMan.SetPowerPellet(true); soundManager.SetGhostBlue(true); } public void EndPowerPellet() { ghostManager.SetPowerPellet(false); pacMan.SetPowerPellet(false); soundManager.SetGhostBlue(false); } public void GotFruit() { AddScore(bonusFruit.Collected()); soundManager.PlayFruitSound(); } public void GhostCaught(int scoreBonus) { if (gameState == PacManGameState.AttractMode) { TimeSequenceSkipToNextStep(); return; } AddScore(scoreBonus); StartTimeSequence(PacManTimeSequence.GhostCaught); pacMan.HideUntilUnfrozen(); } public void PacManCaught() { StartTimeSequence(PacManTimeSequence.PacManCaught); } public void NoGhostsScared() { soundManager.SetGhostBlue(false); } public void NoGhostsRetreating() { soundManager.SetGhostRetreat(false); } void BoardClearAnimation() { ghostManager.gameObject.SetActive(false); mazeSpriteAnimator.SetBool("Blinking", true); } private void HideEverything() { SetMazeActive(false); SetPelletsActive(false); SetMazeVisible(false); SetGhostsActive(false); SetPacManActive(false); SetPressStartButtonScreenVisible(false); statusDisplay.SetGameOverTextVisible(false); statusDisplay.SetExtraLivesDisplayVisible(false); statusDisplay.SetLevelDisplayVisible(false); statusDisplay.SetPlayer1TextVisible(false); statusDisplay.SetReadyTextVisible(false); demo.gameObject.SetActive(false); } void SetMazeActive(bool active) { maze.gameObject.SetActive(active); } void SetPelletsActive(bool active) { pelletPool.gameObject.SetActive(active); } void SetMazeVisible(bool visible) { mazeSpriteAnimator.SetBool("Hidden", !visible); } void SetGhostsActive(bool active) { ghostManager.SetActive(active); } void SetPacManActive(bool active) { pacMan.SetActive(active); } void SetPressStartButtonScreenVisible(bool visible) { pressStartButtonScreen.SetActive(visible); } void SetGameState(PacManGameState newGameState) { // Debug.Log($"{gameObject} State transitioning from {gameState} to {newGameState}"); gameState = newGameState; if (Networking.IsOwner(gameObject)) { RequestSerialization(); } } private void IncrementLevel() { SetLevel(level + 1); } private void SetLevel(int level) { this.level = level; pacMan.SetLevel(level); ghostManager.SetLevel(level); statusDisplay.SetLevel(level); bonusFruit.SetFruitType(PacManConstants.GetFruitTypeForLevel(level)); } void AddScore(int score) { if (gameState == PacManGameState.AttractMode || gameState == PacManGameState.AttractModeDemo) { return; } if (this.score < scoreToExtraLife && this.score + score >= scoreToExtraLife) { BonusLifeReached(); } SetScore(this.score + score); RequestSerialization(); } void SetScore(int score) { this.score = score; statusDisplay.Set1UPScore(score); if (score > highScore) { highScore = score; statusDisplay.SetHighScore(score); } } void SetHighScore(int highScore) { this.highScore = highScore; statusDisplay.SetHighScore(score); } public void DecrementLives() { if (!Networking.IsOwner(gameObject)) { return; } // Debug.Log($"{gameObject} Decremented lives from {extraLives} to {extraLives - 1}"); SetExtraLives(extraLives - 1); } void IncrementLives() { if (!Networking.IsOwner(gameObject)) { return; } // Debug.Log($"{gameObject} Incremented lives from {extraLives} to {extraLives + 1}"); SetExtraLives(extraLives + 1); } void SetExtraLives(int extraLives) { // Debug.Log($"{gameObject} Set lives from {this.extraLives} to {extraLives}"); this.extraLives = extraLives; statusDisplay.SetExtraLives(extraLives); } void BonusLifeReached() { IncrementLives(); soundManager.PlayExtraLifeSound(); } public void SetFrozen(bool frozen, bool ghostIgnoreIfCaught = false, bool ghostKeepAnimating = false) { // Debug.Log($"{gameObject} Set Frozen: {frozen}"); pacMan.SetFrozen(frozen); bonusFruit.SetFrozen(frozen); ghostManager.SetFrozen(frozen, ignoreIfCaught: ghostIgnoreIfCaught); if (!frozen) { pelletManager.SetPowerPelletsBlink(true); } } void TakeOwnership() { Networking.SetOwner(Networking.LocalPlayer, gameObject); Networking.SetOwner(Networking.LocalPlayer, pacMan.gameObject); Networking.SetOwner(Networking.LocalPlayer, pelletPool.gameObject); ghostManager.SetOwner(Networking.LocalPlayer); } public int ExtraLives { set { SetExtraLives(value); } get => extraLives; } public PacManGameState GameState { set { SetGameState(value); } get => gameState; } public bool GhostsScared { set { } get => GhostsScared; } public int Score { set { SetScore(value); } get => score; } public int HighScore { set { SetHighScore(value); } get => score; } public int Level { set { SetLevel(value); } get => level; } #region TIME SEQUENCE BEHAVIOUR // This was supposed to be a separate class, right until the moment I realized Udon doesn't support instantiating classes... bool currentlyInTimeSequence; PacManTimeSequence currentTimeSequence; bool hasTimeSequenceQueued; private DataList timeSequenceQueue; [UdonSynced] float timeSequenceSecondsPassed; int timeSequenceProgress; float[] timeSequenceKeyframeTimes; private void StartTimeSequence(PacManTimeSequence timeSequence) { if (timeSequenceQueue == null) { timeSequenceQueue = new DataList(); } if (currentlyInTimeSequence == true) { int timeSequenceInt = (int)timeSequence; // Doing the conversion in the line below crashes the script. I love working in Udon timeSequenceQueue.Add(timeSequenceInt); hasTimeSequenceQueued = true; return; } Debug.Log($"StartTimeSequence: {timeSequence}"); currentlyInTimeSequence = true; currentTimeSequence = timeSequence; timeSequenceProgress = 0; timeSequenceSecondsPassed = 0; timeSequenceKeyframeTimes = GetTimeSequenceKeyframeTimes(timeSequence); TimeSequenceProgressToTime(timeSequenceSecondsPassed); } private void InsertTimeSequence(PacManTimeSequence timeSequence) { StartTimeSequence(timeSequence); } private void TimeSequenceUpdate(float deltaSeconds) { if (!currentlyInTimeSequence && hasTimeSequenceQueued) { timeSequenceQueue.TryGetValue(0, out DataToken nextTimeSequence); StartTimeSequence((PacManTimeSequence)nextTimeSequence.Int); timeSequenceQueue.RemoveAt(0); hasTimeSequenceQueued = timeSequenceQueue.Count > 0 ? true : false; return; } if (currentlyInTimeSequence) { if (hasTimeSequenceQueued) { while (currentlyInTimeSequence) { TimeSequenceSkipToNextStep(); } } else { TimeSequenceProgressToTime(timeSequenceSecondsPassed + deltaSeconds); } } } private void TimeSequenceSkipToNextStep() { // Debug.Log($"{gameObject} TimeSequenceSkipToNextStep"); if (timeSequenceProgress < timeSequenceKeyframeTimes.Length) { TimeSequenceProgressToTime(timeSequenceKeyframeTimes[timeSequenceProgress]); } else { Debug.LogWarning($"{gameObject} Tried skipping to next time sequence step when already on last step!"); currentlyInTimeSequence = false; } } private void TimeSequenceProgressToTime(float seconds) { timeSequenceSecondsPassed = seconds; while (timeSequenceSecondsPassed >= timeSequenceKeyframeTimes[timeSequenceProgress]) { TimeSequenceExecuteStep(timeSequenceProgress); timeSequenceProgress += 1; if (timeSequenceProgress >= timeSequenceKeyframeTimes.Length) { currentlyInTimeSequence = false; break; } } } private void TimeSequenceExecuteStep(int sequenceProgress) { // Debug.Log($"{gameObject} Triggered time sequence step for sequence {currentTimeSequence} with progress {sequenceProgress}"); switch (currentTimeSequence) { default: Debug.LogError($"{gameObject} No time sequence keyframes known for sequence {currentTimeSequence}"); break; case PacManTimeSequence.AttractScreenIntroduction: TimeSequenceStepAttractScreenIntroduction(sequenceProgress); break; case PacManTimeSequence.AttractScreenDemo: TimeSequenceStepAttractScreenDemo(sequenceProgress); break; case PacManTimeSequence.WaitForStart: TimeSequenceStepWaitForStart(sequenceProgress); break; case PacManTimeSequence.WaitForStartTimeout: TimeSequenceStepWaitForStartTimeout(sequenceProgress); break; case PacManTimeSequence.StartNewGame: TimeSequenceStepStartNewGame(sequenceProgress); break; case PacManTimeSequence.BoardClear: TimeSequenceStepBoardClear(sequenceProgress); break; case PacManTimeSequence.StartNewLevel: TimeSequenceStepStartNewLevel(sequenceProgress); break; case PacManTimeSequence.GhostCaught: TimeSequenceStepGhostCaught(sequenceProgress); break; case PacManTimeSequence.PacManCaught: TimeSequenceStepPacManCaught(sequenceProgress); break; case PacManTimeSequence.RestartLevel: TimeSequenceStepRestartLevel(sequenceProgress); break; case PacManTimeSequence.GameOver: TimeSequenceStepGameOver(sequenceProgress); break; } } private float[] GetTimeSequenceKeyframeTimes(PacManTimeSequence timeSequence) { switch (timeSequence) { default: Debug.LogError($"{gameObject} No time sequence keyframe times known for sequence {timeSequence}"); return new float[0]; case PacManTimeSequence.AttractScreenIntroduction: return DeltaToAbsolute(new float[] { 0, 0.032f, 1f, 1f, .5f, .5f, 1f, .5f, .5f, 1f, .5f, .5f, 1f, .5f, 1f, 1f, 1f, 5f, 0.2f, 2f, 0.91667f, 2f, 0.91667f, 2f, 0.91667f, 2f, 0.91667f }); case PacManTimeSequence.AttractScreenDemo: return DeltaToAbsolute(new float[] { 0, 0.016f, 0.05f, 0.16f, 0.33f, 1.85f, 54f }); case PacManTimeSequence.WaitForStart: return DeltaToAbsolute(new float[] { 0, 0.016f }); case PacManTimeSequence.WaitForStartTimeout: return DeltaToAbsolute(new float[] { 0, 5f }); case PacManTimeSequence.StartNewGame: return DeltaToAbsolute(new float[] { 0, 0.016f, 2.2f, 0.032f, 0.032f, 1.92f, 0.032f }); case PacManTimeSequence.BoardClear: return DeltaToAbsolute(new float[] { 0, 2f, 0.016f, 1.6f - 0.016f, 0.016f, 0.032f, 0.3f }); case PacManTimeSequence.StartNewLevel: return DeltaToAbsolute(new float[] { 0, 0.064f, 0.032f, 1.85f, 0.016f }); case PacManTimeSequence.GhostCaught: return DeltaToAbsolute(new float[] { 0, 0.91667f }); case PacManTimeSequence.PacManCaught: return DeltaToAbsolute(new float[] { 0, 1, 0.35f, 2.40f, 2f }); case PacManTimeSequence.RestartLevel: return DeltaToAbsolute(new float[] { 0, 0.016f, 0.064f, 0.032f, 1.85f, 0.016f }); case PacManTimeSequence.GameOver: return DeltaToAbsolute(new float[] { 0, 1.95f }); } } private static float[] DeltaToAbsolute(float[] delta) { if (delta.Length < 1) { return new float[0]; } float[] absolute = new float[delta.Length]; absolute[0] = delta[0]; for (int i = 1; i < delta.Length; i++) { absolute[i] = delta[i] + absolute[i - 1]; } return absolute; } private void TimeSequenceStepAttractScreenIntroduction(int sequenceProgress) { switch (sequenceProgress) { case 0: SetGameState(PacManGameState.AttractMode); // Initialize soundManager.SuppressSound(true); RestartLevel(); HideEverything(); SetFrozen(true); attractScreen.gameObject.SetActive(true); attractScreen.Initialize(); for (int i = 0; i <= 15; i++) { // Debug.Log($"{gameObject} TimeSequenceAttractScreen deactivating with iteration i"); attractScreenElements[i].SetActive(false); } attractScreen.SetPowerPelletsBlink(false); break; case 1: // show "Character / Nickname" attractScreenElements[0].SetActive(true); break; case 2: // Show blinky sprite attractScreenElements[1].SetActive(true); break; case 3: // Show blinky character attractScreenElements[2].SetActive(true); break; case 4: // Show blinky nickname attractScreenElements[3].SetActive(true); break; case 5: // Show pinky sprite attractScreenElements[4].SetActive(true); break; case 6: // Show pinky character attractScreenElements[5].SetActive(true); break; case 7: // Show pinky nickname attractScreenElements[6].SetActive(true); break; case 8: // Show inky sprite attractScreenElements[7].SetActive(true); break; case 9: // Show inky character attractScreenElements[8].SetActive(true); break; case 10: // Show inky nickname attractScreenElements[9].SetActive(true); break; case 11: // Show clyde sprite attractScreenElements[10].SetActive(true); break; case 12: // Show clyde character attractScreenElements[11].SetActive(true); break; case 13: // Show clyde nickname attractScreenElements[12].SetActive(true); break; case 14: // Show pellet point values attractScreenElements[13].SetActive(true); break; case 15: // Show copyright message, setup pellet demonstration attractScreenElements[14].SetActive(true); attractScreenElements[15].SetActive(true); pacMan.SetLevel(1); pacMan.Reset(); pacMan.SetKinematic(true); pacMan.SetActive(true); pacMan.SetPosition(attractScreenElements[16].transform.localPosition); pacMan.SetDirection(Vector2.left); ghostManager.SetLevel(2); ghostManager.Reset(); ghostManager.SetKinematic(true); ghostManager.SetActive(true); Ghost[] ghosts = ghostManager.Ghosts; for (int i = 0; i < ghosts.Length; i++) { ghosts[i].SetPosition(attractScreenElements[17 + i].transform.localPosition); ghosts[i].SetDirection(Vector2.left); ghosts[i].SetState(PacManGhostState.Normal); } break; case 16: attractScreen.SetPowerPelletsBlink(true); SetFrozen(false); break; case 17: ghostManager.SetPowerPellet(true); pacMan.SetPowerPellet(true); attractScreenElements[15].SetActive(false); break; case 18: // Turn PacMan around after eating power pellet pacMan.SetDirection(Vector2.right); pacMan.SetTargetDirection(Vector2.right); break; case 19: ghostManager.Ghosts[0].Caught(200); pacMan.SetActive(false); SetFrozen(true); break; case 20: ghostManager.Ghosts[0].ReturnHome(); ghostManager.Ghosts[0].SetActive(false); pacMan.SetActive(true); SetFrozen(false); break; case 21: ghostManager.Ghosts[1].Caught(400); pacMan.SetActive(false); SetFrozen(true); break; case 22: ghostManager.Ghosts[1].ReturnHome(); ghostManager.Ghosts[1].SetActive(false); pacMan.SetActive(true); SetFrozen(false); break; case 23: ghostManager.Ghosts[2].Caught(800); pacMan.SetActive(false); SetFrozen(true); break; case 24: ghostManager.Ghosts[2].ReturnHome(); ghostManager.Ghosts[2].SetActive(false); pacMan.SetActive(true); SetFrozen(false); break; case 25: ghostManager.Ghosts[3].Caught(1600); pacMan.SetActive(false); SetFrozen(true); break; case 26: ghostManager.Ghosts[3].ReturnHome(); ghostManager.Ghosts[3].SetActive(false); // Hide elements, start demo attractScreen.gameObject.SetActive(false); if (!hasTimeSequenceQueued) { StartTimeSequence(PacManTimeSequence.AttractScreenDemo); } break; } } private void TimeSequenceStepAttractScreenDemo(int sequenceProgress) { switch (sequenceProgress) { case 0: SetGameState(PacManGameState.AttractModeDemo); HideEverything(); SetFrozen(true); break; case 1: InitializeLevel(); SetMazeActive(true); SetMazeVisible(true); SetLevel(1); break; case 2: // Reset ghosts RestartLevel(); ghostManager.Ghosts[0].SetPredefinedPath(new Vector2[]{ // Blinky Vector2.down, Vector2.left, Vector2.up, Vector2.right, Vector2.zero, Vector2.zero, Vector2.up, Vector2.right, Vector2.down, Vector2.zero, Vector2.right, Vector2.up, Vector2.left, Vector2.down, Vector2.right, Vector2.up, Vector2.zero, Vector2.left, Vector2.down, Vector2.zero, Vector2.left, Vector2.down, Vector2.right, // Goes through tunnel Vector2.zero, Vector2.up, Vector2.left, Vector2.down, Vector2.right, Vector2.down, Vector2.right, Vector2.down, Vector2.right, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.zero, Vector2.down, Vector2.left, Vector2.zero, Vector2.up, Vector2.left, // Gets eaten Vector2.zero, Vector2.up, Vector2.right, Vector2.up, Vector2.right, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.down, Vector2.right, Vector2.down, Vector2.left, }); ghostManager.Ghosts[1].SetPredefinedPath(new Vector2[]{ // Pinky Vector2.down, Vector2.left, Vector2.up, Vector2.zero, Vector2.zero, Vector2.left, Vector2.down, Vector2.right, // Pellet starts Vector2.up, Vector2.right, Vector2.down, Vector2.left, Vector2.up, Vector2.right, // Pellet ends Vector2.zero, Vector2.down, // Pellet starts Vector2.left, Vector2.down, Vector2.zero, Vector2.left, // Caught Vector2.up, Vector2.right, Vector2.zero, Vector2.down, Vector2.right, Vector2.down, Vector2.right, // Home Vector2.down, Vector2.left, Vector2.up, Vector2.right, Vector2.down, Vector2.zero, Vector2.right, Vector2.down, Vector2.right, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.zero, Vector2.down, Vector2.left, // Power pellet active Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.up, Vector2.left, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.up }); ghostManager.Ghosts[2].SetPredefinedPath(new Vector2[]{ // Inky Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.zero, Vector2.zero, Vector2.down, Vector2.right, // Pellet starts Vector2.up, Vector2.right, Vector2.up, // Caught Vector2.right, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.up, Vector2.left, Vector2.down, Vector2.right, Vector2.down, Vector2.right, Vector2.zero, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.zero, Vector2.zero, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.up, Vector2.right, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.zero, Vector2.up, Vector2.left }); ghostManager.Ghosts[3].SetPredefinedPath(new Vector2[]{ // Clyde Vector2.down, Vector2.right, Vector2.up, Vector2.left, Vector2.down, Vector2.zero, Vector2.left, Vector2.zero, Vector2.down, Vector2.right, Vector2.down, Vector2.right, Vector2.up, Vector2.right, Vector2.zero, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.zero, Vector2.up, Vector2.right, Vector2.zero, Vector2.up, Vector2.left, Vector2.down, Vector2.right, Vector2.up, Vector2.left, Vector2.up, Vector2.right, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.zero, Vector2.up, Vector2.right }); pacMan.SetPredefinedPath(new Vector2[]{ Vector2.down, Vector2.right, Vector2.down, Vector2.right, Vector2.zero, Vector2.up, Vector2.left, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.zero, Vector2.up, Vector2.zero, Vector2.left, Vector2.down, Vector2.left, Vector2.up, Vector2.zero, Vector2.zero, Vector2.left, Vector2.down, Vector2.right, Vector2.zero, Vector2.up, Vector2.left, Vector2.down, Vector2.zero, Vector2.left, Vector2.up, Vector2.right, Vector2.zero, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.down, Vector2.left, Vector2.zero, Vector2.down, Vector2.zero, Vector2.right, Vector2.down, Vector2.left, Vector2.zero, Vector2.zero, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.up, Vector2.right, Vector2.zero, Vector2.down, Vector2.right, Vector2.up, Vector2.right, Vector2.zero, Vector2.down, Vector2.left, Vector2.down, Vector2.right, Vector2.down, Vector2.left, Vector2.zero, Vector2.zero, Vector2.up, Vector2.right, Vector2.up, Vector2.left, Vector2.up, Vector2.right, }); break; case 3: SetPelletsActive(true); pelletManager.RestoreAllPellets(); statusDisplay.SetGameOverTextVisible(true); break; case 4: // Show pacman, show ghosts SetPacManActive(true); SetGhostsActive(true); break; case 5: // Unfreeze SetFrozen(false); break; // case 6: // if (!hasTimeSequenceQueued) // { // StartTimeSequence(PacManTimeSequence.AttractScreenWaitForStart); // } // break; } } private void TimeSequenceStepWaitForStart(int sequenceProgress) { switch (sequenceProgress) { case 0: SetGameState(PacManGameState.WaitForStart); HideEverything(); soundManager.SuppressSound(false); soundManager.PlayCoinSound(); break; case 1: SetPressStartButtonScreenVisible(true); if (playerInput.active == false && hasTimeSequenceQueued == false) { StartTimeSequence(PacManTimeSequence.WaitForStartTimeout); } break; } } private void TimeSequenceStepWaitForStartTimeout(int sequenceProgress) { switch (sequenceProgress) { case 0: break; case 1: if (playerInput.active == false && hasTimeSequenceQueued == false) { StartAttractMode(); } break; } } private void TimeSequenceStepStartNewGame(int sequenceProgress) { switch (sequenceProgress) { case 0: // Prepare new game, hide everything except score bar gameState = PacManGameState.InGame; HideEverything(); SetMazeActive(true); InitializeNewGame(); InitializeLevel(); RestartLevel(); SetFrozen(true); soundManager.SuppressSound(false); soundManager.PlayGameStartSound(); break; case 1: // Show maze, lives indicator, level indicator, player 1 and ready text // SOMEWHERE IN HERE UNITY (EDITOR) APPEARS TO HAVE A SMALL RANDOM CHANCE OF CRASHING !! Debug.Log("Log dump in case of crash"); Debug.Log("Setting pellets visible"); SetPelletsActive(true); Debug.Log("Setting maze visible"); SetMazeVisible(true); Debug.Log("Setting extra lives display visible"); statusDisplay.SetExtraLivesDisplayVisible(true); Debug.Log("Setting level display visible"); statusDisplay.SetLevelDisplayVisible(true); Debug.Log("Setting player 1 text visible"); statusDisplay.SetPlayer1TextVisible(true); Debug.Log("Setting ready text visible"); statusDisplay.SetReadyTextVisible(true); Debug.Log("Starting 1UP blink"); statusDisplay.SetLabel1UPTextBlinking(true); break; case 2: // Subtract a life DecrementLives(); break; case 3: // Remove Player 1 text statusDisplay.SetPlayer1TextVisible(false); break; case 4: // Show ghosts and pacman SetGhostsActive(true); SetPacManActive(true); break; case 5: // Remove ready text statusDisplay.SetReadyTextVisible(false); break; case 6: // Start game, end sequence soundManager.StartGhostSound(); SetFrozen(false); break; } } private void TimeSequenceStepBoardClear(int sequenceProgress) { switch (sequenceProgress) { case 0: // Freeze SetFrozen(true); soundManager.StopAllSound(); break; case 1: // Start board blinking, hide pellets in case of rack test BoardClearAnimation(); SetPelletsActive(false); break; case 2: // Hide ghosts SetGhostsActive(false); break; case 3: // Hide maze, lives indicator, level indicator SetMazeVisible(false); statusDisplay.SetExtraLivesDisplayVisible(false); statusDisplay.SetLevelDisplayVisible(false); break; case 4: // Hide score bar statusDisplay.SetScoreDisplayVisible(false); break; case 5: // Hide pacman, show level indicator with old level SetPacManActive(false); statusDisplay.SetLevelDisplayVisible(true); break; case 6: // Call handler for what should happen next InsertTimeSequence(PacManTimeSequence.StartNewLevel); break; } } private void TimeSequenceStepStartNewLevel(int sequenceProgress) { switch (sequenceProgress) { case 0: // Reset, show maze and score display InitializeLevel(); RestartLevel(); SetMazeVisible(true); statusDisplay.SetScoreDisplayVisible(true); soundManager.SuppressSound(false); break; case 1: // Increment level, show ready, show pellets, show lives indicators IncrementLevel(); statusDisplay.SetExtraLivesDisplayVisible(true); statusDisplay.SetReadyTextVisible(true); SetPelletsActive(true); break; case 2: // Show pacman, show ghosts SetPacManActive(true); SetGhostsActive(true); break; case 3: // Hide ready statusDisplay.SetReadyTextVisible(false); break; case 4: // Unfreeze SetFrozen(false); soundManager.StartGhostSound(); break; } } private void TimeSequenceStepGhostCaught(int sequenceProgress) { switch (sequenceProgress) { case 0: // Freeze and hide pacman, but let ghosts already in the caught animation continue SetFrozen(true, ghostIgnoreIfCaught: true); SetPacManActive(false); soundManager.PlayGhostEatSound(); break; case 1: // Unfreeze and reveal pacman SetPacManActive(true); SetFrozen(false); ghostManager.GhostCaughtContinue(); soundManager.SetGhostRetreat(true); break; } } private void TimeSequenceStepPacManCaught(int sequenceProgress) { switch (sequenceProgress) { case 0: // Freeze (except for the ghost animations) SetFrozen(true, ghostKeepAnimating: true); soundManager.StopAllSound(); break; case 1: // Hide ghosts, start pacman death animation SetGhostsActive(false); pacMan.SetDead(true); break; case 2: // Start playing death sound soundManager.PlayDeathSound(); break; case 3: // Hide pacman, start next state SetPacManActive(false); if (gameState == PacManGameState.AttractModeDemo) { break; } if (extraLives > 0) { InsertTimeSequence(PacManTimeSequence.RestartLevel); break; } InsertTimeSequence(PacManTimeSequence.GameOver); break; case 4: if (gameState == PacManGameState.AttractModeDemo) { #if RECORDING_DEMO // recorder.gameObject.SetActive(false); #endif InsertTimeSequence(PacManTimeSequence.AttractScreenIntroduction); } break; } } private void TimeSequenceStepRestartLevel(int sequenceProgress) { switch (sequenceProgress) { case 0: // Hide playfield and pellets SetMazeVisible(false); SetPelletsActive(false); break; case 1: // Make maze visible RestartLevel(); SetMazeVisible(true); break; case 2: // Take life, show ready, show pellets DecrementLives(); statusDisplay.SetReadyTextVisible(true); SetPelletsActive(true); break; case 3: // Show pacman, show ghosts SetPacManActive(true); SetGhostsActive(true); break; case 4: // Hide ready statusDisplay.SetReadyTextVisible(false); break; case 5: // Unfreeze SetFrozen(false); soundManager.SuppressSound(false); soundManager.StartGhostSound(); soundManager.UpdatePelletCount(pelletCountRemaining); break; } } private void TimeSequenceStepGameOver(int sequenceProgress) { switch (sequenceProgress) { case 0: // Show game over text, freeze power pellet blink statusDisplay.SetGameOverTextVisible(true); pelletManager.FreezePowerPelletsBlink(true); break; case 1: // Stop text blinking, transition to attract screen statusDisplay.SetLabel1UPTextBlinking(false); StartAttractMode(); break; } } public int TimeSequenceProgress { get => timeSequenceProgress; } public float TimeSequenceSecondsPassed { get => timeSequenceSecondsPassed; set => TimeSequenceProgressToTime(value); } #endregion } }