diff --git a/Assets/Scripts/Gameplay/CapabilityPresentationBinder.cs b/Assets/Scripts/Gameplay/CapabilityPresentationBinder.cs new file mode 100644 index 000000000..c2ada7f9d --- /dev/null +++ b/Assets/Scripts/Gameplay/CapabilityPresentationBinder.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using UniRx; +using UnityEngine; + +// Wires one CapabilityEvents source to whatever presenter components live on this object or its +// children, subscribing each presenter only to the event kinds it implements (docs/headless- +// playground-cut.md fork L: "presentation is chosen per item, independently of capability... one +// presentation... can visualize any subset of an item's capabilities"). This binder has no knowledge +// of FieldDriver or any other concrete presentation, and no knowledge of what publishes events -- +// today the FieldShieldTest scene calls Bind() directly; a real capability owner will do the same +// later without this binder changing. +public class CapabilityPresentationBinder : MonoBehaviour +{ + private readonly List _subscriptions = new List(); + private Func _grabTargetResolver; + private Func _grabInitialVelocityResolver; + + // Subscribes every presenter interface implemented by components on this object or its children to + // the matching stream of `events`. `grabTargetResolver`/`grabInitialVelocityResolver` resolve a + // GrabEvent's plain target handle to the live Transform/velocity a grab presenter needs; the owner + // of `events` supplies them (the test scene today; the real pickup capability's caller later). + public void Bind(CapabilityEvents events, Func grabTargetResolver = null, + Func grabInitialVelocityResolver = null) + { + Unbind(); + if (events == null) return; + + _grabTargetResolver = grabTargetResolver; + _grabInitialVelocityResolver = grabInitialVelocityResolver; + + foreach (var presenter in GetComponentsInChildren(true)) + { + var p = presenter; + _subscriptions.Add(events.Absorb.Subscribe(e => p.Absorb(e))); + } + + foreach (var presenter in GetComponentsInChildren(true)) + { + var p = presenter; + _subscriptions.Add(events.Grab.Subscribe(e => + { + var target = _grabTargetResolver?.Invoke(e.TargetHandle); + var velocity = _grabInitialVelocityResolver?.Invoke(e.TargetHandle) ?? Vector3.zero; + p.Grab(e, target, velocity); + })); + } + + foreach (var presenter in GetComponentsInChildren(true)) + { + var p = presenter; + _subscriptions.Add(events.Melee.Subscribe(e => p.Melee(e))); + } + + foreach (var presenter in GetComponentsInChildren(true)) + { + var p = presenter; + _subscriptions.Add(events.Thrust.Subscribe(e => p.Thrust(e))); + } + } + + public void Unbind() + { + foreach (var s in _subscriptions) s.Dispose(); + _subscriptions.Clear(); + _grabTargetResolver = null; + _grabInitialVelocityResolver = null; + } + + private void OnDisable() => Unbind(); + private void OnDestroy() => Unbind(); +} diff --git a/Assets/Scripts/Gameplay/CapabilityPresentationBinder.cs.meta b/Assets/Scripts/Gameplay/CapabilityPresentationBinder.cs.meta new file mode 100644 index 000000000..9d508cb8c --- /dev/null +++ b/Assets/Scripts/Gameplay/CapabilityPresentationBinder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8f4aa77146e46a2a4b28d9fefb98496 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/CapabilityPresenters.cs b/Assets/Scripts/Gameplay/CapabilityPresenters.cs new file mode 100644 index 000000000..34e46dd39 --- /dev/null +++ b/Assets/Scripts/Gameplay/CapabilityPresenters.cs @@ -0,0 +1,30 @@ +using UnityEngine; + +// Presentation-side contract for the capability events in Assets/Scripts/ServerShared/CapabilityEvents.cs +// (docs/headless-playground-cut.md fork L). A presentation implements whichever of these interfaces +// match what it visualizes -- one presentation may implement all four (the field shield), or just one +// (a dedicated melee-blade effect). CapabilityPresentationBinder is what actually wires a presenter's +// method to the matching event stream; a presenter never talks to CapabilityEvents itself. +public interface IAbsorbPresenter +{ + void Absorb(AbsorbEvent e); +} + +// Grab needs a live Transform to animate, which the engine-free GrabEvent cannot carry (its +// TargetHandle is a plain identity, not a Unity reference). The binder resolves the handle through a +// delegate supplied by the event source's owner and hands the presenter the result, along with the +// initial velocity the target was already moving with (also owner-supplied, for the same reason). +public interface IGrabPresenter +{ + void Grab(GrabEvent e, Transform target, Vector3 initialVelocity); +} + +public interface IMeleePresenter +{ + void Melee(MeleeEvent e); +} + +public interface IThrustPresenter +{ + void Thrust(ThrustEvent e); +} diff --git a/Assets/Scripts/Gameplay/CapabilityPresenters.cs.meta b/Assets/Scripts/Gameplay/CapabilityPresenters.cs.meta new file mode 100644 index 000000000..e8ebf4275 --- /dev/null +++ b/Assets/Scripts/Gameplay/CapabilityPresenters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68fda84533414b48ac1722696ddb113d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: