namespace Marro.PacManUdon { using System; using UdonSharp; using UnityEngine; using VRC.SDKBase; using VRC.Udon; using VRC.SDK3.Data; public class GhostManager : UdonSharpBehaviour { [NonSerialized] public GameManager gameController; private Ghost[] ghosts; private Ghost blinky; // Level constants private float speedDefault; private float speedScared; private float speedReturn; private float speedTunnel; private float speedHome; private float speedElroy1; private float speedElroy2; private int elroy1PelletCount; private int elroy2PelletCount; // Power Pellet logic private bool powerPelletActive; private float powerPelletDuration; private float powerPelletCountdown; private int powerPelletMultiplier; private DataList ghostScaredQueue; // Blink logic private float blinkCycleRate = 0.233333333f; private bool blinkingActivated; private float blinkCountdown; private bool blinkCurrentlyWhite; // Scattering logic private float scatterCounter; private float[] scatterPattern; private int scatterPatternIndex; // Elroy logic public int elroyLevel; private int pelletsRemaining; // Ghost house logic private bool sharedPelletCounterActive; private int sharedPelletCounter; private int[] sharedPelletCounterReleaseValues = { 0, 7, 17, 32 }; private float pelletTimeout; private float pelletTimeoutLimit; private bool frozen; private bool kinematic; // This should be called once when the game is initialized public void Initialize(GameObject[] ghostTargets, PacMan pacMan, GameManager gameController) { this.gameController = gameController; ghosts = transform.GetComponentsInChildren(true); blinky = ghosts[0]; for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++) { Vector2 homePosition = ghostTargets[0].transform.localPosition; Vector2 idlePosition1 = ghostTargets[1 + ghostIndex * 3].transform.localPosition; Vector2 idlePosition2 = ghostTargets[2 + ghostIndex * 3].transform.localPosition; Vector2 cornerPosition = ghostTargets[3 + ghostIndex * 3].transform.localPosition; ghosts[ghostIndex].Initialize(pacMan, blinky, homePosition, idlePosition1, idlePosition2, cornerPosition); } } // This should be called every time the level is reset public void Reset() { ghostScaredQueue = new DataList(); powerPelletActive = false; scatterCounter = 0; scatterPatternIndex = 0; sharedPelletCounter = 0; pelletTimeout = 0; elroyLevel = 0; kinematic = false; foreach (Ghost ghost in ghosts) { ghost.Reset(); } SetScattering(true, reverseDirection: false); RequestSerialization(); } public void NewLevel() { SetSharedPelletCounterActive(false); foreach (Ghost ghost in ghosts) { ghost.ResetHousePelletCounter(); } } public void LifeLost() { SetSharedPelletCounterActive(true); } public void FixedUpdate() { // gameStateManager.statusDisplay.SetDebugText(1, this.blinkCountdown.ToString()); if (frozen || kinematic) { return; } UpdatePelletTimeout(); if (powerPelletActive) { UpdatePowerPellet(); } else { UpdateScattering(); } } void UpdateScattering() { scatterCounter += Time.deltaTime; if (scatterPatternIndex < scatterPattern.Length && scatterCounter >= scatterPattern[scatterPatternIndex]) { // Debug.Log($"{gameObject} SetScattering: {scatterPatternIndex%1 == 0}, scatterCounter: {scatterCounter}, scatterPattern: {scatterPattern[scatterPatternIndex]}, scatterPatternIndex: {scatterPatternIndex}"); scatterPatternIndex++; SetScattering(scatterPatternIndex % 2 == 0); } } void UpdatePowerPellet() { powerPelletCountdown -= Time.deltaTime; if (powerPelletCountdown <= 0) { gameController.EndPowerPellet(); // End power pellet SetPowerPellet(false); } else if (!blinkingActivated && powerPelletCountdown <= 2.1166667) { SetGhostBlinking(true); } if (blinkingActivated) { blinkCountdown -= Time.deltaTime; if (blinkCountdown <= 0) { blinkCountdown += blinkCycleRate; SetGhostBlinkingState(!blinkCurrentlyWhite); } } } void UpdatePelletTimeout() { pelletTimeout += Time.deltaTime; if (pelletTimeout >= pelletTimeoutLimit) { pelletTimeout -= pelletTimeoutLimit; for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++) { Ghost ghost = ghosts[ghostIndex]; if (ghost.GetGhostState() == PacManGhostState.Home) { ghost.Release(); break; } } } } public void GhostCaughtQueue(Ghost ghost) { if (gameController.GameState == PacManGameState.AttractMode) { gameController.GhostCaught(0); return; } Debug.Log($"{gameObject} GhostCaughtQueue with ghost {ghost}"); ghostScaredQueue.Add(ghost); GhostCaughtExecute(ghost); } public void GhostCaughtContinue() { Debug.Log($"{gameObject} GhostCaughtContinue with ghost queue length {ghostScaredQueue.Count}"); if (!ghostScaredQueue.TryGetValue(0, out DataToken currentGhost)) { Debug.LogError("Called GhostCaughtContinue without a ghost in the queue!"); return; } ((Ghost)currentGhost.Reference).ReturnHome(); ghostScaredQueue.RemoveAt(0); if (ghostScaredQueue.TryGetValue(0, out DataToken nextGhost)) { GhostCaughtExecute((Ghost)nextGhost.Reference); } } void GhostCaughtExecute(Ghost ghost) { int scoreBonus = 200 * (int)Math.Pow(2, powerPelletMultiplier); powerPelletMultiplier += 1; ghost.Caught(scoreBonus); gameController.GhostCaught(scoreBonus); } public void CapturedPacMan() { gameController.PacManCaught(); } public void GhostReturned() { bool noGhostsScared = true; bool noGhostsRetreating = true; foreach (var ghost in ghosts) { var state = ghost.GetGhostState(); if (ghost.IsScared) { noGhostsScared = false; } if (state == PacManGhostState.CaughtScore || state == PacManGhostState.Returning || state == PacManGhostState.Entering) { noGhostsRetreating = false; } } if (noGhostsScared) { gameController.NoGhostsScared(); } if (noGhostsRetreating) { gameController.NoGhostsRetreating(); } } void SetGhostBlinking(bool blinking) { blinkingActivated = blinking; if (blinking == true) { blinkCountdown = blinkCycleRate; SetGhostBlinkingState(true); } } void SetGhostBlinkingState(bool white) { blinkCurrentlyWhite = white; foreach (Ghost ghost in ghosts) { ghost.SetWhite(white); } } public void SetPowerPellet(bool powerPelletActive) { this.powerPelletActive = powerPelletActive; if (powerPelletActive) { powerPelletCountdown = powerPelletDuration; powerPelletMultiplier = 0; SetGhostBlinking(false); foreach (Ghost ghost in ghosts) { ghost.BecomeScared(); } } if (!powerPelletActive || powerPelletDuration == 0) { foreach (Ghost ghost in ghosts) { ghost.CalmDown(); SetGhostBlinking(false); } } } public void SetFrozen(bool frozen, bool ignoreIfCaught = false) { this.frozen = frozen; foreach (Ghost ghost in ghosts) { ghost.SetFrozen(frozen, ignoreIfCaught: ignoreIfCaught); } } public void SetLevel(int level) { Debug.Log($"GhostManager: SetLevel {level}"); speedDefault = PacManConstants.GetGhostDefaultSpeedForLevel(level); speedScared = PacManConstants.GetGhostScaredSpeedForLevel(level); speedReturn = 15f; speedTunnel = PacManConstants.GetGhostTunnelSpeedForLevel(level); speedHome = PacManConstants.GetGhostHomeSpeed(); speedElroy1 = PacManConstants.GetBlinkyElroy1SpeedForLevel(level); speedElroy2 = PacManConstants.GetBlinkyElroy2SpeedForLevel(level); elroy1PelletCount = PacManConstants.GetElroy1PelletsRemainingForLevel(level); elroy2PelletCount = PacManConstants.GetElroy2PelletsRemainingForLevel(level); powerPelletDuration = PacManConstants.GetScaredDurationForLevel(level); scatterPattern = PacManConstants.GetScatterPatternForLevel(level); pelletTimeoutLimit = PacManConstants.GetGhostHousePelletTimeoutLimitForLevel(level); int[] privatePelletCounterReleaseValues = PacManConstants.GetGhostHousePrivatePelletCounterLimitForLevel(level); for (int i = 0; i < Math.Min(sharedPelletCounterReleaseValues.Length, ghosts.Length); i++) { ghosts[i].SetHousePelletCounterLimit(privatePelletCounterReleaseValues[i]); } } public void SetOwner(VRCPlayerApi player) { Networking.SetOwner(player, gameObject); foreach (Ghost ghost in ghosts) { Networking.SetOwner(player, ghost.gameObject); } } public float GetTargetSpeed(Ghost ghost, PacManGhostState ghostState, bool isScared, bool inTunnel) { if (ghostState == PacManGhostState.Returning || ghostState == PacManGhostState.Entering) { return speedReturn; } if (inTunnel) { return speedTunnel; } if (ghostState == PacManGhostState.Exiting || ghostState == PacManGhostState.Home) { return speedHome; } if (isScared) { return speedScared; } if (ghost == blinky) { if (elroyLevel == 1) { return speedElroy1; } if (elroyLevel == 2) { return speedElroy2; } } return speedDefault; } void SetScattering(bool scattering, bool reverseDirection = true) { Debug.Log($"{gameObject} SetScattering: {scattering}"); foreach (Ghost ghost in ghosts) { if (ghost == blinky && pelletsRemaining <= elroy1PelletCount) { continue; } ghost.SetScattering(scattering, reverseDirection); } } public void SetPelletsRemaining(int pelletsRemaining) { this.pelletsRemaining = pelletsRemaining; UpdateElroyLevel(); } void SetSharedPelletCounterActive(bool active) { // Debug.Log($"{gameObject} SetSharedPelletCounterActive {active}"); sharedPelletCounterActive = active; foreach (Ghost ghost in ghosts) { ghost.SetHousePelletCounterActive(!active); } } public void PelletConsumed() { SetPelletsRemaining(pelletsRemaining - 1); pelletTimeout = 0; if (sharedPelletCounterActive) { IncrementSharedPelletCounter(); } else { IncrementPrivatePelletCounter(); } } void IncrementSharedPelletCounter() { sharedPelletCounter++; for (int ghostIndex = 0; ghostIndex < sharedPelletCounterReleaseValues.Length; ghostIndex++) { Ghost ghost = ghosts[ghostIndex]; if (ghost.GetGhostState() == PacManGhostState.Home && sharedPelletCounter == sharedPelletCounterReleaseValues[ghostIndex]) { ghost.Release(); if (ghostIndex == sharedPelletCounterReleaseValues.Length - 1) { SetSharedPelletCounterActive(false); } } } } void IncrementPrivatePelletCounter() { for (int ghostIndex = 0; ghostIndex < ghosts.Length; ghostIndex++) { Ghost ghost = ghosts[ghostIndex]; if (ghost.GetGhostState() == PacManGhostState.Home) { ghost.IncrementHousePelletCounter(); break; } } } void UpdateElroyLevel() { // Debug.Log($"{gameObject} Updating Elroy Level with pelletsRemaining {pelletsRemaining} with elroy2PelletCount {elroy2PelletCount} and elroy1PelletCount {elroy1PelletCount}"); int oldElroyLevel = elroyLevel; if (pelletsRemaining < elroy2PelletCount) elroyLevel = 2; else if (pelletsRemaining < elroy1PelletCount) elroyLevel = 1; else elroyLevel = 0; if (elroyLevel != oldElroyLevel) { blinky.SetElroy(elroyLevel); } } public void SetActive(bool active) { gameObject.SetActive(active); foreach (Ghost ghost in ghosts) { ghost.SetActive(active); } } public void SetKinematic(bool kinematic) { this.kinematic = kinematic; foreach (Ghost ghost in ghosts) { ghost.SetKinematic(kinematic); } } public Ghost[] Ghosts { get => ghosts; } } }