150 lines
4.3 KiB
C#
150 lines
4.3 KiB
C#
using System;
|
||
using UnityEngine;
|
||
|
||
namespace LiveOpsExamples
|
||
{
|
||
/// <summary>
|
||
/// 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 <see cref="CozyCollectorController"/>.
|
||
///
|
||
/// 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.
|
||
/// </summary>
|
||
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<int, int> 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);
|
||
}
|
||
}
|
||
}
|