Files
PacManUdon/Assets/Scripts/PacMan.cs
2026-06-18 11:59:21 +02:00

373 lines
14 KiB
C#

using System;
using UnityEngine;
namespace Marro.PacManUdon
{
[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 const string AnimatorKeyDead = "Dead";
private const string AnimatorKeyDirection = "Direction";
private const float AnimatorDirectionNone = 0f;
private const float AnimatorDirectionRight = 0.25f;
private const float AnimatorDirectionLeft = 0.50f;
private const float AnimatorDirectionDown = 0.75f;
private const float AnimatorDirectionUp = 1f;
private const float AnimatorDirectionRightBig = 1.25f;
#endregion
public void Initialize(PlayerInput input, Transform startTransform, GameManager gameManager, CollisionManager collisionManager)
{
this.gameManager = gameManager;
this.collisionManager = collisionManager;
this.input = input;
animator = GetComponent<Animator>();
renderer = GetComponent<Renderer>();
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);
animator.SetTrigger("Reset");
//Debug.Log($"{gameObject} Reset! Position is now {GetPosition()}.");
}
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;
animator.SetBool(AnimatorKeyDead, dead);
if (dead)
{
animator.speed = 1;
return;
}
if (frozen || direction.Equals(Direction.Zero))
{
animator.SetFloat(AnimatorKeyDirection, AnimatorDirectionNone);
animator.speed = 0;
}
else
{
animator.speed = 1;
if (targetDirection.Equals(Direction.Right))
{
animator.SetFloat(AnimatorKeyDirection, AnimatorDirectionRight);
}
else if (targetDirection.Equals(Direction.Left))
{
animator.SetFloat(AnimatorKeyDirection, AnimatorDirectionLeft);
}
else if (targetDirection.Equals(Direction.Down))
{
animator.SetFloat(AnimatorKeyDirection, AnimatorDirectionDown);
}
else if (targetDirection.Equals(Direction.Up))
{
animator.SetFloat(AnimatorKeyDirection, AnimatorDirectionUp);
}
}
}
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()
{
animator.SetFloat(AnimatorKeyDirection, AnimatorDirectionRightBig);
}
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);
}
}
}