Files
Workspace/Assets/Scenes/ShrinkEmbeddedHostNetworkDemoSceneController.cs
T

353 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using ShrinkEventBus;
using ShrinkNetwork;
using ShrinkNetwork.Integration;
using UnityEngine;
public sealed class ShrinkEmbeddedHostNetworkDemoSceneController : MonoBehaviour
{
private const long DemoSessionId = 1;
private const string DemoPlayerId = "local-player";
private const string FireballSkillId = "fireball";
private const string UltimateSkillId = "ultimate";
private readonly List<string> _logs = new();
private ShrinkNetworkService _embeddedHostService;
private ShrinkNetworkService _localClientService;
private ShrinkLoopbackTransport _embeddedHostTransport;
private ShrinkLoopbackTransport _localClientTransport;
private ShrinkNetworkSession _localClientSession;
private int _clientHp;
private int _hostObservedHp;
private int _hostMana;
private long _nextDeltaVersion;
private long _lastAppliedDeltaVersion;
private string _lastSkillDecision = "未请求";
private Vector2 _logScroll;
private bool _initialized;
private void Start()
{
ResetDemo();
}
private void OnDestroy()
{
ShutdownDemo();
}
[ContextMenu("重置演示")]
public void ResetDemo()
{
ShutdownDemo();
ResetState();
RegisterEventHandlers();
SetupEmbeddedHostTopology();
_initialized = true;
AppendLog("演示已就绪:本地客户端已接入内置 Host Server。");
}
private void OnGUI()
{
var areaHeight = Mathf.Min(Screen.height - 32f, 860f);
var logHeight = Mathf.Max(180f, areaHeight - 610f);
var area = new Rect(16f, 16f, 560f, areaHeight);
GUILayout.BeginArea(area, GUI.skin.window);
GUILayout.Label("ShrinkSDK 内置 Host 联机演示");
GUILayout.Label("拓扑:本地客户端 -> 内置 Host Server(演示阶段使用 Loopback 代替 SteamP2P/LAN");
GUILayout.Space(8f);
GUILayout.Label("客户端状态");
GUILayout.Label($"血量:{_clientHp}");
GUILayout.Label($"下一个增量版本号:{_nextDeltaVersion + 1}");
GUILayout.Label($"最近一次技能裁决:{_lastSkillDecision}");
GUILayout.Space(8f);
GUILayout.Label("内置 Host 状态");
GUILayout.Label($"Host 观察到的客户端血量:{_hostObservedHp}");
GUILayout.Label($"Host 最近应用的增量版本:{_lastAppliedDeltaVersion}");
GUILayout.Label($"Host 当前可用法力:{_hostMana}");
GUILayout.Space(8f);
GUI.enabled = _initialized && _localClientSession != null;
if (GUILayout.Button("发送血量增量 -7", GUILayout.Height(32f)))
{
SendFreshHpDeltaAsync().Forget();
}
GUILayout.Label("作用:模拟客户端只同步变化后的血量,并携带新的 DeltaVersion。");
if (GUILayout.Button("重放旧版本增量(hp=999", GUILayout.Height(32f)))
{
SendStaleHpDeltaAsync().Forget();
}
GUILayout.Label("作用:发送一个过期版本号的旧增量,验证 Host 会拒绝重复或回退版本。");
if (GUILayout.Button("请求释放火球术(消耗 3)", GUILayout.Height(32f)))
{
RequestSkillAsync(FireballSkillId, 3).Forget();
}
GUILayout.Label("作用:客户端向 Host 发起 HasResult 请求,由 Host 判断法力是否足够并返回 ALLOW / DENY。");
if (GUILayout.Button("请求释放终极技能(消耗 6)", GUILayout.Height(32f)))
{
RequestSkillAsync(UltimateSkillId, 6).Forget();
}
GUILayout.Label("作用:制造一次更容易失败的裁决请求,观察 IsCanceled 与 EventResult 回传。");
GUI.enabled = _initialized;
if (GUILayout.Button("给 Host 补充法力 +2", GUILayout.Height(28f)))
{
_hostMana += 2;
AppendLog($"Host 法力已补充到 {_hostMana}。");
}
GUILayout.Label("作用:手动提高 Host 侧法力,用于重新测试技能裁决。");
if (GUILayout.Button("重置演示", GUILayout.Height(28f)))
{
ResetDemo();
}
GUILayout.Label("作用:重建内置 Host / Client 拓扑并清空当前演示状态。");
GUI.enabled = true;
GUILayout.Space(10f);
GUILayout.Label("事件日志");
_logScroll = GUILayout.BeginScrollView(_logScroll, GUILayout.Height(logHeight));
for (var i = 0; i < _logs.Count; i++)
{
GUILayout.Label(_logs[i]);
}
GUILayout.EndScrollView();
GUILayout.EndArea();
}
private void ResetState()
{
_clientHp = 100;
_hostObservedHp = 100;
_hostMana = 5;
_nextDeltaVersion = 0;
_lastAppliedDeltaVersion = 0;
_lastSkillDecision = "未请求";
_logs.Clear();
_logScroll = Vector2.zero;
}
private void RegisterEventHandlers()
{
EventBus.RegisterEvent<DemoPlayerHpDeltaEvent>(OnEmbeddedHostHpDelta, EventPriority.NORMAL);
EventBus.RegisterEvent<DemoCanUseSkillEvent>(OnEmbeddedHostSkillRequest, EventPriority.HIGH);
}
private void SetupEmbeddedHostTopology()
{
_embeddedHostService = new ShrinkNetworkService(new ShrinkJsonNetworkSerializer(),
new ShrinkNetworkMessageRegistry(), new ShrinkNetworkRouter());
_localClientService = new ShrinkNetworkService(new ShrinkJsonNetworkSerializer(),
new ShrinkNetworkMessageRegistry(), new ShrinkNetworkRouter());
_embeddedHostTransport = new ShrinkLoopbackTransport();
_localClientTransport = new ShrinkLoopbackTransport();
_embeddedHostTransport.LinkPeer(_localClientTransport);
_embeddedHostService.OnSessionConnected += OnEmbeddedHostSessionConnected;
_localClientService.OnSessionConnected += OnLocalClientSessionConnected;
_embeddedHostService.BindTransport(_embeddedHostTransport);
_localClientService.BindTransport(_localClientTransport);
_localClientTransport.OpenSession(DemoSessionId, "embedded-host");
}
private void ShutdownDemo()
{
EventBus.UnregisterAllEventsForObject(this);
if (_localClientTransport != null)
{
try
{
_localClientTransport.CloseSession(DemoSessionId, "embedded-host");
}
catch
{
}
}
if (_embeddedHostService != null)
{
_embeddedHostService.BindTransport(null);
_embeddedHostService.OnSessionConnected -= OnEmbeddedHostSessionConnected;
}
if (_localClientService != null)
{
_localClientService.BindTransport(null);
_localClientService.OnSessionConnected -= OnLocalClientSessionConnected;
}
_embeddedHostTransport?.Stop();
_localClientTransport?.Stop();
_localClientSession = null;
_embeddedHostService = null;
_localClientService = null;
_embeddedHostTransport = null;
_localClientTransport = null;
_initialized = false;
}
private void OnEmbeddedHostSessionConnected(ShrinkNetworkSession session)
{
session.SetPeerKind(ShrinkNetworkPeerKind.Client);
AppendLog($"内置 Host 已接收玩家会话 {session.SessionId}。");
}
private void OnLocalClientSessionConnected(ShrinkNetworkSession session)
{
session.SetPeerKind(ShrinkNetworkPeerKind.Server);
_localClientSession = session;
AppendLog($"本地客户端已作为会话 {session.SessionId} 接入内置 Host。");
}
private async UniTaskVoid SendFreshHpDeltaAsync()
{
if (_localClientSession == null)
return;
_clientHp = Mathf.Max(0, _clientHp - 7);
var version = ++_nextDeltaVersion;
AppendLog($"客户端发送血量增量 v{version},目标血量 {_clientHp}。");
await _localClientSession.PublishEventAsync(new DemoPlayerHpDeltaEvent
{
PlayerId = DemoPlayerId,
TargetNode = DemoNetworkNodeRole.EmbeddedHost,
Hp = _clientHp,
DeltaVersion = version
});
await UniTask.Yield();
AppendLog($"Host 应用后状态:血量={_hostObservedHp},版本={_lastAppliedDeltaVersion}。");
}
private async UniTaskVoid SendStaleHpDeltaAsync()
{
if (_localClientSession == null)
return;
if (_nextDeltaVersion <= 1)
{
AppendLog("请先发送至少一次新的增量,再重放旧版本增量。");
return;
}
var staleVersion = _nextDeltaVersion - 1;
AppendLog($"客户端重放旧版本增量 v{staleVersion},并伪造 hp=999。");
await _localClientSession.PublishEventAsync(new DemoPlayerHpDeltaEvent
{
PlayerId = DemoPlayerId,
TargetNode = DemoNetworkNodeRole.EmbeddedHost,
Hp = 999,
DeltaVersion = staleVersion
});
await UniTask.Yield();
AppendLog($"Host 已忽略旧增量,血量仍为 {_hostObservedHp},版本={_lastAppliedDeltaVersion}。");
}
private async UniTaskVoid RequestSkillAsync(string skillId, int manaCost)
{
if (_localClientSession == null)
return;
var evt = new DemoCanUseSkillEvent
{
PlayerId = DemoPlayerId,
SkillId = skillId,
ManaCost = manaCost,
TargetNode = DemoNetworkNodeRole.EmbeddedHost
};
AppendLog($"客户端请求释放技能“{skillId}”,法力消耗 {manaCost}。");
var outcome = await _localClientSession.RequestEventAsync(evt);
if (!outcome.IsSuccess)
{
_lastSkillDecision = $"错误 {outcome.ErrorCode}";
AppendLog($"技能请求失败:{outcome.ErrorMessage}");
return;
}
_lastSkillDecision = outcome.Result.ToString();
AppendLog($"客户端收到裁决:{outcome.Result}Canceled={outcome.IsCanceled}Host 法力={_hostMana}。");
}
private void OnEmbeddedHostHpDelta(DemoPlayerHpDeltaEvent evt)
{
if (evt.TargetNode != DemoNetworkNodeRole.EmbeddedHost)
return;
_hostObservedHp = evt.Hp;
_lastAppliedDeltaVersion = evt.DeltaVersion;
AppendLog($"内置 Host 已应用血量增量 v{evt.DeltaVersion}hp={evt.Hp}。");
}
private void OnEmbeddedHostSkillRequest(DemoCanUseSkillEvent evt)
{
if (evt.TargetNode != DemoNetworkNodeRole.EmbeddedHost)
return;
if (_hostMana >= evt.ManaCost)
{
_hostMana -= evt.ManaCost;
evt.SetResult(EventResult.ALLOW);
AppendLog($"内置 Host 批准技能“{evt.SkillId}”,剩余法力 {_hostMana}。");
return;
}
evt.SetResult(EventResult.DENY);
evt.SetCanceled(true);
AppendLog($"内置 Host 拒绝技能“{evt.SkillId}”,需要 {evt.ManaCost},当前仅有 {_hostMana}。");
}
private void AppendLog(string message)
{
_logs.Add($"[{DateTime.Now:HH:mm:ss}] {message}");
if (_logs.Count > 18)
_logs.RemoveAt(0);
_logScroll.y = float.MaxValue;
}
}
public enum DemoNetworkNodeRole
{
LocalClient = 0,
EmbeddedHost = 1
}
[ShrinkNetworkEvent]
[ShrinkNetworkMessage(3401, "demo/player_hp_delta")]
public sealed class DemoPlayerHpDeltaEvent : EventBase, IShrinkNetworkMessage, IShrinkNetworkDeltaEvent
{
public DemoNetworkNodeRole TargetNode { get; set; }
public string PlayerId { get; set; } = "";
public int Hp { get; set; }
public long DeltaVersion { get; set; }
public string DeltaKey => $"{TargetNode}:{PlayerId}";
}
[Cancelable]
[HasResult]
[ShrinkNetworkEvent]
[ShrinkNetworkMessage(3410, "demo/can_use_skill")]
public sealed class DemoCanUseSkillEvent : EventBase, IShrinkNetworkRequest
{
public DemoNetworkNodeRole TargetNode { get; set; }
public string PlayerId { get; set; } = "";
public string SkillId { get; set; } = "";
public int ManaCost { get; set; }
}