Files
PacManUdon/Assets/Scripts/GameManager.cs
2025-12-29 21:02:05 +01:00

504 lines
15 KiB
C#

#define RECORDING_DEMO
namespace Marro.PacManUdon
{
using Assets.Scripts;
using System;
using UdonSharp;
using UnityEngine;
using VRC.SDK3.Components;
using VRC.SDKBase;
public partial class GameManager : SyncedObject
{
[Header("Static game components")]
[SerializeField] private Maze[] mazes;
[SerializeField] private PacMan pacMan;
[SerializeField] private GhostManager ghostManager;
[SerializeField] private BonusFruit bonusFruit;
[SerializeField] private PelletManager pelletManager;
[SerializeField] public StatusDisplay statusDisplay; // This one is public so other scripts can write to the debug display
[SerializeField] private PelletManager attractScreen;
[SerializeField] private GameObject intermissionScreen;
[SerializeField] private GameObject pressStartButtonScreen;
[SerializeField] private PlayerInput playerInput;
[SerializeField] private Animator demo;
[SerializeField] private SoundManager soundManager;
[SerializeField] private NetworkManager networkManager;
[SerializeField] private GameObject recorder;
[Header("Game settings")]
[SerializeField] private int startingExtraLives = 3;
[SerializeField] private int scoreToExtraLife = 10000;
[Tooltip("Override amount of pellets needed to clear stage, set to -1 to disable.")]
[SerializeField] private int pelletCountOverride = -1;
private Maze maze;
private VRCObjectPool pelletPool;
private Intermission2Pole intermission2Pole;
private Animator mazeSpriteAnimator;
private int pelletCountTotal;
private int pelletCountRemaining;
private GameObject[] attractScreenElements;
private GameObject[] intermissionScreenElements;
private PacManGameState gameState;
[UdonSynced, FieldChangeCallback(nameof(Score))] private int score;
[UdonSynced, FieldChangeCallback(nameof(Level))] private int level;
[UdonSynced, FieldChangeCallback(nameof(HighScore))] private int highScore;
[UdonSynced, FieldChangeCallback(nameof(ExtraLives))] private int extraLives;
public void Start()
{
attractScreenElements = new GameObject[attractScreen.transform.childCount];
for (int i = 0; i < attractScreenElements.Length; i++)
{
attractScreenElements[i] = attractScreen.transform.GetChild(i).gameObject;
}
intermissionScreenElements = new GameObject[intermissionScreen.transform.childCount];
for (int i = 0; i < intermissionScreenElements.Length; i++)
{
intermissionScreenElements[i] = intermissionScreen.transform.GetChild(i).gameObject;
}
maze = mazes[0];
pelletPool = maze.pelletContainer.GetComponent<VRCObjectPool>();
mazeSpriteAnimator = maze.mazeSprite.GetComponent<Animator>();
intermission2Pole = intermissionScreenElements[4].GetComponent<Intermission2Pole>();
ghostManager.Initialize(maze.ghostTargets, pacMan, this);
pacMan.Initialize(playerInput, pelletPool, this);
bonusFruit.Initialize();
pelletManager.Initialize(pelletPool);
statusDisplay.Initialize();
playerInput.Initialize(this);
soundManager.Initialize();
intermission2Pole.Initialize(this, ghostManager.Ghosts[0]);
networkManager.Initialize(Networking.IsOwner(gameObject));
HideEverything();
SetScore(0);
SetHighScore(0);
SetLevel(0);
StartAttractMode();
}
public void FixedUpdate()
{
TimeSequenceUpdate(Time.deltaTime);
}
public void JoystickGrabbed()
{
if (gameState == PacManGameState.AttractMode || gameState == PacManGameState.AttractModeDemo)
StartTimeSequence(PacManTimeSequence.WaitForStart);
}
public void JoystickReleased()
{
if (gameState == PacManGameState.WaitForStart)
StartTimeSequence(PacManTimeSequence.WaitForStartTimeout);
}
public void ResetButtonPressed()
{
Debug.Log($"{gameObject} Reset button was pressed!");
Start();
}
public void StartGameButtonPressed()
{
Debug.Log($"{gameObject} Start Game Button was pressed!");
TakeOwnership();
StartTimeSequence(PacManTimeSequence.StartNewGame);
}
private void StartAttractMode()
{
// #if RECORDING_DEMO
// recorder.gameObject.SetActive(true);
StartTimeSequence(PacManTimeSequence.AttractScreenIntroduction);
// #else
// SetGameState(PacManGameState.AttractMode);
// HideEverything();
// demo.gameObject.SetActive(true);
// #endif
}
private void InitializeNewGame()
{
Debug.Log($"{gameObject} Started new game!");
SetScore(0);
SetExtraLives(startingExtraLives);
SetLevel(1);
}
private void InitializeLevel()
{
Debug.Log($"{gameObject} New level started!");
pelletCountTotal = pelletPool.Pool.Length;
pelletCountRemaining = pelletCountTotal;
ghostManager.SetPelletsRemaining(pelletCountRemaining);
ghostManager.NewLevel();
pelletManager.RestoreAllPellets();
if (pelletCountOverride > 0)
{
pelletCountRemaining = pelletCountOverride;
}
mazeSpriteAnimator.SetBool("Blinking", false);
}
private void RestartLevel()
{
Debug.Log($"{gameObject} (Re)started level!");
// SetInGameComponentVisibility(true);
ghostManager.Reset();
pacMan.Reset();
bonusFruit.Despawn();
soundManager.Reset();
pelletManager.SetPowerPelletsBlink(false);
}
private void PrepareForCutscene()
{
HideEverything();
RestartLevel();
SetFrozen(true);
}
public void GotPellet(bool addScore = true)
{
pelletCountRemaining--;
if (addScore) AddScore(10);
ghostManager.PelletConsumed();
soundManager.PlayPelletSound();
soundManager.UpdatePelletCount(pelletCountRemaining);
int pelletsConsumed = pelletCountTotal - pelletCountRemaining;
if (pelletCountRemaining <= 0)
{
StartTimeSequence(PacManTimeSequence.BoardClear);
}
else if (pelletsConsumed == 70 || pelletsConsumed == 170)
{
bonusFruit.Spawn();
}
}
public void GotPowerPellet()
{
if (gameState == PacManGameState.AttractMode)
{
TimeSequenceSkipToNextStep();
return;
}
GotPellet(addScore: false);
AddScore(50);
ghostManager.SetPowerPellet(true);
pacMan.SetPowerPellet(true);
soundManager.SetGhostBlue(true);
}
public void EndPowerPellet()
{
ghostManager.SetPowerPellet(false);
pacMan.SetPowerPellet(false);
soundManager.SetGhostBlue(false);
}
public void GotFruit()
{
AddScore(bonusFruit.Collected());
soundManager.PlayFruitSound();
}
public void GhostCaught(int scoreBonus)
{
if (gameState == PacManGameState.AttractMode)
{
TimeSequenceSkipToNextStep();
return;
}
AddScore(scoreBonus);
StartTimeSequence(PacManTimeSequence.GhostCaught);
pacMan.HideUntilUnfrozen();
}
public void PacManCaught()
{
StartTimeSequence(PacManTimeSequence.PacManCaught);
}
public void NoGhostsScared()
{
soundManager.SetGhostBlue(false);
}
public void NoGhostsRetreating()
{
soundManager.SetGhostRetreat(false);
}
public void Intermission2PoleUpdate()
{
TimeSequenceSkipToNextStep();
}
void BoardClearAnimation()
{
ghostManager.gameObject.SetActive(false);
mazeSpriteAnimator.SetBool("Blinking", true);
}
private void HideEverything()
{
SetPelletsActive(false);
SetMazeVisible(false);
SetGhostsActive(false);
SetPacManActive(false);
SetPressStartButtonScreenVisible(false);
SetIntermissionScreenVisible(false);
statusDisplay.SetGameOverTextVisible(false);
statusDisplay.SetExtraLivesDisplayVisible(false);
statusDisplay.SetLevelDisplayVisible(false);
statusDisplay.SetPlayer1TextVisible(false);
statusDisplay.SetReadyTextVisible(false);
demo.gameObject.SetActive(false);
}
void SetPelletsActive(bool active)
{
pelletPool.gameObject.SetActive(active);
}
void SetMazeVisible(bool visible)
{
mazeSpriteAnimator.SetBool("Hidden", !visible);
}
void SetGhostsActive(bool active)
{
ghostManager.SetActive(active);
}
void SetPacManActive(bool active)
{
pacMan.SetActive(active);
}
void SetPressStartButtonScreenVisible(bool visible)
{
pressStartButtonScreen.SetActive(visible);
}
void SetIntermissionScreenVisible(bool visible)
{
intermissionScreen.SetActive(visible);
}
void SetGameState(PacManGameState newGameState)
{
// Debug.Log($"{gameObject} State transitioning from {gameState} to {newGameState}");
gameState = newGameState;
if (Networking.IsOwner(gameObject))
{
RequestSerialization();
}
}
private void IncrementLevel()
{
SetLevel(level + 1);
}
private void SetLevel(int level)
{
this.level = level;
pacMan.SetLevel(level);
ghostManager.SetLevel(level);
statusDisplay.SetLevel(level);
bonusFruit.SetFruitType(PacManConstants.GetFruitTypeForLevel(level));
}
void AddScore(int score)
{
if (gameState == PacManGameState.AttractMode || gameState == PacManGameState.AttractModeDemo)
{
return;
}
if (this.score < scoreToExtraLife && this.score + score >= scoreToExtraLife)
{
BonusLifeReached();
}
SetScore(this.score + score);
RequestSerialization();
}
void SetScore(int score)
{
this.score = score;
statusDisplay.Set1UPScore(score);
if (score > highScore)
{
highScore = score;
statusDisplay.SetHighScore(score);
}
}
void SetHighScore(int highScore)
{
this.highScore = highScore;
statusDisplay.SetHighScore(score);
}
public void DecrementLives()
{
if (!Networking.IsOwner(gameObject))
{
return;
}
// Debug.Log($"{gameObject} Decremented lives from {extraLives} to {extraLives - 1}");
SetExtraLives(extraLives - 1);
}
void IncrementLives()
{
if (!Networking.IsOwner(gameObject))
{
return;
}
// Debug.Log($"{gameObject} Incremented lives from {extraLives} to {extraLives + 1}");
SetExtraLives(extraLives + 1);
}
void SetExtraLives(int extraLives)
{
// Debug.Log($"{gameObject} Set lives from {this.extraLives} to {extraLives}");
this.extraLives = extraLives;
statusDisplay.SetExtraLives(extraLives);
}
void BonusLifeReached()
{
IncrementLives();
soundManager.PlayExtraLifeSound();
}
public void SetFrozen(bool frozen, bool ghostIgnoreIfCaught = false, bool ghostKeepAnimating = false)
{
// Debug.Log($"{gameObject} Set Frozen: {frozen}");
pacMan.SetFrozen(frozen);
bonusFruit.SetFrozen(frozen);
ghostManager.SetFrozen(frozen, ignoreIfCaught: ghostIgnoreIfCaught);
if (!frozen)
{
pelletManager.SetPowerPelletsBlink(true);
}
}
void TakeOwnership()
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
Networking.SetOwner(Networking.LocalPlayer, pacMan.gameObject);
Networking.SetOwner(Networking.LocalPlayer, pelletPool.gameObject);
ghostManager.SetOwner(Networking.LocalPlayer);
}
public override void AppendSyncedData(byte[][] data, ref int offset)
{
data[offset++] = new byte[] { Int32ToByte((int)gameState) };
data[offset++] = BitConverter.GetBytes(currentlyInTimeSequence);
data[offset++] = new byte[] { Int32ToByte((int)currentTimeSequence) };
data[offset++] = BitConverter.GetBytes(timeSequenceProgress);
}
public override bool SetSyncedData(byte[] data, ref int offset)
{
SetGameState((PacManGameState)data[offset++]);
var currentlyInTimeSequence = BitConverter.ToBoolean(data, offset++);
var currentTimeSequence = (PacManTimeSequence)data[offset++];
var timeSequenceProgress = BitConverter.ToSingle(data, offset);
offset += 4;
TimeSequenceSyncWithRemote(currentlyInTimeSequence, currentTimeSequence, timeSequenceProgress);
return true;
}
public int ExtraLives
{
set
{
SetExtraLives(value);
}
get => extraLives;
}
public PacManGameState GameState
{
set
{
SetGameState(value);
}
get => gameState;
}
public bool GhostsScared
{
set
{
}
get => GhostsScared;
}
public int Score
{
set
{
SetScore(value);
}
get => score;
}
public int HighScore
{
set
{
SetHighScore(value);
}
get => score;
}
public int Level
{
set
{
SetLevel(value);
}
get => level;
}
public static byte Int32ToByte(int value) =>
byte.Parse(value.ToString()); // This is the only way I could find to cast an int to byte in Udon, a regular cast crashes in runtime...
}
}