using System.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.XR.Content.Interaction; namespace XRMultiplayer.MiniGames { /// /// Represents a networked version of the Whack-A-Pig mini game. /// public class NetworkedWhackAPig : NetworkBehaviour { /// /// The proxy pigs to use for showing the trick. /// [SerializeField] GameObject[] m_ProxyPigs; /// /// The hammers to use for hitting the pigs. /// [SerializeField] NetworkPhysicsInteractable[] m_Hammers; [SerializeField] Collider[] m_HammerIgnoreColliders; /// /// The pig prefab to spawn. /// [SerializeField] GameObject m_PigPrefab; /// /// The bad pig prefab to spawn. /// [SerializeField] GameObject m_BadPigPrefab; /// /// The time to stay spawned before despawning. /// [SerializeField] Vector2 m_TimeToStaySpawnedMinMax = new Vector2(.5f, 1.0f); /// /// The time to wait before spawning a new pig. /// [SerializeField] float m_TimeToSpawn = .5f; /// /// The time to show the proxy pig. /// [SerializeField] float m_ProxyShowTime = .35f; /// /// The hidden height of the proxy pig. /// [SerializeField] float m_HiddenHeight = -0.25f; [SerializeField] float m_SpawnStartHeight = 0.0f; [SerializeField] float m_SpawnShowHeight = 0.1f; /// /// The trick height min and max for the proxy pig. /// [SerializeField] Vector2 m_TrickHeightMinMax = new Vector2(-0.25f, -.05f); /// /// The trick time min and max for the proxy pig. /// [SerializeField] Vector2 m_TrickTimeMinMax = new Vector2(1.0f, 3.0f); /// /// The bad pig spawn chance. /// [SerializeField] float m_BadPigSpawn = .7f; [SerializeField] Collider gameBarrierCollider; /// /// The mini game to use for handling the mini game logic. /// MiniGame_Whack m_MiniGame; /// /// The current breakable pig. /// Breakable m_CurrentBreakablePig; /// /// The current routine being played. /// IEnumerator m_CurrentRoutine; /// /// The current proxy ID. /// int m_CurrentProxyId = 0; /// /// Whether we are currently spawning a pig. /// bool m_Spawning = false; /// 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); } } } } /// /// Starts the trick routine. /// [ContextMenu("Test Trick Routine")] void ShowTrickRoutine() { if (m_CurrentRoutine != null) StopTrickRoutine(); m_CurrentRoutine = TrickRoutine(); StartCoroutine(m_CurrentRoutine); } /// /// Stops the trick routine. /// [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); } /// /// Performs the trick routine. /// /// An IEnumerator representing the trick routine. 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); } } /// /// Spawns a pig on the server. /// public void SpawnProcessServer() { if (IsServer) SpawnProcessClientRpc(Random.Range(m_TrickTimeMinMax.x, m_TrickTimeMinMax.y)); } /// /// Spawns a pig on the clients. /// /// The time to wait before spawning the pig. [ClientRpc] public void SpawnProcessClientRpc(float waitTime) { m_Spawning = true; StartCoroutine(SpawnAfterTime(waitTime)); } /// /// Spawns a pig after a certain amount of time. /// /// The time to wait before spawning the pig. /// An IEnumerator representing the spawn after time routine. IEnumerator SpawnAfterTime(float time) { yield return new WaitForSeconds(.25f); ShowTrickRoutine(); yield return new WaitForSeconds(time); SpawnNewPig(); } /// /// Called from mini game manager when entering pre game state. /// public void ResetGame() { StopTrickRoutine(); StopAllCoroutines(); if (m_CurrentBreakablePig != null) Destroy(m_CurrentBreakablePig.gameObject); } /// /// Ends the game and cleans up. /// public void EndGame() { StopTrickRoutine(); StopAllCoroutines(); if (m_CurrentBreakablePig != null) Destroy(m_CurrentBreakablePig.gameObject); } /// /// Spawns a new pig on the server. /// public void SpawnNewPig() { if (!IsServer) return; SpawnPigClientRpc(Random.Range(0, m_ProxyPigs.Length), Random.value, Random.Range(m_TimeToStaySpawnedMinMax.x, m_TimeToStaySpawnedMinMax.y)); } /// /// Spawns a pig on the clients. /// /// The index of the spawn transform to use. /// A random value used to determine if a bad pig should be spawned. [ClientRpc] void SpawnPigClientRpc(int spawnIdx, float randomValue, float timeToStaySpawned) { StopTrickRoutine(); m_Spawning = false; m_CurrentRoutine = SpawnRoutine(spawnIdx, randomValue, timeToStaySpawned); StartCoroutine(m_CurrentRoutine); } /// /// Spawns a pig on the server and sets up collision ignoring. /// /// The index of the spawn transform to use. /// A random value used to determine if a bad pig should be spawned. /// An IEnumerator representing the spawn routine. 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(); 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(); } /// /// Handles the destruction of a pig on the local client. /// /// The collider of the pig that was destroyed. void LocalPigDestroyed(Collider collider) { NetworkPhysicsInteractable grabInteractable = collider.GetComponentInParent(); 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); } } /// /// Destroys a pig on the server and notifies the clients. /// /// The client ID of the owner of the pig. [ServerRpc(RequireOwnership = false)] void DestroyPigServerRpc(ulong clientId) { DestroyPigClientRpc(clientId); } /// /// Destroys a pig on the clients. /// /// The client ID of the owner of the pig. [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(); } } }