Initial commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared constants for the Cozy Collector demo: remote config keys with their
|
||||
/// defaults, LiveOps slugs, and the inspector capability palette.
|
||||
/// Mirrors liveops-phaser-demo/src/config.ts.
|
||||
/// </summary>
|
||||
public static class CozyCollectorConsts
|
||||
{
|
||||
// Remote config keys (mirror DemoRemoteConfig in the Phaser demo).
|
||||
public const string ConfigRoundSeconds = "demo_round_seconds";
|
||||
public const string ConfigPlayerSpeed = "demo_player_speed";
|
||||
public const string ConfigSpawnIntervalMs = "demo_spawn_interval_ms";
|
||||
public const string ConfigCollectibleScore = "demo_collectible_score";
|
||||
|
||||
public const float DefaultRoundSeconds = 150f;
|
||||
public const float DefaultPlayerSpeed = 260f;
|
||||
public const float DefaultSpawnIntervalMs = 900f;
|
||||
public const float DefaultCollectibleScore = 10f;
|
||||
|
||||
public const string LeaderboardSlug = "cozy-collector-score";
|
||||
public const string StoreSlug = "cozy-camp-shop";
|
||||
public const string OfferId = "moonberry-boost";
|
||||
public const string StorageType = "cozy_fantasy_save";
|
||||
public const string StorageId = "main";
|
||||
public const string NicknamePrefsKey = "rudder_demo_nickname";
|
||||
public const string Region = "global";
|
||||
public const string Language = "en";
|
||||
|
||||
// Inspector capability tags (same tags as the Phaser demo).
|
||||
public const string CapabilityAuth = "auth";
|
||||
public const string CapabilityConfig = "config";
|
||||
public const string CapabilityScenario = "scenario";
|
||||
public const string CapabilityStore = "store";
|
||||
public const string CapabilityLeaderboard = "leaderboard";
|
||||
public const string CapabilityStorage = "storage";
|
||||
public const string CapabilityInventory = "inventory";
|
||||
public const string CapabilityWallet = "wallet";
|
||||
public const string CapabilitySystem = "system";
|
||||
|
||||
/// <summary>Color used by the event inspector to tag a capability line.</summary>
|
||||
public static Color CapabilityColor(string capability)
|
||||
{
|
||||
switch (capability)
|
||||
{
|
||||
case CapabilityAuth: return new Color(0.55f, 0.76f, 1.00f);
|
||||
case CapabilityConfig: return new Color(0.61f, 0.90f, 0.69f);
|
||||
case CapabilityScenario: return new Color(0.85f, 0.68f, 1.00f);
|
||||
case CapabilityStore: return new Color(1.00f, 0.82f, 0.51f);
|
||||
case CapabilityLeaderboard: return new Color(1.00f, 0.72f, 0.58f);
|
||||
case CapabilityStorage: return new Color(0.56f, 0.87f, 0.87f);
|
||||
case CapabilityInventory: return new Color(0.78f, 0.86f, 0.55f);
|
||||
case CapabilityWallet: return new Color(1.00f, 0.88f, 0.63f);
|
||||
default: return new Color(0.75f, 0.75f, 0.75f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0f8000a396a64ea7890de20a70265ce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05ab47060c2ef4476800002c4a956b85
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
/// <summary>
|
||||
/// Baked sprite references for the Cozy Collector demo. Mirrors the Phaser
|
||||
/// texture keys; <see cref="ForSlug"/> falls back to the star sprite for
|
||||
/// unknown slugs, same as the Phaser backpack icon mapping.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "LiveOps Examples/Cozy Collector Sprite Library")]
|
||||
public class CozyCollectorSpriteLibrary : ScriptableObject
|
||||
{
|
||||
[SerializeField] private Sprite background;
|
||||
[SerializeField] private Sprite player;
|
||||
[SerializeField] private Sprite moonberry;
|
||||
[SerializeField] private Sprite acorn;
|
||||
[SerializeField] private Sprite star;
|
||||
[SerializeField] private Sprite snack;
|
||||
|
||||
public Sprite Background => background;
|
||||
public Sprite Player => player;
|
||||
public Sprite Moonberry => moonberry;
|
||||
public Sprite Acorn => acorn;
|
||||
public Sprite Star => star;
|
||||
public Sprite Snack => snack;
|
||||
|
||||
public Sprite ForSlug(string slug)
|
||||
{
|
||||
switch (slug)
|
||||
{
|
||||
case "background":
|
||||
case "camp-bg": return background;
|
||||
case "player": return player;
|
||||
case "moonberry": return moonberry;
|
||||
case "acorn": return acorn;
|
||||
case "snack": return snack;
|
||||
case "star":
|
||||
default: return star;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cd851b86da394b9ca06595f889a49b3
|
||||
@@ -0,0 +1,149 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 952f52af0af8546eeab332b11e2e4bb7
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
/// <summary>
|
||||
/// World-space "+N" score popup. Floats up ~0.5 world units and fades out over
|
||||
/// 0.7 s (Cubic.easeOut, like the Phaser floatScore tween), then destroys itself.
|
||||
/// </summary>
|
||||
public class ScorePopupView : MonoBehaviour
|
||||
{
|
||||
private const float FloatDistance = 0.5f;
|
||||
private const float LifetimeSeconds = 0.7f;
|
||||
|
||||
[SerializeField] private TextMeshPro label;
|
||||
|
||||
public void Set(int gain)
|
||||
{
|
||||
label.text = "+" + gain;
|
||||
StartCoroutine(Animate());
|
||||
}
|
||||
|
||||
private IEnumerator Animate()
|
||||
{
|
||||
var start = transform.position;
|
||||
var color = label.color;
|
||||
var elapsed = 0f;
|
||||
|
||||
while (elapsed < LifetimeSeconds)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
var t = Mathf.Clamp01(elapsed / LifetimeSeconds);
|
||||
var eased = 1f - Mathf.Pow(1f - t, 3f);
|
||||
transform.position = start + new Vector3(0f, FloatDistance * eased, 0f);
|
||||
label.color = new Color(color.r, color.g, color.b, color.a * (1f - eased));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1db4bf03192148bf95ea374d128f349
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
/// <summary>
|
||||
/// A collectible snack. Bobs gently (sine, 0.08 world units amplitude, 650 ms
|
||||
/// half-period — mirrors the Phaser yoyo tween) and raises <see cref="Collected"/>
|
||||
/// when the player overlaps its trigger, then destroys itself.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(SpriteRenderer))]
|
||||
public class Snack : MonoBehaviour
|
||||
{
|
||||
private const float BobAmplitude = 0.08f;
|
||||
private const float BobHalfPeriodSeconds = 0.65f;
|
||||
|
||||
public SpriteRenderer Player { get; set; }
|
||||
|
||||
public event Action<Snack> Collected;
|
||||
|
||||
private Vector3 _basePosition;
|
||||
private float _elapsed;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_basePosition = transform.position;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_elapsed += Time.deltaTime;
|
||||
var offset = Mathf.Sin(_elapsed * Mathf.PI / BobHalfPeriodSeconds) * BobAmplitude;
|
||||
transform.position = _basePosition + new Vector3(0f, offset, 0f);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (Player == null || other.gameObject != Player.gameObject)
|
||||
return;
|
||||
|
||||
Collected?.Invoke(this);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36b117cefc78541b9b42188266102562
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns collectible snacks at random positions inside the configured bounds,
|
||||
/// picking a random collectible slug (moonberry/acorn/star/snack) and applying
|
||||
/// its sprite from the library. Mirrors GameScene.spawnCollectible.
|
||||
///
|
||||
/// Phaser spawn margins x∈[115,1165], y∈[118,625] px on a 1280×720 field at
|
||||
/// 100 PPU map to x∈[-5.25,5.25], y∈[-2.65,2.42] world units (y is flipped).
|
||||
/// </summary>
|
||||
public class SnackSpawner : MonoBehaviour
|
||||
{
|
||||
private static readonly string[] CollectibleSlugs = { "moonberry", "acorn", "star", "snack" };
|
||||
|
||||
[SerializeField] private Snack prefab;
|
||||
[SerializeField] private CozyCollectorSpriteLibrary library;
|
||||
[SerializeField] private Vector2 spawnMin = new Vector2(-5.25f, -2.65f);
|
||||
[SerializeField] private Vector2 spawnMax = new Vector2(5.25f, 2.42f);
|
||||
[SerializeField] private Transform parent;
|
||||
|
||||
private readonly List<Snack> _spawned = new List<Snack>();
|
||||
|
||||
public SpriteRenderer Player { get; set; }
|
||||
|
||||
public event Action<Snack> Collected;
|
||||
|
||||
public Snack Spawn()
|
||||
{
|
||||
var slug = CollectibleSlugs[UnityEngine.Random.Range(0, CollectibleSlugs.Length)];
|
||||
var position = new Vector3(
|
||||
UnityEngine.Random.Range(spawnMin.x, spawnMax.x),
|
||||
UnityEngine.Random.Range(spawnMin.y, spawnMax.y),
|
||||
0f);
|
||||
|
||||
var snack = Instantiate(prefab, position, Quaternion.identity, parent);
|
||||
var sprite = library.ForSlug(slug);
|
||||
snack.GetComponent<SpriteRenderer>().sprite = sprite;
|
||||
// Phaser setDisplaySize(52, 52) at 100 PPU -> 0.52 x 0.52 world units.
|
||||
var spriteBounds = sprite.bounds.size;
|
||||
var scale = new Vector3(0.52f / spriteBounds.x, 0.52f / spriteBounds.y, 1f);
|
||||
snack.transform.localScale = scale;
|
||||
// The prefab collider radius is tuned to snack.png; a circle collider's
|
||||
// world radius scales by the largest transform axis, so retune per sprite.
|
||||
snack.GetComponent<CircleCollider2D>().radius = 0.22f / Mathf.Max(scale.x, scale.y);
|
||||
snack.Player = Player;
|
||||
snack.Collected += OnSnackCollected;
|
||||
_spawned.Add(snack);
|
||||
return snack;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var snack in _spawned)
|
||||
{
|
||||
if (snack == null)
|
||||
continue;
|
||||
|
||||
snack.Collected -= OnSnackCollected;
|
||||
Destroy(snack.gameObject);
|
||||
}
|
||||
|
||||
_spawned.Clear();
|
||||
}
|
||||
|
||||
private void OnSnackCollected(Snack snack)
|
||||
{
|
||||
snack.Collected -= OnSnackCollected;
|
||||
_spawned.Remove(snack);
|
||||
Collected?.Invoke(snack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec58b83d93d5b435abaaa64ecc05fe1a
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c21c943a5e1bd4f79a2d6bcb2e3f30fb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class BackpackModalPanel : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
[SerializeField] private VisualTreeAsset backpackItemRowTemplate;
|
||||
[SerializeField] private CozyCollectorSpriteLibrary spriteLibrary;
|
||||
|
||||
private VisualElement _root;
|
||||
private ScrollView _itemsScroll;
|
||||
private VisualElement _emptyNote;
|
||||
private Button _closeButton;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged += ApplyVisibility;
|
||||
controller.BackpackChanged += Rebuild;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged -= ApplyVisibility;
|
||||
controller.BackpackChanged -= Rebuild;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_root = root.Q<VisualElement>("backpack-modal");
|
||||
_itemsScroll = _root.Q<ScrollView>("backpack-items-scroll");
|
||||
_emptyNote = _root.Q<VisualElement>("backpack-empty");
|
||||
_closeButton = _root.Q<Button>("backpack-close-button");
|
||||
|
||||
_closeButton.clicked -= HandleCloseClicked;
|
||||
_closeButton.clicked += HandleCloseClicked;
|
||||
|
||||
ApplyVisibility();
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
_root.style.display = controller.ActiveModal == CozyModal.Backpack ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void Rebuild()
|
||||
{
|
||||
_itemsScroll.contentContainer.Clear();
|
||||
|
||||
var items = controller.BackpackItems;
|
||||
_emptyNote.style.display = items == null || items.Count == 0 ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (items == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var rowRoot = backpackItemRowTemplate.CloneTree();
|
||||
_itemsScroll.contentContainer.Add(rowRoot);
|
||||
|
||||
var row = new BackpackItemRowView(rowRoot);
|
||||
row.Set(
|
||||
spriteLibrary.ForSlug(item.Slug),
|
||||
string.IsNullOrEmpty(item.Name) ? item.Slug : item.Name,
|
||||
item.Slug,
|
||||
item.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCloseClicked()
|
||||
{
|
||||
controller.CloseModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c73aae57f5c749b88f5bf2c22538af5
|
||||
@@ -0,0 +1,70 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class BootOverlayPanel : MonoBehaviour
|
||||
{
|
||||
private const float SpinnerDegreesPerSecond = 180f;
|
||||
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
|
||||
private VisualElement _overlay;
|
||||
private Label _statusLabel;
|
||||
private VisualElement _spinner;
|
||||
private IVisualElementScheduledItem _spinSchedule;
|
||||
private float _angle;
|
||||
private float _lastTick;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.PhaseChanged += ApplyVisibility;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.PhaseChanged -= ApplyVisibility;
|
||||
_spinSchedule?.Pause();
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_overlay = root.Q<VisualElement>("boot-overlay");
|
||||
_statusLabel = root.Q<Label>("boot-status");
|
||||
_spinner = root.Q<VisualElement>("boot-spinner");
|
||||
_spinSchedule = _spinner.schedule.Execute(Spin).Every(16);
|
||||
ApplyVisibility();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_statusLabel.text = controller.StatusMessage;
|
||||
}
|
||||
|
||||
private void Spin()
|
||||
{
|
||||
var now = Time.unscaledTime;
|
||||
_angle = (_angle + SpinnerDegreesPerSecond * (now - _lastTick)) % 360f;
|
||||
_lastTick = now;
|
||||
_spinner.style.rotate = new Rotate(Angle.Degrees(_angle));
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
var visible = controller.Phase == CozyPhase.Booting || controller.Phase == CozyPhase.Connecting;
|
||||
_overlay.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (visible)
|
||||
{
|
||||
_lastTick = Time.unscaledTime;
|
||||
_spinSchedule.Resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
_spinSchedule.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f54172384bff5402686bb558e2abb5b7
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class EventInspectorPanel : MonoBehaviour
|
||||
{
|
||||
private static readonly Dictionary<string, Color> CapabilityColors = new Dictionary<string, Color>
|
||||
{
|
||||
{ "auth", new Color(0.486f, 0.769f, 1f) },
|
||||
{ "config", new Color(0.945f, 0.776f, 0.490f) },
|
||||
{ "scenario", new Color(0.769f, 0.635f, 1f) },
|
||||
{ "store", new Color(1f, 0.698f, 0.490f) },
|
||||
{ "leaderboard", new Color(0.482f, 0.878f, 0.690f) },
|
||||
{ "storage", new Color(0.624f, 0.690f, 0.769f) },
|
||||
{ "inventory", new Color(0.898f, 0.604f, 0.796f) },
|
||||
{ "wallet", new Color(1f, 0.831f, 0.475f) },
|
||||
{ "system", new Color(0.667f, 0.706f, 0.635f) },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> CapabilityLabels = new Dictionary<string, string>
|
||||
{
|
||||
{ "auth", "Auth" },
|
||||
{ "config", "Config" },
|
||||
{ "scenario", "Scenario" },
|
||||
{ "store", "Store" },
|
||||
{ "leaderboard", "Board" },
|
||||
{ "storage", "Storage" },
|
||||
{ "inventory", "Items" },
|
||||
{ "wallet", "Wallet" },
|
||||
{ "system", "System" },
|
||||
};
|
||||
|
||||
private static readonly Color FallbackColor = new Color(0.667f, 0.706f, 0.635f);
|
||||
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
[SerializeField] private VisualTreeAsset logRowTemplate;
|
||||
|
||||
private ScrollView _scroll;
|
||||
private VisualElement _emptyNote;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.LogAdded += Rebuild;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.LogAdded -= Rebuild;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_scroll = root.Q<ScrollView>("event-log-scroll");
|
||||
_emptyNote = root.Q("event-log-empty");
|
||||
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
private void Rebuild()
|
||||
{
|
||||
var container = _scroll.contentContainer;
|
||||
container.Clear();
|
||||
|
||||
var entries = controller.LogEntries;
|
||||
_emptyNote.style.display = entries == null || entries.Count == 0 ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (entries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var rowRoot = logRowTemplate.CloneTree();
|
||||
container.Add(rowRoot);
|
||||
|
||||
string key = entry.Capability == null ? string.Empty : entry.Capability.ToLowerInvariant();
|
||||
string label = CapabilityLabels.TryGetValue(key, out var known) ? known : entry.Capability;
|
||||
Color color = CapabilityColors.TryGetValue(key, out var knownColor) ? knownColor : FallbackColor;
|
||||
string detail = string.IsNullOrEmpty(entry.Detail) ? string.Empty : $" — {entry.Detail}";
|
||||
new LogRowView(rowRoot).Set(entry.Timestamp.ToString("HH:mm:ss"), label, color, entry.Message, detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a134e625b6e114c4b80b9f55a14e48b3
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class HudPanel : MonoBehaviour
|
||||
{
|
||||
private const string TimerLowClass = "timer-low";
|
||||
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
[SerializeField] private GameWorld world;
|
||||
|
||||
private Label _scoreLabel;
|
||||
private Label _bestLabel;
|
||||
private Label _timeLeftLabel;
|
||||
private VisualElement _timerFill;
|
||||
private Label _walletLabel;
|
||||
private Button _shopButton;
|
||||
private Button _backpackButton;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
world.StateChanged += Refresh;
|
||||
world.RoundFinished += HandleRoundFinished;
|
||||
controller.WalletsChanged += RefreshWallet;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
world.StateChanged -= Refresh;
|
||||
world.RoundFinished -= HandleRoundFinished;
|
||||
controller.WalletsChanged -= RefreshWallet;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_scoreLabel = root.Q<Label>("hud-score");
|
||||
_bestLabel = root.Q<Label>("hud-best");
|
||||
_timeLeftLabel = root.Q<Label>("hud-time-left");
|
||||
_timerFill = root.Q("hud-timer-fill");
|
||||
_walletLabel = root.Q<Label>("hud-wallet");
|
||||
_shopButton = root.Q<Button>("hud-shop-button");
|
||||
_backpackButton = root.Q<Button>("hud-backpack-button");
|
||||
|
||||
_shopButton.clicked -= HandleShopClicked;
|
||||
_shopButton.clicked += HandleShopClicked;
|
||||
_backpackButton.clicked -= HandleBackpackClicked;
|
||||
_backpackButton.clicked += HandleBackpackClicked;
|
||||
|
||||
Refresh();
|
||||
RefreshWallet();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
RefreshTimer();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
_scoreLabel.text = world.Score.ToString();
|
||||
_bestLabel.text = controller.Save != null ? controller.Save.BestScore.ToString() : "0";
|
||||
RefreshTimer();
|
||||
}
|
||||
|
||||
private void RefreshTimer()
|
||||
{
|
||||
float roundSeconds = controller.Tuning != null ? Mathf.Max(1f, controller.Tuning.RoundSeconds) : 1f;
|
||||
float fraction = Mathf.Clamp01(world.TimeLeftSeconds / roundSeconds);
|
||||
_timeLeftLabel.text = $"{Mathf.CeilToInt(Mathf.Max(0f, world.TimeLeftSeconds))}s";
|
||||
_timerFill.style.width = Length.Percent(fraction * 100f);
|
||||
_timerFill.EnableInClassList(TimerLowClass, fraction <= 0.2f);
|
||||
}
|
||||
|
||||
private void RefreshWallet()
|
||||
{
|
||||
_walletLabel.text = WalletLabel();
|
||||
}
|
||||
|
||||
private string WalletLabel()
|
||||
{
|
||||
var wallets = controller.Wallets;
|
||||
if (wallets == null || wallets.Count == 0)
|
||||
{
|
||||
return "—";
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var wallet in wallets)
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append(" · ");
|
||||
}
|
||||
builder.Append($"{wallet.Balance:0.##} {wallet.Currency}".TrimEnd());
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void HandleRoundFinished(int score, int collected)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void HandleShopClicked()
|
||||
{
|
||||
controller.OpenModal(CozyModal.Shop);
|
||||
}
|
||||
|
||||
private void HandleBackpackClicked()
|
||||
{
|
||||
controller.OpenModal(CozyModal.Backpack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e5e5cb2ad54545a6847ad8c90d1f7d8
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class OfferModalPanel : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
|
||||
private VisualElement root;
|
||||
private Label titleLabel;
|
||||
private Label nameLabel;
|
||||
private Label priceLabel;
|
||||
private Button buyButton;
|
||||
private Button declineButton;
|
||||
private Button closeButton;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged += ApplyVisibility;
|
||||
controller.OfferChanged += Refresh;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged -= ApplyVisibility;
|
||||
controller.OfferChanged -= Refresh;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
this.root = root.Q<VisualElement>("offer-modal");
|
||||
titleLabel = this.root.Q<Label>("offer-title");
|
||||
nameLabel = this.root.Q<Label>("offer-name");
|
||||
priceLabel = this.root.Q<Label>("offer-price");
|
||||
buyButton = this.root.Q<Button>("offer-buy-button");
|
||||
declineButton = this.root.Q<Button>("offer-decline-button");
|
||||
closeButton = this.root.Q<Button>("offer-close-button");
|
||||
|
||||
buyButton.clicked -= HandleBuyClicked;
|
||||
buyButton.clicked += HandleBuyClicked;
|
||||
declineButton.clicked -= HandleDeclineClicked;
|
||||
declineButton.clicked += HandleDeclineClicked;
|
||||
closeButton.clicked -= HandleCloseClicked;
|
||||
closeButton.clicked += HandleCloseClicked;
|
||||
|
||||
ApplyVisibility();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
root.style.display = controller.ActiveModal == CozyModal.Offer ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
var offerInfo = controller.CurrentOffer;
|
||||
if (offerInfo == null)
|
||||
{
|
||||
titleLabel.text = "Offer";
|
||||
nameLabel.text = "—";
|
||||
priceLabel.text = string.Empty;
|
||||
buyButton.SetEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
titleLabel.text = string.IsNullOrEmpty(offerInfo.StoreName) ? offerInfo.StoreSlug : offerInfo.StoreName;
|
||||
|
||||
var offer = offerInfo.Offers != null && offerInfo.Offers.Count > 0 ? offerInfo.Offers[0] : null;
|
||||
if (offer == null)
|
||||
{
|
||||
nameLabel.text = "—";
|
||||
priceLabel.text = string.Empty;
|
||||
buyButton.SetEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
nameLabel.text = string.IsNullOrEmpty(offer.Name) ? offer.OfferId : offer.Name;
|
||||
priceLabel.text = ShopModalPanel.PriceLabel(offer.Price, offer.Currency);
|
||||
buyButton.SetEnabled(true);
|
||||
}
|
||||
|
||||
private void HandleBuyClicked()
|
||||
{
|
||||
controller.BuyScenarioOffer();
|
||||
}
|
||||
|
||||
private void HandleDeclineClicked()
|
||||
{
|
||||
controller.DeclineScenarioOffer();
|
||||
}
|
||||
|
||||
private void HandleCloseClicked()
|
||||
{
|
||||
controller.CloseModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b996708622cc54d328cac546b893e111
|
||||
@@ -0,0 +1,92 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class RemoteConfigPanel : MonoBehaviour
|
||||
{
|
||||
private const string FlashClass = "flash";
|
||||
private const long FlashMilliseconds = 600;
|
||||
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
|
||||
private Label _playerSpeedValue;
|
||||
private Label _spawnIntervalValue;
|
||||
private Label _collectibleScoreValue;
|
||||
private Label _roundSecondsValue;
|
||||
private VisualElement _playerSpeedRow;
|
||||
private VisualElement _spawnIntervalRow;
|
||||
private VisualElement _collectibleScoreRow;
|
||||
private VisualElement _roundSecondsRow;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.TuningChanged += HandleTuningChanged;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.TuningChanged -= HandleTuningChanged;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_playerSpeedValue = root.Q<Label>("rc-value-player-speed");
|
||||
_spawnIntervalValue = root.Q<Label>("rc-value-spawn-interval");
|
||||
_collectibleScoreValue = root.Q<Label>("rc-value-collectible-score");
|
||||
_roundSecondsValue = root.Q<Label>("rc-value-round-seconds");
|
||||
_playerSpeedRow = root.Q("rc-row-player-speed");
|
||||
_spawnIntervalRow = root.Q("rc-row-spawn-interval");
|
||||
_collectibleScoreRow = root.Q("rc-row-collectible-score");
|
||||
_roundSecondsRow = root.Q("rc-row-round-seconds");
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
var tuning = controller.Tuning;
|
||||
if (tuning == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_playerSpeedValue.text = $"{Mathf.RoundToInt(tuning.PlayerSpeed)}";
|
||||
_spawnIntervalValue.text = $"{Mathf.RoundToInt(tuning.SpawnIntervalMs)} ms";
|
||||
_collectibleScoreValue.text = $"+{Mathf.RoundToInt(tuning.CollectibleScore)}";
|
||||
_roundSecondsValue.text = $"{Mathf.RoundToInt(tuning.RoundSeconds)} s";
|
||||
}
|
||||
|
||||
private void HandleTuningChanged(string key)
|
||||
{
|
||||
Refresh();
|
||||
|
||||
string normalized = key == null ? string.Empty : key.ToLowerInvariant().Replace("_", string.Empty).Replace("-", string.Empty).Replace(" ", string.Empty);
|
||||
if (normalized.Contains("playerspeed"))
|
||||
{
|
||||
Flash(_playerSpeedRow);
|
||||
}
|
||||
else if (normalized.Contains("spawninterval"))
|
||||
{
|
||||
Flash(_spawnIntervalRow);
|
||||
}
|
||||
else if (normalized.Contains("collectiblescore"))
|
||||
{
|
||||
Flash(_collectibleScoreRow);
|
||||
}
|
||||
else if (normalized.Contains("roundseconds"))
|
||||
{
|
||||
Flash(_roundSecondsRow);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Flash(VisualElement row)
|
||||
{
|
||||
row.AddToClassList(FlashClass);
|
||||
row.schedule.Execute(() => row.RemoveFromClassList(FlashClass)).StartingIn(FlashMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d57e6a031317461cb98e41f6940c8d3
|
||||
@@ -0,0 +1,117 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class RoundResultModalPanel : MonoBehaviour
|
||||
{
|
||||
private const int MaxLeaderboardRows = 10;
|
||||
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
[SerializeField] private VisualTreeAsset leaderboardRowTemplate;
|
||||
|
||||
private VisualElement root;
|
||||
private Label scoreLabel;
|
||||
private Label bestLabel;
|
||||
private Label collectedLabel;
|
||||
private ScrollView leaderboardScroll;
|
||||
private VisualElement emptyNote;
|
||||
private Button playAgainButton;
|
||||
private Button claimButton;
|
||||
private Button endButton;
|
||||
private Button closeButton;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged += ApplyVisibility;
|
||||
controller.RoundResultChanged += Refresh;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged -= ApplyVisibility;
|
||||
controller.RoundResultChanged -= Refresh;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
this.root = root.Q<VisualElement>("round-result-modal");
|
||||
scoreLabel = this.root.Q<Label>("rr-score");
|
||||
bestLabel = this.root.Q<Label>("rr-best");
|
||||
collectedLabel = this.root.Q<Label>("rr-collected");
|
||||
leaderboardScroll = this.root.Q<ScrollView>("rr-leaderboard-scroll");
|
||||
emptyNote = this.root.Q<VisualElement>("rr-empty");
|
||||
playAgainButton = this.root.Q<Button>("rr-play-again-button");
|
||||
claimButton = this.root.Q<Button>("rr-claim-button");
|
||||
endButton = this.root.Q<Button>("rr-end-button");
|
||||
closeButton = this.root.Q<Button>("rr-close-button");
|
||||
|
||||
playAgainButton.clicked -= HandlePlayAgainClicked;
|
||||
playAgainButton.clicked += HandlePlayAgainClicked;
|
||||
claimButton.clicked -= HandleClaimClicked;
|
||||
claimButton.clicked += HandleClaimClicked;
|
||||
endButton.clicked -= HandleEndClicked;
|
||||
endButton.clicked += HandleEndClicked;
|
||||
closeButton.clicked -= HandleCloseClicked;
|
||||
closeButton.clicked += HandleCloseClicked;
|
||||
|
||||
ApplyVisibility();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
root.style.display = controller.ActiveModal == CozyModal.RoundResult ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
var result = controller.RoundResult;
|
||||
scoreLabel.text = result != null ? result.Score.ToString() : "0";
|
||||
bestLabel.text = result != null ? result.Best.ToString() : "0";
|
||||
collectedLabel.text = result != null ? result.Collected.ToString() : "0";
|
||||
|
||||
leaderboardScroll.Clear();
|
||||
|
||||
var entries = result != null ? result.TopEntries : null;
|
||||
emptyNote.style.display = entries == null || entries.Count == 0 ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (entries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = Mathf.Min(entries.Count, MaxLeaderboardRows);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var entry = entries[i];
|
||||
var rowRoot = leaderboardRowTemplate.CloneTree();
|
||||
var row = new LeaderboardRowView(rowRoot);
|
||||
row.Set(entry.Rank, string.IsNullOrEmpty(entry.PlayerName) ? "player" : entry.PlayerName, entry.Score);
|
||||
leaderboardScroll.Add(rowRoot);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePlayAgainClicked()
|
||||
{
|
||||
controller.PlayAgain();
|
||||
}
|
||||
|
||||
private void HandleClaimClicked()
|
||||
{
|
||||
controller.ClaimScenarioLeaderboardReward();
|
||||
}
|
||||
|
||||
private void HandleEndClicked()
|
||||
{
|
||||
controller.EndScenarioLeaderboard();
|
||||
}
|
||||
|
||||
private void HandleCloseClicked()
|
||||
{
|
||||
controller.CloseModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f3916e69e6ce4c78b909c659160ad95
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cde562fa676ee420795c27bd66549cab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class BackpackItemRowView
|
||||
{
|
||||
private readonly VisualElement _icon;
|
||||
private readonly Label _nameLabel;
|
||||
private readonly Label _metaLabel;
|
||||
private readonly Label _amountLabel;
|
||||
|
||||
public BackpackItemRowView(VisualElement root)
|
||||
{
|
||||
_icon = root.Q<VisualElement>("item-icon");
|
||||
_nameLabel = root.Q<Label>("item-name");
|
||||
_metaLabel = root.Q<Label>("item-meta");
|
||||
_amountLabel = root.Q<Label>("item-amount");
|
||||
}
|
||||
|
||||
public void Set(Sprite icon, string itemName, string slug, int amount)
|
||||
{
|
||||
_icon.style.backgroundImage = new StyleBackground(icon);
|
||||
_nameLabel.text = itemName;
|
||||
_metaLabel.text = slug;
|
||||
_amountLabel.text = $"×{amount}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7a25568da97f48f3b07abf321ad2f82
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class LeaderboardRowView
|
||||
{
|
||||
private readonly Label rankLabel;
|
||||
private readonly Label playerLabel;
|
||||
private readonly Label scoreLabel;
|
||||
|
||||
public LeaderboardRowView(VisualElement root)
|
||||
{
|
||||
rankLabel = root.Q<Label>("lb-rank");
|
||||
playerLabel = root.Q<Label>("lb-player");
|
||||
scoreLabel = root.Q<Label>("lb-score");
|
||||
}
|
||||
|
||||
public void Set(int rank, string playerName, int score)
|
||||
{
|
||||
rankLabel.text = rank > 0 ? $"#{rank}" : "—";
|
||||
playerLabel.text = playerName;
|
||||
scoreLabel.text = score.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29788c49080d642ff919c1d25b5dd979
|
||||
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class LogRowView
|
||||
{
|
||||
private readonly Label _timeLabel;
|
||||
private readonly Label _tagLabel;
|
||||
private readonly Label _messageLabel;
|
||||
private readonly Label _detailLabel;
|
||||
|
||||
public LogRowView(VisualElement root)
|
||||
{
|
||||
_timeLabel = root.Q<Label>("log-time");
|
||||
_tagLabel = root.Q<Label>("log-tag");
|
||||
_messageLabel = root.Q<Label>("log-message");
|
||||
_detailLabel = root.Q<Label>("log-detail");
|
||||
}
|
||||
|
||||
public void Set(string time, string tag, Color tagColor, string message, string detail)
|
||||
{
|
||||
_timeLabel.text = time;
|
||||
_tagLabel.text = tag;
|
||||
_tagLabel.style.color = tagColor;
|
||||
_tagLabel.style.backgroundColor = new Color(tagColor.r, tagColor.g, tagColor.b, 0.18f);
|
||||
_messageLabel.text = message;
|
||||
_detailLabel.text = detail;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffa213acafee84d818a3cf631f0bb01b
|
||||
@@ -0,0 +1,26 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class ShopOfferRowView
|
||||
{
|
||||
private readonly Label _nameLabel;
|
||||
private readonly Label _priceLabel;
|
||||
private readonly Button _buyButton;
|
||||
|
||||
public Button BuyButton => _buyButton;
|
||||
|
||||
public ShopOfferRowView(VisualElement root)
|
||||
{
|
||||
_nameLabel = root.Q<Label>("offer-name");
|
||||
_priceLabel = root.Q<Label>("offer-price");
|
||||
_buyButton = root.Q<Button>("offer-buy-button");
|
||||
}
|
||||
|
||||
public void Set(string offerName, string price)
|
||||
{
|
||||
_nameLabel.text = offerName;
|
||||
_priceLabel.text = price;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94bf1e4d6fb50418487ed96aa4d65d4b
|
||||
@@ -0,0 +1,32 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class ToastView
|
||||
{
|
||||
private const string ToneInfoClass = "tone-info";
|
||||
private const string ToneSuccessClass = "tone-success";
|
||||
private const string ToneErrorClass = "tone-error";
|
||||
|
||||
private readonly Label _titleLabel;
|
||||
private readonly Label _messageLabel;
|
||||
|
||||
public ToastView(VisualElement root)
|
||||
{
|
||||
Root = root;
|
||||
_titleLabel = root.Q<Label>("toast-title");
|
||||
_messageLabel = root.Q<Label>("toast-message");
|
||||
}
|
||||
|
||||
public VisualElement Root { get; }
|
||||
|
||||
public void Set(string title, string message, CozyToastTone tone)
|
||||
{
|
||||
_titleLabel.text = title;
|
||||
_messageLabel.text = message;
|
||||
Root.EnableInClassList(ToneInfoClass, tone == CozyToastTone.Info);
|
||||
Root.EnableInClassList(ToneSuccessClass, tone == CozyToastTone.Success);
|
||||
Root.EnableInClassList(ToneErrorClass, tone == CozyToastTone.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d468efab7b7645f78186c03b82f5d32
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class SessionCardPanel : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
|
||||
private Label _nicknameLabel;
|
||||
private Label _playerIdLabel;
|
||||
private Label _deviceIdLabel;
|
||||
private Label _regionLabel;
|
||||
private Label _projectKeyLabel;
|
||||
private Label _walletsLabel;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.PhaseChanged += Refresh;
|
||||
controller.WalletsChanged += RefreshWallets;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.PhaseChanged -= Refresh;
|
||||
controller.WalletsChanged -= RefreshWallets;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_nicknameLabel = root.Q<Label>("session-nickname");
|
||||
_playerIdLabel = root.Q<Label>("session-player-id");
|
||||
_deviceIdLabel = root.Q<Label>("session-device-id");
|
||||
_regionLabel = root.Q<Label>("session-region");
|
||||
_projectKeyLabel = root.Q<Label>("session-project-key");
|
||||
_walletsLabel = root.Q<Label>("session-wallets");
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
var identity = controller.Identity;
|
||||
if (identity == null)
|
||||
{
|
||||
_nicknameLabel.text = "—";
|
||||
_playerIdLabel.text = "—";
|
||||
_deviceIdLabel.text = "—";
|
||||
_regionLabel.text = "—";
|
||||
_projectKeyLabel.text = "—";
|
||||
}
|
||||
else
|
||||
{
|
||||
_nicknameLabel.text = string.IsNullOrEmpty(identity.Nickname)
|
||||
? $"Player {identity.PlayerId.Substring(0, Mathf.Min(6, identity.PlayerId.Length))}"
|
||||
: identity.Nickname;
|
||||
_playerIdLabel.text = identity.PlayerId;
|
||||
_deviceIdLabel.text = identity.DeviceId;
|
||||
_regionLabel.text = string.IsNullOrEmpty(identity.Region) ? "global" : identity.Region;
|
||||
_projectKeyLabel.text = identity.ProjectKey;
|
||||
}
|
||||
|
||||
RefreshWallets();
|
||||
}
|
||||
|
||||
private void RefreshWallets()
|
||||
{
|
||||
var wallets = controller.Wallets;
|
||||
if (wallets == null || wallets.Count == 0)
|
||||
{
|
||||
_walletsLabel.text = "No wallets";
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var wallet in wallets)
|
||||
{
|
||||
builder.AppendLine($"{wallet.Balance:0.##} {wallet.Currency}".TrimEnd());
|
||||
}
|
||||
_walletsLabel.text = builder.ToString().TrimEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 900b9e31987914b56a3bba037e2a830c
|
||||
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class SetupOverlayPanel : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
|
||||
private VisualElement _overlay;
|
||||
private Label _errorLabel;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.PhaseChanged += ApplyVisibility;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.PhaseChanged -= ApplyVisibility;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_overlay = root.Q<VisualElement>("setup-overlay");
|
||||
_errorLabel = root.Q<Label>("setup-error");
|
||||
ApplyVisibility();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_errorLabel.text = controller.SetupError;
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
_overlay.style.display = controller.Phase == CozyPhase.Setup ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6de014257693b4ced830566d18e49444
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class ShopModalPanel : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
[SerializeField] private VisualTreeAsset shopOfferRowTemplate;
|
||||
|
||||
private VisualElement _root;
|
||||
private Label _walletLabel;
|
||||
private ScrollView _offersScroll;
|
||||
private VisualElement _emptyNote;
|
||||
private Button _closeButton;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged += ApplyVisibility;
|
||||
controller.ShopChanged += Rebuild;
|
||||
controller.WalletsChanged += RefreshWallet;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.ModalChanged -= ApplyVisibility;
|
||||
controller.ShopChanged -= Rebuild;
|
||||
controller.WalletsChanged -= RefreshWallet;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_root = root.Q<VisualElement>("shop-modal");
|
||||
_walletLabel = _root.Q<Label>("shop-wallet");
|
||||
_offersScroll = _root.Q<ScrollView>("shop-offers-scroll");
|
||||
_emptyNote = _root.Q<VisualElement>("shop-empty");
|
||||
_closeButton = _root.Q<Button>("shop-close-button");
|
||||
|
||||
_closeButton.clicked -= HandleCloseClicked;
|
||||
_closeButton.clicked += HandleCloseClicked;
|
||||
|
||||
ApplyVisibility();
|
||||
RefreshWallet();
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
_root.style.display = controller.ActiveModal == CozyModal.Shop ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void RefreshWallet()
|
||||
{
|
||||
var wallets = controller.Wallets;
|
||||
if (wallets == null || wallets.Count == 0)
|
||||
{
|
||||
_walletLabel.text = "Wallet: —";
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder("Wallet: ");
|
||||
foreach (var wallet in wallets)
|
||||
{
|
||||
if (builder.Length > "Wallet: ".Length)
|
||||
{
|
||||
builder.Append(" · ");
|
||||
}
|
||||
builder.Append($"{wallet.Balance:0.##} {wallet.Currency}".TrimEnd());
|
||||
}
|
||||
_walletLabel.text = builder.ToString();
|
||||
}
|
||||
|
||||
private void Rebuild()
|
||||
{
|
||||
_offersScroll.contentContainer.Clear();
|
||||
|
||||
var offers = controller.ShopOffers;
|
||||
_emptyNote.style.display = offers == null || offers.Count == 0 ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (offers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var offer in offers)
|
||||
{
|
||||
var rowRoot = shopOfferRowTemplate.CloneTree();
|
||||
_offersScroll.contentContainer.Add(rowRoot);
|
||||
|
||||
var row = new ShopOfferRowView(rowRoot);
|
||||
row.Set(string.IsNullOrEmpty(offer.Name) ? offer.OfferId : offer.Name, PriceLabel(offer.Price, offer.Currency));
|
||||
|
||||
string offerId = offer.OfferId;
|
||||
row.BuyButton.clicked += () => controller.BuyShopOffer(offerId);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string PriceLabel(double price, string currency)
|
||||
{
|
||||
if (price <= 0)
|
||||
{
|
||||
return "Free";
|
||||
}
|
||||
return $"{price:0.##} {currency}".TrimEnd();
|
||||
}
|
||||
|
||||
private void HandleCloseClicked()
|
||||
{
|
||||
controller.CloseModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36789e549043d400a92f3a84849b4038
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public class ToastsPanel : MonoBehaviour
|
||||
{
|
||||
private const float ToastLifetimeSeconds = 4.2f;
|
||||
|
||||
[SerializeField] private PanelRenderer panelRenderer;
|
||||
[SerializeField] private CozyCollectorController controller;
|
||||
[SerializeField] private VisualTreeAsset toastTemplate;
|
||||
|
||||
private readonly List<ToastView> _toasts = new List<ToastView>();
|
||||
private readonly List<float> _dismissAt = new List<float>();
|
||||
|
||||
private VisualElement _container;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
controller.ToastRequested += HandleToastRequested;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
controller.ToastRequested -= HandleToastRequested;
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_container = root.Q("toasts");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
for (int i = _toasts.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Time.unscaledTime >= _dismissAt[i])
|
||||
{
|
||||
Dismiss(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleToastRequested(string title, string message, CozyToastTone tone)
|
||||
{
|
||||
var root = toastTemplate.CloneTree();
|
||||
_container.Add(root);
|
||||
|
||||
var toast = new ToastView(root);
|
||||
toast.Set(title, message, tone);
|
||||
toast.Root.RegisterCallback<ClickEvent>(_ => Dismiss(toast));
|
||||
|
||||
_toasts.Add(toast);
|
||||
_dismissAt.Add(Time.unscaledTime + ToastLifetimeSeconds);
|
||||
}
|
||||
|
||||
private void Dismiss(ToastView toast)
|
||||
{
|
||||
int index = _toasts.IndexOf(toast);
|
||||
if (index >= 0)
|
||||
{
|
||||
Dismiss(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void Dismiss(int index)
|
||||
{
|
||||
var toast = _toasts[index];
|
||||
_toasts.RemoveAt(index);
|
||||
_dismissAt.RemoveAt(index);
|
||||
toast.Root.RemoveFromHierarchy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57e0ed949960344bd80072dd54a52c4f
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace LiveOpsExamples
|
||||
{
|
||||
public static class UiDebugProbe
|
||||
{
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
private static void Install()
|
||||
{
|
||||
new GameObject("UiDebugProbe").AddComponent<Probe>();
|
||||
}
|
||||
|
||||
private sealed class Probe : MonoBehaviour
|
||||
{
|
||||
private PanelRenderer _panelRenderer;
|
||||
private VisualElement _root;
|
||||
|
||||
private IEnumerator Start()
|
||||
{
|
||||
yield return null;
|
||||
yield return null;
|
||||
|
||||
_panelRenderer = FindFirstObjectByType<PanelRenderer>();
|
||||
if (_panelRenderer == null)
|
||||
{
|
||||
Debug.Log("[UiProbe] PanelRenderer: NOT FOUND");
|
||||
}
|
||||
else
|
||||
{
|
||||
_panelRenderer.RegisterUIReloadCallback(OnUIReload);
|
||||
}
|
||||
|
||||
if (_root == null)
|
||||
{
|
||||
Dump("after-scene-load");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(10f);
|
||||
Dump("periodic");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_panelRenderer != null)
|
||||
{
|
||||
_panelRenderer.UnregisterUIReloadCallback(OnUIReload);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUIReload(PanelRenderer renderer, VisualElement root)
|
||||
{
|
||||
_root = root;
|
||||
Dump("ui-reload");
|
||||
}
|
||||
|
||||
private void Dump(string tag)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"[UiProbe] --- {tag} ---");
|
||||
sb.AppendLine($"screen={Screen.width}x{Screen.height}");
|
||||
|
||||
if (_panelRenderer == null)
|
||||
{
|
||||
sb.AppendLine("PanelRenderer: NOT FOUND");
|
||||
Debug.Log(sb.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
var ps = _panelRenderer.panelSettings;
|
||||
sb.AppendLine(ps == null
|
||||
? "panelSettings: NULL"
|
||||
: $"panelSettings={ps.name} scaleMode={ps.scaleMode} ref={ps.referenceResolution.x}x{ps.referenceResolution.y} match={ps.match} scale={ps.scale}");
|
||||
|
||||
if (_root == null)
|
||||
{
|
||||
sb.AppendLine("root: NULL (reload callback not fired yet)");
|
||||
Debug.Log(sb.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
sb.AppendLine($"root worldBound={_root.worldBound} children={_root.childCount}");
|
||||
var uiRoot = _root.childCount > 0 ? _root[0] : null;
|
||||
sb.AppendLine($"uiRoot={uiRoot?.name} children={uiRoot?.childCount}");
|
||||
if (uiRoot != null)
|
||||
{
|
||||
foreach (var child in uiRoot.Children())
|
||||
{
|
||||
Describe(sb, child);
|
||||
}
|
||||
}
|
||||
|
||||
var renderers = FindObjectsByType<PanelRenderer>(FindObjectsSortMode.None);
|
||||
sb.AppendLine($"panelRenderers={renderers.Length} panel={(_root.panel == null ? "null" : "ok")}");
|
||||
|
||||
Describe(sb, _root.Q("hud-score"));
|
||||
Describe(sb, _root.Q("hud-timer-fill"));
|
||||
Describe(sb, _root.Q("session-card"));
|
||||
Describe(sb, _root.Q("remote-config"));
|
||||
Describe(sb, _root.Q("event-log-scroll"));
|
||||
|
||||
var controller = FindFirstObjectByType<CozyCollectorController>();
|
||||
sb.AppendLine(controller == null
|
||||
? "controller: NOT FOUND"
|
||||
: $"controller phase={controller.Phase} activeModal={controller.ActiveModal} status='{controller.StatusMessage}'");
|
||||
|
||||
var cam = Camera.main;
|
||||
sb.AppendLine(cam == null
|
||||
? "camera: NOT FOUND"
|
||||
: $"camera rect={cam.rect} targetTexture={(cam.targetTexture == null ? "null" : cam.targetTexture.name)} enabled={cam.enabled} clearFlags={cam.clearFlags} time={Time.time:0.#} timeScale={Time.timeScale}");
|
||||
|
||||
Debug.Log(sb.ToString());
|
||||
}
|
||||
|
||||
private static void Describe(StringBuilder sb, VisualElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
sb.AppendLine(" <element not found>");
|
||||
return;
|
||||
}
|
||||
|
||||
var r = element.resolvedStyle;
|
||||
sb.AppendLine($" {element.name}: display={r.display} visibility={r.visibility} opacity={r.opacity} " +
|
||||
$"size={r.width:0.#}x{r.height:0.#} worldBound={element.worldBound} classes='{string.Join(",", element.GetClasses())}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b70a6370f0b8b48aabebe030ae087adf
|
||||
Reference in New Issue
Block a user