Integrate CultMesh UI and URP cleanup

This commit is contained in:
Meta Vens 2026-06-30 19:14:49 +01:00
commit 4d7bb65868
131 changed files with 7209 additions and 5084 deletions

View file

@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using GameCult.Aetheria.EveRuntime;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.UIElements;
using UiButton = UnityEngine.UIElements.Button;
public class MainMenu : MonoBehaviour
{
@ -15,37 +18,46 @@ public class MainMenu : MonoBehaviour
public GameSettings Settings;
public ConfirmationDialog Dialog;
public bool InGame;
public float FadeTime = 0.5f;
public float FadeDistance = 512f;
public float FadeAlphaExponent = 2f;
public float FadePositionExponent = 2f;
public float HoverSpacing = 8f;
public float HoverAnimationDuration = 0.16f;
private UIDocument _menuSurfaceDocument;
private UIDocument _currentMenuSurfaceDocument;
private UIDocument _nextMenuSurfaceDocument;
private Func<bool> _canOpenRuntimeInputScreen;
private Action _openRuntimeInputScreen;
private bool _fadeFromRight;
private bool _fading;
private float _fadeLerp;
private readonly List<MenuHoverButton> _hoverButtons = new List<MenuHoverButton>();
private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome
{
RootAlignItems = Align.Center,
RootJustifyContent = Justify.Center,
RootPaddingLeft = 24f,
UseShell = false,
RootAlignItems = Align.FlexStart,
RootJustifyContent = Justify.FlexStart,
RootPaddingLeft = 96f,
RootPaddingTop = 92f,
RootPaddingRight = 24f,
RootPaddingTop = 24f,
RootPaddingBottom = 24f,
RootBackgroundColor = new Color(0f, 0f, 0f, 0.6f),
Width = 560f,
MinWidth = 0f,
MaxWidth = 560f,
PaddingLeft = 20f,
PaddingRight = 20f,
PaddingTop = 20f,
PaddingBottom = 20f,
BorderRadius = 0f,
BorderWidth = 0f,
BackgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f)
RootBackgroundColor = new Color(0f, 0f, 0f, 0f),
RootPickingMode = PickingMode.Position
};
void Start()
{
ShowMain();
ShowMain(false);
}
private void ShowMain()
void Update()
{
UpdateMenuFade();
UpdateHoverButtons();
}
private void ShowMain(bool animateFromRight = true)
{
var stateBoot = CurrentStateBoot();
var sectorMap = ResolveSectorMap(stateBoot);
@ -60,7 +72,8 @@ public class MainMenu : MonoBehaviour
CanOpenRuntimeInputScreen(),
InGame,
DateTime.UtcNow.ToString("O")),
HandleMainSurfaceCommand);
HandleMainSurfaceCommand,
animateFromRight);
}
private void HandleMainSurfaceCommand(EveSurfaceCommandRequest request)
@ -77,7 +90,7 @@ public class MainMenu : MonoBehaviour
if (ResolveSectorMap(CurrentStateBoot()) == null)
{
Debug.LogWarning("Main-menu Continue requested without a typed Aetheria sector-map projection.");
ShowMain();
ShowMain(false);
return;
}
@ -87,7 +100,7 @@ public class MainMenu : MonoBehaviour
StartNewGame();
return;
case AetheriaRuntimeMainMenuCommandKind.ShowSettings:
ShowSettings();
ShowSettings(true);
return;
case AetheriaRuntimeMainMenuCommandKind.Quit:
Application.Quit();
@ -102,7 +115,7 @@ public class MainMenu : MonoBehaviour
{
if (!TryStartDaemonObservedGame("Starting Daemon Run"))
{
ShowMain();
ShowMain(false);
}
}
@ -145,7 +158,9 @@ public class MainMenu : MonoBehaviour
try
{
return AetheriaUnityRuntimeClientProvider
.CurrentSectorMap(stateBoot, "unity-main-menu");
.RuntimeState(stateBoot, "unity-main-menu")
.SectorMap
.Latest();
}
catch (Exception ex)
{
@ -162,7 +177,9 @@ public class MainMenu : MonoBehaviour
try
{
return AetheriaUnityRuntimeClientProvider
.CurrentPlayerSettings(stateBoot, "unity-main-menu");
.RuntimeState(stateBoot, "unity-main-menu")
.PlayerSettings
.Latest();
}
catch (Exception ex)
{
@ -179,7 +196,9 @@ public class MainMenu : MonoBehaviour
try
{
return AetheriaUnityRuntimeClientProvider
.CurrentVerseHostSettings(stateBoot, "unity-main-menu");
.RuntimeState(stateBoot, "unity-main-menu")
.VerseHostSettings
.Latest();
}
catch (Exception ex)
{
@ -192,18 +211,19 @@ public class MainMenu : MonoBehaviour
{
if (!TryStartDaemonObservedGame("Continuing Daemon Run"))
{
ShowMain();
ShowMain(false);
}
}
private void ShowSettings()
private void ShowSettings(bool animateFromRight = true)
{
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(DateTime.UtcNow.ToString("O")),
HandleSettingsSurfaceCommand);
HandleSettingsSurfaceCommand,
animateFromRight);
}
private void ShowInputSettings()
private void ShowInputSettings(bool animateFromRight = true)
{
var stateBoot = CurrentStateBoot();
RenderMenuSurface(
@ -213,19 +233,21 @@ public class MainMenu : MonoBehaviour
CanOpenRuntimeInputScreen(),
InGame,
DateTime.UtcNow.ToString("O")),
HandleInputSettingsSurfaceCommand);
HandleInputSettingsSurfaceCommand,
animateFromRight);
}
private void ShowPlayerSettingsSurface()
private void ShowPlayerSettingsSurface(bool animateFromRight = true)
{
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell(
ResolvePlayerSettings(CurrentStateBoot()),
DateTime.UtcNow.ToString("O")),
HandlePlayerSettingsSurfaceCommand);
HandlePlayerSettingsSurfaceCommand,
animateFromRight);
}
private void ShowVerseSettingsSurface()
private void ShowVerseSettingsSurface(bool animateFromRight = true)
{
var stateBoot = CurrentStateBoot();
RenderMenuSurface(
@ -234,20 +256,377 @@ public class MainMenu : MonoBehaviour
stateBoot,
ResolveVerseHostSettings(stateBoot),
DateTime.UtcNow.ToString("O"))),
HandleVerseSettingsSurfaceCommand);
HandleVerseSettingsSurfaceCommand,
animateFromRight);
}
private void RenderMenuSurface(
AetheriaRuntimeSurfaceDocument document,
Action<EveSurfaceCommandRequest> commandHandler)
Action<EveSurfaceCommandRequest> commandHandler,
bool animateFromRight)
{
_menuSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
var initialRender = _currentMenuSurfaceDocument == null;
var targetDocument = initialRender ? _currentMenuSurfaceDocument : _nextMenuSurfaceDocument;
targetDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
transform,
_menuSurfaceDocument,
"Aetheria Menu Surface",
targetDocument,
initialRender ? "Aetheria Menu Surface" : "Aetheria Menu Surface Transition",
document,
commandHandler,
_menuSurfaceChrome);
_menuSurfaceChrome,
sortingOrder: initialRender ? 1000 : 1001);
ApplyMenuStyles(targetDocument.rootVisualElement, IsRootMainMenu(document));
SetDocumentTransition(targetDocument, initialRender ? 0f : (animateFromRight ? FadeDistance : -FadeDistance), initialRender ? 1f : 0f);
if (initialRender)
{
_currentMenuSurfaceDocument = targetDocument;
return;
}
_nextMenuSurfaceDocument = targetDocument;
_fadeFromRight = animateFromRight;
_fadeLerp = 0f;
_fading = true;
}
private static bool IsRootMainMenu(AetheriaRuntimeSurfaceDocument document)
{
return string.Equals(
document?.Surface?.Id,
AetheriaRuntimeMainMenuCommands.RootSurfaceId,
StringComparison.Ordinal);
}
private void UpdateMenuFade()
{
if (!_fading)
return;
_fadeLerp += Time.unscaledDeltaTime / Mathf.Max(0.01f, FadeTime);
var t = Mathf.Clamp01(_fadeLerp);
var outgoing = Mathf.Pow(t, FadePositionExponent);
var incoming = Mathf.Pow(1f - t, FadePositionExponent);
var direction = _fadeFromRight ? 1f : -1f;
SetDocumentTransition(
_currentMenuSurfaceDocument,
-direction * FadeDistance * outgoing,
Mathf.Pow(1f - t, FadeAlphaExponent));
SetDocumentTransition(
_nextMenuSurfaceDocument,
direction * FadeDistance * incoming,
Mathf.Pow(t, FadeAlphaExponent));
if (_fadeLerp <= 1f)
return;
_fading = false;
AetheriaEveUnitySurfaceHost.Hide(_currentMenuSurfaceDocument);
var previous = _currentMenuSurfaceDocument;
_currentMenuSurfaceDocument = _nextMenuSurfaceDocument;
_nextMenuSurfaceDocument = previous;
SetDocumentTransition(_currentMenuSurfaceDocument, 0f, 1f);
}
private static void SetDocumentTransition(UIDocument document, float x, float opacity)
{
if (document?.rootVisualElement == null)
return;
document.rootVisualElement.style.translate = new Translate(new Length(x, LengthUnit.Pixel), 0f, 0f);
document.rootVisualElement.style.opacity = opacity;
}
private void UpdateHoverButtons()
{
for (var i = _hoverButtons.Count - 1; i >= 0; i--)
{
var hover = _hoverButtons[i];
if (hover.Button == null)
{
_hoverButtons.RemoveAt(i);
continue;
}
var direction = hover.Hovering && hover.Button.enabledInHierarchy ? 1f : -1f;
hover.Lerp = Mathf.Clamp01(hover.Lerp + Time.unscaledDeltaTime / Mathf.Max(0.01f, HoverAnimationDuration) * direction);
hover.Button.style.letterSpacing = HoverSpacing * hover.Lerp;
}
}
private void ApplyMenuStyles(VisualElement root, bool rootMainMenu)
{
if (root == null)
return;
StyleMenuTree(root, rootMainMenu);
var surface = rootMainMenu
? FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.root")
: FindFirstKind(root, "surface");
if (surface != null)
{
surface.style.flexGrow = 0f;
surface.style.width = rootMainMenu ? 680f : 560f;
surface.style.alignItems = Align.FlexStart;
surface.pickingMode = PickingMode.Position;
}
var title = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.title") as Label;
if (title != null)
{
title.style.fontSize = 82f;
title.style.color = new Color(0.82f, 0.95f, 1f, 0.96f);
title.style.unityFontStyleAndWeight = FontStyle.Normal;
title.style.unityTextAlign = TextAnchor.MiddleLeft;
title.style.marginLeft = 0f;
title.style.marginBottom = -12f;
title.style.width = 660f;
}
var subtitle = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.subtitle") as Label;
if (subtitle != null)
{
subtitle.style.fontSize = 38f;
subtitle.style.color = new Color(0.82f, 0.95f, 1f, 0.92f);
subtitle.style.unityFontStyleAndWeight = FontStyle.Normal;
subtitle.style.unityTextAlign = TextAnchor.MiddleRight;
subtitle.style.marginTop = 0f;
subtitle.style.marginBottom = 36f;
subtitle.style.width = 660f;
}
RegisterHoverButtons(root);
}
private void StyleMenuTree(VisualElement element, bool rootMainMenu)
{
if (element == null)
return;
element.pickingMode = PickingMode.Position;
if (element is UiButton button)
StyleMenuButton(button, rootMainMenu);
else if (element is TextField textField)
StyleMenuTextField(textField, rootMainMenu);
else if (element is Label label)
StyleMenuLabel(label, rootMainMenu);
else
StyleMenuContainer(element, rootMainMenu);
foreach (var child in element.Children())
StyleMenuTree(child, rootMainMenu);
}
private static void StyleMenuContainer(VisualElement element, bool rootMainMenu)
{
element.style.backgroundColor = new Color(0f, 0f, 0f, 0f);
element.style.borderLeftWidth = 0f;
element.style.borderRightWidth = 0f;
element.style.borderTopWidth = 0f;
element.style.borderBottomWidth = 0f;
element.style.marginLeft = 0f;
element.style.marginTop = 0f;
element.style.marginBottom = 0f;
if (element.ClassListContains("eve-row"))
{
element.style.flexDirection = FlexDirection.Row;
element.style.flexWrap = Wrap.Wrap;
element.style.alignItems = Align.Center;
element.style.marginTop = 2f;
element.style.marginBottom = 6f;
return;
}
if (element.ClassListContains("eve-card"))
{
element.style.flexDirection = FlexDirection.Column;
element.style.alignItems = Align.FlexStart;
element.style.width = rootMainMenu ? 680f : 540f;
element.style.marginBottom = 18f;
element.style.paddingTop = 0f;
element.style.paddingBottom = 2f;
return;
}
if (element.ClassListContains("eve-metric") || element.ClassListContains("eve-field"))
{
element.style.flexDirection = FlexDirection.Row;
element.style.alignItems = Align.Center;
element.style.width = rootMainMenu ? 320f : 520f;
element.style.marginBottom = 5f;
return;
}
if (element.ClassListContains("eve-kind-control-text"))
{
element.style.flexDirection = FlexDirection.Row;
element.style.alignItems = Align.Center;
element.style.width = rootMainMenu ? 520f : 520f;
element.style.marginBottom = rootMainMenu ? 6f : 10f;
return;
}
if (element.ClassListContains("eve-surface-root") || element.ClassListContains("eve-kind-surface"))
{
element.style.flexDirection = FlexDirection.Column;
element.style.alignItems = Align.FlexStart;
element.style.width = rootMainMenu ? 680f : 560f;
return;
}
element.style.flexDirection = FlexDirection.Column;
element.style.alignItems = Align.FlexStart;
}
private static void StyleMenuLabel(Label label, bool rootMainMenu)
{
label.style.color = new Color(0.86f, 0.98f, 1f, 0.94f);
label.style.unityFontStyleAndWeight = FontStyle.Normal;
label.style.unityTextAlign = TextAnchor.MiddleLeft;
label.style.whiteSpace = WhiteSpace.Normal;
label.style.marginLeft = 0f;
label.style.marginTop = 0f;
label.style.marginBottom = 0f;
if (label.ClassListContains("eve-title"))
{
label.style.fontSize = rootMainMenu ? 24f : 24f;
label.style.unityFontStyleAndWeight = FontStyle.Normal;
label.style.marginTop = rootMainMenu ? 0f : 2f;
label.style.marginBottom = rootMainMenu ? 0f : 10f;
label.style.maxWidth = rootMainMenu ? 620f : 520f;
return;
}
if (label.ClassListContains("eve-muted"))
{
label.style.fontSize = rootMainMenu ? 18f : 15f;
label.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f);
label.style.width = rootMainMenu ? 120f : 190f;
label.style.marginRight = 12f;
return;
}
if (label.ClassListContains("eve-value"))
{
label.style.fontSize = rootMainMenu ? 20f : 16f;
label.style.unityFontStyleAndWeight = FontStyle.Normal;
label.style.maxWidth = rootMainMenu ? 360f : 300f;
return;
}
label.style.fontSize = rootMainMenu ? 24f : 16f;
label.style.maxWidth = rootMainMenu ? 620f : 520f;
label.style.marginBottom = rootMainMenu ? 0f : 10f;
}
private static void StyleMenuTextField(TextField field, bool rootMainMenu)
{
field.style.width = rootMainMenu ? 360f : 520f;
field.style.height = rootMainMenu ? 30f : 28f;
field.style.marginLeft = 0f;
field.style.marginTop = 0f;
field.style.marginBottom = rootMainMenu ? 4f : 8f;
field.style.color = new Color(0.86f, 0.98f, 1f, 0.96f);
field.style.fontSize = rootMainMenu ? 18f : 15f;
field.pickingMode = PickingMode.Position;
field.labelElement.style.minWidth = rootMainMenu ? 100f : 190f;
field.labelElement.style.width = rootMainMenu ? 100f : 190f;
field.labelElement.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f);
}
private static void StyleMenuButton(UiButton button, bool rootMainMenu)
{
button.style.backgroundColor = new Color(0f, 0f, 0f, 0f);
button.style.borderLeftWidth = 0f;
button.style.borderRightWidth = 0f;
button.style.borderTopWidth = 0f;
button.style.borderBottomWidth = 0f;
button.style.color = new Color(0.86f, 0.98f, 1f, 0.96f);
button.style.fontSize = rootMainMenu ? 24f : 16f;
button.style.unityFontStyleAndWeight = FontStyle.Normal;
button.style.unityTextAlign = TextAnchor.MiddleLeft;
button.style.width = rootMainMenu ? 220f : 230f;
button.style.height = rootMainMenu ? 32f : 24f;
button.style.marginLeft = 0f;
button.style.marginTop = 0f;
button.style.marginRight = rootMainMenu ? 0f : 14f;
button.style.marginBottom = rootMainMenu ? 0f : 4f;
button.style.paddingLeft = 0f;
button.style.paddingRight = 0f;
button.style.paddingTop = 0f;
button.style.paddingBottom = 0f;
button.style.letterSpacing = 0f;
button.focusable = true;
button.pickingMode = PickingMode.Position;
}
private void RegisterHoverButtons(VisualElement root)
{
foreach (var button in FindButtons(root))
{
var hover = new MenuHoverButton(button);
_hoverButtons.Add(hover);
button.RegisterCallback<PointerEnterEvent>(_ => hover.Hovering = true);
button.RegisterCallback<PointerLeaveEvent>(_ => hover.Hovering = false);
}
}
private static IEnumerable<UiButton> FindButtons(VisualElement root)
{
if (root == null)
yield break;
if (root is UiButton button)
yield return button;
foreach (var child in root.Children())
{
foreach (var nested in FindButtons(child))
yield return nested;
}
}
private static VisualElement FindFirstKind(VisualElement root, string kind)
{
if (root == null)
return null;
if (root.ClassListContains($"eve-kind-{kind}"))
return root;
foreach (var child in root.Children())
{
var match = FindFirstKind(child, kind);
if (match != null)
return match;
}
return null;
}
private static VisualElement FindElement(VisualElement root, string name)
{
if (root == null)
return null;
if (string.Equals(root.name, name, StringComparison.Ordinal))
return root;
foreach (var child in root.Children())
{
var match = FindElement(child, name);
if (match != null)
return match;
}
return null;
}
private void HandleSettingsSurfaceCommand(EveSurfaceCommandRequest request)
@ -261,16 +640,16 @@ public class MainMenu : MonoBehaviour
switch (command.Kind)
{
case AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings:
ShowPlayerSettingsSurface();
ShowPlayerSettingsSurface(true);
return;
case AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings:
ShowVerseSettingsSurface();
ShowVerseSettingsSurface(true);
return;
case AetheriaRuntimeMainMenuCommandKind.ShowInputSettings:
ShowInputSettings();
ShowInputSettings(true);
return;
case AetheriaRuntimeMainMenuCommandKind.BackToMain:
ShowMain();
ShowMain(false);
return;
default:
Debug.LogWarning($"Unhandled settings menu command kind: {command.Kind}");
@ -289,12 +668,12 @@ public class MainMenu : MonoBehaviour
switch (command.Kind)
{
case AetheriaRuntimeMainMenuCommandKind.BackToSettings:
ShowSettings();
ShowSettings(false);
return;
case AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen:
if (!TryOpenRuntimeInputScreen())
{
ShowInputSettings();
ShowInputSettings(false);
}
return;
@ -315,11 +694,11 @@ public class MainMenu : MonoBehaviour
switch (command.Kind)
{
case AetheriaRuntimeMainMenuCommandKind.BackToSettings:
ShowSettings();
ShowSettings(false);
return;
case AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand:
SendKnownAetheriaEveCommand(request, "player-settings");
ShowPlayerSettingsSurface();
ShowPlayerSettingsSurface(false);
return;
default:
Debug.LogWarning($"Unhandled player-settings command kind: {command.Kind}");
@ -338,15 +717,15 @@ public class MainMenu : MonoBehaviour
switch (command.Kind)
{
case AetheriaRuntimeMainMenuCommandKind.BackToSettings:
ShowSettings();
ShowSettings(false);
return;
case AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand:
RequestClientTargetCommand(request);
ShowVerseSettingsSurface();
ShowVerseSettingsSurface(false);
return;
case AetheriaRuntimeMainMenuCommandKind.VerseHostCommand:
SendKnownAetheriaEveCommand(request, "Verse-host");
ShowVerseSettingsSurface();
ShowVerseSettingsSurface(false);
return;
default:
Debug.LogWarning($"Unhandled verse-settings command kind: {command.Kind}");
@ -356,10 +735,8 @@ public class MainMenu : MonoBehaviour
private void HideMenuSurface()
{
if (_menuSurfaceDocument == null)
return;
AetheriaEveUnitySurfaceHost.Hide(_menuSurfaceDocument);
AetheriaEveUnitySurfaceHost.Hide(_currentMenuSurfaceDocument);
AetheriaEveUnitySurfaceHost.Hide(_nextMenuSurfaceDocument);
}
public void SetRuntimeInputScreenShell(Func<bool> canOpenRuntimeInputScreen, Action openRuntimeInputScreen)
@ -446,10 +823,28 @@ public class MainMenu : MonoBehaviour
private void OnDestroy()
{
if (_menuSurfaceDocument != null)
if (_currentMenuSurfaceDocument != null)
{
Destroy(_menuSurfaceDocument.gameObject);
_menuSurfaceDocument = null;
Destroy(_currentMenuSurfaceDocument.gameObject);
_currentMenuSurfaceDocument = null;
}
if (_nextMenuSurfaceDocument != null)
{
Destroy(_nextMenuSurfaceDocument.gameObject);
_nextMenuSurfaceDocument = null;
}
}
private sealed class MenuHoverButton
{
public MenuHoverButton(UiButton button)
{
Button = button;
}
public UiButton Button { get; }
public bool Hovering { get; set; }
public float Lerp { get; set; }
}
}