Mount provider-owned Eve UI in playable worlds

This commit is contained in:
Meta Vens 2026-07-20 04:52:04 +01:00
commit 691c75dfa9
9 changed files with 113 additions and 3 deletions

View file

@ -5,7 +5,12 @@ interactive worlds.
Providers implement the surface-document, asset-manifest, command-sink, and
receipt-source ports. `EveUnityPlayableWorldClientBootstrap` wires those ports
to the generic scene host, input driver, camera rig, and `GameObject` sink.
to the generic scene host, input driver, camera rig, `GameObject` sink, and UI
Toolkit surface overlay. The overlay lowers provider-owned panes, metrics,
progress, inventory, and controls from the same Eve document used by the scene.
Stable structures update their native controls in place; only structural
changes rebuild the visual tree. Scene, field, and reactive render subtrees are
owned by their native lowerers and are not duplicated into empty UI elements.
`EveUnityCultMeshPlayableWorldProvider` is the runtime composition root for a
networked client. Give it one Verse rendezvous endpoint and it discovers an

View file

@ -121,6 +121,9 @@ namespace GameCult.Eve.UnityScene
var actionBar = GetComponent<EveUnityInputActionBar>();
if (actionBar == null) actionBar = gameObject.AddComponent<EveUnityInputActionBar>();
actionBar.Bind(this);
var uiOverlay = GetComponent<EveUnityUiToolkitOverlay>();
if (uiOverlay == null) uiOverlay = gameObject.AddComponent<EveUnityUiToolkitOverlay>();
uiOverlay.Bind(providerSurfaceDocuments!, commandSink!);
var presentation = Runtime.Connect();
ConnectionEpoch++;

View file

@ -0,0 +1,87 @@
using System;
using GameCult.Eve.UnityUIToolkit;
using UnityEngine;
using UnityEngine.UIElements;
#nullable enable
namespace GameCult.Eve.UnityScene
{
public sealed class EveUnityUiToolkitOverlay : MonoBehaviour
{
private IEveUnitySceneProviderSurfaceDocumentSource? _documents;
private IEveUnitySceneCommandSink? _commands;
private UIDocument? _document;
private EveUiToolkitSurfacePresenter? _presenter;
private VisualElement? _mountedRoot;
public VisualElement? Root => _mountedRoot;
public void Bind(MonoBehaviour documentSource, MonoBehaviour commandSink)
{
if (documentSource is not IEveUnitySceneProviderSurfaceDocumentSource documents)
throw new ArgumentException(
$"{nameof(documentSource)} must implement {nameof(IEveUnitySceneProviderSurfaceDocumentSource)}.",
nameof(documentSource));
if (commandSink is not IEveUnitySceneCommandSink commands)
throw new ArgumentException(
$"{nameof(commandSink)} must implement {nameof(IEveUnitySceneCommandSink)}.",
nameof(commandSink));
Unsubscribe();
_documents = documents;
_commands = commands;
_documents.DocumentAvailable += OnDocumentAvailable;
EnsureDocument();
Present(_documents.CurrentDocument);
}
private void OnDestroy() => Unsubscribe();
private void OnDocumentAvailable(EveUnitySceneProviderSurfaceDocument document) => Present(document);
private void Present(EveUnitySceneProviderSurfaceDocument document)
{
if (document == null || _commands == null) return;
_presenter ??= new EveUiToolkitSurfacePresenter();
var replaced = _presenter.Present(document.SurfaceDocument, _commands.Submit);
if (replaced && _presenter.Root != null)
{
_mountedRoot?.RemoveFromHierarchy();
_mountedRoot = _presenter.Root;
_mountedRoot.AddToClassList("eve-playable-world-ui-overlay");
}
AttachToDocument();
}
private void EnsureDocument()
{
_document = GetComponent<UIDocument>();
if (_document != null) return;
var panel = ScriptableObject.CreateInstance<PanelSettings>();
panel.name = "Eve playable-world surface panel";
panel.scaleMode = PanelScaleMode.ScaleWithScreenSize;
panel.referenceResolution = new Vector2Int(1920, 1080);
panel.match = 0.5f;
_document = gameObject.AddComponent<UIDocument>();
_document.panelSettings = panel;
_document.sortingOrder = 100;
}
private void AttachToDocument()
{
EnsureDocument();
var documentRoot = _document?.rootVisualElement;
if (_mountedRoot != null && _mountedRoot.parent == null && documentRoot != null)
documentRoot.Add(_mountedRoot);
}
private void Unsubscribe()
{
if (_documents != null)
_documents.DocumentAvailable -= OnDocumentAvailable;
_documents = null;
_commands = null;
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 843283a94b8a41bab85db52bd84aa59a

View file

@ -4,6 +4,7 @@
"references": [
"GameCult.Eve.Surface",
"GameCult.Eve.PluginFields",
"GameCult.Eve.UnityUIToolkit",
"Unity.Mathematics",
"Unity.RenderPipelines.Core.Runtime",
"Unity.RenderPipelines.Universal.Runtime"

View file

@ -1098,6 +1098,8 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(bootstrap.SceneRoot!.childCount, Is.EqualTo(3));
Assert.That(hostObject.GetComponent<EveUnityPlayableWorldInputDriver>(), Is.Not.Null);
Assert.That(hostObject.GetComponent<EveUnityPlayableWorldCameraRig>(), Is.Not.Null);
Assert.That(hostObject.GetComponent<EveUnityUiToolkitOverlay>(), Is.Not.Null);
Assert.That(hostObject.GetComponent<EveUnityUiToolkitOverlay>().Root, Is.Not.Null);
Assert.That(bootstrap.CameraTransform, Is.Not.Null);
Assert.That(provider.RefreshCount, Is.EqualTo(1));

View file

@ -5,6 +5,7 @@
"GameCult.Eve.Surface",
"GameCult.Eve.UnityScene",
"GameCult.Eve.UnityScene.CultMesh",
"GameCult.Eve.UnityUIToolkit",
"GameCult.Eve.PluginFields",
"Unity.RenderPipelines.Core.Runtime",
"Unity.RenderPipelines.Universal.Runtime"

View file

@ -113,6 +113,7 @@
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs",
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs",
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs",
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityUiToolkitOverlay.cs",
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs",
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs",
"packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs",
@ -159,10 +160,17 @@
"requiredPackageDependencies": [
{
"packageName": "org.gamecult.eve.surface",
"version": "0.1.0",
"version": "0.2.4",
"packageManager": "upm",
"ownerRepo": "EveUnity",
"purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents."
},
{
"packageName": "org.gamecult.eve.unity-uitoolkit",
"version": "0.1.2",
"packageManager": "upm",
"ownerRepo": "EveUnity",
"purpose": "Generic UI Toolkit lowering for provider-owned Eve panes, metrics, progress, text, and controls."
}
]
},

View file

@ -1,13 +1,14 @@
{
"name": "org.gamecult.eve.unity-scene",
"displayName": "Eve Unity Scene Lowering",
"version": "0.3.100",
"version": "0.3.101",
"unity": "6000.0",
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
"dependencies": {
"org.gamecult.cultlib": "1.0.43",
"org.gamecult.eve.plugin-fields": "0.2.3",
"org.gamecult.eve.surface": "0.2.4",
"org.gamecult.eve.unity-uitoolkit": "0.1.2",
"com.unity.mathematics": "1.3.2",
"com.unity.render-pipelines.universal": "17.4.0",
"com.unity.modules.particlesystem": "1.0.0"