(Feat) Add Network Body
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Ashwild.Network;
|
||||
using Ashwild.Player;
|
||||
|
||||
namespace Ashwild.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// The in-game feed that spawns a "X a rejoint / a quitté la partie" line whenever a remote player
|
||||
/// joins or leaves the session. Listens to the network layer's SessionPresenceEvents (raised on
|
||||
/// every client, host or not) and instantiates a PresenceFeedEntry under a holder whose layout group
|
||||
/// stacks the lines. Drop this on the holder (or any object referencing it) in the game scene.
|
||||
///
|
||||
/// While the local player is tearing the session down (returning to the menu, or the host stopping)
|
||||
/// every remote copy despawns at once, which would spam "left" lines; this feed goes quiet on those
|
||||
/// bus events so that storm never reaches the screen.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerPresenceFeedUI : MonoBehaviour
|
||||
{
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("Holder the entries are parented to — give it a VerticalLayoutGroup so lines stack.")]
|
||||
[SerializeField] private RectTransform holder;
|
||||
|
||||
[Tooltip("Prefab with a PresenceFeedEntry (CanvasGroup + label) instantiated per notification.")]
|
||||
[SerializeField] private PresenceFeedEntry entryPrefab;
|
||||
|
||||
[Header("Messages")]
|
||||
[Tooltip("Join line format — {0} is the player name.")]
|
||||
[SerializeField] private string joinFormat = "{0} a rejoint la partie";
|
||||
|
||||
[Tooltip("Leave line format — {0} is the player name.")]
|
||||
[SerializeField] private string leaveFormat = "{0} a quitté la partie";
|
||||
|
||||
[Header("Colours")]
|
||||
[SerializeField] private Color joinColor = new Color(0.45f, 1f, 0.45f);
|
||||
[SerializeField] private Color leaveColor = new Color(1f, 0.55f, 0.45f);
|
||||
|
||||
[Header("Timing")]
|
||||
[Tooltip("Seconds a line stays fully visible before fading out.")]
|
||||
[SerializeField] private float holdDuration = 4f;
|
||||
[SerializeField] private float fadeInDuration = 0.3f;
|
||||
[SerializeField] private float fadeOutDuration = 0.5f;
|
||||
|
||||
[Header("Limits")]
|
||||
[Tooltip("Oldest lines are removed once this many are on screen at once.")]
|
||||
[SerializeField] private int maxEntries = 5;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// Currently displayed lines, oldest first — trimmed from the front when over the cap.
|
||||
/// </summary>
|
||||
private readonly List<PresenceFeedEntry> activeEntries = new List<PresenceFeedEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// True once the session is tearing down, so the despawn-driven "left" storm is ignored.
|
||||
/// </summary>
|
||||
private bool suppressed;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to presence notifications and the teardown signals that mute the feed.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
SessionPresenceEvents.PlayerJoined += HandlePlayerJoined;
|
||||
SessionPresenceEvents.PlayerLeft += HandlePlayerLeft;
|
||||
PlayerEvents.ReturningToMenu += HandleTeardown;
|
||||
PlayerEvents.SessionStopped += HandleTeardown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes — mirrors OnEnable exactly.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
SessionPresenceEvents.PlayerJoined -= HandlePlayerJoined;
|
||||
SessionPresenceEvents.PlayerLeft -= HandlePlayerLeft;
|
||||
PlayerEvents.ReturningToMenu -= HandleTeardown;
|
||||
PlayerEvents.SessionStopped -= HandleTeardown;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Shows a green arrival line for the named player.
|
||||
/// </summary>
|
||||
private void HandlePlayerJoined(string playerName) => Push(string.Format(joinFormat, playerName), joinColor);
|
||||
|
||||
/// <summary>
|
||||
/// Shows an orange departure line for the named player.
|
||||
/// </summary>
|
||||
private void HandlePlayerLeft(string playerName) => Push(string.Format(leaveFormat, playerName), leaveColor);
|
||||
|
||||
/// <summary>
|
||||
/// Mutes the feed for the rest of its lifetime once the session is being torn down.
|
||||
/// </summary>
|
||||
private void HandleTeardown() => suppressed = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a feed line, trimming the oldest ones past the cap. Ignored while muted or when
|
||||
/// a reference is missing (logged once so a mis-wired holder/prefab is obvious).
|
||||
/// </summary>
|
||||
private void Push(string message, Color color)
|
||||
{
|
||||
if (suppressed) return;
|
||||
|
||||
if (holder == null || entryPrefab == null)
|
||||
{
|
||||
Debug.LogError("[PresenceFeed] Holder or entry prefab not assigned — cannot show notifications.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
activeEntries.RemoveAll(e => e == null);
|
||||
|
||||
while (activeEntries.Count >= maxEntries)
|
||||
{
|
||||
PresenceFeedEntry oldest = activeEntries[0];
|
||||
activeEntries.RemoveAt(0);
|
||||
if (oldest != null) Destroy(oldest.gameObject);
|
||||
}
|
||||
|
||||
PresenceFeedEntry entry = Instantiate(entryPrefab, holder);
|
||||
entry.Play(message, color, holdDuration, fadeInDuration, fadeOutDuration);
|
||||
activeEntries.Add(entry);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e34a77305857ab84a87431aca9903af0
|
||||
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using DG.Tweening;
|
||||
|
||||
namespace Ashwild.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A single line in the player presence feed (e.g. "Alex a rejoint la partie"). Fades itself in,
|
||||
/// holds, fades out, then self-destructs — the parent holder's layout group handles stacking, so
|
||||
/// this only ever animates its own alpha and never fights the layout for position.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(CanvasGroup))]
|
||||
public class PresenceFeedEntry : MonoBehaviour
|
||||
{
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("The label that shows the join/leave message.")]
|
||||
[SerializeField] private TMP_Text label;
|
||||
|
||||
[Tooltip("Faded in and out over the entry's lifetime. Auto-fetched from this object if left empty.")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
/// <summary>
|
||||
/// The full lifecycle tween (fade in → hold → fade out → destroy), killed on teardown.
|
||||
/// </summary>
|
||||
private Sequence lifecycle;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Caches the CanvasGroup when it wasn't wired in the inspector.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the lifecycle tween so it never targets a destroyed object.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
lifecycle?.Kill();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Fills in the message and colour, then runs the fade-in → hold → fade-out → destroy sequence.
|
||||
/// Safe to call once right after instantiation.
|
||||
/// </summary>
|
||||
public void Play(string message, Color color, float holdDuration, float fadeInDuration, float fadeOutDuration)
|
||||
{
|
||||
if (label != null)
|
||||
{
|
||||
label.text = message;
|
||||
label.color = color;
|
||||
}
|
||||
|
||||
canvasGroup.alpha = 0f;
|
||||
|
||||
lifecycle = DOTween.Sequence();
|
||||
lifecycle.Append(canvasGroup.DOFade(1f, fadeInDuration).SetEase(Ease.OutQuad));
|
||||
lifecycle.AppendInterval(holdDuration);
|
||||
lifecycle.Append(canvasGroup.DOFade(0f, fadeOutDuration).SetEase(Ease.InQuad));
|
||||
lifecycle.OnComplete(() => Destroy(gameObject));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c87382f506a62e489d4ea7a432accac
|
||||
Reference in New Issue
Block a user