Files
PacManUdon/Assets/Scripts/CollisionManager.cs
2026-06-18 11:03:56 +02:00

267 lines
8.5 KiB
C#

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;
public void Initialize(GameManager gameManager, BonusFruit bonusFruit, Ghost[] ghosts)
{
this.gameManager = gameManager;
this.bonusFruit = bonusFruit;
this.ghosts = ghosts;
gameObject.SetActive(true);
pellets = GetComponentsInChildren<Pellet>(includeInactive: true);
powerPellets = GetComponentsInChildren<Animator>(true);
powerPelletBlinkToggleInterval = PacManConstants.GetPowerPelletBlinkToggleInterval();
SetPowerPelletsBlink(false);
collisionMap = PacManConstants.GetMazeCollisionInfo();
RestoreAllPellets();
SubscribeToEvent(NetworkEventType.SyncPellets);
}
#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 (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;
TryEatGhost(tilemapIndex);
TryCollectFruit(tile, position);
return TryCollectPellet(tile, tilemapIndex);
}
private void TryEatGhost(int tilemapIndex)
{
for (int i = 0; i < ghosts.Length; i++)
{
if (ghostPositions[i] == tilemapIndex)
{
Debug.Log("PacMan hit ghost!");
ghosts[i].HitPacMan();
}
}
}
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);
}
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();
return true;
}
}
}