297 lines
9.1 KiB
C#
297 lines
9.1 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;
|
|
|
|
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<Pellet>(includeInactive: true);
|
|
|
|
powerPellets = GetComponentsInChildren<Animator>(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;
|
|
}
|
|
}
|
|
} |