500 lines
23 KiB
C#
500 lines
23 KiB
C#
#nullable enable
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Cysharp.Threading.Tasks;
|
||
using Demo2.Domain;
|
||
using Demo2.ECS;
|
||
using ShrinkTutorial;
|
||
using TMPro;
|
||
using Unity.Entities;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
|
||
namespace Demo2.Runtime
|
||
{
|
||
public sealed class Demo2DemoController : MonoBehaviour
|
||
{
|
||
private enum ScreenKind { Title, Lobby, Factory, Reward, End }
|
||
|
||
[SerializeField] private Camera? worldCamera;
|
||
[SerializeField] private Demo2MachineProxyPool? proxyPool;
|
||
[SerializeField] private Demo2ItemBatchRenderer? itemBatchRenderer;
|
||
[SerializeField] private UIDocument? uiDocument;
|
||
[SerializeField] private StyleSheet? uiStyleSheet;
|
||
[SerializeField] private Sprite? titleBackground;
|
||
[SerializeField] private Sprite[] machineIcons = Array.Empty<Sprite>();
|
||
[SerializeField] private Font? uiFont;
|
||
[SerializeField] private TMP_FontAsset? tutorialFont;
|
||
[SerializeField] private RectTransform? tutorialAnchorRoot;
|
||
|
||
private readonly Dictionary<ScreenKind, VisualElement> _screens = new();
|
||
private readonly Dictionary<string, RectTransform> _tutorialAnchors = new(StringComparer.Ordinal);
|
||
private Demo2GameService? _service;
|
||
private Demo2LanRuntime? _lan;
|
||
private Demo2GridInput? _gridInput;
|
||
private VisualElement? _root;
|
||
private ScreenKind _screen;
|
||
private Demo2MachineKind _selectedKind = Demo2MachineKind.Conveyor;
|
||
private byte _rotation;
|
||
private bool _removeMode;
|
||
private Demo2TransportMode _transportMode = Demo2TransportMode.Tcp;
|
||
private bool _dirty = true;
|
||
private bool _uiReady;
|
||
private float _visualTimer;
|
||
private long _networkBuildSequence;
|
||
private int _selectedRecipeId;
|
||
|
||
private static readonly (string anchorId, string elementName)[] TutorialTargets =
|
||
{
|
||
("demo2.miner", "miner-button"),
|
||
("demo2.conveyor", "conveyor-button"),
|
||
("demo2.grid", "grid-input-surface"),
|
||
("demo2.smelter", "smelter-button"),
|
||
("demo2.recipe", "recipe-button"),
|
||
("demo2.turret", "turret-button"),
|
||
("demo2.ready", "ready-button"),
|
||
("demo2.reward", "reward-choose-1")
|
||
};
|
||
|
||
private void Awake()
|
||
{
|
||
if (Application.isBatchMode || Array.IndexOf(Environment.GetCommandLineArgs(), "-demo2-headless") >= 0)
|
||
{
|
||
enabled = false;
|
||
return;
|
||
}
|
||
_service = Demo2GameService.Current ?? new Demo2GameService();
|
||
_lan = new Demo2LanRuntime(_service);
|
||
if (tutorialFont != null && !TMP_Settings.fallbackFontAssets.Contains(tutorialFont)) TMP_Settings.fallbackFontAssets.Add(tutorialFont);
|
||
var tutorialSettings = Resources.Load<ShrinkTutorialSettings>("Demo2TutorialSettings");
|
||
if (tutorialSettings != null) ShrinkTutorialManager.EnsureInstance(tutorialSettings);
|
||
_service.Changed += MarkDirty;
|
||
_lan.Changed += MarkDirty;
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
if (uiDocument == null) throw new InvalidOperationException("Demo2 UIDocument is not assigned.");
|
||
_root = uiDocument.rootVisualElement;
|
||
if (uiStyleSheet != null && !_root.styleSheets.Contains(uiStyleSheet)) _root.styleSheets.Add(uiStyleSheet);
|
||
if (uiFont != null) _root.style.unityFont = uiFont;
|
||
CacheScreens();
|
||
CacheTutorialAnchors();
|
||
ApplyArt();
|
||
BindTitle();
|
||
BindLobby();
|
||
BindFactory();
|
||
BindReward();
|
||
BindEnd();
|
||
if (worldCamera == null) throw new InvalidOperationException("Demo2 WorldCamera is not assigned.");
|
||
_gridInput = new Demo2GridInput(Require<VisualElement>("grid-input-surface"), worldCamera);
|
||
_gridInput.GridClicked += OnGridClicked;
|
||
_root.RegisterCallback<GeometryChangedEvent>(_ =>
|
||
{
|
||
ApplySafeArea();
|
||
SyncTutorialAnchors();
|
||
});
|
||
_uiReady = true;
|
||
Show(ScreenKind.Title);
|
||
_root.schedule.Execute(ApplySafeArea).ExecuteLater(1);
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (!_uiReady) return;
|
||
_lan?.Pump();
|
||
if (_screen == ScreenKind.Factory && ShrinkTutorialManager.Instance?.IsRunning != true) _service?.AdvanceFrame(Time.unscaledDeltaTime);
|
||
RoutePhaseScreen();
|
||
_visualTimer -= Time.unscaledDeltaTime;
|
||
if (_visualTimer <= 0)
|
||
{
|
||
_visualTimer = 0.1f;
|
||
proxyPool?.Refresh(_service?.EcsWorld);
|
||
itemBatchRenderer?.Bind(_service?.EcsWorld);
|
||
_dirty = true;
|
||
}
|
||
if (_dirty) Refresh();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (_service != null) _service.Changed -= MarkDirty;
|
||
if (_lan != null) _lan.Changed -= MarkDirty;
|
||
if (_gridInput != null)
|
||
{
|
||
_gridInput.GridClicked -= OnGridClicked;
|
||
_gridInput.Dispose();
|
||
}
|
||
_lan?.Dispose();
|
||
}
|
||
|
||
private void CacheScreens()
|
||
{
|
||
_screens[ScreenKind.Title] = Require<VisualElement>("title-screen");
|
||
_screens[ScreenKind.Lobby] = Require<VisualElement>("lobby-screen");
|
||
_screens[ScreenKind.Factory] = Require<VisualElement>("factory-screen");
|
||
_screens[ScreenKind.Reward] = Require<VisualElement>("reward-screen");
|
||
_screens[ScreenKind.End] = Require<VisualElement>("end-screen");
|
||
}
|
||
|
||
private void CacheTutorialAnchors()
|
||
{
|
||
if (tutorialAnchorRoot == null) return;
|
||
foreach (var target in TutorialTargets)
|
||
{
|
||
var child = tutorialAnchorRoot.Find(target.anchorId);
|
||
if (child is RectTransform rect) _tutorialAnchors[target.anchorId] = rect;
|
||
}
|
||
}
|
||
|
||
private void ApplyArt()
|
||
{
|
||
if (titleBackground != null) Require<VisualElement>("title-background").style.backgroundImage = new StyleBackground(titleBackground);
|
||
var names = new[] { "miner", "conveyor", "splitter", "smelter", "assembler", "loader", "turret", "generator" };
|
||
for (var i = 0; i < names.Length && i < machineIcons.Length; i++)
|
||
if (machineIcons[i] != null) Require<VisualElement>(names[i] + "-icon").style.backgroundImage = new StyleBackground(machineIcons[i]);
|
||
}
|
||
|
||
private void BindTitle()
|
||
{
|
||
Bind("solo-button", StartSolo);
|
||
Bind("host-button", HostLan);
|
||
Bind("join-button", JoinLan);
|
||
Bind("transport-button", ToggleTransport);
|
||
}
|
||
|
||
private void BindLobby()
|
||
{
|
||
Bind("lobby-back-button", LeaveLobby);
|
||
Bind("lobby-ready-button", () => _lan?.SetReady(true));
|
||
Bind("lobby-start-button", StartHostedMatch);
|
||
}
|
||
|
||
private void BindFactory()
|
||
{
|
||
var pairs = new[]
|
||
{
|
||
(Demo2MachineKind.Miner, "miner-button"),
|
||
(Demo2MachineKind.Conveyor, "conveyor-button"),
|
||
(Demo2MachineKind.Splitter, "splitter-button"),
|
||
(Demo2MachineKind.Smelter, "smelter-button"),
|
||
(Demo2MachineKind.Assembler, "assembler-button"),
|
||
(Demo2MachineKind.Loader, "loader-button"),
|
||
(Demo2MachineKind.Turret, "turret-button"),
|
||
(Demo2MachineKind.Generator, "generator-button")
|
||
};
|
||
foreach (var pair in pairs)
|
||
{
|
||
var local = pair.Item1;
|
||
Bind(pair.Item2, () => SelectMachine(local));
|
||
}
|
||
Bind("rotate-button", () => { _rotation = (byte)((_rotation + 1) & 3); _dirty = true; });
|
||
Bind("remove-button", () => { _removeMode = !_removeMode; _dirty = true; });
|
||
Bind("ready-button", ReadyForProduction);
|
||
Bind("speed-1-button", () => _service?.SetSpeed(1));
|
||
Bind("speed-4-button", () => _service?.SetSpeed(4));
|
||
Bind("speed-10-button", () => _service?.SetSpeed(10));
|
||
Bind("save-button", () => _service?.SaveAsync().Forget(LogException));
|
||
Bind("load-button", () => _service?.LoadAsync().Forget(LogException));
|
||
Bind("exit-button", ExitToTitle);
|
||
Bind("recipe-button", ConfirmRecipe);
|
||
Bind("rule-up-1", () => _service?.ReorderRule(0, 0));
|
||
Bind("rule-up-2", () => _service?.ReorderRule(1, 0));
|
||
Bind("rule-up-3", () => _service?.ReorderRule(2, 1));
|
||
Bind("rule-up-4", () => _service?.ReorderRule(3, 2));
|
||
Bind("rule-up-5", () => _service?.ReorderRule(4, 3));
|
||
}
|
||
|
||
private void BindReward()
|
||
{
|
||
Bind("reward-choose-1", () => ChooseReward(0));
|
||
Bind("reward-choose-2", () => ChooseReward(1));
|
||
Bind("reward-choose-3", () => ChooseReward(2));
|
||
}
|
||
|
||
private void BindEnd()
|
||
{
|
||
Bind("restart-button", StartSolo);
|
||
Bind("title-button", ExitToTitle);
|
||
}
|
||
|
||
private void StartSolo()
|
||
{
|
||
_lan?.Stop();
|
||
_service?.StartNewGame();
|
||
Show(ScreenKind.Factory);
|
||
ShrinkTutorialManager.Instance?.StartTutorial("demo2.first-cycle");
|
||
}
|
||
|
||
private void HostLan()
|
||
{
|
||
_service?.StartNewGame();
|
||
_lan?.Host(27320, _transportMode, "host");
|
||
Show(ScreenKind.Lobby);
|
||
}
|
||
|
||
private void JoinLan()
|
||
{
|
||
var value = Require<TextField>("direct-ip-input").value;
|
||
var host = string.IsNullOrWhiteSpace(value) ? "127.0.0.1" : value.Trim();
|
||
_service?.StartNewGame(20260817);
|
||
_lan?.Join(host, 27320, _transportMode, "client");
|
||
Show(ScreenKind.Lobby);
|
||
}
|
||
|
||
private void StartHostedMatch()
|
||
{
|
||
if (_lan?.IsHost == true) Show(ScreenKind.Factory);
|
||
}
|
||
|
||
private void LeaveLobby()
|
||
{
|
||
_lan?.Stop();
|
||
Show(ScreenKind.Title);
|
||
}
|
||
|
||
private void ExitToTitle()
|
||
{
|
||
_lan?.Stop();
|
||
Show(ScreenKind.Title);
|
||
}
|
||
|
||
private void ToggleTransport()
|
||
{
|
||
_transportMode = _transportMode == Demo2TransportMode.Tcp ? Demo2TransportMode.Kcp : Demo2TransportMode.Tcp;
|
||
_dirty = true;
|
||
}
|
||
|
||
private void SelectMachine(Demo2MachineKind kind)
|
||
{
|
||
_selectedKind = kind;
|
||
_selectedRecipeId = _service != null && _service.Content.Machines.TryGetValue(kind, out var machine) ? machine.RecipeId : 0;
|
||
_removeMode = false;
|
||
_dirty = true;
|
||
if (kind == Demo2MachineKind.Miner) ShrinkTutorialManager.Instance?.CompleteStep("demo2.miner-selected");
|
||
if (kind == Demo2MachineKind.Conveyor) ShrinkTutorialManager.Instance?.CompleteStep("demo2.conveyor-selected");
|
||
if (kind == Demo2MachineKind.Smelter) ShrinkTutorialManager.Instance?.CompleteStep("demo2.smelter-selected");
|
||
if (kind == Demo2MachineKind.Turret) ShrinkTutorialManager.Instance?.CompleteStep("demo2.turret-selected");
|
||
}
|
||
|
||
private void ConfirmRecipe()
|
||
{
|
||
if (_service != null && _selectedRecipeId == 0 && _service.Content.Recipes.Count > 0)
|
||
_selectedRecipeId = _service.Content.Recipes.Keys.OrderBy(value => value).First();
|
||
ShrinkTutorialManager.Instance?.CompleteStep("demo2.recipe-selected");
|
||
_dirty = true;
|
||
}
|
||
|
||
private void ReadyForProduction()
|
||
{
|
||
if (_service == null) return;
|
||
if (_lan?.IsConnected == true) _lan.SetReady(true);
|
||
else _service.SetReady("local", true);
|
||
ShrinkTutorialManager.Instance?.CompleteStep("demo2.production-started");
|
||
}
|
||
|
||
private void OnGridClicked(int x, int y, int button)
|
||
{
|
||
if (_service == null || _service.Phase != Demo2Phase.Build) return;
|
||
var remove = _removeMode || button == 1;
|
||
var command = new Demo2BuildCommand
|
||
{
|
||
Sequence = ++_networkBuildSequence,
|
||
IdempotencyToken = (_lan?.LocalPlayerId ?? "local") + "-" + _networkBuildSequence,
|
||
PlayerId = _lan?.LocalPlayerId ?? "local",
|
||
Kind = _selectedKind,
|
||
X = x,
|
||
Y = y,
|
||
Rotation = _rotation,
|
||
RecipeId = _selectedRecipeId,
|
||
Remove = remove
|
||
};
|
||
if (_lan?.IsConnected == true) _lan.SendBuild(command);
|
||
else
|
||
{
|
||
var result = _service.SubmitBuild(command);
|
||
if (result.Accepted && !remove)
|
||
{
|
||
if (_selectedKind == Demo2MachineKind.Miner) ShrinkTutorialManager.Instance?.CompleteStep("demo2.miner-placed");
|
||
if (_selectedKind == Demo2MachineKind.Conveyor) ShrinkTutorialManager.Instance?.CompleteStep("demo2.conveyor-placed");
|
||
if (_selectedKind == Demo2MachineKind.Turret) ShrinkTutorialManager.Instance?.CompleteStep("demo2.turret-placed");
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ChooseReward(int index)
|
||
{
|
||
if (_service == null || index < 0 || index >= _service.RewardChoices.Count) return;
|
||
var ruleId = _service.RewardChoices[index];
|
||
if (_lan?.IsConnected == true) _lan.ChooseReward(ruleId, -1);
|
||
else if (_service.ChooseReward(ruleId)) ShrinkTutorialManager.Instance?.CompleteStep("demo2.rule-chosen");
|
||
}
|
||
|
||
private void RoutePhaseScreen()
|
||
{
|
||
if (_service == null || _screen is ScreenKind.Title or ScreenKind.Lobby) return;
|
||
if (_service.Phase == Demo2Phase.Reward && _screen != ScreenKind.Reward) Show(ScreenKind.Reward);
|
||
else if (_service.Phase is Demo2Phase.Victory or Demo2Phase.Defeat && _screen != ScreenKind.End) Show(ScreenKind.End);
|
||
else if (_service.Phase is Demo2Phase.Build or Demo2Phase.Production or Demo2Phase.Defense && _screen != ScreenKind.Factory) Show(ScreenKind.Factory);
|
||
}
|
||
|
||
private void Show(ScreenKind screen)
|
||
{
|
||
_screen = screen;
|
||
foreach (var pair in _screens) pair.Value.style.display = pair.Key == screen ? DisplayStyle.Flex : DisplayStyle.None;
|
||
if (worldCamera != null) worldCamera.gameObject.SetActive(screen == ScreenKind.Factory);
|
||
if (proxyPool != null) proxyPool.gameObject.SetActive(screen == ScreenKind.Factory);
|
||
if (itemBatchRenderer != null) itemBatchRenderer.gameObject.SetActive(screen == ScreenKind.Factory);
|
||
_dirty = true;
|
||
_root?.schedule.Execute(SyncTutorialAnchors).ExecuteLater(1);
|
||
}
|
||
|
||
private void Refresh()
|
||
{
|
||
_dirty = false;
|
||
SetText("transport-button", "传输:" + _transportMode.ToString().ToUpperInvariant());
|
||
if (_service == null) return;
|
||
SetText("lobby-status", _lan?.Status ?? "offline");
|
||
SetText("lobby-players", BuildPlayerList());
|
||
Require<Button>("lobby-start-button").SetEnabled(_lan?.IsHost == true);
|
||
|
||
if (_service.EcsWorld != null)
|
||
{
|
||
var manager = _service.EcsWorld.EntityManager;
|
||
var economy = manager.GetComponentData<Demo2EconomyState>(_service.EcsWorld.Singleton);
|
||
var core = manager.GetComponentData<Demo2CoreHealth>(_service.EcsWorld.Singleton);
|
||
SetText("core-text", "核心 " + new string('◆', Math.Max(0, core.Value)));
|
||
SetText("money-text", "资金 " + SaturatingMath.Format(economy.Money));
|
||
SetText("phase-text", $"周期 {_service.Cycle}/5 · {PhaseName(_service.Phase)}");
|
||
SetText("timer-text", TimeSpan.FromSeconds(_service.PhaseRemainingSeconds).ToString(@"mm\:ss"));
|
||
SetText("target-text", $"目标火力 {SaturatingMath.Format(_service.Content.Waves[Math.Max(0, _service.Cycle - 1)].TotalHealth)}\n载荷 × 倍率\n{SaturatingMath.Format(economy.Payload)} × {economy.MultiplierMilli / 1000f:0.00}");
|
||
}
|
||
SetText("selection-text", (_removeMode ? "拆除模式" : "建造 " + MachineName(_selectedKind)) + $" · 朝向 {_rotation * 90}°");
|
||
SetText("error-text", _service.LastError);
|
||
SetText("network-text", _lan?.Status ?? "单人本地");
|
||
var recipeName = _service.Content.Recipes.TryGetValue(_selectedRecipeId, out var recipe) ? recipe.Name : "无";
|
||
SetText("recipe-button", "配方:" + recipeName);
|
||
RefreshMachineSelection();
|
||
RefreshRules();
|
||
RefreshRewards();
|
||
SetText("end-title", _service.Phase == Demo2Phase.Victory ? "工厂守住了核心" : "核心防线失守");
|
||
SetText("end-summary", $"最终周期 {_service.Cycle}/5\n火力 {SaturatingMath.Format(_service.LastFirepower)} / 敌军 {SaturatingMath.Format(_service.LastEnemyHealth)}");
|
||
}
|
||
|
||
private void RefreshMachineSelection()
|
||
{
|
||
var names = new[] { "miner", "conveyor", "splitter", "smelter", "assembler", "loader", "turret", "generator" };
|
||
var kinds = new[] { Demo2MachineKind.Miner, Demo2MachineKind.Conveyor, Demo2MachineKind.Splitter, Demo2MachineKind.Smelter, Demo2MachineKind.Assembler, Demo2MachineKind.Loader, Demo2MachineKind.Turret, Demo2MachineKind.Generator };
|
||
for (var i = 0; i < names.Length; i++) Require<Button>(names[i] + "-button").EnableInClassList("selected-machine", !_removeMode && kinds[i] == _selectedKind);
|
||
Require<Button>("remove-button").EnableInClassList("selected-remove", _removeMode);
|
||
}
|
||
|
||
private void RefreshRules()
|
||
{
|
||
for (var i = 0; i < 5; i++)
|
||
{
|
||
if (i < _service!.RuleIds.Count && _service.Content.Rules.TryGetValue(_service.RuleIds[i], out var rule))
|
||
{
|
||
SetText("rule-name-" + (i + 1), $"{i + 1}. {rule.Name}");
|
||
SetText("rule-description-" + (i + 1), rule.Description);
|
||
}
|
||
else
|
||
{
|
||
SetText("rule-name-" + (i + 1), $"{i + 1}. 空槽位");
|
||
SetText("rule-description-" + (i + 1), "防守成功后选择规则");
|
||
}
|
||
}
|
||
}
|
||
|
||
private void RefreshRewards()
|
||
{
|
||
for (var i = 0; i < 3; i++)
|
||
{
|
||
var button = Require<Button>("reward-choose-" + (i + 1));
|
||
Demo2RuleDefinition? rule = null;
|
||
var available = i < _service!.RewardChoices.Count && _service.Content.Rules.TryGetValue(_service.RewardChoices[i], out rule);
|
||
button.SetEnabled(available);
|
||
if (!available || rule == null) continue;
|
||
SetText("reward-category-" + (i + 1), CategoryName(rule.Category));
|
||
SetText("reward-name-" + (i + 1), rule.Name);
|
||
SetText("reward-description-" + (i + 1), rule.Description);
|
||
}
|
||
}
|
||
|
||
private string BuildPlayerList()
|
||
{
|
||
if (_lan == null || _lan.Players.Count == 0) return "等待房间同步…";
|
||
return string.Join("\n", _lan.Players.Select(player => (player.Connected ? "● " : "○ ") + player.DisplayName + (player.Ready ? " 已准备" : " 未准备")));
|
||
}
|
||
|
||
private void ApplySafeArea()
|
||
{
|
||
if (_root == null) return;
|
||
var safe = Require<VisualElement>("safe-area");
|
||
var area = Screen.safeArea;
|
||
var width = Mathf.Max(1, Screen.width);
|
||
var height = Mathf.Max(1, Screen.height);
|
||
var panelWidth = _root.resolvedStyle.width;
|
||
var panelHeight = _root.resolvedStyle.height;
|
||
if (float.IsNaN(panelWidth) || float.IsNaN(panelHeight) || panelWidth <= 0 || panelHeight <= 0) return;
|
||
safe.style.left = area.xMin / width * panelWidth;
|
||
safe.style.right = (width - area.xMax) / width * panelWidth;
|
||
safe.style.top = (height - area.yMax) / height * panelHeight;
|
||
safe.style.bottom = area.yMin / height * panelHeight;
|
||
}
|
||
|
||
private void SyncTutorialAnchors()
|
||
{
|
||
if (_root == null || tutorialAnchorRoot == null) return;
|
||
var panelWidth = Mathf.Max(1, _root.resolvedStyle.width);
|
||
var panelHeight = Mathf.Max(1, _root.resolvedStyle.height);
|
||
tutorialAnchorRoot.sizeDelta = new Vector2(Screen.width, Screen.height);
|
||
foreach (var target in TutorialTargets)
|
||
{
|
||
if (!_tutorialAnchors.TryGetValue(target.anchorId, out var anchor)) continue;
|
||
var element = _root.Q<VisualElement>(target.elementName);
|
||
var displayed = element != null && IsDisplayed(element);
|
||
anchor.gameObject.SetActive(displayed);
|
||
if (!displayed || element == null) continue;
|
||
var bounds = element.worldBound;
|
||
var x = bounds.xMin / panelWidth * Screen.width;
|
||
var y = Screen.height - bounds.yMax / panelHeight * Screen.height;
|
||
var width = bounds.width / panelWidth * Screen.width;
|
||
var height = bounds.height / panelHeight * Screen.height;
|
||
anchor.anchorMin = Vector2.zero;
|
||
anchor.anchorMax = Vector2.zero;
|
||
anchor.pivot = Vector2.zero;
|
||
anchor.anchoredPosition = new Vector2(x, y);
|
||
anchor.sizeDelta = new Vector2(width, height);
|
||
}
|
||
}
|
||
|
||
private static bool IsDisplayed(VisualElement element)
|
||
{
|
||
for (var current = element; current != null; current = current.parent)
|
||
if (current.resolvedStyle.display == DisplayStyle.None) return false;
|
||
return true;
|
||
}
|
||
|
||
private void Bind(string name, Action action) => Require<Button>(name).clicked += action;
|
||
|
||
private T Require<T>(string name) where T : VisualElement
|
||
=> _root?.Q<T>(name) ?? throw new InvalidOperationException("Missing Demo2 UI Toolkit element: " + name);
|
||
|
||
private void SetText(string name, string value)
|
||
{
|
||
var text = Require<TextElement>(name);
|
||
text.text = value;
|
||
}
|
||
|
||
private void MarkDirty() => _dirty = true;
|
||
|
||
private static string PhaseName(Demo2Phase phase) => phase switch { Demo2Phase.Build => "建造", Demo2Phase.Production => "自动生产", Demo2Phase.Defense => "炮塔结算", Demo2Phase.Reward => "规则选牌", _ => phase.ToString() };
|
||
private static string MachineName(Demo2MachineKind kind) => kind switch { Demo2MachineKind.Miner => "采集器", Demo2MachineKind.Conveyor => "传送带", Demo2MachineKind.Splitter => "分流器", Demo2MachineKind.Smelter => "熔炼机", Demo2MachineKind.Assembler => "组装机", Demo2MachineKind.Loader => "装填器", Demo2MachineKind.Turret => "炮塔", Demo2MachineKind.Generator => "能量站", _ => kind.ToString() };
|
||
private static string CategoryName(Demo2RuleCategory category) => category switch { Demo2RuleCategory.Production => "生产链修正", Demo2RuleCategory.Logistics => "物流条件", Demo2RuleCategory.MachineState => "机器状态", Demo2RuleCategory.CycleSettlement => "周期结算", Demo2RuleCategory.RiskExchange => "风险交换", _ => category.ToString() };
|
||
private static void LogException(Exception exception) => Debug.LogException(exception);
|
||
}
|
||
}
|