Initial commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a78f6038f14d0444912fbd94677552a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a climbing mini-game.
|
||||
/// </summary>
|
||||
public class MiniGame_Climber : MiniGameBase
|
||||
{
|
||||
[SerializeField] SubTrigger[] m_FinishBells;
|
||||
[SerializeField, ColorUsage(true, true)] Color m_BellStartColor;
|
||||
[SerializeField, ColorUsage(true, true)] Color m_BellCompleteColor;
|
||||
|
||||
List<Renderer> m_BellRenderers = new();
|
||||
protected float m_LocalPlayerTimer = 0.0f;
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
m_LocalPlayerTimer = 0.0f;
|
||||
m_CurrentTimer = m_GameLength;
|
||||
foreach (var bell in m_FinishBells)
|
||||
{
|
||||
bell.subTriggerCollider.enabled = false;
|
||||
|
||||
var rend = bell.GetComponent<Renderer>();
|
||||
bell.OnTriggerAction += (Collider c, bool b) => { HitBell(c, b, rend); };
|
||||
m_BellRenderers.Add(rend);
|
||||
}
|
||||
}
|
||||
|
||||
////<inheritdoc/>
|
||||
void OnDestroy()
|
||||
{
|
||||
foreach (var bell in m_FinishBells)
|
||||
{
|
||||
bell.OnTriggerAction -= (Collider c, bool b) => { HitBell(c, b, bell.GetComponent<Renderer>()); };
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetupGame()
|
||||
{
|
||||
base.SetupGame();
|
||||
foreach (var rend in m_BellRenderers)
|
||||
{
|
||||
rend.material.color = m_BellStartColor;
|
||||
rend.material.SetColor("_EmissionColor", m_BellStartColor);
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void StartGame()
|
||||
{
|
||||
base.StartGame();
|
||||
foreach (var bell in m_FinishBells)
|
||||
{
|
||||
bell.subTriggerCollider.enabled = true;
|
||||
}
|
||||
|
||||
foreach (var rend in m_BellRenderers)
|
||||
{
|
||||
rend.material.color = m_BellStartColor;
|
||||
rend.material.SetColor("_EmissionColor", m_BellStartColor);
|
||||
}
|
||||
|
||||
m_LocalPlayerTimer = 0.0f;
|
||||
m_CurrentTimer = m_GameLength;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void UpdateGame(float deltaTime)
|
||||
{
|
||||
base.UpdateGame(deltaTime);
|
||||
if (!m_Finished)
|
||||
{
|
||||
m_LocalPlayerTimer += Time.deltaTime;
|
||||
}
|
||||
m_MiniGameManager.UpdatePlayerScores();
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void FinishGame(bool submitScore = true)
|
||||
{
|
||||
base.FinishGame(submitScore);
|
||||
|
||||
foreach (var bells in m_FinishBells)
|
||||
{
|
||||
bells.subTriggerCollider.enabled = false;
|
||||
}
|
||||
|
||||
if (submitScore)
|
||||
m_MiniGameManager.SubmitScoreServerRpc(m_LocalPlayerTimer, XRINetworkPlayer.LocalPlayer.OwnerClientId, true);
|
||||
}
|
||||
|
||||
void HitBell(Collider collider, bool isTriggered, Renderer bell)
|
||||
{
|
||||
if (isTriggered && collider.CompareTag("PlayerHand"))
|
||||
{
|
||||
XRINetworkPlayer player = collider.gameObject.GetComponentInParent<XRINetworkPlayer>();
|
||||
if (player.IsLocalPlayer & !m_Finished)
|
||||
{
|
||||
bell.material.color = m_BellCompleteColor;
|
||||
bell.material.SetColor("_EmissionColor", m_BellCompleteColor);
|
||||
var particle = bell.GetComponentInChildren<ParticleSystem>();
|
||||
if (particle != null)
|
||||
{
|
||||
particle.Play();
|
||||
}
|
||||
|
||||
FinishGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1431406ae6692f9469391da4015beca4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,206 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for mini-games.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(MiniGameManager))]
|
||||
public class MiniGameBase : MonoBehaviour, IMiniGame
|
||||
{
|
||||
/// <summary>
|
||||
/// External flag for the game being finished.
|
||||
/// </summary>
|
||||
public bool finished
|
||||
{
|
||||
get => m_Finished;
|
||||
set => m_Finished = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal flag for the game being finished.
|
||||
/// </summary>
|
||||
protected bool m_Finished;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the game.
|
||||
/// </summary>
|
||||
public string gameName;
|
||||
|
||||
/// <summary>
|
||||
/// The type of game.
|
||||
/// </summary>
|
||||
public enum GameType { Time, Score }
|
||||
|
||||
/// <summary>
|
||||
/// The current game type.
|
||||
/// </summary>
|
||||
public GameType currentGameType
|
||||
{
|
||||
get => m_GameType;
|
||||
set => m_GameType = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The game type.
|
||||
/// </summary>
|
||||
[SerializeField] GameType m_GameType;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the game.
|
||||
/// </summary>
|
||||
[SerializeField] protected float m_GameLength = 90.0f;
|
||||
|
||||
[SerializeField] protected XRBaseInteractable[] m_GameInteractables;
|
||||
|
||||
/// <summary>
|
||||
/// Manager for the mini-game.
|
||||
/// </summary>
|
||||
protected MiniGameManager m_MiniGameManager;
|
||||
|
||||
/// <summary>
|
||||
/// Manager for the interaction.
|
||||
/// </summary>
|
||||
protected XRInteractionManager m_InteractionManager;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The current timer for the game.
|
||||
/// </summary>
|
||||
protected float m_CurrentTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating whether the game ending notification has been sent.
|
||||
/// </summary>
|
||||
bool m_GameEndingNotificationSent = false;
|
||||
|
||||
///<inheritdoc/>
|
||||
public virtual void Start()
|
||||
{
|
||||
TryGetComponent(out m_MiniGameManager);
|
||||
m_CurrentTimer = m_GameLength;
|
||||
m_InteractionManager = FindFirstObjectByType<XRInteractionManager>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the mini-game.
|
||||
/// </summary>
|
||||
public virtual void SetupGame()
|
||||
{
|
||||
if (m_GameType == GameType.Score)
|
||||
{
|
||||
m_CurrentTimer = m_GameLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the mini-game.
|
||||
/// </summary>
|
||||
public virtual void StartGame()
|
||||
{
|
||||
m_GameEndingNotificationSent = false;
|
||||
m_Finished = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the mini-game.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">The time since the last frame.</param>
|
||||
public virtual void UpdateGame(float deltaTime)
|
||||
{
|
||||
m_CurrentTimer -= deltaTime;
|
||||
if (m_GameType == GameType.Score)
|
||||
{
|
||||
m_MiniGameManager.m_GameStateText.text = $"Time: {m_CurrentTimer:F0}";
|
||||
}
|
||||
CheckForGameEnd();
|
||||
}
|
||||
|
||||
protected void CheckForGameEnd()
|
||||
{
|
||||
if (m_CurrentTimer <= 3.5f & !m_GameEndingNotificationSent)
|
||||
{
|
||||
m_GameEndingNotificationSent = true;
|
||||
StartCoroutine(CheckForGameEndingRoutine());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the mini-game.
|
||||
/// </summary>
|
||||
/// <param name="submitScore">Flag indicating whether to submit the score.</param>
|
||||
public virtual void FinishGame(bool submitScore = true)
|
||||
{
|
||||
RemoveInteractables();
|
||||
m_Finished = true;
|
||||
m_CurrentTimer = m_GameLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine for displaying the game end notification.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerator.</returns>
|
||||
IEnumerator CheckForGameEndingRoutine()
|
||||
{
|
||||
int seconds = 3;
|
||||
while (seconds > 0)
|
||||
{
|
||||
if (m_MiniGameManager.LocalPlayerInGame)
|
||||
{
|
||||
PlayerHudNotification.Instance.ShowText($"Game Ending In {seconds}");
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
seconds--;
|
||||
}
|
||||
if (m_MiniGameManager.LocalPlayerInGame)
|
||||
{
|
||||
PlayerHudNotification.Instance.ShowText($"Game Complete!");
|
||||
}
|
||||
|
||||
if (m_MiniGameManager.IsServer && m_MiniGameManager.currentNetworkedGameState == MiniGameManager.GameState.InGame)
|
||||
m_MiniGameManager.StopGameServerRpc();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the interactables from the mini-game.
|
||||
/// </summary>
|
||||
public virtual void RemoveInteractables()
|
||||
{
|
||||
foreach (IXRInteractable interactable in m_GameInteractables)
|
||||
{
|
||||
m_InteractionManager.CancelInteractableSelection((IXRSelectInteractable)interactable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for mini-games.
|
||||
/// </summary>
|
||||
public interface IMiniGame
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up the mini-game.
|
||||
/// </summary>
|
||||
void SetupGame();
|
||||
|
||||
/// <summary>
|
||||
/// Starts the mini-game.
|
||||
/// </summary>
|
||||
void StartGame();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the mini-game.
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">The time since the last frame.</param>
|
||||
void UpdateGame(float deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the mini-game.
|
||||
/// </summary>
|
||||
/// <param name="submitScore">Flag indicating whether to submit the score.</param>
|
||||
void FinishGame(bool submitScore = true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cce0516f7012ad4ebd24237070e9483
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,969 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of the minigame
|
||||
/// </summary>
|
||||
public class MiniGameManager : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Format for the time text
|
||||
/// </summary>
|
||||
private const string TIME_FORMAT = "mm':'ss'.'ff";
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of the current game state
|
||||
/// </summary>
|
||||
public GameState currentNetworkedGameState
|
||||
{
|
||||
get => networkedGameState.Value;
|
||||
}
|
||||
public enum GameState { None, PreGame, InGame, PostGame }
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of the current game state synchronized across the network
|
||||
/// </summary>
|
||||
readonly NetworkVariable<GameState> networkedGameState = new(GameState.PreGame, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of players and their assigned scoreboard slots
|
||||
/// </summary>
|
||||
public Dictionary<XRINetworkPlayer, ScoreboardSlot> currentPlayerDictionary = new();
|
||||
|
||||
[Tooltip("The current minigame being used")]
|
||||
public MiniGameBase currentMiniGame;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the local player is in the game
|
||||
/// </summary>
|
||||
public bool LocalPlayerInGame => m_LocalPlayerInGame;
|
||||
bool m_LocalPlayerInGame = false;
|
||||
|
||||
[Header("UI")]
|
||||
public TMP_Text m_GameStateText;
|
||||
[SerializeField] TMP_Text m_BestAllText;
|
||||
[SerializeField] TMP_Text m_GameNameText;
|
||||
[SerializeField, Tooltip("Prefab used for scoreboard ui slots")] GameObject m_PlayerScoreboardSlotPrefab;
|
||||
[SerializeField, Tooltip("Prefab used for scoreboard ui slots")] Transform m_ContentListParent;
|
||||
[SerializeField] TextButton m_DynamicButton;
|
||||
|
||||
[Header("Video Player")]
|
||||
[SerializeField] GameObject m_VideoPlayerObject;
|
||||
[SerializeField] GameObject m_TooltipObject;
|
||||
[SerializeField] Mask m_TopMask;
|
||||
[SerializeField] Mask m_BottomMask;
|
||||
|
||||
[Header("Game")]
|
||||
public int maxAllowedPlayers = 4;
|
||||
[SerializeField] int m_ReadyUpTimeInSeconds = 15;
|
||||
[SerializeField] int m_StartCoutdownTimeInSeconds = 5;
|
||||
[SerializeField] int m_PostGameWaitTimeInSeconds = 3;
|
||||
[SerializeField] int m_PostGameCountdownTimeInSeconds = 7;
|
||||
[SerializeField] GameObject m_TeleportZonesObject;
|
||||
[SerializeField] SubTrigger[] m_StartZoneTrigger;
|
||||
|
||||
[Header("Transform References")]
|
||||
[SerializeField] Transform m_ScoreboardTransform;
|
||||
[SerializeField] Transform m_ScoreboardInGameTransform;
|
||||
[SerializeField] Transform m_JoinTeleportTransform;
|
||||
[SerializeField] Transform m_LeaveTeleportTransform;
|
||||
[SerializeField] Transform m_FinishTeleportTransform;
|
||||
|
||||
[Header("Transform Offsets")]
|
||||
[SerializeField] bool m_UseInGameOffset = true;
|
||||
[SerializeField, Tooltip("Determines the offset of the canvas during game")] Vector3 m_InGameOffset;
|
||||
[SerializeField, Tooltip("Determines the offset of the canvas during the pre-game")] Vector3 m_PreGameOffset;
|
||||
[SerializeField] float m_ScoreboardLerpSpeed = 5.0f;
|
||||
|
||||
[Header("Barrier")]
|
||||
[SerializeField] bool m_UseBarrier = true;
|
||||
[SerializeField] float m_DistanceCheckTime = .5f;
|
||||
[SerializeField] float m_BarrierRenderDistance = 30.0f;
|
||||
[SerializeField] Renderer m_BarrierRend;
|
||||
|
||||
readonly List<ScoreboardSlot> m_ScoreboardSlots = new();
|
||||
NetworkList<ulong> m_CurrentPlayers;
|
||||
NetworkList<ulong> m_QueuedUpPlayers;
|
||||
readonly NetworkVariable<float> m_BestAllScore = new(0.0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
|
||||
TeleportationProvider m_LocalPlayerTeleportProvider;
|
||||
|
||||
float m_CurrentTimer = 0.0f;
|
||||
Pose m_ScoreboardStartPose;
|
||||
IEnumerator m_StartGameRoutine;
|
||||
IEnumerator m_PostGameRoutine;
|
||||
|
||||
/// <inheritdoc/>
|
||||
void Start()
|
||||
{
|
||||
if (currentMiniGame == null)
|
||||
{
|
||||
TryGetComponent(out currentMiniGame);
|
||||
}
|
||||
|
||||
m_LocalPlayerTeleportProvider = FindFirstObjectByType<TeleportationProvider>();
|
||||
|
||||
m_TeleportZonesObject.SetActive(false);
|
||||
m_BestAllText.text = "<b>Current Record</b>: No Record Set";
|
||||
m_ScoreboardStartPose = new Pose(m_ScoreboardTransform.position, m_ScoreboardTransform.rotation);
|
||||
m_GameNameText.text = currentMiniGame.gameName;
|
||||
|
||||
foreach (var trigger in m_StartZoneTrigger)
|
||||
{
|
||||
trigger.OnTriggerAction += TriggerReadyState;
|
||||
}
|
||||
|
||||
m_QueuedUpPlayers = new NetworkList<ulong>();
|
||||
m_CurrentPlayers = new NetworkList<ulong>();
|
||||
|
||||
if (m_BarrierRend == null)
|
||||
{
|
||||
m_UseBarrier = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_UseBarrier)
|
||||
{
|
||||
StartCoroutine(CheckBarrierRendererDistance());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BarrierRend.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
SetupPlayerSlots();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Update()
|
||||
{
|
||||
if (networkedGameState.Value == GameState.InGame)
|
||||
{
|
||||
float dt = Time.deltaTime;
|
||||
m_CurrentTimer += dt;
|
||||
currentMiniGame.UpdateGame(dt);
|
||||
}
|
||||
if ((networkedGameState.Value == GameState.PreGame || (networkedGameState.Value == GameState.InGame && m_UseInGameOffset)) && LocalPlayerInGame)
|
||||
{
|
||||
UpdateScoreboardPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
foreach (var trigger in m_StartZoneTrigger)
|
||||
{
|
||||
trigger.OnTriggerAction -= TriggerReadyState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
base.OnNetworkSpawn();
|
||||
networkedGameState.OnValueChanged += GameStateValueChanged;
|
||||
m_BestAllScore.OnValueChanged += BestAllScoreChanged;
|
||||
m_CurrentPlayers.OnListChanged += UpdatePlayerList;
|
||||
UpdateBestScore(m_BestAllScore.Value, m_BestAllText);
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
networkedGameState.Value = GameState.PreGame;
|
||||
m_BestAllScore.Value = 0;
|
||||
}
|
||||
UpdateGameState();
|
||||
|
||||
if (networkedGameState.Value == GameState.InGame)
|
||||
{
|
||||
ResetContestants(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
base.OnNetworkDespawn();
|
||||
m_LocalPlayerInGame = false;
|
||||
currentPlayerDictionary.Clear();
|
||||
m_ScoreboardTransform.SetPositionAndRotation(m_ScoreboardStartPose.position, m_ScoreboardStartPose.rotation);
|
||||
}
|
||||
|
||||
private void UpdatePlayerList(NetworkListEvent<ulong> changeEvent)
|
||||
{
|
||||
if (networkedGameState.Value != GameState.InGame) return;
|
||||
|
||||
// Wipe scoreboard.
|
||||
foreach (ScoreboardSlot s in m_ScoreboardSlots)
|
||||
{
|
||||
s.SetSlotOpen();
|
||||
}
|
||||
|
||||
currentPlayerDictionary.Clear();
|
||||
|
||||
foreach (var playerId in m_CurrentPlayers)
|
||||
{
|
||||
AddPlayerToList(playerId);
|
||||
}
|
||||
|
||||
for (int i = currentPlayerDictionary.Count; i < m_ScoreboardSlots.Count; i++)
|
||||
{
|
||||
m_ScoreboardSlots[i].gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
for (int i = 0; i < currentPlayerDictionary.Count; i++)
|
||||
{
|
||||
m_ScoreboardSlots[i].gameObject.SetActive(true);
|
||||
m_ScoreboardSlots[i].UpdateScore(0, currentMiniGame.currentGameType);
|
||||
}
|
||||
}
|
||||
|
||||
void BestAllScoreChanged(float old, float current)
|
||||
{
|
||||
if (m_BestAllScore.Value <= 0.0f)
|
||||
{
|
||||
m_BestAllText.text = $"<b>Current Record</b>: No Record Set";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentMiniGame.currentGameType == MiniGameBase.GameType.Time)
|
||||
{
|
||||
TimeSpan time = TimeSpan.FromSeconds(current);
|
||||
m_BestAllText.text = $"<b>Current Record</b>: {time.ToString(TIME_FORMAT)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BestAllText.text = $"<b>Current Record</b>: {current:N0}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameStateValueChanged(GameState oldState, GameState currentState)
|
||||
{
|
||||
UpdateGameState();
|
||||
}
|
||||
|
||||
void UpdateGameState()
|
||||
{
|
||||
switch (networkedGameState.Value)
|
||||
{
|
||||
case GameState.PreGame:
|
||||
SetPreGameState();
|
||||
break;
|
||||
case GameState.InGame:
|
||||
SetInGameState();
|
||||
break;
|
||||
case GameState.PostGame:
|
||||
SetPostGameState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPreGameState()
|
||||
{
|
||||
m_LocalPlayerInGame = false;
|
||||
if (m_PostGameRoutine != null)
|
||||
{
|
||||
StopCoroutine(m_PostGameRoutine);
|
||||
}
|
||||
|
||||
currentMiniGame.SetupGame();
|
||||
m_ScoreboardTransform.SetPositionAndRotation(m_ScoreboardStartPose.position, m_ScoreboardStartPose.rotation);
|
||||
|
||||
for (int i = 0; i < m_ScoreboardSlots.Count; i++)
|
||||
{
|
||||
m_ScoreboardSlots[i].gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
ResetContestants(false);
|
||||
|
||||
m_GameStateText.text = "Pre Game";
|
||||
|
||||
m_DynamicButton.UpdateButton(AddLocalPlayer, "Join");
|
||||
StartCoroutine(ResetReadyZones());
|
||||
}
|
||||
|
||||
void SetInGameState()
|
||||
{
|
||||
m_CurrentTimer = 0.0f;
|
||||
ResetContestants(true);
|
||||
|
||||
foreach (var slot in currentPlayerDictionary.Values)
|
||||
{
|
||||
slot.UpdateScore(0.0f, currentMiniGame.currentGameType);
|
||||
}
|
||||
|
||||
for (int i = currentPlayerDictionary.Count; i < m_ScoreboardSlots.Count; i++)
|
||||
{
|
||||
m_ScoreboardSlots[i].gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
foreach (var trigger in m_StartZoneTrigger)
|
||||
{
|
||||
trigger.subTriggerCollider.enabled = false;
|
||||
}
|
||||
|
||||
m_GameStateText.text = "In Progess";
|
||||
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
m_DynamicButton.button.interactable = true;
|
||||
PlayerHudNotification.Instance.ShowText($"Game Started!");
|
||||
ToggleShrink(true);
|
||||
if (!m_UseInGameOffset)
|
||||
{
|
||||
m_ScoreboardTransform.SetPositionAndRotation(m_ScoreboardInGameTransform.position, m_ScoreboardInGameTransform.rotation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
}
|
||||
|
||||
currentMiniGame.StartGame();
|
||||
}
|
||||
|
||||
void SetPostGameState()
|
||||
{
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
ToggleShrink(false);
|
||||
TeleportToArea(m_LeaveTeleportTransform);
|
||||
m_BarrierRend.gameObject.SetActive(true);
|
||||
m_ScoreboardTransform.SetPositionAndRotation(m_ScoreboardStartPose.position, m_ScoreboardStartPose.rotation);
|
||||
}
|
||||
|
||||
m_LocalPlayerInGame = false;
|
||||
m_TeleportZonesObject.SetActive(false);
|
||||
SortPlayers();
|
||||
m_GameStateText.text = "Post Game";
|
||||
m_DynamicButton.UpdateButton(ResetGame, $"Wait", true, false);
|
||||
if (!currentMiniGame.finished)
|
||||
{
|
||||
currentMiniGame.FinishGame(false);
|
||||
}
|
||||
|
||||
m_PostGameRoutine = PostGameRoutine();
|
||||
StartCoroutine(m_PostGameRoutine);
|
||||
if (currentPlayerDictionary.Count <= 0)
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
networkedGameState.Value = GameState.PreGame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator PostGameRoutine()
|
||||
{
|
||||
yield return new WaitForSeconds(m_PostGameWaitTimeInSeconds);
|
||||
m_GameStateText.text = "Next Game in";
|
||||
for (int i = m_PostGameCountdownTimeInSeconds; i > 0; i--)
|
||||
{
|
||||
m_DynamicButton.UpdateButton(ResetGame, $"{i}", true, false);
|
||||
yield return new WaitForSeconds(1);
|
||||
}
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
networkedGameState.Value = GameState.PreGame;
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerReadyState(Collider other, bool entered)
|
||||
{
|
||||
if (other.TryGetComponent(out CharacterController controller))
|
||||
{
|
||||
TogglePlayerReadyServerRpc(XRINetworkPlayer.LocalPlayer.OwnerClientId, entered);
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
void TogglePlayerReadyServerRpc(ulong clientId, bool isReady)
|
||||
{
|
||||
TogglePlayerReadyClientRpc(clientId, isReady);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void TogglePlayerReadyClientRpc(ulong clientId, bool isReady)
|
||||
{
|
||||
if (XRINetworkGameManager.Instance.GetPlayerByID(clientId, out var player))
|
||||
{
|
||||
if (currentPlayerDictionary.ContainsKey(player))
|
||||
{
|
||||
currentPlayerDictionary[player].ToggleReady(isReady);
|
||||
}
|
||||
}
|
||||
|
||||
if (networkedGameState.Value != GameState.InGame)
|
||||
{
|
||||
CheckPlayersReady();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckPlayersReady()
|
||||
{
|
||||
int readyCount = 0;
|
||||
if (m_QueuedUpPlayers.Count <= 0) return;
|
||||
|
||||
foreach (var clientId in m_QueuedUpPlayers)
|
||||
{
|
||||
if (XRINetworkGameManager.Instance.GetPlayerByID(clientId, out var player))
|
||||
{
|
||||
if (currentPlayerDictionary.ContainsKey(player))
|
||||
{
|
||||
if (currentPlayerDictionary[player].isReady)
|
||||
{
|
||||
readyCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readyCount > 0 && readyCount < m_QueuedUpPlayers.Count)
|
||||
{
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
}
|
||||
if (m_StartGameRoutine != null) StopCoroutine(m_StartGameRoutine);
|
||||
m_StartGameRoutine = StartGameAfterTime(m_ReadyUpTimeInSeconds);
|
||||
StartCoroutine(m_StartGameRoutine);
|
||||
}
|
||||
else if (readyCount <= 0)
|
||||
{
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
m_DynamicButton.button.interactable = true;
|
||||
}
|
||||
if (m_StartGameRoutine != null) StopCoroutine(m_StartGameRoutine);
|
||||
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
PlayerHudNotification.Instance.ShowText("Game Start Cancelled");
|
||||
}
|
||||
m_GameStateText.text = "Pre Game";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
}
|
||||
if (m_StartGameRoutine != null) StopCoroutine(m_StartGameRoutine);
|
||||
m_StartGameRoutine = StartGameAfterTime(m_StartCoutdownTimeInSeconds);
|
||||
StartCoroutine(m_StartGameRoutine);
|
||||
}
|
||||
}
|
||||
IEnumerator StartGameAfterTime(int countdownTime)
|
||||
{
|
||||
for (int i = countdownTime; i > 0; i--)
|
||||
{
|
||||
m_GameStateText.text = $"Game Starting In {i}";
|
||||
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
PlayerHudNotification.Instance.ShowText(m_GameStateText.text);
|
||||
}
|
||||
yield return new WaitForSeconds(1);
|
||||
}
|
||||
|
||||
m_GameStateText.text = $"Game Starting Now!";
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
StartGameServerRpc();
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
void StartGameServerRpc()
|
||||
{
|
||||
for (int i = 0; i < m_QueuedUpPlayers.Count; i++)
|
||||
{
|
||||
m_CurrentPlayers.Add(m_QueuedUpPlayers[i]);
|
||||
}
|
||||
m_QueuedUpPlayers.Clear();
|
||||
networkedGameState.Value = GameState.InGame;
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
public void StopGameServerRpc()
|
||||
{
|
||||
networkedGameState.Value = GameState.PostGame;
|
||||
m_CurrentPlayers.Clear();
|
||||
if (currentPlayerDictionary.Count > 0)
|
||||
{
|
||||
float score = currentPlayerDictionary.First().Value.currentScore;
|
||||
if (currentMiniGame.currentGameType == MiniGameBase.GameType.Time)
|
||||
{
|
||||
if (score < m_BestAllScore.Value || m_BestAllScore.Value <= 0.0f)
|
||||
{
|
||||
m_BestAllScore.Value = score;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (score > m_BestAllScore.Value || m_BestAllScore.Value <= 0.0f)
|
||||
{
|
||||
m_BestAllScore.Value = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits a player score. If <see cref="finishGameOnScoreSubmit"/> is true, it will finish the game for that player.
|
||||
/// This function will also check if all players have finished the game, and if so, will stop the game.
|
||||
/// </summary>
|
||||
/// <param name="score">The Score to set for the player.</param>
|
||||
/// <param name="clientId">Client ID of the player to set the score for.</param>
|
||||
/// <param name="finishGameOnScoreSubmit">Whether or not to finish the game on score submit.</param>
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
public void SubmitScoreServerRpc(float score, ulong clientId, bool finishGameOnScoreSubmit = false)
|
||||
{
|
||||
SubmitScoreClientRpc(score, clientId, finishGameOnScoreSubmit);
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void SubmitScoreClientRpc(float score, ulong clientId, bool finishGameOnScoreSubmit = false)
|
||||
{
|
||||
if (XRINetworkGameManager.Instance.GetPlayerByID(clientId, out XRINetworkPlayer player))
|
||||
{
|
||||
if (currentPlayerDictionary.ContainsKey(player))
|
||||
{
|
||||
currentPlayerDictionary[player].UpdateScore(score, currentMiniGame.currentGameType);
|
||||
if (finishGameOnScoreSubmit)
|
||||
{
|
||||
currentPlayerDictionary[player].isFinished = true;
|
||||
if (player.IsLocalPlayer)
|
||||
{
|
||||
FinishGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SortPlayers();
|
||||
CheckIfAllPlayersAreFinished();
|
||||
}
|
||||
|
||||
void CheckIfAllPlayersAreFinished()
|
||||
{
|
||||
bool gameOver = true;
|
||||
foreach (KeyValuePair<XRINetworkPlayer, ScoreboardSlot> kvp in currentPlayerDictionary)
|
||||
{
|
||||
if (!kvp.Value.isFinished)
|
||||
{
|
||||
gameOver = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (gameOver && IsServer)
|
||||
{
|
||||
StopGameServerRpc();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called localled on each client when the game is finished.
|
||||
/// </summary>
|
||||
public void FinishGame()
|
||||
{
|
||||
if (LocalPlayerInGame)
|
||||
{
|
||||
ToggleShrink(false);
|
||||
}
|
||||
StartCoroutine(TeleportAfterFinish());
|
||||
}
|
||||
|
||||
IEnumerator TeleportAfterFinish()
|
||||
{
|
||||
yield return new WaitForSeconds(1.5f);
|
||||
if (networkedGameState.Value == GameState.InGame)
|
||||
{
|
||||
TeleportToArea(m_FinishTeleportTransform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from UI Buttons
|
||||
/// </summary>
|
||||
public void AddLocalPlayer()
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
AddPlayerServerRpc(XRINetworkPlayer.LocalPlayer.OwnerClientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from UI buttons
|
||||
/// </summary>
|
||||
public void RemoveLocalPlayer()
|
||||
{
|
||||
m_DynamicButton.UpdateButton(AddLocalPlayer, "Join", false, false);
|
||||
RemovePlayerServerRpc(XRINetworkPlayer.LocalPlayer.OwnerClientId);
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
void AddPlayerServerRpc(ulong clientId)
|
||||
{
|
||||
AddPlayerClientRpc(clientId);
|
||||
if (m_QueuedUpPlayers.Count < maxAllowedPlayers)
|
||||
{
|
||||
m_QueuedUpPlayers.Add(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
void RemovePlayerServerRpc(ulong clientId)
|
||||
{
|
||||
RemovePlayerClientRpc(clientId);
|
||||
|
||||
if (m_QueuedUpPlayers.Contains(clientId))
|
||||
{
|
||||
m_QueuedUpPlayers.Remove(clientId);
|
||||
}
|
||||
|
||||
if (m_CurrentPlayers.Contains(clientId))
|
||||
{
|
||||
m_CurrentPlayers.Remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void AddPlayerClientRpc(ulong clientId)
|
||||
{
|
||||
if (currentPlayerDictionary.Count < maxAllowedPlayers)
|
||||
{
|
||||
if (networkedGameState.Value != GameState.PostGame)
|
||||
{
|
||||
AddPlayerToList(clientId);
|
||||
}
|
||||
|
||||
if (clientId == XRINetworkPlayer.LocalPlayer.OwnerClientId)
|
||||
{
|
||||
m_LocalPlayerInGame = true;
|
||||
m_TeleportZonesObject.SetActive(true);
|
||||
m_DynamicButton.UpdateButton(RemoveLocalPlayer, "Leave");
|
||||
|
||||
TeleportRequest teleportRequest = new()
|
||||
{
|
||||
destinationPosition = m_JoinTeleportTransform.position,
|
||||
destinationRotation = m_JoinTeleportTransform.rotation,
|
||||
matchOrientation = MatchOrientation.TargetUpAndForward
|
||||
};
|
||||
|
||||
m_LocalPlayerTeleportProvider.QueueTeleportRequest(teleportRequest);
|
||||
Transform destination = GetClosestReadyPosition(m_JoinTeleportTransform.position);
|
||||
m_ScoreboardTransform.rotation = destination.rotation;
|
||||
m_ScoreboardTransform.position = destination.position + (m_ScoreboardTransform.forward + m_PreGameOffset);
|
||||
PlayerHudNotification.Instance.ShowText($"Joined {currentMiniGame.gameName}");
|
||||
m_BarrierRend.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (currentPlayerDictionary.Count >= maxAllowedPlayers & !LocalPlayerInGame && networkedGameState.Value != GameState.PostGame)
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddPlayerToList(ulong clientId)
|
||||
{
|
||||
if (XRINetworkGameManager.Instance.GetPlayerByID(clientId, out XRINetworkPlayer player))
|
||||
{
|
||||
if (!currentPlayerDictionary.ContainsKey(player))
|
||||
{
|
||||
ScoreboardSlot slot = m_ScoreboardSlots[currentPlayerDictionary.Count];
|
||||
currentPlayerDictionary.Add(player, slot);
|
||||
slot.SetupPlayerSlot(currentPlayerDictionary.Count, player.playerName);
|
||||
player.onDisconnected += PlayerDisconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
void RemovePlayerClientRpc(ulong clientId)
|
||||
{
|
||||
if (XRINetworkGameManager.Instance.GetPlayerByID(clientId, out XRINetworkPlayer player))
|
||||
{
|
||||
CheckDroppedPlayer(player);
|
||||
}
|
||||
|
||||
if (clientId == XRINetworkPlayer.LocalPlayer.OwnerClientId)
|
||||
{
|
||||
m_LocalPlayerInGame = false;
|
||||
m_TeleportZonesObject.SetActive(false);
|
||||
|
||||
//If local player left, and we are still in game, don't let them rejoin mid game.
|
||||
if (networkedGameState.Value != GameState.InGame)
|
||||
{
|
||||
m_DynamicButton.button.interactable = true;
|
||||
}
|
||||
|
||||
ToggleShrink(false);
|
||||
currentMiniGame.RemoveInteractables();
|
||||
PlayerHudNotification.Instance.ShowText($"Left {currentMiniGame.gameName}");
|
||||
TeleportToArea(m_LeaveTeleportTransform);
|
||||
m_ScoreboardTransform.SetPositionAndRotation(m_ScoreboardStartPose.position, m_ScoreboardStartPose.rotation);
|
||||
m_BarrierRend.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerDisconnected(XRINetworkPlayer droppedPlayer)
|
||||
{
|
||||
CheckDroppedPlayer(droppedPlayer);
|
||||
}
|
||||
|
||||
void CheckDroppedPlayer(XRINetworkPlayer droppedPlayer)
|
||||
{
|
||||
ScoreboardSlot removedSlot = null;
|
||||
if (currentPlayerDictionary.ContainsKey(droppedPlayer) && networkedGameState.Value != GameState.PostGame)
|
||||
{
|
||||
removedSlot = currentPlayerDictionary[droppedPlayer];
|
||||
removedSlot.SetSlotOpen();
|
||||
currentPlayerDictionary.Remove(droppedPlayer);
|
||||
droppedPlayer.onDisconnected -= PlayerDisconnected;
|
||||
|
||||
SortPlayers();
|
||||
}
|
||||
|
||||
if (IsOwner && m_QueuedUpPlayers.Contains(droppedPlayer.OwnerClientId))
|
||||
{
|
||||
m_QueuedUpPlayers.Remove(droppedPlayer.OwnerClientId);
|
||||
}
|
||||
|
||||
if (networkedGameState.Value == GameState.InGame)
|
||||
{
|
||||
if (removedSlot != null)
|
||||
{
|
||||
removedSlot.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (currentPlayerDictionary.Count <= 0)
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
if (IsServer)
|
||||
{
|
||||
StopGameServerRpc();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckIfAllPlayersAreFinished();
|
||||
}
|
||||
}
|
||||
else if (networkedGameState.Value == GameState.PreGame)
|
||||
{
|
||||
if (currentPlayerDictionary.Count > 0)
|
||||
{
|
||||
if (currentPlayerDictionary.Count >= maxAllowedPlayers)
|
||||
{
|
||||
m_DynamicButton.button.interactable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DynamicButton.button.interactable = true;
|
||||
}
|
||||
}
|
||||
CheckPlayersReady();
|
||||
}
|
||||
}
|
||||
void SortPlayers()
|
||||
{
|
||||
if (currentMiniGame.currentGameType == MiniGameBase.GameType.Time)
|
||||
{
|
||||
currentPlayerDictionary = currentPlayerDictionary.OrderBy(x => x.Value.currentScore).ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentPlayerDictionary = currentPlayerDictionary.OrderByDescending(x => x.Value.currentScore).ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
OrganizePlayerList();
|
||||
}
|
||||
|
||||
void OrganizePlayerList()
|
||||
{
|
||||
int currentPlace = 1;
|
||||
foreach (var slot in currentPlayerDictionary.Values)
|
||||
{
|
||||
slot.transform.SetSiblingIndex(currentPlace - 1);
|
||||
slot.UpdatePlace(currentPlace++);
|
||||
}
|
||||
|
||||
m_ScoreboardSlots.Sort((a, b) => a.transform.GetSiblingIndex().CompareTo(b.transform.GetSiblingIndex()));
|
||||
}
|
||||
|
||||
void ToggleShrink(bool toggle)
|
||||
{
|
||||
m_BottomMask.enabled = toggle;
|
||||
m_BottomMask.graphic.enabled = toggle;
|
||||
|
||||
m_TopMask.enabled = !toggle;
|
||||
m_TopMask.graphic.enabled = !toggle;
|
||||
m_GameNameText.enabled = !toggle;
|
||||
m_VideoPlayerObject.SetActive(!toggle);
|
||||
m_TooltipObject.SetActive(!toggle);
|
||||
}
|
||||
|
||||
IEnumerator CheckBarrierRendererDistance()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(m_DistanceCheckTime);
|
||||
if (m_UseBarrier)
|
||||
{
|
||||
m_BarrierRend.enabled = Vector3.Distance(m_BarrierRend.transform.position, Camera.main.transform.position) < m_BarrierRenderDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateScoreboardPosition()
|
||||
{
|
||||
Vector3 offset = networkedGameState.Value == GameState.InGame ? m_InGameOffset : m_PreGameOffset;
|
||||
Transform destination = GetClosestReadyPosition(XRINetworkPlayer.LocalPlayer.transform.position);
|
||||
m_ScoreboardTransform.rotation = destination.rotation;
|
||||
Vector3 destinationPosition = destination.position + (m_ScoreboardTransform.right * offset.x) + (m_ScoreboardTransform.up * offset.y) + (m_ScoreboardTransform.forward * offset.z);
|
||||
m_ScoreboardTransform.position = Vector3.Lerp(m_ScoreboardTransform.position, destinationPosition, Time.deltaTime * m_ScoreboardLerpSpeed);
|
||||
}
|
||||
|
||||
Transform GetClosestReadyPosition(Vector3 position)
|
||||
{
|
||||
Transform closestTransform = null;
|
||||
|
||||
foreach (var readyZone in m_StartZoneTrigger)
|
||||
{
|
||||
if (closestTransform == null || Vector3.Distance(readyZone.transform.position, position) < Vector3.Distance(closestTransform.position, position))
|
||||
{
|
||||
closestTransform = readyZone.transform;
|
||||
}
|
||||
}
|
||||
|
||||
return closestTransform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the player scores based on <see cref="m_CurrentTimer"/>.
|
||||
/// </summary>
|
||||
public void UpdatePlayerScores()
|
||||
{
|
||||
foreach (var p in currentPlayerDictionary)
|
||||
{
|
||||
if (!p.Value.isFinished)
|
||||
{
|
||||
p.Value.UpdateScore(m_CurrentTimer, currentMiniGame.currentGameType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Game State
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function is called locally at times, which creates a divergence between local game state and network game state
|
||||
/// </remarks>
|
||||
void ResetGame()
|
||||
{
|
||||
networkedGameState.Value = GameState.PreGame;
|
||||
SetPreGameState();
|
||||
}
|
||||
|
||||
IEnumerator ResetReadyZones()
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
foreach (var trigger in m_StartZoneTrigger)
|
||||
{
|
||||
trigger.subTriggerCollider.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetContestants(bool showGamePlayers)
|
||||
{
|
||||
// Wipe scoreboard.
|
||||
foreach (ScoreboardSlot s in m_ScoreboardSlots)
|
||||
{
|
||||
s.SetSlotOpen();
|
||||
}
|
||||
|
||||
currentPlayerDictionary.Clear();
|
||||
|
||||
if (showGamePlayers)
|
||||
{
|
||||
// Add all contestants in current match.
|
||||
foreach (var playerId in m_CurrentPlayers)
|
||||
{
|
||||
AddPlayerToList(playerId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add all contestants in queue.
|
||||
foreach (var playerId in m_QueuedUpPlayers)
|
||||
{
|
||||
AddPlayerToList(playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeleportToArea(Transform teleportTransform)
|
||||
{
|
||||
TeleportRequest teleportRequest = new TeleportRequest
|
||||
{
|
||||
destinationPosition = teleportTransform.position,
|
||||
destinationRotation = teleportTransform.rotation,
|
||||
matchOrientation = MatchOrientation.TargetUpAndForward
|
||||
};
|
||||
|
||||
m_LocalPlayerTeleportProvider.QueueTeleportRequest(teleportRequest);
|
||||
}
|
||||
|
||||
void UpdateBestScore(float score, TMP_Text textAsset)
|
||||
{
|
||||
if (m_BestAllScore.Value <= 0.0f)
|
||||
{
|
||||
textAsset.text = $"<b>Current Record</b>: No Record Set";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentMiniGame.currentGameType == MiniGameBase.GameType.Time)
|
||||
{
|
||||
if (score <= m_BestAllScore.Value && m_BestAllScore.Value > 0.0f)
|
||||
{
|
||||
TimeSpan time = TimeSpan.FromSeconds(score);
|
||||
textAsset.text = $"<b>Current Record</b>: {time.ToString(TIME_FORMAT)}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (score >= m_BestAllScore.Value && m_BestAllScore.Value > 0.0f)
|
||||
{
|
||||
textAsset.text = $"<b>Current Record</b>: {score:N0}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetupPlayerSlots()
|
||||
{
|
||||
for (int i = 0; i < maxAllowedPlayers; i++)
|
||||
{
|
||||
Instantiate(m_PlayerScoreboardSlotPrefab, m_ContentListParent).TryGetComponent(out ScoreboardSlot slot);
|
||||
m_ScoreboardSlots.Add(slot);
|
||||
slot.SetSlotOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea9983fea18a9d04696e97a1c7de0f9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a slot in the scoreboard for a player.
|
||||
/// </summary>
|
||||
public class ScoreboardSlot : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the current score of the player.
|
||||
/// </summary>
|
||||
public float currentScore
|
||||
{
|
||||
get => m_Score;
|
||||
set => m_Score = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Internal value that gets or sets the current score of the player.
|
||||
/// </summary>
|
||||
float m_Score;
|
||||
|
||||
/// <summary>
|
||||
/// External value that gets or sets a value indicating whether the player has finished the game.
|
||||
/// </summary>
|
||||
public bool isFinished
|
||||
{
|
||||
get => m_IsFinished;
|
||||
set => m_IsFinished = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal value that gets or sets a value indicating whether the player has finished the game.
|
||||
/// </summary>
|
||||
bool m_IsFinished;
|
||||
|
||||
/// <summary>
|
||||
/// External value that gets or sets a value indicating whether the player is ready.
|
||||
/// </summary>
|
||||
public bool isReady
|
||||
{
|
||||
get => m_IsReady;
|
||||
set => m_IsReady = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal value that gets or sets a value indicating whether the player is ready.
|
||||
/// </summary>
|
||||
bool m_IsReady;
|
||||
|
||||
/// <summary>
|
||||
/// The text for the place of the player.
|
||||
/// </summary>
|
||||
[SerializeField] TMP_Text m_PlaceText;
|
||||
|
||||
/// <summary>
|
||||
/// The text for the name of the player.
|
||||
/// </summary>
|
||||
[SerializeField] TMP_Text m_PlayerNameText;
|
||||
|
||||
/// <summary>
|
||||
/// The text for the score of the player.
|
||||
/// </summary>
|
||||
[SerializeField] TMP_Text m_PlayerScoreText;
|
||||
|
||||
/// <summary>
|
||||
/// The icon to indicate the player is ready.
|
||||
/// </summary>
|
||||
[SerializeField] Image m_ReadyIcon;
|
||||
|
||||
/// <summary>
|
||||
/// The icon to indicate the player is ready.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_PlayerIcon;
|
||||
|
||||
/// <summary>
|
||||
/// The object to display when the slot is open.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_OpenObject;
|
||||
|
||||
/// <summary>
|
||||
/// The object to display when the slot is closed.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_ClosedObject;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the player slot with the specified place and player name.
|
||||
/// </summary>
|
||||
/// <param name="place">The place of the player.</param>
|
||||
/// <param name="playerName">The name of the player.</param>
|
||||
public void SetupPlayerSlot(int place, string playerName)
|
||||
{
|
||||
m_OpenObject.SetActive(false);
|
||||
m_ClosedObject.SetActive(true);
|
||||
m_PlayerIcon.SetActive(true);
|
||||
m_PlaceText.enabled = false;
|
||||
m_PlayerNameText.text = playerName;
|
||||
UpdatePlace(place);
|
||||
ToggleReady(false);
|
||||
m_PlayerScoreText.text = "";
|
||||
m_IsFinished = false;
|
||||
m_IsReady = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the slot as open.
|
||||
/// </summary>
|
||||
public void SetSlotOpen()
|
||||
{
|
||||
m_OpenObject.SetActive(true);
|
||||
m_ClosedObject.SetActive(false);
|
||||
m_IsFinished = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the score of the player.
|
||||
/// </summary>
|
||||
/// <param name="score">The new score of the player.</param>
|
||||
/// <param name="gameType">The type of the game.</param>
|
||||
public void UpdateScore(float score, MiniGameBase.GameType gameType)
|
||||
{
|
||||
m_PlayerIcon.SetActive(false);
|
||||
m_PlaceText.enabled = true;
|
||||
m_PlayerScoreText.enabled = true;
|
||||
m_ReadyIcon.enabled = false;
|
||||
currentScore = score;
|
||||
|
||||
if (gameType == MiniGameBase.GameType.Time)
|
||||
{
|
||||
TimeSpan time = TimeSpan.FromSeconds(score);
|
||||
m_PlayerScoreText.text = time.ToString("mm':'ss'.'ff");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_PlayerScoreText.text = score.ToString("N0");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the place of the player.
|
||||
/// </summary>
|
||||
/// <param name="place">The new place of the player.</param>
|
||||
public void UpdatePlace(int place)
|
||||
{
|
||||
m_PlaceText.text = $"{place}<voffset=.5em><size=3>{Utils.GetOrdinal(place)}</voffset></size>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the ready state of the player.
|
||||
/// </summary>
|
||||
/// <param name="toggle">The new ready state.</param>
|
||||
public void ToggleReady(bool toggle)
|
||||
{
|
||||
m_IsReady = toggle;
|
||||
m_PlayerScoreText.enabled = false;
|
||||
m_ReadyIcon.enabled = true;
|
||||
m_ReadyIcon.color = toggle ? Color.green : Color.red;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa6be18f862e80d428c7aa2b5194b4f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 831d53858019c7944a2f40ea59633e41
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a slingshot mini-game.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(TargetManager))]
|
||||
public class MiniGame_Slingshot : MiniGameBase
|
||||
{
|
||||
[SerializeField] SlingshotVisualUpdater[] m_Slingshots;
|
||||
|
||||
[SerializeField] float m_PreGameHeight = 0.65f;
|
||||
[SerializeField] float m_StartGameHeight = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The target manager.
|
||||
/// </summary>
|
||||
TargetManager m_TargetManager;
|
||||
|
||||
/// <summary>
|
||||
/// The current player score.
|
||||
/// </summary>
|
||||
int m_CurrentPlayerScore = 0;
|
||||
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
TryGetComponent(out m_TargetManager);
|
||||
SetSlingshotHeights(m_PreGameHeight);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void SetupGame()
|
||||
{
|
||||
base.SetupGame();
|
||||
SetSlingshotHeights(m_PreGameHeight);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void StartGame()
|
||||
{
|
||||
base.StartGame();
|
||||
m_CurrentPlayerScore = 0;
|
||||
m_TargetManager.ActivateTargets();
|
||||
SetSlingshotHeights(m_StartGameHeight);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void FinishGame(bool submitScore = true)
|
||||
{
|
||||
base.FinishGame(submitScore);
|
||||
m_TargetManager.DeactivateTargets();
|
||||
m_MiniGameManager.FinishGame();
|
||||
|
||||
SetSlingshotHeights(m_PreGameHeight);
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void UpdateGame(float deltaTime)
|
||||
{
|
||||
base.UpdateGame(deltaTime);
|
||||
}
|
||||
|
||||
void SetSlingshotHeights(float height)
|
||||
{
|
||||
foreach (var slingshot in m_Slingshots)
|
||||
{
|
||||
slingshot.ResetSlingshot(height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local player hits a target.
|
||||
/// </summary>
|
||||
/// <param name="targetValue"></param>
|
||||
public void LocalPlayerHitTarget(int targetValue)
|
||||
{
|
||||
if (m_MiniGameManager.currentNetworkedGameState == MiniGameManager.GameState.InGame)
|
||||
{
|
||||
m_CurrentPlayerScore += targetValue;
|
||||
m_MiniGameManager.SubmitScoreServerRpc(m_CurrentPlayerScore, XRINetworkPlayer.LocalPlayer.OwnerClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51a3259d5ba52f449b702e0717f0c721
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// A networked slingshot that shoots projectiles.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(SlingshotVisualUpdater))]
|
||||
public class Slingshot : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The projectile prefab to shoot from the slingshot.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_ProjectilePrefab;
|
||||
|
||||
/// <summary>
|
||||
/// The projectile proxy object to show when the bucket is held.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_ProjectileProxyObject;
|
||||
|
||||
/// <summary>
|
||||
/// The bucket interactable to use for setting the power of the slingshot.
|
||||
/// </summary>
|
||||
[SerializeField] NetworkPhysicsInteractable m_BucketInteractable;
|
||||
|
||||
/// <summary>
|
||||
/// The time to wait before resetting the proxy object.
|
||||
/// </summary>
|
||||
[SerializeField] float m_ResetTime = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The colliders to ignore when shooting the projectile.
|
||||
/// </summary>
|
||||
[SerializeField] Collider[] m_CollidersToIgnore;
|
||||
|
||||
/// <summary>
|
||||
/// The slingshot visual updater to use for updating the slingshot visuals.
|
||||
/// </summary>
|
||||
SlingshotVisualUpdater m_SlingshotVisualUpdater;
|
||||
|
||||
/// <summary>
|
||||
/// The slingshot mini game to use for handling the mini game logic.
|
||||
/// </summary>
|
||||
MiniGame_Slingshot m_MiniGame;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the script instance is being loaded.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
TryGetComponent(out m_SlingshotVisualUpdater);
|
||||
m_MiniGame = GetComponentInParent<MiniGame_Slingshot>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the bucket's held state changes.
|
||||
/// </summary>
|
||||
/// <param name="old">The previous held state.</param>
|
||||
/// <param name="current">The current held state.</param>
|
||||
public void OnBucketHeldChanged(bool current)
|
||||
{
|
||||
if (!current)
|
||||
{
|
||||
ShootProjectile();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_BucketInteractable.IsOwner)
|
||||
{
|
||||
m_SlingshotVisualUpdater.trajectoryLineRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shoots a projectile from the slingshot.
|
||||
/// </summary>
|
||||
private void ShootProjectile()
|
||||
{
|
||||
m_ProjectileProxyObject.SetActive(false);
|
||||
SlingshotProjectile projectile = Instantiate(m_ProjectilePrefab, m_BucketInteractable.transform.position, Quaternion.identity).GetComponent<SlingshotProjectile>();
|
||||
XRINetworkGameManager.Instance.GetPlayerByID(m_BucketInteractable.OwnerClientId, out XRINetworkPlayer player);
|
||||
projectile.LaunchProjectile(m_SlingshotVisualUpdater.GetShotForce(), player.IsLocalPlayer, player.playerColor);
|
||||
|
||||
foreach (var collider in m_CollidersToIgnore)
|
||||
{
|
||||
Physics.IgnoreCollision(projectile.GetComponent<Collider>(), collider);
|
||||
}
|
||||
|
||||
if (m_BucketInteractable.IsOwner)
|
||||
{
|
||||
projectile.localPlayerHitTarget += OnLocalPlayerHitTarget;
|
||||
m_SlingshotVisualUpdater.ResetBucketPosition();
|
||||
ResetProxyObjectServerRpc();
|
||||
m_SlingshotVisualUpdater.trajectoryLineRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the proxy object on the server.
|
||||
/// </summary>
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
private void ResetProxyObjectServerRpc()
|
||||
{
|
||||
ResetProxyObjectClientRpc();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the proxy object on the clients.
|
||||
/// </summary>
|
||||
[ClientRpc]
|
||||
private void ResetProxyObjectClientRpc()
|
||||
{
|
||||
StartCoroutine(ResetProxyAfterTime());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the proxy object after a specified time.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator to control the coroutine.</returns>
|
||||
private IEnumerator ResetProxyAfterTime()
|
||||
{
|
||||
yield return new WaitForSeconds(m_ResetTime);
|
||||
m_ProjectileProxyObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local player hits a target with the projectile.
|
||||
/// </summary>
|
||||
/// <param name="projectile">The projectile that hit the target.</param>
|
||||
/// <param name="targetValue">The value of the target hit.</param>
|
||||
private void OnLocalPlayerHitTarget(SlingshotProjectile projectile, int targetValue)
|
||||
{
|
||||
projectile.localPlayerHitTarget -= OnLocalPlayerHitTarget;
|
||||
m_MiniGame.LocalPlayerHitTarget(targetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3ea272e22c548240beeed591726da11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a projectile for the slingshot mini-game.
|
||||
/// </summary>
|
||||
public class SlingshotProjectile : Projectile
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is triggered when the local player hits a target with the projectile.
|
||||
/// </summary>
|
||||
public Action<SlingshotProjectile, int> localPlayerHitTarget;
|
||||
|
||||
/// <summary>
|
||||
/// The lifetime of the projectile.
|
||||
/// </summary>
|
||||
[SerializeField] float m_LifeTime = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The collider for the projectile.
|
||||
/// </summary>
|
||||
[SerializeField] Collider m_Collider;
|
||||
|
||||
/// <summary>
|
||||
/// The rigidbody for the projectile.
|
||||
/// </summary>
|
||||
[SerializeField] Rigidbody m_Rigidbody;
|
||||
|
||||
/// <summary>
|
||||
/// Called before the first frame update.
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
Destroy(gameObject, m_LifeTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches the projectile with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="launchForce">The force to launch the projectile with.</param>
|
||||
/// <param name="isLocalPlayer">Indicates whether the player launching the projectile is the local player.</param>
|
||||
/// <param name="playerColor">The color of the player launching the projectile.</param>
|
||||
public void LaunchProjectile(Vector3 launchForce, bool isLocalPlayer, Color playerColor)
|
||||
{
|
||||
Setup(isLocalPlayer, playerColor);
|
||||
m_Collider.enabled = false;
|
||||
m_Rigidbody.linearVelocity = launchForce;
|
||||
StartCoroutine(LaunchRoutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that enables the collider after a short delay.
|
||||
/// </summary>
|
||||
IEnumerator LaunchRoutine()
|
||||
{
|
||||
yield return new WaitForSeconds(.15f);
|
||||
m_Collider.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the projectile hits a target.
|
||||
/// </summary>
|
||||
/// <param name="target">The target that was hit.</param>
|
||||
protected override void HitTarget(Target target)
|
||||
{
|
||||
base.HitTarget(target);
|
||||
localPlayerHitTarget?.Invoke(this, target.targetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3285d21d1d58fa34b876d5042734f3bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,190 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the visual elements of the slingshot in the gameplay.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class SlingshotVisualUpdater : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The line renderer displaying the current trajectory.
|
||||
/// </summary>
|
||||
public LineRenderer trajectoryLineRenderer;
|
||||
|
||||
[SerializeField] Rigidbody m_Rigidbody;
|
||||
|
||||
/// <summary>
|
||||
/// The transform of the bucket.
|
||||
/// </summary>
|
||||
[SerializeField] Rigidbody m_BucketRigibody;
|
||||
|
||||
/// <summary>
|
||||
/// The transform of the shot center.
|
||||
/// </summary>
|
||||
[SerializeField] Transform m_ShotCenterTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The line renderers of the slingshot.
|
||||
/// </summary>
|
||||
[SerializeField] LineRenderer[] m_LineRenderers;
|
||||
|
||||
/// <summary>
|
||||
/// The transform of the line end.
|
||||
/// </summary>
|
||||
[SerializeField] Transform[] m_LineEndTransforms;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum and maximum distance of the slingshot.
|
||||
/// </summary>
|
||||
[SerializeField] Vector2 m_MinMaxDistance = new Vector2(0.0f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum and maximum size of the line.
|
||||
/// </summary>
|
||||
[SerializeField] Vector2 m_MinMaxLineSize = new Vector2(.05f, .01f);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum and maximum shot force.
|
||||
/// </summary>
|
||||
[SerializeField] Vector2 m_MinMaxShotForce = new Vector2(5.0f, 20.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The force multiplier of the shot.
|
||||
/// </summary>
|
||||
[SerializeField] float m_ForceMultiplier = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance to display the debug line.
|
||||
/// </summary>
|
||||
[SerializeField] float m_MaxDebugShowDistance = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The time to reset the bucket position.
|
||||
/// </summary>
|
||||
[SerializeField] float m_BucketResetTime = .35f;
|
||||
|
||||
/// <summary>
|
||||
/// The color divisor of the debug line.
|
||||
/// </summary>
|
||||
[SerializeField] float m_ColorDivisor = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The line display distance.
|
||||
/// </summary>
|
||||
[SerializeField] float m_LineDisplayDistance = 2.0f;
|
||||
|
||||
[SerializeField] bool m_LockBucketRotation = true;
|
||||
|
||||
/// <summary>
|
||||
/// The shot force of the slingshot.
|
||||
/// </summary>
|
||||
float m_ShotForce;
|
||||
|
||||
/// <summary>
|
||||
/// The aim direction of the slingshot.
|
||||
/// </summary>
|
||||
Vector3 m_AimDirection;
|
||||
|
||||
/// <summary>
|
||||
/// The reset position of the bucket.
|
||||
/// </summary>
|
||||
// Vector3 m_ResetPosition;
|
||||
|
||||
Pose m_ResetPose;
|
||||
|
||||
/// <inheritdoc/>
|
||||
void Awake()
|
||||
{
|
||||
trajectoryLineRenderer.enabled = false;
|
||||
m_ResetPose = new Pose(m_BucketRigibody.transform.localPosition, m_BucketRigibody.transform.localRotation);
|
||||
// m_ResetPosition = m_BucketTransform.localPosition;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void LateUpdate()
|
||||
{
|
||||
for (int i = 0; i < m_LineRenderers.Length; i++)
|
||||
{
|
||||
m_LineRenderers[i].SetPosition(0, m_LineRenderers[i].transform.position);
|
||||
m_LineRenderers[i].SetPosition(1, m_LineEndTransforms[i].position);
|
||||
|
||||
float distance = Vector3.Distance(m_LineRenderers[i].transform.position, m_LineEndTransforms[i].position);
|
||||
float perc = Utils.GetPercentOfValueBetweenTwoValues(m_MinMaxDistance.x, m_MinMaxDistance.y, distance);
|
||||
float size = Mathf.Lerp(m_MinMaxLineSize.x, m_MinMaxLineSize.y, perc);
|
||||
m_LineRenderers[i].startWidth = size;
|
||||
m_LineRenderers[i].endWidth = size;
|
||||
}
|
||||
|
||||
float lineDistance = Vector3.Distance(m_ShotCenterTransform.position, m_BucketRigibody.position);
|
||||
m_ShotForce = Mathf.Clamp(lineDistance * m_ForceMultiplier, m_MinMaxShotForce.x, m_MinMaxShotForce.y);
|
||||
m_AimDirection = (m_ShotCenterTransform.position - m_BucketRigibody.position).normalized;
|
||||
|
||||
Vector3 lineEndPos = m_BucketRigibody.position + (m_AimDirection * Mathf.Clamp(lineDistance * m_LineDisplayDistance, 0.0f, m_MaxDebugShowDistance));
|
||||
|
||||
Color forceColor = Color.Lerp(Color.yellow, Color.red, Mathf.Clamp01(m_ShotForce / m_MaxDebugShowDistance / m_ColorDivisor));
|
||||
Color transparentForceColor = new Color(forceColor.r, forceColor.g, forceColor.b, 0.0f);
|
||||
|
||||
trajectoryLineRenderer.startColor = forceColor;
|
||||
trajectoryLineRenderer.endColor = transparentForceColor;
|
||||
|
||||
trajectoryLineRenderer.SetPosition(0, m_BucketRigibody.position);
|
||||
trajectoryLineRenderer.SetPosition(1, lineEndPos);
|
||||
if (m_LockBucketRotation)
|
||||
m_BucketRigibody.transform.forward = m_AimDirection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the force of the shot.
|
||||
/// </summary>
|
||||
/// <returns>The force of the shot.</returns>
|
||||
public Vector3 GetShotForce()
|
||||
{
|
||||
return m_AimDirection * m_ShotForce;
|
||||
}
|
||||
|
||||
public void ResetSlingshot(float height)
|
||||
{
|
||||
StartCoroutine(ResetSlingshotHeightRoutine(height));
|
||||
ResetBucketPosition();
|
||||
}
|
||||
|
||||
IEnumerator ResetSlingshotHeightRoutine(float height)
|
||||
{
|
||||
bool wasKinematic = m_Rigidbody.isKinematic;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
transform.localPosition = new Vector3(transform.localPosition.x, height, transform.localPosition.z);
|
||||
|
||||
yield return new WaitForSeconds(.15f);
|
||||
m_Rigidbody.isKinematic = wasKinematic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the position of the bucket.
|
||||
/// </summary>
|
||||
public void ResetBucketPosition()
|
||||
{
|
||||
StartCoroutine(ResetBucketPositionRoutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine to reset the position of the bucket over time.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerator used for the coroutine.</returns>
|
||||
IEnumerator ResetBucketPositionRoutine()
|
||||
{
|
||||
bool wasKinematic = m_BucketRigibody.isKinematic;
|
||||
m_BucketRigibody.isKinematic = true;
|
||||
for (float i = 0; i < m_BucketResetTime; i += Time.deltaTime)
|
||||
{
|
||||
m_BucketRigibody.transform.localPosition = Vector3.Lerp(m_BucketRigibody.transform.localPosition, m_ResetPose.position, i / m_BucketResetTime);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
m_BucketRigibody.transform.SetLocalPositionAndRotation(m_ResetPose.position, m_ResetPose.rotation);
|
||||
m_BucketRigibody.isKinematic = wasKinematic;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa9bce44a758b044abdbe44f257ab085
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ca2f6b9ddc18d447b8007be1a66b086
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects a collision with a tagged collider, replacing this object with a 'broken' version
|
||||
/// </summary>
|
||||
public class Breakable : MonoBehaviour
|
||||
{
|
||||
public UnityAction<Collider> onBreak;
|
||||
public int pointValue = 1;
|
||||
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
public Collider collider => m_Collider;
|
||||
#pragma warning restore CS0108 // Member hides inherited member; missing new keyword
|
||||
|
||||
[SerializeField]
|
||||
Collider m_Collider;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The 'broken' version of this object.")]
|
||||
GameObject m_BrokenVersion;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The tag a collider must have to cause this object to break.")]
|
||||
string m_ColliderTag = "Destroyer";
|
||||
|
||||
bool m_Destroyed = false;
|
||||
|
||||
void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (m_Destroyed)
|
||||
return;
|
||||
|
||||
if (collision.gameObject.CompareTag(m_ColliderTag))
|
||||
{
|
||||
Break(collision.collider);
|
||||
}
|
||||
}
|
||||
|
||||
public void Break(Collider collider)
|
||||
{
|
||||
if (m_Destroyed) return;
|
||||
m_Destroyed = true;
|
||||
Instantiate(m_BrokenVersion, transform.position, transform.rotation);
|
||||
onBreak?.Invoke(collider);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecc3a8c5ad509204cb2ac78f2182017c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mini-game called "Whack-A-Pig" where the player needs to hit pigs with a hammer.
|
||||
/// </summary>
|
||||
public class MiniGame_Whack : MiniGameBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The time to wait before resetting the hammer's position.
|
||||
/// </summary>
|
||||
[SerializeField] float m_HammerResetTime = .25f;
|
||||
|
||||
/// <summary>
|
||||
/// The interactable objects to use for the mini-game.
|
||||
/// </summary>
|
||||
readonly Dictionary<XRBaseInteractable, Pose> m_InteractablePoses = new();
|
||||
|
||||
/// <summary>
|
||||
/// The networked gameplay to use for handling the networked gameplay logic.
|
||||
/// </summary>
|
||||
NetworkedWhackAPig m_NetworkedGameplay;
|
||||
|
||||
/// <summary>
|
||||
/// The current score of the mini-game.
|
||||
/// </summary>
|
||||
int m_CurrentScore = 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
TryGetComponent(out m_NetworkedGameplay);
|
||||
|
||||
foreach (var interactable in m_GameInteractables)
|
||||
{
|
||||
if (!m_InteractablePoses.ContainsKey(interactable))
|
||||
{
|
||||
m_InteractablePoses.Add(interactable, new Pose(interactable.transform.position, interactable.transform.rotation));
|
||||
interactable.selectExited.AddListener(HammerDropped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void OnDestroy()
|
||||
{
|
||||
foreach (var kvp in m_InteractablePoses)
|
||||
{
|
||||
kvp.Key.selectExited.RemoveListener(HammerDropped);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the game by resetting the current score.
|
||||
/// </summary>
|
||||
public override void SetupGame()
|
||||
{
|
||||
base.SetupGame();
|
||||
m_CurrentScore = 0;
|
||||
m_NetworkedGameplay.ResetGame();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the game by spawning pigs if the player is the server.
|
||||
/// </summary>
|
||||
public override void StartGame()
|
||||
{
|
||||
base.StartGame();
|
||||
if (m_NetworkedGameplay.IsServer)
|
||||
{
|
||||
m_NetworkedGameplay.SpawnProcessServer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the game and ends the networked gameplay.
|
||||
/// </summary>
|
||||
/// <param name="submitScore">Whether to submit the score or not.</param>
|
||||
public override void FinishGame(bool submitScore = true)
|
||||
{
|
||||
base.FinishGame(submitScore);
|
||||
m_NetworkedGameplay.EndGame();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the hammer is dropped on an interactable object.
|
||||
/// </summary>
|
||||
/// <param name="args">The interaction event arguments.</param>
|
||||
void HammerDropped(BaseInteractionEventArgs args)
|
||||
{
|
||||
XRBaseInteractable interactable = (XRBaseInteractable)args.interactableObject;
|
||||
if (m_InteractablePoses.ContainsKey(interactable))
|
||||
{
|
||||
StartCoroutine(DropHammerAfterTimeRoutine(interactable));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that drops the hammer after a specified time and resets the interactable's position.
|
||||
/// </summary>
|
||||
/// <param name="interactable">The interactable object.</param>
|
||||
IEnumerator DropHammerAfterTimeRoutine(XRBaseInteractable interactable)
|
||||
{
|
||||
yield return new WaitForSeconds(m_HammerResetTime);
|
||||
if (!interactable.isSelected)
|
||||
{
|
||||
Rigidbody body = interactable.GetComponent<Rigidbody>();
|
||||
bool wasKinematic = body.isKinematic;
|
||||
body.isKinematic = true;
|
||||
interactable.transform.SetPositionAndRotation(m_InteractablePoses[interactable].position, m_InteractablePoses[interactable].rotation);
|
||||
yield return new WaitForFixedUpdate();
|
||||
body.isKinematic = wasKinematic;
|
||||
foreach (var collider in interactable.colliders)
|
||||
{
|
||||
collider.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the local player's score and submits it to the server.
|
||||
/// </summary>
|
||||
/// <param name="pointValue">The point value to add to the score.</param>
|
||||
public void LocalPlayerScored(int pointValue)
|
||||
{
|
||||
m_CurrentScore += pointValue;
|
||||
if (m_CurrentScore < 0) m_CurrentScore = 0;
|
||||
m_MiniGameManager.SubmitScoreServerRpc(m_CurrentScore, XRINetworkPlayer.LocalPlayer.OwnerClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0ba65058d372504d92b0a44fa579df1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,376 @@
|
||||
using System.Collections;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Content.Interaction;
|
||||
|
||||
namespace XRMultiplayer.MiniGames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a networked version of the Whack-A-Pig mini game.
|
||||
/// </summary>
|
||||
public class NetworkedWhackAPig : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The proxy pigs to use for showing the trick.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject[] m_ProxyPigs;
|
||||
|
||||
/// <summary>
|
||||
/// The hammers to use for hitting the pigs.
|
||||
/// </summary>
|
||||
[SerializeField] NetworkPhysicsInteractable[] m_Hammers;
|
||||
|
||||
[SerializeField] Collider[] m_HammerIgnoreColliders;
|
||||
|
||||
/// <summary>
|
||||
/// The pig prefab to spawn.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_PigPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// The bad pig prefab to spawn.
|
||||
/// </summary>
|
||||
[SerializeField] GameObject m_BadPigPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// The time to stay spawned before despawning.
|
||||
/// </summary>
|
||||
[SerializeField] Vector2 m_TimeToStaySpawnedMinMax = new Vector2(.5f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The time to wait before spawning a new pig.
|
||||
/// </summary>
|
||||
[SerializeField] float m_TimeToSpawn = .5f;
|
||||
|
||||
/// <summary>
|
||||
/// The time to show the proxy pig.
|
||||
/// </summary>
|
||||
[SerializeField] float m_ProxyShowTime = .35f;
|
||||
|
||||
/// <summary>
|
||||
/// The hidden height of the proxy pig.
|
||||
/// </summary>
|
||||
[SerializeField] float m_HiddenHeight = -0.25f;
|
||||
|
||||
[SerializeField] float m_SpawnStartHeight = 0.0f;
|
||||
|
||||
[SerializeField] float m_SpawnShowHeight = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// The trick height min and max for the proxy pig.
|
||||
/// </summary>
|
||||
[SerializeField] Vector2 m_TrickHeightMinMax = new Vector2(-0.25f, -.05f);
|
||||
|
||||
/// <summary>
|
||||
/// The trick time min and max for the proxy pig.
|
||||
/// </summary>
|
||||
[SerializeField] Vector2 m_TrickTimeMinMax = new Vector2(1.0f, 3.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The bad pig spawn chance.
|
||||
/// </summary>
|
||||
[SerializeField] float m_BadPigSpawn = .7f;
|
||||
|
||||
[SerializeField] Collider gameBarrierCollider;
|
||||
|
||||
/// <summary>
|
||||
/// The mini game to use for handling the mini game logic.
|
||||
/// </summary>
|
||||
MiniGame_Whack m_MiniGame;
|
||||
|
||||
/// <summary>
|
||||
/// The current breakable pig.
|
||||
/// </summary>
|
||||
Breakable m_CurrentBreakablePig;
|
||||
|
||||
/// <summary>
|
||||
/// The current routine being played.
|
||||
/// </summary>
|
||||
IEnumerator m_CurrentRoutine;
|
||||
|
||||
/// <summary>
|
||||
/// The current proxy ID.
|
||||
/// </summary>
|
||||
int m_CurrentProxyId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we are currently spawning a pig.
|
||||
/// </summary>
|
||||
bool m_Spawning = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
void Start()
|
||||
{
|
||||
TryGetComponent(out m_MiniGame);
|
||||
foreach (var pig in m_ProxyPigs)
|
||||
{
|
||||
pig.SetActive(false);
|
||||
}
|
||||
|
||||
foreach (var areaCollider in m_HammerIgnoreColliders)
|
||||
{
|
||||
foreach (var hammer in m_Hammers)
|
||||
{
|
||||
foreach (var hammerInteractableCollider in hammer.baseInteractable.colliders)
|
||||
{
|
||||
Physics.IgnoreCollision(hammerInteractableCollider, areaCollider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the trick routine.
|
||||
/// </summary>
|
||||
[ContextMenu("Test Trick Routine")]
|
||||
void ShowTrickRoutine()
|
||||
{
|
||||
if (m_CurrentRoutine != null) StopTrickRoutine();
|
||||
m_CurrentRoutine = TrickRoutine();
|
||||
StartCoroutine(m_CurrentRoutine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the trick routine.
|
||||
/// </summary>
|
||||
[ContextMenu("Stop Trick Routine")]
|
||||
void StopTrickRoutine()
|
||||
{
|
||||
if (m_CurrentRoutine != null) StopCoroutine(m_CurrentRoutine);
|
||||
m_ProxyPigs[m_CurrentProxyId].SetActive(false);
|
||||
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = new Vector3(m_ProxyPigs[m_CurrentProxyId].transform.localPosition.x, m_HiddenHeight, m_ProxyPigs[m_CurrentProxyId].transform.localPosition.z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the trick routine.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerator representing the trick routine.</returns>
|
||||
IEnumerator TrickRoutine()
|
||||
{
|
||||
while (true & !m_MiniGame.finished)
|
||||
{
|
||||
// Choose a random proxy pig to show
|
||||
m_CurrentProxyId = Random.Range(0, m_ProxyPigs.Length);
|
||||
float riseTime = m_ProxyShowTime / 2;
|
||||
Vector3 startPosition = new Vector3(m_ProxyPigs[m_CurrentProxyId].transform.localPosition.x, 0, m_ProxyPigs[m_CurrentProxyId].transform.localPosition.z);
|
||||
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * m_HiddenHeight;
|
||||
m_ProxyPigs[m_CurrentProxyId].SetActive(true);
|
||||
|
||||
float trickHeight = Random.Range(m_TrickHeightMinMax.x, m_TrickHeightMinMax.y);
|
||||
|
||||
|
||||
// Move the pig up to the trick height
|
||||
for (float i = 0; i < riseTime; i += Time.deltaTime)
|
||||
{
|
||||
float perc = i / riseTime;
|
||||
|
||||
float lerpHeight = Mathf.Lerp(m_HiddenHeight, trickHeight, perc);
|
||||
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * lerpHeight;
|
||||
yield return null;
|
||||
}
|
||||
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * trickHeight;
|
||||
|
||||
// Move the pig back down to the hidden height
|
||||
for (float i = 0; i < riseTime; i += Time.deltaTime)
|
||||
{
|
||||
float perc = i / riseTime;
|
||||
|
||||
float lerpHeight = Mathf.Lerp(trickHeight, m_HiddenHeight, perc);
|
||||
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * lerpHeight;
|
||||
yield return null;
|
||||
}
|
||||
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * m_HiddenHeight;
|
||||
|
||||
// Hide the pig
|
||||
m_ProxyPigs[m_CurrentProxyId].SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a pig on the server.
|
||||
/// </summary>
|
||||
public void SpawnProcessServer()
|
||||
{
|
||||
if (IsServer)
|
||||
SpawnProcessClientRpc(Random.Range(m_TrickTimeMinMax.x, m_TrickTimeMinMax.y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a pig on the clients.
|
||||
/// </summary>
|
||||
/// <param name="waitTime">The time to wait before spawning the pig.</param>
|
||||
[ClientRpc]
|
||||
public void SpawnProcessClientRpc(float waitTime)
|
||||
{
|
||||
m_Spawning = true;
|
||||
StartCoroutine(SpawnAfterTime(waitTime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a pig after a certain amount of time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to wait before spawning the pig.</param>
|
||||
/// <returns>An IEnumerator representing the spawn after time routine.</returns>
|
||||
IEnumerator SpawnAfterTime(float time)
|
||||
{
|
||||
yield return new WaitForSeconds(.25f);
|
||||
ShowTrickRoutine();
|
||||
yield return new WaitForSeconds(time);
|
||||
SpawnNewPig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from mini game manager when entering pre game state.
|
||||
/// </summary>
|
||||
public void ResetGame()
|
||||
{
|
||||
StopTrickRoutine();
|
||||
StopAllCoroutines();
|
||||
if (m_CurrentBreakablePig != null)
|
||||
Destroy(m_CurrentBreakablePig.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the game and cleans up.
|
||||
/// </summary>
|
||||
public void EndGame()
|
||||
{
|
||||
StopTrickRoutine();
|
||||
StopAllCoroutines();
|
||||
if (m_CurrentBreakablePig != null)
|
||||
Destroy(m_CurrentBreakablePig.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a new pig on the server.
|
||||
/// </summary>
|
||||
public void SpawnNewPig()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
SpawnPigClientRpc(Random.Range(0, m_ProxyPigs.Length), Random.value, Random.Range(m_TimeToStaySpawnedMinMax.x, m_TimeToStaySpawnedMinMax.y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a pig on the clients.
|
||||
/// </summary>
|
||||
/// <param name="spawnIdx">The index of the spawn transform to use.</param>
|
||||
/// <param name="randomValue">A random value used to determine if a bad pig should be spawned.</param>
|
||||
[ClientRpc]
|
||||
void SpawnPigClientRpc(int spawnIdx, float randomValue, float timeToStaySpawned)
|
||||
{
|
||||
StopTrickRoutine();
|
||||
m_Spawning = false;
|
||||
m_CurrentRoutine = SpawnRoutine(spawnIdx, randomValue, timeToStaySpawned);
|
||||
StartCoroutine(m_CurrentRoutine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a pig on the server and sets up collision ignoring.
|
||||
/// </summary>
|
||||
/// <param name="spawnIdx">The index of the spawn transform to use.</param>
|
||||
/// <param name="randomValue">A random value used to determine if a bad pig should be spawned.</param>
|
||||
/// <returns>An IEnumerator representing the spawn routine.</returns>
|
||||
IEnumerator SpawnRoutine(int spawnIdx, float randomValue, float timeToStaySpawned)
|
||||
{
|
||||
Vector3 spawnPos = m_ProxyPigs[spawnIdx].transform.position + (Vector3.up * m_SpawnStartHeight);
|
||||
|
||||
// Determine if we are spawning a bad pig or a good pig
|
||||
m_CurrentBreakablePig = Instantiate(randomValue > m_BadPigSpawn ? m_BadPigPrefab : m_PigPrefab, spawnPos, m_ProxyPigs[spawnIdx].transform.rotation).GetComponent<Breakable>();
|
||||
|
||||
m_CurrentBreakablePig.collider.enabled = false;
|
||||
// Updates Physics Ignore Collision to non local hammers cannot interact with the pigs
|
||||
foreach (var hammer in m_Hammers)
|
||||
{
|
||||
if (!hammer.IsOwner || !hammer.isInteracting)
|
||||
{
|
||||
foreach (var collider in hammer.baseInteractable.colliders)
|
||||
{
|
||||
Physics.IgnoreCollision(collider, m_CurrentBreakablePig.collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_CurrentBreakablePig.onBreak += LocalPigDestroyed;
|
||||
|
||||
Vector3 startPosition = m_CurrentBreakablePig.transform.position;
|
||||
// Move the pig up to the show height
|
||||
for (float i = 0; i < m_TimeToSpawn; i += Time.deltaTime)
|
||||
{
|
||||
float perc = i / m_TimeToSpawn;
|
||||
|
||||
float lerpHeight = Mathf.Lerp(m_SpawnStartHeight, m_SpawnShowHeight, perc);
|
||||
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * lerpHeight;
|
||||
yield return null;
|
||||
}
|
||||
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * m_SpawnShowHeight;
|
||||
|
||||
m_CurrentBreakablePig.collider.enabled = true;
|
||||
yield return new WaitForSeconds(timeToStaySpawned);
|
||||
m_CurrentBreakablePig.collider.enabled = false;
|
||||
// Move the pig back down to the hidden height
|
||||
for (float i = 0; i < m_TimeToSpawn; i += Time.deltaTime)
|
||||
{
|
||||
float perc = i / m_TimeToSpawn;
|
||||
|
||||
float lerpHeight = Mathf.Lerp(m_SpawnShowHeight, m_SpawnStartHeight, perc);
|
||||
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * lerpHeight;
|
||||
yield return null;
|
||||
}
|
||||
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * m_SpawnStartHeight;
|
||||
|
||||
// Destroy the pig and spawn a new one if we are the server
|
||||
Destroy(m_CurrentBreakablePig.gameObject);
|
||||
if (IsServer)
|
||||
SpawnProcessServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the destruction of a pig on the local client.
|
||||
/// </summary>
|
||||
/// <param name="collider">The collider of the pig that was destroyed.</param>
|
||||
void LocalPigDestroyed(Collider collider)
|
||||
{
|
||||
NetworkPhysicsInteractable grabInteractable = collider.GetComponentInParent<NetworkPhysicsInteractable>();
|
||||
|
||||
if (grabInteractable == null) // Probably in editor testing
|
||||
{
|
||||
if (IsServer & !m_Spawning)
|
||||
SpawnProcessServer();
|
||||
}
|
||||
else if (grabInteractable.IsOwner)
|
||||
{
|
||||
m_MiniGame.LocalPlayerScored(m_CurrentBreakablePig.pointValue);
|
||||
DestroyPigServerRpc(grabInteractable.OwnerClientId);
|
||||
StopCoroutine(m_CurrentRoutine);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys a pig on the server and notifies the clients.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client ID of the owner of the pig.</param>
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
void DestroyPigServerRpc(ulong clientId)
|
||||
{
|
||||
DestroyPigClientRpc(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys a pig on the clients.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client ID of the owner of the pig.</param>
|
||||
[ClientRpc]
|
||||
void DestroyPigClientRpc(ulong clientId)
|
||||
{
|
||||
if (XRINetworkPlayer.LocalPlayer.OwnerClientId != clientId && m_CurrentBreakablePig != null)
|
||||
{
|
||||
StopCoroutine(m_CurrentRoutine);
|
||||
m_CurrentBreakablePig.onBreak -= LocalPigDestroyed;
|
||||
m_CurrentBreakablePig.Break(null);
|
||||
}
|
||||
|
||||
if (IsServer & !m_Spawning)
|
||||
SpawnProcessServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bba13bfc67c46d4186c65bf73e39e71
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user