using System; using UnityEngine.Pool; using UnityEngine.SceneManagement; // This sample can be optimized with pooled explosions. namespace UnityEngine.InputSystem.Samples.RebindUI { public class GameplayManager : MonoBehaviour { [Tooltip("The game camera")] public Camera gameCamera; [Tooltip("The enemy spawn rate")] public float enemySpawnRate = 1.0f; [Tooltip("The enemy spawn distance from center")] public float spawnDistance = 10.0f; [Tooltip("The enemy prefab for the mini game")] public GameObject enemy; [Tooltip("The explosion prefab for the mini game")] public GameObject enemyExplosion; [Tooltip("The player prefab for the mini game")] public GameObject player; /// /// Returns the current game level. /// public int level { get; private set; } /// /// Game state. /// public enum GameplayState { None, StartLevel, Playing, CompleteLevel, GameOver, ResetGame, } /// /// Returns the current game state. /// public GameplayState state => m_GameplayState; /// /// Event fired when game state changes. /// public event Action GameplayStateChanged; /// /// Event fired when pause state changes. /// public event Action PauseChanged; private double m_TimeToNextSpawn; private GameObject m_Player; private ObjectPool m_EnemyPool; private float m_ShakeForce; private float m_ShakeMaxForce; private float m_ShakeDuration; private double m_ShakeTime; private Vector3 m_CameraPosition; private int m_RemainingEnemiesOnThisLevel; private int m_EnemySpawnCount; private FeedbackController m_FeedbackController; private GameplayState m_GameplayState = GameplayState.None; private double m_EarliestTimeToChangeState; /// /// Get/set whether the game is paused. /// public bool paused { get => Time.timeScale == 0.0f; set { if ((value && Time.timeScale == 0.0f) || (!value && Time.timeScale != 0.0f)) return; Time.timeScale = value ? 0.0f : 1.0f; UpdateCursor(); PauseChanged?.Invoke(value); } } public void KillEnemy() { --m_RemainingEnemiesOnThisLevel; } public void GameOver() { m_Player.SetActive(false); m_NextGameplayState = GameplayState.GameOver; } private void Shake(float duration, float amplitude) { m_ShakeMaxForce = amplitude; m_ShakeForce = amplitude; m_ShakeDuration = duration; m_ShakeTime = Time.timeAsDouble; } public void Explosion(Transform target, Vector3 position, float amplitude, Color color, Material material = null) { var obj = Instantiate(enemyExplosion); obj.transform.position = target.position; obj.transform.rotation = target.rotation; // If we are provided a material, use that for all debris if (material != null) { var renderers = obj.GetComponentsInChildren(); foreach (var childRenderer in renderers) childRenderer.sharedMaterial = material; } // Set explosion position var exp = obj.GetComponent(); exp.explosionPosition = position; // Modify the particle color var particles = exp.GetComponent(); ParticleSystem.MainModule main = particles.main; main.startColor = color; Shake(duration: 0.4f, amplitude: amplitude); } private static void WrapAround(ref float x, float min, float max) { if (x <= min) x = max; else if (x >= max) x = min; } internal bool IsInsideGameplayArea(Vector3 position, float margin = 0.8f) { if (!gameCamera || !gameCamera.orthographic) return true; var orthoSize = gameCamera.orthographicSize; var horizontalExtent = orthoSize * gameCamera.aspect; return (position.x >= -horizontalExtent - margin) && (position.x <= horizontalExtent + margin) && (position.y >= -orthoSize - margin) && (position.y <= orthoSize + margin); } private static bool TryTeleportOrthographicExtents(Camera camera, Vector3 position, out Vector3 result, float margin = 0.8f) { // Wrap around constraint x, y and teleport player if outside orthographic camera bounds if (camera && camera.orthographic) { var orthoSize = camera.orthographicSize; var horizontalExtent = orthoSize * camera.aspect; var newPosition = position; WrapAround(ref newPosition.x, -horizontalExtent - margin, horizontalExtent + margin); WrapAround(ref newPosition.y, -orthoSize - margin, orthoSize + margin); if (newPosition != position) { result = newPosition; return true; } } result = position; return false; } internal bool TryTeleportOrthographicExtents(Vector3 position, out Vector3 result, float margin = 0.8f) { return TryTeleportOrthographicExtents(gameCamera, position, out result, margin); } private void Awake() { // This game is designed for landscape orientation, so make sure we use it. Screen.orientation = ScreenOrientation.LandscapeLeft; m_FeedbackController = GetComponent(); m_EnemyPool = new ObjectPool( createFunc: () => { var enemyComponent = Instantiate(enemy).GetComponent(); enemyComponent.pool = m_EnemyPool; enemyComponent.target = m_Player.transform; enemyComponent.manager = this; return enemyComponent; }, actionOnGet: (obj) => obj.gameObject.SetActive(true), actionOnRelease: (obj) => obj.gameObject.SetActive(false), actionOnDestroy: (obj) => Destroy(obj.gameObject)); m_CameraPosition = gameCamera.transform.position; m_EarliestTimeToChangeState = Time.timeAsDouble; } private void Start() { // Instantiate and initialize the player m_Player = Instantiate(player, transform, worldPositionStays: true); var playerComponent = m_Player.GetComponent(); playerComponent.manager = this; // Setup feedback controller var playerController = m_Player.GetComponent(); playerController.feedbackController = m_FeedbackController; // Delay first spawn so player has a chance to get ready m_TimeToNextSpawn = 3.0f; } private void OnEnable() { Application.focusChanged += OnApplicationFocusChanged; paused = !Application.isFocused; } private void OnDisable() { Application.focusChanged -= OnApplicationFocusChanged; paused = true; } private void OnApplicationFocusChanged(bool focus) { paused = !focus; } void SpawnEnemy() { if (m_EnemySpawnCount == 0) return; m_TimeToNextSpawn -= Time.deltaTime; if (m_TimeToNextSpawn > 0.0f) return; m_TimeToNextSpawn += enemySpawnRate; --m_EnemySpawnCount; // Rent an enemy from the enemy pool var enemyComponent = m_EnemyPool.Get(); // Make the enemy spawn on border of visible game area var orthoSize = gameCamera.orthographicSize; var horizontalExtent = orthoSize * gameCamera.aspect; var axis = Random.Range(-1.0f, 1.0f); var margin = 0.5f; var random = Random.Range(0, 4); switch (random) { case 0: enemyComponent.transform.position = new Vector3(axis * horizontalExtent, orthoSize + margin, 0.0f); break; case 1: enemyComponent.transform.position = new Vector3(axis * horizontalExtent, -orthoSize - margin, 0.0f); break; case 2: enemyComponent.transform.position = new Vector3(-horizontalExtent - margin, axis * orthoSize, 0.0f); break; case 3: enemyComponent.transform.position = new Vector3(horizontalExtent + margin, axis * orthoSize, 0.0f); break; } } void AnimateCameraShake() { var time = Time.timeAsDouble; var elapsed = (time - m_ShakeTime); var t = m_ShakeDuration <= 0.0f ? 1.0f : elapsed / m_ShakeDuration; m_ShakeForce = Mathf.Lerp(m_ShakeMaxForce, 0.0f, (float)t); var cameraShakeOffset = new Vector3( m_ShakeForce * Mathf.Sin((float)time * 71.0f), m_ShakeForce * Mathf.Sin((float)time * 53.0f + Mathf.PI / 3.0f), 0f); gameCamera.transform.position = m_CameraPosition + cameraShakeOffset; // Apply shake to feedback controller if available if (m_FeedbackController != null) m_FeedbackController.rumble = m_ShakeForce; } private GameplayState m_NextGameplayState = GameplayState.StartLevel; void Update() { var now = Time.time; while (now >= m_EarliestTimeToChangeState && m_NextGameplayState != m_GameplayState) { m_EarliestTimeToChangeState = now; // Transition exit switch (m_GameplayState) { case GameplayState.None: break; case GameplayState.StartLevel: break; case GameplayState.Playing: break; case GameplayState.CompleteLevel: break; case GameplayState.GameOver: case GameplayState.ResetGame: break; } m_GameplayState = m_NextGameplayState; // Transition enter switch (m_NextGameplayState) { case GameplayState.None: m_NextGameplayState = GameplayState.StartLevel; break; case GameplayState.StartLevel: m_EnemySpawnCount = 5 + (++level) * 2; m_RemainingEnemiesOnThisLevel = m_EnemySpawnCount; enemySpawnRate *= 0.9f; m_EarliestTimeToChangeState += 2.0; m_NextGameplayState = GameplayState.Playing; break; case GameplayState.Playing: break; case GameplayState.CompleteLevel: m_NextGameplayState = GameplayState.StartLevel; break; case GameplayState.GameOver: m_EarliestTimeToChangeState += 3.0; m_NextGameplayState = GameplayState.ResetGame; break; case GameplayState.ResetGame: SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); break; } // Notify listeners about gameplay state being updated GameplayStateChanged?.Invoke(m_GameplayState); } // Spawn enemies while in playing state only if (state == GameplayState.Playing) { if (m_RemainingEnemiesOnThisLevel == 0) m_NextGameplayState = GameplayState.CompleteLevel; else SpawnEnemy(); } // Always animate regardless of state AnimateCameraShake(); } void UpdateCursor() { if (paused) { Cursor.visible = true; Cursor.lockState = CursorLockMode.None; } else { Cursor.visible = false; Cursor.lockState = CursorLockMode.Locked; } } } }