using System; using UnityEngine; namespace Marro.PacManUdon { enum EatResult { None, Pellet, PowerPellet } public class CollisionManager : SyncedObject { public int PelletCount => pellets.Length; public int PelletCollectedCount { get; private set; } private GameManager gameManager; private BonusFruit bonusFruit; Pellet[] pellets; Animator[] powerPellets; Ghost[] ghosts; bool powerPelletBlinkEnabled; float powerPelletBlinkToggleInterval; float powerPelletBlinkProgress; bool powerPelletBlinkCurrentlyVisible; byte[] syncedPelletsCollected; byte[] collisionMap; byte[] pelletMap; const int mazeWidth = 32; const int mazeHeight = 32; private int[] ghostPositions = new int[4]; private int pacManPosition; private bool frozen; public void Initialize(GameManager gameManager, BonusFruit bonusFruit, Ghost[] ghosts) { this.gameManager = gameManager; this.bonusFruit = bonusFruit; this.ghosts = ghosts; gameObject.SetActive(true); pellets = GetComponentsInChildren(includeInactive: true); powerPellets = GetComponentsInChildren(true); powerPelletBlinkToggleInterval = PacManConstants.GetPowerPelletBlinkToggleInterval(); collisionMap = PacManConstants.GetMazeCollisionInfo(); SubscribeToEvent(NetworkEventType.SyncPellets); Reset(); } public void Reset() { SetPowerPelletsBlink(false); RestoreAllPellets(); } internal void SetFrozen(bool frozen) { this.frozen = frozen; if (!frozen) { SetPowerPelletsBlink(true); } } #region Collision public bool IsWallUpcoming(Vector2 position, Vector2 directionVector) { var index = GetTilemapIndex(position + directionVector); var tile = collisionMap[index]; var result = (tile & (int)PacManCollisionInfoType.Wall) != 0; //Debug.Log($"IsWallUpcoming {position}, {directionVector}. index {index}, tile {tile}, result {result}, collisionMap.Length {collisionMap.Length}"); return result; } public bool GhostMoveToTile(Vector2 position, int ghostIndex) { var tile = GetTilemapIndex(position); ghostPositions[ghostIndex] = tile; if (!frozen && tile == pacManPosition) { //Debug.Log("Ghost hit PacMan!"); ghosts[ghostIndex].HitPacMan(); } return (collisionMap[tile] & (int)PacManCollisionInfoType.Tunnel) != 0; } internal EatResult PacManMoveToTile(Vector2 position, Vector2 nextPosition) { var tilemapIndex = GetTilemapIndex(nextPosition); var tile = pelletMap[tilemapIndex]; pacManPosition = tilemapIndex; if (!frozen) { PacManTryEatGhost(); } TryCollectFruit(tile, position); return TryCollectPellet(tile, tilemapIndex); } internal void PacManTryEatGhost() { for (int i = 0; i < ghosts.Length; i++) { if (ghostPositions[i] != pacManPosition) { continue; } if (ghosts[i].HitPacMan()) // Only one collision may happen at a time { //Debug.Log("PacMan hit ghost!"); return; } } } private void TryCollectFruit(int tile, Vector2 position) { if (tile != (int)PacManConsumableType.FruitLeft && tile != (int)PacManConsumableType.FruitRight || !bonusFruit.Active) { return; } var previousTile = pelletMap[GetTilemapIndex(position)]; //Debug.Log($"On fruit tile {tile}, previous tile was {previousTile}. Position {position}, nextPosition {nextPosition}"); if (tile == (int)PacManConsumableType.FruitLeft && previousTile == (int)PacManConsumableType.FruitRight || tile == (int)PacManConsumableType.FruitRight && previousTile == (int)PacManConsumableType.FruitLeft) { //Debug.Log("Collecting fruit"); gameManager.GotFruit(); } } public int GetAvailableDirections(Vector2 position) { var directions = collisionMap[GetTilemapIndex(position)]; return directions; } internal static int GetTilemapIndex(Vector2 position) { // (int)position.x %% mazeWidth + (int)position.y %% mazeHeight * mazeWidth, where %% is an absolute modulo // Absoliute modulo in this case is implemented by just adding the divisor, works good enough and is performant. var index = ((int)position.x + mazeWidth) % mazeWidth + ((int)position.y + mazeHeight) % mazeHeight * mazeWidth; return index; } #endregion #region Pellet collecting private EatResult TryCollectPellet(int tile, int tilemapIndex) { if (tile < 0 || tile >= pellets.Length) { return EatResult.None; } pelletMap[tilemapIndex] = (byte)PacManConsumableType.None; var pellet = pellets[tile]; pellet.gameObject.SetActive(false); var index = pellet.transform.GetSiblingIndex(); syncedPelletsCollected[index / 8] |= (byte)(1 << index % 8); PelletCollectedCount++; var pelletType = pellet.isPowerPellet ? EatResult.PowerPellet : EatResult.Pellet; gameManager.GotPellet(pellet, pellet.isPowerPellet, PelletCollectedCount, PelletCount - PelletCollectedCount); return pelletType; } public int RestoreAllPellets() { foreach (var pellet in pellets) { pellet.gameObject.SetActive(true); } syncedPelletsCollected = new byte[pellets.Length/8 + 1]; PelletCollectedCount = 0; pelletMap = PacManConstants.GetMazePelletMap(); return PelletCount; } private void SetPelletsCollectedFromSync() { for (int i = 0; i < pellets.Length; i++) { var active = (syncedPelletsCollected[i/8] & (byte)(1 << i%8)) == 0; pellets[i].gameObject.SetActive(active); } } #endregion #region Power pellet blink public override void SyncedUpdate() { if (!powerPelletBlinkEnabled) { return; } powerPelletBlinkProgress += networkManager.SyncedDeltaTime; if (powerPelletBlinkProgress >= powerPelletBlinkToggleInterval) { // Debug.Log($"{gameObject} PowerPelletBlink toggle"); powerPelletBlinkProgress -= powerPelletBlinkToggleInterval; powerPelletBlinkCurrentlyVisible = !powerPelletBlinkCurrentlyVisible; SetPowerPelletsVisible(powerPelletBlinkCurrentlyVisible); } } void SetPowerPelletsVisible(bool visible) { // Debug.Log($"{gameObject} SetPowerPelletVisible {visible}, powerPellets.Length: {powerPellets.Length}"); foreach (Animator powerPellet in powerPellets) { powerPellet.SetBool("Visible", visible); } } public void SetPowerPelletsBlink(bool enabled) { // Debug.Log($"{gameObject} SetPowerPelletBlink {enabled}"); powerPelletBlinkEnabled = enabled; powerPelletBlinkCurrentlyVisible = true; powerPelletBlinkProgress = 0; SetPowerPelletsVisible(true); } public void FreezePowerPelletsBlink(bool frozen) { powerPelletBlinkEnabled = !frozen; } #endregion public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType) { if (eventType != NetworkEventType.SyncPellets) { return; } data.Append((byte)PelletCollectedCount, ref index); data.Append(syncedPelletsCollected, ref index); data.Append(frozen, ref index); } public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType) { if (eventType != NetworkEventType.SyncPellets) { return true; } PelletCollectedCount = data.ReadByte(ref index); Array.Copy(data, index, syncedPelletsCollected, 0, syncedPelletsCollected.Length); index += syncedPelletsCollected.Length; SetPelletsCollectedFromSync(); frozen = data.ReadBool(ref index); return true; } } }