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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|