using System; using UnityEngine; namespace Marro.PacManUdon { enum PacManAnimatorState { Idle, Moving, Stopped, Dead, Big } [RequireComponent(typeof(Animator))] [RequireComponent(typeof(Renderer))] public class PacMan : GridMover { private GameManager gameManager; private PlayerInput input; private CollisionManager collisionManager; private float defaultSpeed; private float powerPelletSpeed; private float speed; private Vector3 startPosition; private Quaternion startRotation; private Animator animator; new Renderer renderer; private bool hideUntilUnfrozen; private bool dead; private bool kinematic; private bool followingPredefinedPath; private Direction[] predefinedPath; private int predefinedPathIndex; private float freezeSeconds; private bool frozen; #region Animator constants private readonly int animatorKeyState = Animator.StringToHash("State"); private readonly int animatorKeyDirection = Animator.StringToHash("Direction"); #endregion public void Initialize(PlayerInput input, Transform startTransform, GameManager gameManager, CollisionManager collisionManager) { this.gameManager = gameManager; this.collisionManager = collisionManager; this.input = input; animator = GetComponent(); renderer = GetComponent(); frozen = false; hideUntilUnfrozen = false; startPosition = startTransform.localPosition; startRotation = startTransform.localRotation; SubscribeToEvent(NetworkEventType.PacManTurn); } public void Reset() { transform.SetLocalPositionAndRotation(startPosition, startRotation); direction = Direction.Left; targetDirection = Direction.Left; speed = defaultSpeed; kinematic = false; followingPredefinedPath = false; SetDead(false); SetAnimatorState((int)PacManAnimatorState.Idle); } public override void SyncedUpdate() { // gameStateManager.statusDisplay.SetDebugText(1, this.targetDirection.ToString()); if (frozen) { return; } if (hideUntilUnfrozen) { hideUntilUnfrozen = false; SetVisibility(true); } float speed = this.speed; if (freezeSeconds > 0) { float freezePart = freezeSeconds / networkManager.SyncedDeltaTime; if (freezePart >= 1) { freezeSeconds -= networkManager.SyncedDeltaTime; animator.speed = 0; return; } speed *= 1 - freezePart; animator.speed = 1 - freezePart; freezeSeconds = 0; } else { animator.speed = 1; } Vector2 position = GetPosition(); Vector2 nextPosition = GetNextPosition(position, directionVectors[(int)direction], speed, networkManager.SyncedDeltaTime); // The position pacman will move to, assuming it doens't get changed if (!kinematic) { if (followingPredefinedPath) { nextPosition = ProcessPredefinedPath(position, nextPosition); } else { nextPosition = ProcessNextPosition(position, nextPosition); } } if (CrossesTileBorder(position, nextPosition, direction)) { CheckNewTile(position, nextPosition); } SetPosition(nextPosition); } private Vector2 ProcessNextPosition(Vector2 position, Vector2 nextPosition) { if (CrossesTileCenter(position, nextPosition, Direction.Left) // If pacman is moving horizontally, check if he may cross the center of a tile in that axis && (!IsHorizontal(targetDirection) || collisionManager.IsWallUpcoming(nextPosition, directionVectors[(int)HorizontalComponent(direction)]))) { // If the target direction is in the other axis or if we're about to run into a wall nextPosition.x = PositionToGrid(nextPosition).x; // Snap pacman to the center of his current tile in this axis SetDirection(VerticalComponent(direction)); //Debug.Log($"{gameObject} crossed X tile center from {position}, nextPosition is now {nextPosition} and direction is now {direction}"); } if (CrossesTileCenter(position, nextPosition, Direction.Down) // See comments above but now vertical && (!IsVertical(targetDirection) || collisionManager.IsWallUpcoming(nextPosition, directionVectors[(int)VerticalComponent(direction)]))) { nextPosition.y = PositionToGrid(nextPosition).y; SetDirection(HorizontalComponent(direction)); //Debug.Log($"{gameObject} crossed Y tile center from {position} with targetDirection {targetDirection}, nextPosition is now {nextPosition} and direction is now {direction}"); } var inputDirection = input.GetDirection(); if (!inputDirection.Equals(Direction.Zero) && !inputDirection.Equals(targetDirection) // Ignore neutral input or input in our current direction && !collisionManager.IsWallUpcoming(nextPosition, directionVectors[(int)inputDirection])) // Check if the requested direction does not have a wall { // Move in the requested direction, as well as perpundicular to it to get to the center of the tunnel if (IsHorizontal(inputDirection)) { var directionToCenter = VerticalToDirection(PositionToGrid(nextPosition).y - nextPosition.y); SetDirection((Direction)((int)inputDirection | (int)directionToCenter)); } else { var directionToCenter = HorizontalToDirection(PositionToGrid(nextPosition).x - nextPosition.x); SetDirection((Direction)((int)inputDirection | (int)directionToCenter)); } SetTargetDirection(inputDirection); // This is the direction most logic should assume pacman is moving, the actual direction may be different due to cornering if (!followingPredefinedPath) { networkManager.SendEventSoon(NetworkEventType.PacManTurn); } } return nextPosition; } private Vector2 ProcessPredefinedPath(Vector2 position, Vector2 nextPosition) { if (CrossesTileCenter(position, nextPosition, direction)) { // Find the next valid direction which isn't Vector2.zero int nextValidDirectionIndex = predefinedPathIndex; while (predefinedPath[nextValidDirectionIndex] == Direction.Zero) { nextValidDirectionIndex += 1; } if (!collisionManager.IsWallUpcoming(nextPosition, directionVectors[(int)predefinedPath[nextValidDirectionIndex]])) { // If we're at a Vector2.zero, we skip applying the direction and only increment. if (nextValidDirectionIndex == predefinedPathIndex) { SetDirectionAndTargetDirection(predefinedPath[nextValidDirectionIndex]); nextPosition = PositionToGrid(nextPosition) + directionVectors[(int)direction] * 0.01f; // Check if we've reached the end of the path, which includes making sure the path doesn't end on Vector2.zero do { nextValidDirectionIndex += 1; if (nextValidDirectionIndex >= predefinedPath.Length) { followingPredefinedPath = false; break; } } while (predefinedPath[nextValidDirectionIndex] == Direction.Zero); } // gameStateManager.statusDisplay.SetDebugText(1, predefinedPathIndex.ToString()); predefinedPathIndex++; } } return nextPosition; } private void CheckNewTile(Vector2 position, Vector2 nextPosition) { var eatResult = collisionManager.PacManMoveToTile(position, nextPosition); if (eatResult == EatResult.Pellet) { freezeSeconds = 0.0166666666666667f; } else if (eatResult == EatResult.PowerPellet) { freezeSeconds = freezeSeconds = 0.05f; } } protected override void UpdateAnimator() { Debug.Log($"{gameObject} UpdateAnimator with direction {direction}, dead {dead}, frozen {frozen}"); if (!gameObject.activeInHierarchy) return; if (dead) { SetAnimatorState((int)PacManAnimatorState.Dead); animator.speed = 1; return; } animator.speed = frozen ? 0 : 1; if (frozen) { return; } if (direction.Equals(Direction.Zero)) { SetAnimatorState((int)PacManAnimatorState.Stopped); } else { SetAnimatorState((int)PacManAnimatorState.Moving); SetAnimatorDirection((int)targetDirection); } } private void SetAnimatorDirection(int value) => animator.SetFloat(animatorKeyDirection, value); private void SetAnimatorState(int value) => animator.SetFloat(animatorKeyState, value); public void SetDead(bool dead) { this.dead = dead; UpdateAnimator(); } public void SetFrozen(bool frozen) { this.frozen = frozen; UpdateAnimator(); } public void HideUntilUnfrozen() { hideUntilUnfrozen = true; SetVisibility(false); } public void SetLevel(int level) { // Debug.Log($"{gameObject} SetLevel {level}"); defaultSpeed = PacManConstants.GetPacManDefaultSpeedForLevel(level); powerPelletSpeed = PacManConstants.GetPacManPowerPelletSpeedForLevel(level); } public void SetPowerPellet(bool powerPellet) { if (powerPellet) { speed = powerPelletSpeed; } else { speed = defaultSpeed; } } public void SetActive(bool active) { gameObject.SetActive(active); renderer.enabled = active; if (active) { UpdateAnimator(); } } public void SetKinematic(bool kinematic) { this.kinematic = kinematic; } public void SetPredefinedPath(Direction[] predefinedPath) { this.predefinedPath = predefinedPath; followingPredefinedPath = true; predefinedPathIndex = 0; } public void BecomeBig() { SetAnimatorState((int)PacManAnimatorState.Big); } void SetVisibility(bool visible) { renderer.enabled = visible; } public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType) { if (eventType != NetworkEventType.PacManTurn) { return; } if (kinematic || frozen || !enabled) { index += 1; base.PadSyncedData(data, ref index, eventType); return; } data.AppendAsByte((int)targetDirection, ref index); base.CollectSyncedData(data, ref index, eventType); } public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType) { if (eventType != NetworkEventType.PacManTurn) { return true; } if (kinematic || frozen || !enabled) { index += 1; base.ConsumeSyncedData(data, ref index, eventType); return true; } SetTargetDirection((Direction)data.ReadByte(ref index)); return base.WriteSyncedData(data, ref index, eventType); } } }