(Feat) Add Network Body

This commit is contained in:
2026-07-04 09:33:53 +02:00
parent 7bd2aa3120
commit 031ac42e5d
32 changed files with 1385 additions and 49 deletions
@@ -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
}
}