Befund aus dem Spieltest des U3-Nachtrags: Nichts legte LuneNetPlay in die Szene. Das Spiel begann direkt in einer Partie, der Startbildschirm aus U4 war unerreichbar, und der Verlassen-Dialog beendete das Spiel statt zum Hauptmenü zu führen. - LuneWorldBuilder legt LuneNetPlay auf das Match-Objekt; Szene neu gebaut. - Das Menü bekommt ein eigenes UIDocument über dem HUD. Vorher hing es an keinem Panel, und das HUD hätte es beim ersten Partiestart geleert. - Rückkehr über „Menü“ → „Zum Menü“: HUD und Aufstellungsflächen weg, Sitzung beendet, leere Karte. Die Kamera ruht hinter dem Menü und beginnt jede Partie ungedreht. Die Bund-Wahl ist markiert, die Knopfreihe passt in die Tafel. - Tests und Benchmark ohne Startbildschirm (LuneMatchDriver.DirectStart, -luneBenchmark); Bot-Partien und Fingerabdrücke unverändert. - Neu: LuneSmoke (-luneSmoke <Ordner>), Selbsttest eines Players nach lune_smoke.gd. StartScreenTests führt ihn im Editor aus. - Tests: EditMode 29/29, PlayMode 14/14 (StartScreenTests 26/26), Verify 718/0, Fingerabdrücke DCE95C96556F3A1A und 7B8E43AEFC0D60C5 unverändert. macOS-Player: Selbsttest 26/26, Bilder in docs/port/screenshots/u4/startbildschirm/. Ein zusammenhängender PlayMode-Lauf auf dem Endstand und ein Player-Benchmark stehen aus, weil der Mac zugeklappt schlief (siehe U4-NETWORK.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
7.2 KiB
C#
187 lines
7.2 KiB
C#
using System;
|
||
using Luneraria.Core;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
|
||
namespace Luneraria.UI
|
||
{
|
||
/// <summary>
|
||
/// Start screen of Champions of Luneraria (port of the network part of lune_menu.gd):
|
||
/// offline (local host with bot seats), host a direct connection, or join one. The menu is
|
||
/// the start screen of the game; the HUD's "Menü" returns here. Peer 1 is the host; free
|
||
/// seats stay bots. The host is authoritative.
|
||
/// </summary>
|
||
public sealed class LuneMainMenu : VisualElement
|
||
{
|
||
public event Action<MatchOptions> StartOffline;
|
||
public event Action<int, string> HostRequested;
|
||
public event Action<string, int, string> JoinRequested;
|
||
public event Action QuitRequested;
|
||
|
||
public string Bund = "wurzelbund";
|
||
public string Lane = "mid";
|
||
|
||
readonly TextField _name;
|
||
readonly TextField _address;
|
||
readonly TextField _port;
|
||
readonly Label _status;
|
||
LuneButton _bundGlut;
|
||
LuneButton _bundWurzel;
|
||
LuneButton _laneTop;
|
||
LuneButton _laneMid;
|
||
LuneButton _laneBot;
|
||
|
||
public LuneMainMenu()
|
||
{
|
||
style.position = Position.Absolute;
|
||
style.left = 0f;
|
||
style.top = 0f;
|
||
style.right = 0f;
|
||
style.bottom = 0f;
|
||
style.justifyContent = Justify.Center;
|
||
style.alignItems = Align.Center;
|
||
style.backgroundColor = new Color(.02f, .03f, .05f, .92f);
|
||
|
||
var panel = new LunePanel(LunePanels.Menu);
|
||
panel.style.minWidth = 560f;
|
||
// The four action buttons need about 650 px; at 640 they stuck out of the frame.
|
||
panel.style.maxWidth = 760f;
|
||
Add(panel);
|
||
VisualElement column = LuneUi.Box(true, 14f, panel);
|
||
column.style.alignItems = Align.Center;
|
||
|
||
LuneUi.Label("CHAMPIONS", "TitleLabel", column).style.fontSize = 56f;
|
||
LuneUi.Label("OF LUNERARIA", "TitleLabel", column).style.fontSize = 40f;
|
||
LuneUi.Label("DREI LANES · 3 GEGEN 3 · AUTOBATTLER", "EyebrowLabel", column);
|
||
LuneUi.Wrap(LuneUi.Label("Team Blau (Südwest) mit zwei Bot-Verbündeten gegen drei Bots in Team Rot. Kein Rundenlimit: Es gewinnt, wer den gegnerische Ancient zerstört. Für sechs Menschen hosten oder beitreten; freie Plätze bleiben Bots. Der Host ist autoritativ.", "MutedLabel", column));
|
||
|
||
LuneUi.Label("DEIN BUND", "SectionLabel", column);
|
||
VisualElement bundRow = LuneUi.Box(false, 10f, column);
|
||
_bundGlut = new LuneButton("Glutpakt", LuneButtons.HudToggle, bundRow);
|
||
_bundWurzel = new LuneButton("Wurzelbund", LuneButtons.HudToggle, bundRow);
|
||
_bundGlut.ToggleMode = true;
|
||
_bundWurzel.ToggleMode = true;
|
||
_bundGlut.Clicked += () => SetBund("glutpakt");
|
||
_bundWurzel.Clicked += () => SetBund("wurzelbund");
|
||
SetBund(Bund);
|
||
|
||
LuneUi.Label("DEINE LANE", "SectionLabel", column);
|
||
VisualElement laneRow = LuneUi.Box(false, 10f, column);
|
||
_laneTop = new LuneButton("Top", LuneButtons.HudToggle, laneRow);
|
||
_laneMid = new LuneButton("Mid", LuneButtons.HudToggle, laneRow);
|
||
_laneBot = new LuneButton("Bottom", LuneButtons.HudToggle, laneRow);
|
||
_laneTop.ToggleMode = true;
|
||
_laneMid.ToggleMode = true;
|
||
_laneBot.ToggleMode = true;
|
||
_laneTop.Clicked += () => SetLane("top");
|
||
_laneMid.Clicked += () => SetLane("mid");
|
||
_laneBot.Clicked += () => SetLane("bot");
|
||
SetLane("mid");
|
||
|
||
LuneUi.Label("NETZWERK · DIREKTVERBINDUNG", "SectionLabel", column);
|
||
_name = Field(column, "Anzeigename", "Spieler");
|
||
VisualElement netRow = LuneUi.Box(false, 10f, column);
|
||
_address = Field(netRow, "Host-Adresse", "127.0.0.1");
|
||
_address.style.flexGrow = 1f;
|
||
_port = Field(netRow, "Port", LuneNet.DefaultPort.ToString());
|
||
_port.style.width = 100f;
|
||
|
||
VisualElement actions = LuneUi.Box(false, 12f, column);
|
||
new LuneButton("ALLEIN SPIELEN", LuneButtons.Primary, actions).Clicked += StartAlone;
|
||
new LuneButton("HOSTEN", LuneButtons.Secondary, actions).Clicked += StartHost;
|
||
new LuneButton("BEITRETEN", LuneButtons.Secondary, actions).Clicked += StartJoin;
|
||
new LuneButton("BEENDEN", LuneButtons.Secondary, actions).Clicked += () => QuitRequested?.Invoke();
|
||
|
||
_status = LuneUi.Label("", "MutedLabel", column);
|
||
_status.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||
}
|
||
|
||
static TextField Field(VisualElement parent, string label, string value)
|
||
{
|
||
VisualElement box = LuneUi.Box(true, 4f, parent);
|
||
box.style.flexGrow = 1f;
|
||
LuneUi.Label(label, "EyebrowLabel", box);
|
||
var field = new TextField { value = value };
|
||
field.style.flexGrow = 1f;
|
||
field.style.minHeight = 32f;
|
||
field.style.fontSize = 16f;
|
||
box.Add(field);
|
||
return field;
|
||
}
|
||
|
||
void SetBund(string bund)
|
||
{
|
||
Bund = bund;
|
||
_bundGlut.On = bund == "glutpakt";
|
||
_bundWurzel.On = bund == "wurzelbund";
|
||
}
|
||
|
||
void SetLane(string lane)
|
||
{
|
||
Lane = lane;
|
||
_laneTop.On = lane == "top";
|
||
_laneMid.On = lane == "mid";
|
||
_laneBot.On = lane == "bot";
|
||
}
|
||
|
||
/// <summary>Own seat on team Blau: slots 0–2 map to top, mid, bot.</summary>
|
||
public MatchOptions OfflineOptions()
|
||
{
|
||
int seat = Array.IndexOf(LuneMap.Lanes, Lane);
|
||
if (seat < 0)
|
||
seat = 1;
|
||
var options = new MatchOptions { Seed = Environment.TickCount & 0xffff, HumanSlot = seat };
|
||
for (int slot = 0; slot < 6; slot++)
|
||
options.Bunds.Add(slot == seat ? Bund : DataCatalog.Factions[(slot + options.Seed) % DataCatalog.Factions.Length]);
|
||
return options;
|
||
}
|
||
|
||
void StartAlone()
|
||
{
|
||
StartOffline?.Invoke(OfflineOptions());
|
||
}
|
||
|
||
void StartHost()
|
||
{
|
||
int port = ParsePort();
|
||
HostRequested?.Invoke(port, CleanName());
|
||
}
|
||
|
||
void StartJoin()
|
||
{
|
||
int port = ParsePort();
|
||
JoinRequested?.Invoke(_address.value.Trim(), port, CleanName());
|
||
}
|
||
|
||
int ParsePort()
|
||
{
|
||
int port;
|
||
return int.TryParse(_port.value.Trim(), out port) && port > 0 ? port : LuneNet.DefaultPort;
|
||
}
|
||
|
||
string CleanName()
|
||
{
|
||
string name = (_name.value ?? "").Trim();
|
||
if (name.Length == 0)
|
||
name = "Spieler";
|
||
return name.Length > 24 ? name.Substring(0, 24) : name;
|
||
}
|
||
|
||
public void Notify(string message, bool error = false)
|
||
{
|
||
_status.text = message ?? "";
|
||
_status.style.color = error ? LuneUiTheme.Hex("ff8a70") : LuneUiTheme.Muted;
|
||
}
|
||
|
||
public void Enter()
|
||
{
|
||
LuneUi.Show(this, true);
|
||
Notify("");
|
||
}
|
||
|
||
public void Leave()
|
||
{
|
||
LuneUi.Show(this, false);
|
||
}
|
||
}
|
||
}
|