using System; using UnityEngine; namespace LiveOpsExamples { /// /// Gameplay world for the Cozy Collector demo — the Unity port of the Phaser /// GameScene.ts. Renders only the game world: player movement (WASD/arrows), /// round timer, spawn cadence and pickups. All chrome lives in the UI panels, /// all SDK calls in . /// /// The Phaser field is 1280×720 px at 100 PPU, i.e. 12.8×7.2 world units /// centered on the origin; tuning speeds/intervals stay in Phaser units /// (px/s, ms) and are converted here. /// public class GameWorld : MonoBehaviour { private const float PixelsPerUnit = 100f; [SerializeField] private SpriteRenderer player; [SerializeField] private SnackSpawner spawner; [SerializeField] private CozyCollectorController controller; [SerializeField] private ScorePopupView scorePopupPrefab; [SerializeField] private ParticleSystem collectBurstPrefab; [SerializeField] private Vector2 moveMin = new Vector2(-6.0f, -3.2f); [SerializeField] private Vector2 moveMax = new Vector2(6.0f, 3.2f); private bool _paused; private float _spawnElapsedMs; private int _lastSecond = -1; private bool _finishRaised; public int Score { get; private set; } public int Collected { get; private set; } public float TimeLeftSeconds { get; private set; } public bool RoundActive { get; private set; } public event Action StateChanged; public event Action RoundFinished; private void Awake() { spawner.Player = player; spawner.Collected += OnSnackCollected; } private void OnDestroy() { spawner.Collected -= OnSnackCollected; } private void Update() { MovePlayer(_paused || !RoundActive); if (!RoundActive || _paused) return; TimeLeftSeconds = Mathf.Max(0f, TimeLeftSeconds - Time.deltaTime); _spawnElapsedMs += Time.deltaTime * 1000f; if (_spawnElapsedMs >= controller.Tuning.SpawnIntervalMs) { _spawnElapsedMs = 0f; spawner.Spawn(); } var second = Mathf.CeilToInt(TimeLeftSeconds); if (second != _lastSecond) { _lastSecond = second; StateChanged?.Invoke(); } if (TimeLeftSeconds <= 0f) FinishRound(); } public void SetPaused(bool paused) { _paused = paused; } public void RestartRound() { Score = 0; Collected = 0; TimeLeftSeconds = controller.Tuning.RoundSeconds; _spawnElapsedMs = 0f; _lastSecond = -1; _finishRaised = false; spawner.Clear(); RoundActive = true; spawner.Spawn(); StateChanged?.Invoke(); } private void MovePlayer(bool frozen) { if (frozen) return; var vx = (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A) ? -1 : 0) + (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D) ? 1 : 0); var vy = (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S) ? -1 : 0) + (Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W) ? 1 : 0); if (vx == 0 && vy == 0) return; var direction = new Vector2(vx, vy).normalized; var speed = controller.Tuning.PlayerSpeed / PixelsPerUnit; var position = player.transform.position; position += (Vector3)(direction * speed * Time.deltaTime); position.x = Mathf.Clamp(position.x, moveMin.x, moveMax.x); position.y = Mathf.Clamp(position.y, moveMin.y, moveMax.y); player.transform.position = position; if (vx != 0) player.flipX = vx < 0; } private void OnSnackCollected(Snack snack) { var position = snack.transform.position; var gain = Mathf.RoundToInt(controller.Tuning.CollectibleScore); Score += gain; Collected += 1; var popup = Instantiate(scorePopupPrefab, position, Quaternion.identity); popup.Set(gain); Instantiate(collectBurstPrefab, position, Quaternion.identity); StateChanged?.Invoke(); } private void FinishRound() { if (_finishRaised) return; _finishRaised = true; RoundActive = false; spawner.Clear(); StateChanged?.Invoke(); RoundFinished?.Invoke(Score, Collected); } } }