Das G3-HUD steht im Spiel; die Abnahme (U3D) folgt. - U3B: alle Bereiche und Komponenten aus Godot übertragen (LuneHud, Lanes, Bossgrotte, nächste Welle, Kaufkarten, Armee, Held mit Items und Talenten, Angriffschampions, Team, Ereignisse, Minimap, Tooltip, Optionen, Partieende). Porträts aus den echten Modellen, einmal gerendert. - Offene G3-Punkte umgesetzt: „1 WELLE“ als Band am Porträtrand, Schadens- und Rüstungssymbole auf den Karten, Armeewert, Heldenporträts auf der Minimap. Befehlsplatz für die Ultimates der geplanten Helden reserviert (verborgen). - U3C: LuneGame verdrahtet Befehle (gleiche Schnittstelle wie die Bots), Bereit mit 1-s-Sperre nach dem Kampf, Tempo und Pause, Tasten wie in G3, Kamera-Ziele, Auswahl im Feld, Planungsvorschau und Aufstellungsflächen. Ersetzt die U2-Debug-Anzeige. Bot-Partien und Benchmark unverändert. - Szenen- und Bibliotheksbauer idempotent: Animator-Controller und Post-Processing-Profil werden an Ort und Stelle aktualisiert. Die Szene bekommt bei jedem Bau neue Objekt-IDs, der Inhalt ist gleich. - Prüfung (Port von verify_g3, Teile A–H): 699 Prüfungen ohne Fehler. EditMode 23/23, PlayMode 12/12, Fingerabdrücke der Bot-Partien unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
537 lines
21 KiB
C#
537 lines
21 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Luneraria.Core;
|
||
using Luneraria.View;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
|
||
namespace Luneraria.UI
|
||
{
|
||
/// <summary>
|
||
/// One Champions of Luneraria match on screen with the HUD (port of the HUD side of
|
||
/// scripts/luneraria/lune_game.gd): the human player's commands go through the same command
|
||
/// interface as the bots (LuneMatch.Submit); Ready, tempo and pause go to the host loop; HUD
|
||
/// clicks and hotkeys move the camera; a left click in the field selects an own unit. During
|
||
/// planning the own draft army stands in its formation and the own deployment areas light up.
|
||
/// Offline, planning waits for the human; pause and tempo are local. Nothing here decides a rule.
|
||
/// </summary>
|
||
public sealed class LuneGame : MonoBehaviour
|
||
{
|
||
public LuneMatchDriver Driver;
|
||
public LuneMatchView View;
|
||
public LuneCameraRig Rig;
|
||
public LuneUiAssets UiAssets;
|
||
/// <summary>The human seat (Godot default 1: Blau Mid). -1 makes the match a bot match watched from seat 0.</summary>
|
||
public int HumanSlot = 1;
|
||
/// <summary>Optional render target for tests and captures (null: the screen).</summary>
|
||
public RenderTexture Target;
|
||
|
||
public LuneHud Hud { get; private set; }
|
||
public LuneMatch Match { get { return Driver.Runner.Match; } }
|
||
/// <summary>The human seat of the running match (-1: a bot match, e.g. tests and the benchmark).</summary>
|
||
int Seat { get { return _runner != null ? _runner.HumanSlot : -1; } }
|
||
bool Human { get { return Seat >= 0; } }
|
||
/// <summary>Whose view the HUD shows: the human seat, or in a bot match seat max(0, HumanSlot) as in Godot.</summary>
|
||
int Viewer { get { return Human ? Seat : Math.Max(0, HumanSlot); } }
|
||
|
||
Camera _camera;
|
||
LuneMatchRunner _runner;
|
||
LuneMatchRunner.Stage _lastStage = (LuneMatchRunner.Stage)(-1);
|
||
int _laneCycle;
|
||
bool _pausedBySettings;
|
||
VisualElement _leave;
|
||
readonly List<(Renderer renderer, int team, string lane)> _pads = new List<(Renderer, int, string)>();
|
||
MaterialPropertyBlock _block;
|
||
|
||
void Start()
|
||
{
|
||
_camera = View != null && View.ViewCamera != null ? View.ViewCamera : Camera.main;
|
||
// The benchmark keeps its bot match; otherwise the configured seat plays.
|
||
if (!LuneBenchmark.Running && (Driver.HumanSlot != HumanSlot || Driver.Runner == null || Driver.Runner.HumanSlot != Driver.HumanSlot))
|
||
{
|
||
Driver.HumanSlot = HumanSlot;
|
||
Driver.Restart();
|
||
}
|
||
FindPads();
|
||
Hud = gameObject.GetComponent<LuneHud>() ?? gameObject.AddComponent<LuneHud>();
|
||
Bind();
|
||
LuneSettingsPanel.Apply();
|
||
if (Rig != null)
|
||
{
|
||
Rig.PointerBlocked = () => Hud != null && Hud.IsPointerOverHud(Input.mousePosition);
|
||
Rig.KeysBlocked = () => Hud != null && Hud.Settings.Open;
|
||
Rig.ResetRequested = () => ResetCamera();
|
||
}
|
||
ResetCamera(true);
|
||
}
|
||
|
||
/// <summary>(Re)builds the HUD for the driver's current match and wires its events.</summary>
|
||
void Bind()
|
||
{
|
||
_runner = Driver.Runner;
|
||
_lastStage = (LuneMatchRunner.Stage)(-1);
|
||
Hud.Init(_runner.Match, Viewer, View.Library, UiAssets, Target);
|
||
Hud.CommandRequested += command => Feedback(Send(command));
|
||
Hud.UndoRequested += () => Feedback(Human ? Match.Undo(Seat) : new CommandResult(false, "Kein eigener Platz."));
|
||
Hud.ReadyRequested += OnReady;
|
||
Hud.StarterRequested += Starter;
|
||
Hud.SpeedRequested += CycleSpeed;
|
||
Hud.PauseRequested += TogglePause;
|
||
Hud.LaneFocusRequested += lane => FocusLane(lane);
|
||
Hud.MapFocusRequested += point => Rig?.LookAt(point);
|
||
Hud.CameraRequested += FocusTarget;
|
||
Hud.MenuRequested += AskLeave;
|
||
Hud.RematchRequested += Rematch;
|
||
Hud.SettingsRequested += () => Hud.Settings.Show();
|
||
Hud.Settings.Opened += () =>
|
||
{
|
||
// A running combat pauses offline while the options are open (lune_settings.gd).
|
||
_pausedBySettings = _runner.Current == LuneMatchRunner.Stage.Combat && !Driver.Paused;
|
||
if (_pausedBySettings)
|
||
Driver.Paused = true;
|
||
};
|
||
Hud.Settings.Closed += () =>
|
||
{
|
||
if (_pausedBySettings)
|
||
Driver.Paused = false;
|
||
_pausedBySettings = false;
|
||
Hud.SetSpeedLabel(Driver.Tempo, Driver.Paused);
|
||
};
|
||
Hud.SetSpeedLabel(Driver.Tempo, Driver.Paused);
|
||
BuildLeaveDialog();
|
||
}
|
||
|
||
CommandResult Send(Command command)
|
||
{
|
||
if (!Human)
|
||
return new CommandResult(false, "Zuschauer können nicht planen.");
|
||
return Match.Submit(Seat, command);
|
||
}
|
||
|
||
void Feedback(CommandResult result)
|
||
{
|
||
Hud.Notify(result.Message, !result.Ok);
|
||
RebuildPreview();
|
||
Hud.MarkDirty();
|
||
}
|
||
|
||
void Starter()
|
||
{
|
||
List<string> ids = Match.BaseIds(Match.Players[Viewer].Bund);
|
||
foreach (int index in new[] { 1, 0, 2 })
|
||
{
|
||
CommandResult result = Send(Command.Buy(ids[index]));
|
||
if (!result.Ok)
|
||
{
|
||
Feedback(result);
|
||
return;
|
||
}
|
||
}
|
||
Feedback(new CommandResult(true, "Startaufstellung: Front, Nahkampf und Fernkampf gekauft."));
|
||
}
|
||
|
||
void OnReady()
|
||
{
|
||
if (Match.Phase != Phase.Planning || !Human)
|
||
return;
|
||
_runner.Ready();
|
||
Hud.MarkDirty();
|
||
}
|
||
|
||
void CycleSpeed()
|
||
{
|
||
Driver.CycleSpeed();
|
||
Hud.SetSpeedLabel(Driver.Tempo, Driver.Paused);
|
||
}
|
||
|
||
void TogglePause()
|
||
{
|
||
if (Driver.TogglePause())
|
||
Hud.SetSpeedLabel(Driver.Tempo, Driver.Paused);
|
||
}
|
||
|
||
void Rematch()
|
||
{
|
||
Driver.Seed += 1;
|
||
Driver.Restart();
|
||
View.Preview = null;
|
||
Bind();
|
||
ResetCamera(true);
|
||
}
|
||
|
||
// ---------------------------------------------------------------- camera targets
|
||
int OwnTeam { get { return Match.Players[Viewer].Team; } }
|
||
|
||
public void FocusLane(string lane, bool instant = false)
|
||
{
|
||
if (Rig == null)
|
||
return;
|
||
LanePath path = Match.Map.LanePaths[lane];
|
||
// Planning looks at the own deployment and inner tower, combat at the contested middle.
|
||
double fraction = Match.Phase == Phase.Planning ? .2 : .42;
|
||
Rig.LookAt(Match.Map.LanePoint(OwnTeam, lane, path.Length * fraction), 24f, instant);
|
||
}
|
||
|
||
public void FocusTarget(string target)
|
||
{
|
||
if (Rig == null)
|
||
return;
|
||
switch (target)
|
||
{
|
||
case "grotto":
|
||
Rig.LookAt(Match.Map.GrottoCenter(), 22f);
|
||
break;
|
||
case "base":
|
||
Rig.LookAt(Match.Map.CorePosition(OwnTeam), 26f);
|
||
break;
|
||
case "enemy_base":
|
||
Rig.LookAt(Match.Map.CorePosition(1 - OwnTeam), 26f);
|
||
break;
|
||
case "hero":
|
||
FocusHero();
|
||
break;
|
||
default:
|
||
if (Array.IndexOf(LuneMap.Lanes, target) >= 0)
|
||
FocusLane(target);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>The own hero: its planning preview or, in combat, the live unit.</summary>
|
||
LuneUnit OwnHeroUnit()
|
||
{
|
||
if (!Human)
|
||
return null;
|
||
LuneCombat source = Match.Phase == Phase.Combat ? Match.Combat : View.Preview;
|
||
return source?.Units.FirstOrDefault(u => u.Kind == "hero" && u.OwnerSlot == Seat && u.Alive);
|
||
}
|
||
|
||
public bool FocusHero()
|
||
{
|
||
LuneUnit unit = OwnHeroUnit();
|
||
if (unit == null)
|
||
{
|
||
Hud.Notify("Kein Held im Feld – im Reiter „Held“ anwerben.", true);
|
||
return false;
|
||
}
|
||
Rig?.LookAt(unit.Position, 16f);
|
||
return true;
|
||
}
|
||
|
||
public void ResetCamera(bool instant = false)
|
||
{
|
||
if (Rig == null)
|
||
return;
|
||
string lane = Match.Players[Viewer].Lane;
|
||
Rig.ResetView(Match.Map.LanePoint(OwnTeam, lane, Match.Map.LanePaths[lane].Length * .2), instant);
|
||
}
|
||
|
||
// ---------------------------------------------------------------- planning preview and deployment areas
|
||
void FindPads()
|
||
{
|
||
_pads.Clear();
|
||
foreach (Renderer renderer in FindObjectsByType<Renderer>(FindObjectsSortMode.None))
|
||
{
|
||
string name = renderer.gameObject.name;
|
||
if (!name.StartsWith("Deployment_", StringComparison.Ordinal))
|
||
continue;
|
||
string[] parts = name.Split('_');
|
||
if (parts.Length >= 3 && int.TryParse(parts[1], out int team))
|
||
_pads.Add((renderer, team, parts[2]));
|
||
}
|
||
}
|
||
|
||
/// <summary>Planning shows the own formation areas; lanes that hold own units light up (Godot show_deployment).</summary>
|
||
void UpdateDeployment()
|
||
{
|
||
bool planning = Match.Phase == Phase.Planning && Human;
|
||
var used = new Dictionary<string, int>();
|
||
if (Human)
|
||
{
|
||
Draft draft = Match.Players[Seat].Draft;
|
||
foreach (RosterEntry entry in draft.Roster)
|
||
used[entry.Lane] = used.GetValueOrDefault(entry.Lane, 0) + 1;
|
||
if (draft.Hero != null)
|
||
used[draft.Hero.Lane] = used.GetValueOrDefault(draft.Hero.Lane, 0) + 1;
|
||
}
|
||
_block ??= new MaterialPropertyBlock();
|
||
foreach (var pad in _pads)
|
||
{
|
||
if (!Human)
|
||
continue;
|
||
bool visible = planning && pad.team == OwnTeam;
|
||
if (pad.renderer.enabled != visible)
|
||
pad.renderer.enabled = visible;
|
||
if (!visible)
|
||
continue;
|
||
pad.renderer.GetPropertyBlock(_block);
|
||
_block.SetFloat("_Highlight", used.GetValueOrDefault(pad.lane, 0) > 0 ? 1f : 0f);
|
||
pad.renderer.SetPropertyBlock(_block);
|
||
}
|
||
}
|
||
|
||
/// <summary>The human player's current draft army in its formation (Godot _rebuild_preview).</summary>
|
||
void RebuildPreview()
|
||
{
|
||
UpdateDeployment();
|
||
if (Match.Phase != Phase.Planning || !Human)
|
||
return;
|
||
var preview = new LuneCombat(Match.Map, Match.Balance, Match.RoundNumber);
|
||
Player player = Match.Players[Seat];
|
||
var perLane = new Dictionary<string, int>();
|
||
var entries = new List<(UnitDef def, string lane, string uid, string kind)>();
|
||
foreach (RosterEntry entry in player.Draft.Roster)
|
||
entries.Add((DataCatalog.GetUnit(entry.TypeId), entry.Lane, entry.Uid, "troop"));
|
||
HeroState hero = player.Draft.Hero;
|
||
if (hero != null)
|
||
entries.Add((LuneContent.HeroDefinition(hero), hero.Lane, hero.Uid, "hero"));
|
||
foreach (var entry in entries)
|
||
{
|
||
int index = perLane.GetValueOrDefault(entry.lane, 0);
|
||
perLane[entry.lane] = index + 1;
|
||
LuneUnit unit;
|
||
if (entry.lane == LuneMatch.Grotto)
|
||
{
|
||
// Grotto orders preview at the team's grotto entrance.
|
||
Vec2 at = Match.Map.GrottoEntry(player.Team) + new Vec2(index % 3 - 1.0, index / 3) * 1.1;
|
||
unit = preview.SpawnAt(entry.kind, player.Team, "grotto", entry.def, at, Seat, entry.uid);
|
||
}
|
||
else
|
||
{
|
||
unit = preview.Spawn(entry.kind, player.Team, entry.lane, entry.def, Math.Max(.4, 7.2 - (index / 4) * 1.5), (index % 4 - 1.5) * 1.2, Seat, entry.uid);
|
||
}
|
||
unit.PreviousPosition = unit.Position;
|
||
}
|
||
View.Preview = preview;
|
||
}
|
||
|
||
// ---------------------------------------------------------------- frame
|
||
void Update()
|
||
{
|
||
if (Driver.Runner != _runner)
|
||
{
|
||
// Somebody restarted the driver (tests, rematch from elsewhere): follow it.
|
||
View.Preview = null;
|
||
Bind();
|
||
}
|
||
TrackStage();
|
||
HandleKeys();
|
||
HandleClicks();
|
||
if (Rig != null && _camera != null)
|
||
Hud.Minimap.SetCamera(Rig.Focus, Rig.Zoom, Rig.Footprint(_camera));
|
||
}
|
||
|
||
void TrackStage()
|
||
{
|
||
LuneMatchRunner.Stage stage = _runner.Current;
|
||
if (stage == _lastStage)
|
||
return;
|
||
LuneMatchRunner.Stage previous = _lastStage;
|
||
_lastStage = stage;
|
||
if (stage == LuneMatchRunner.Stage.Planning)
|
||
{
|
||
RebuildPreview();
|
||
}
|
||
else if (stage == LuneMatchRunner.Stage.Combat)
|
||
{
|
||
View.Preview = null;
|
||
UpdateDeployment();
|
||
}
|
||
else if (stage == LuneMatchRunner.Stage.Over && Rig != null && Match.Combat != null)
|
||
{
|
||
Driver.Paused = false;
|
||
int fallen = Match.Combat.DestroyedCores.Contains(0) ? 0 : 1;
|
||
Rig.LookAt(Match.Map.CorePosition(fallen), 26f);
|
||
}
|
||
Hud.SetSpeedLabel(Driver.Tempo, Driver.Paused);
|
||
}
|
||
|
||
static readonly KeyCode[] GameKeys =
|
||
{
|
||
KeyCode.Escape, KeyCode.F1, KeyCode.M, KeyCode.F11, KeyCode.Space, KeyCode.P, KeyCode.Tab, KeyCode.F2, KeyCode.F3, KeyCode.F4,
|
||
KeyCode.B, KeyCode.G, KeyCode.H, KeyCode.Alpha1, KeyCode.Alpha2, KeyCode.Alpha3, KeyCode.Alpha4, KeyCode.Alpha5,
|
||
KeyCode.Keypad1, KeyCode.Keypad2, KeyCode.Keypad3, KeyCode.Keypad4, KeyCode.Keypad5,
|
||
};
|
||
|
||
/// <summary>Game keys (Godot _input); they act even while a HUD button has focus.</summary>
|
||
void HandleKeys()
|
||
{
|
||
foreach (KeyCode code in GameKeys)
|
||
{
|
||
if (Input.GetKeyDown(code))
|
||
Key(code);
|
||
}
|
||
}
|
||
|
||
bool Blocked { get { return Hud.Settings.Open || (_leave != null && LuneUi.Visible(_leave)); } }
|
||
|
||
/// <summary>One game key, as pressed by the player (also the entry point of the playtest).</summary>
|
||
public void Key(KeyCode code)
|
||
{
|
||
switch (code)
|
||
{
|
||
case KeyCode.Escape:
|
||
if (_leave != null && LuneUi.Visible(_leave))
|
||
LuneUi.Show(_leave, false);
|
||
else
|
||
Hud.HandleEscape();
|
||
return;
|
||
case KeyCode.F1:
|
||
if (Hud.Settings.Open)
|
||
Hud.Settings.Close();
|
||
else
|
||
Hud.Settings.Show();
|
||
return;
|
||
case KeyCode.M:
|
||
LunePreferences.Muted = !LunePreferences.Muted;
|
||
LunePreferences.Commit();
|
||
LuneSettingsPanel.Apply();
|
||
return;
|
||
case KeyCode.F11:
|
||
LunePreferences.Fullscreen = !LunePreferences.Fullscreen;
|
||
LunePreferences.Commit();
|
||
LuneSettingsPanel.Apply();
|
||
return;
|
||
}
|
||
if (Blocked)
|
||
return;
|
||
switch (code)
|
||
{
|
||
case KeyCode.Space:
|
||
if (Match.Phase == Phase.Planning)
|
||
{
|
||
// A Space meant as pause at the very end of a combat must not skip the planning.
|
||
if (!Hud.ReadyBlocked())
|
||
OnReady();
|
||
}
|
||
else
|
||
{
|
||
TogglePause();
|
||
}
|
||
return;
|
||
case KeyCode.P:
|
||
TogglePause();
|
||
return;
|
||
case KeyCode.Tab:
|
||
_laneCycle = (_laneCycle + 1) % 3;
|
||
FocusLane(LuneMap.Lanes[_laneCycle]);
|
||
return;
|
||
case KeyCode.F2:
|
||
case KeyCode.F3:
|
||
case KeyCode.F4:
|
||
// F1 stays the options key; F2–F4 jump to Top, Mid and Bottom.
|
||
_laneCycle = code - KeyCode.F2;
|
||
FocusLane(LuneMap.Lanes[_laneCycle]);
|
||
return;
|
||
case KeyCode.B:
|
||
FocusTarget("base");
|
||
return;
|
||
case KeyCode.G:
|
||
FocusTarget("grotto");
|
||
return;
|
||
case KeyCode.H:
|
||
FocusHero();
|
||
return;
|
||
}
|
||
int index = code >= KeyCode.Alpha1 && code <= KeyCode.Alpha5 ? code - KeyCode.Alpha1 : code >= KeyCode.Keypad1 && code <= KeyCode.Keypad5 ? code - KeyCode.Keypad1 : -1;
|
||
if (index < 0 || Match.Phase != Phase.Planning || !Human)
|
||
return;
|
||
if (Match.Players[Seat].Ready)
|
||
{
|
||
// After Ready the plan is binding (the host refuses); the keys do nothing.
|
||
Hud.Notify("Bereit gemeldet – die Planung ist verbindlich.", true);
|
||
return;
|
||
}
|
||
List<string> ids = Match.BaseIds(Match.Players[Seat].Bund);
|
||
Feedback(Send(Command.Buy(ids[index])));
|
||
}
|
||
|
||
/// <summary>Left click in the field selects an own unit (details in the HUD); it never orders anything.</summary>
|
||
void HandleClicks()
|
||
{
|
||
if (Input.GetMouseButtonDown(0))
|
||
ClickField(Input.mousePosition);
|
||
}
|
||
|
||
/// <summary>A left click at a screen point (pixels, origin bottom left); HUD areas take it themselves.</summary>
|
||
public void ClickField(Vector2 screen)
|
||
{
|
||
if (_camera == null || Blocked || Hud.IsPointerOverHud(screen))
|
||
return;
|
||
LuneUnit picked = PickOwnUnit(screen);
|
||
if (picked != null && picked.Kind == "hero")
|
||
Hud.OpenHero();
|
||
else if (picked != null)
|
||
Hud.SelectUnit(picked.PersistentUid);
|
||
else if (Hud.SelectedUid.Length > 0)
|
||
Hud.ClearSelection();
|
||
}
|
||
|
||
/// <summary>The own troop or hero whose figure is nearest to a screen point (within 34 px), or null.</summary>
|
||
public LuneUnit PickOwnUnit(Vector2 screen)
|
||
{
|
||
if (!Human)
|
||
return null;
|
||
LuneUnit best = null;
|
||
float bestDistance = 34f;
|
||
foreach (LuneUnitView view in View.Units)
|
||
{
|
||
LuneUnit unit = view.Unit;
|
||
if (unit == null || !unit.Alive || unit.OwnerSlot != Seat || (unit.Kind != "troop" && unit.Kind != "hero") || !view.gameObject.activeInHierarchy)
|
||
continue;
|
||
Vector3 at = _camera.WorldToScreenPoint(view.AimPoint);
|
||
if (at.z <= 0f)
|
||
continue;
|
||
float distance = Vector2.Distance(new Vector2(at.x, at.y), screen);
|
||
if (distance < bestDistance)
|
||
{
|
||
bestDistance = distance;
|
||
best = unit;
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
|
||
// ---------------------------------------------------------------- leaving the match
|
||
void BuildLeaveDialog()
|
||
{
|
||
_leave = new VisualElement();
|
||
_leave.style.position = Position.Absolute;
|
||
_leave.style.left = 0f;
|
||
_leave.style.top = 0f;
|
||
_leave.style.right = 0f;
|
||
_leave.style.bottom = 0f;
|
||
_leave.style.backgroundColor = new Color(.02f, .03f, .04f, .72f);
|
||
_leave.style.justifyContent = Justify.Center;
|
||
_leave.style.alignItems = Align.Center;
|
||
var panel = new LunePanel(LunePanels.Menu);
|
||
panel.style.minWidth = 520f;
|
||
_leave.Add(panel);
|
||
VisualElement column = LuneUi.Box(true, 14f, panel);
|
||
column.style.alignItems = Align.Center;
|
||
LuneUi.Label("Partie verlassen?", "HeadingLabel", column);
|
||
LuneUi.Wrap(LuneUi.Label("Ein Hauptmenü gibt es im Unity-Port noch nicht; Verlassen beendet das Spiel.", "MutedLabel", column)).style.unityTextAlign = TextAnchor.MiddleCenter;
|
||
VisualElement row = LuneUi.Box(false, 12f, column);
|
||
new LuneButton("SPIEL BEENDEN", LuneButtons.Primary, row).Clicked += Quit;
|
||
new LuneButton("Zurück", LuneButtons.Secondary, row).Clicked += () => LuneUi.Show(_leave, false);
|
||
LuneUi.Show(_leave, false);
|
||
Hud.Root.Add(_leave);
|
||
}
|
||
|
||
void AskLeave()
|
||
{
|
||
LuneUi.Show(_leave, true);
|
||
_leave.BringToFront();
|
||
}
|
||
|
||
static void Quit()
|
||
{
|
||
#if UNITY_EDITOR
|
||
UnityEditor.EditorApplication.isPlaying = false;
|
||
#else
|
||
Application.Quit();
|
||
#endif
|
||
}
|
||
}
|
||
}
|