341 lines
12 KiB
C#
341 lines
12 KiB
C#
using Fighter3D.Core;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using XGame.MiniGame;
|
|
using XWorld.Framework;
|
|
using XWorld.Framework.Protocol;
|
|
|
|
namespace Fighter3D.Client
|
|
{
|
|
public sealed class FighterGameClient : IGameClient
|
|
{
|
|
private const ushort InputOpcode = 1;
|
|
private const ushort SnapshotOpcode = 100;
|
|
private const string UiPrefabPath = "Assets/MiniGames/Fighter3D/res/UI/Prefab/UI_Fighter3D.prefab";
|
|
private const string BehaviorCatalogType = "fighter3d";
|
|
private const string BehaviorConfigRoot = "Assets/MiniGames/Fighter3D/res/Config/ActorBehavior";
|
|
|
|
private readonly FighterLogic _logic = new FighterLogic();
|
|
private IGameClientCtx _ctx;
|
|
private FighterState _state;
|
|
private FighterHud _hud;
|
|
private MiniGameActorPresentation _actors;
|
|
private int _lastAxis = int.MinValue;
|
|
private FighterButtons _lastButtons = (FighterButtons)255;
|
|
|
|
public void OnEnter(IGameClientCtx ctx)
|
|
{
|
|
_ctx = ctx;
|
|
_actors = MiniGameActorPresentation.Create("Fighter3D_ActorPresentation", BehaviorCatalogType, BehaviorConfigRoot);
|
|
RenderActors(_state);
|
|
_ctx.Assets.Load(UiPrefabPath, OnPrefabLoaded);
|
|
_ctx.Logger.Info("fighter3d client entered");
|
|
}
|
|
|
|
public void OnNetMessage(NetMessage message)
|
|
{
|
|
if (message.Opcode != SnapshotOpcode)
|
|
return;
|
|
|
|
_state = _logic.Decode(message.Payload);
|
|
_hud?.Render(_state);
|
|
RenderActors(_state);
|
|
}
|
|
|
|
public void OnUpdate(float deltaTime)
|
|
{
|
|
if (_ctx == null)
|
|
return;
|
|
|
|
int axis = ReadAxis();
|
|
FighterButtons buttons = ReadButtons();
|
|
if (axis != _lastAxis || buttons != _lastButtons)
|
|
{
|
|
SendControls(axis, buttons);
|
|
_lastAxis = axis;
|
|
_lastButtons = buttons;
|
|
}
|
|
|
|
_hud?.Render(_state);
|
|
RenderActors(_state);
|
|
_actors?.Tick(deltaTime);
|
|
}
|
|
|
|
public void OnExit()
|
|
{
|
|
_ctx?.Logger.Info("fighter3d client exited");
|
|
_hud?.Destroy();
|
|
_actors?.Destroy();
|
|
_hud = null;
|
|
_actors = null;
|
|
_ctx = null;
|
|
_state = null;
|
|
_lastAxis = int.MinValue;
|
|
_lastButtons = (FighterButtons)255;
|
|
}
|
|
|
|
private void OnPrefabLoaded(object loaded)
|
|
{
|
|
if (_ctx == null)
|
|
return;
|
|
|
|
GameObject root = loaded as GameObject;
|
|
if (root != null)
|
|
{
|
|
root = UnityEngine.Object.Instantiate(root);
|
|
root.name = "UI_Fighter3D";
|
|
}
|
|
else
|
|
{
|
|
_ctx.Logger.Info("fighter3d ui prefab missing, using runtime hud");
|
|
root = FighterHud.CreateFallbackRoot();
|
|
}
|
|
|
|
UnityEngine.Object.DontDestroyOnLoad(root);
|
|
_hud = new FighterHud(root);
|
|
_hud.Render(_state);
|
|
}
|
|
|
|
private void RenderActors(FighterState state)
|
|
{
|
|
if (_actors == null)
|
|
return;
|
|
|
|
if (state == null)
|
|
{
|
|
_actors.RenderActor(CreateActorFrame(0, -1f, 1, FighterAction.Idle, true));
|
|
_actors.RenderActor(CreateActorFrame(1, 1f, -1, FighterAction.Idle, true));
|
|
return;
|
|
}
|
|
|
|
_actors.RenderActor(CreateActorFrame(state.Fighters[0], state.Phase != FighterPhase.Finished));
|
|
_actors.RenderActor(CreateActorFrame(state.Fighters[1], state.Phase != FighterPhase.Finished));
|
|
}
|
|
|
|
private void SendControls(int axis, FighterButtons buttons)
|
|
{
|
|
var writer = new PacketWriter();
|
|
writer.WriteVarInt(axis);
|
|
writer.WriteByte((byte)buttons);
|
|
_ctx.Send(new NetMessage(InputOpcode, writer.ToArray()));
|
|
}
|
|
|
|
private static int ReadAxis()
|
|
{
|
|
bool left = Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow);
|
|
bool right = Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow);
|
|
if (left == right)
|
|
return 0;
|
|
return left ? -1 : 1;
|
|
}
|
|
|
|
private static FighterButtons ReadButtons()
|
|
{
|
|
FighterButtons buttons = FighterButtons.None;
|
|
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.LeftShift))
|
|
buttons |= FighterButtons.Guard;
|
|
if (Input.GetKey(KeyCode.J))
|
|
buttons |= FighterButtons.Light;
|
|
if (Input.GetKey(KeyCode.K))
|
|
buttons |= FighterButtons.Heavy;
|
|
return buttons;
|
|
}
|
|
|
|
private static MiniGameActorFrame CreateActorFrame(FighterUnitState unit, bool running)
|
|
{
|
|
return CreateActorFrame(unit.Seat, unit.X, unit.Facing, unit.Action, running && unit.Hp > 0);
|
|
}
|
|
|
|
private static MiniGameActorFrame CreateActorFrame(int seat, float x, int facing, FighterAction action, bool alive)
|
|
{
|
|
int actorId = seat + 1;
|
|
return new MiniGameActorFrame
|
|
{
|
|
ActorId = actorId,
|
|
Figure = actorId,
|
|
TeamId = actorId,
|
|
Position = new Vector3(x, action == FighterAction.Knockout ? 0.16f : 0f, 0f),
|
|
Forward = facing >= 0 ? Vector3.right : Vector3.left,
|
|
Moving = action == FighterAction.Moving,
|
|
Alive = alive && action != FighterAction.Knockout,
|
|
TriggerKey = action.ToString(),
|
|
BehaviorId = GetBehaviorId(action)
|
|
};
|
|
}
|
|
|
|
private static string GetBehaviorId(FighterAction action)
|
|
{
|
|
if (action == FighterAction.LightAttack)
|
|
return "fighter3d_light";
|
|
if (action == FighterAction.HeavyAttack)
|
|
return "fighter3d_heavy";
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal sealed class FighterHud
|
|
{
|
|
private readonly GameObject _root;
|
|
private readonly HudText _title;
|
|
private readonly HudText _leftText;
|
|
private readonly HudText _rightText;
|
|
private readonly Image _leftHp;
|
|
private readonly Image _rightHp;
|
|
|
|
public FighterHud(GameObject root)
|
|
{
|
|
_root = root;
|
|
_title = FindText("Txt_Title");
|
|
_leftText = FindText("Txt_Left");
|
|
_rightText = FindText("Txt_Right");
|
|
_leftHp = Find<Image>("Img_LeftHpFill");
|
|
_rightHp = Find<Image>("Img_RightHpFill");
|
|
}
|
|
|
|
public void Render(FighterState state)
|
|
{
|
|
if (state == null)
|
|
{
|
|
_title.Set("Fighter3D waiting for snapshot");
|
|
_leftText.Set("P1");
|
|
_rightText.Set("P2");
|
|
SetFill(_leftHp, 1f);
|
|
SetFill(_rightHp, 1f);
|
|
return;
|
|
}
|
|
|
|
var left = state.Fighters[0];
|
|
var right = state.Fighters[1];
|
|
string title = state.Phase == FighterPhase.Finished
|
|
? state.WinnerSeat == 2 ? "Draw" : "Winner Seat " + state.WinnerSeat
|
|
: "Fighter3D " + state.ElapsedSeconds.ToString("0.0") + "s";
|
|
|
|
_title.Set(title);
|
|
_leftText.Set("P1 HP " + left.Hp + " " + left.Action + " X " + left.X.ToString("0.00"));
|
|
_rightText.Set("P2 HP " + right.Hp + " " + right.Action + " X " + right.X.ToString("0.00"));
|
|
SetFill(_leftHp, left.Hp / 1000f);
|
|
SetFill(_rightHp, right.Hp / 1000f);
|
|
}
|
|
|
|
public void Destroy()
|
|
{
|
|
if (_root != null)
|
|
UnityEngine.Object.Destroy(_root);
|
|
}
|
|
|
|
public static GameObject CreateFallbackRoot()
|
|
{
|
|
var root = new GameObject("UI_Fighter3D_Runtime");
|
|
var canvas = root.AddComponent<Canvas>();
|
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
root.AddComponent<CanvasScaler>().referenceResolution = new Vector2(2048, 1024);
|
|
root.AddComponent<GraphicRaycaster>();
|
|
|
|
CreateText(root.transform, "Txt_Title", new Vector2(0, 430), new Vector2(900, 70), 34, TextAnchor.MiddleCenter);
|
|
CreateText(root.transform, "Txt_Left", new Vector2(-520, 340), new Vector2(700, 60), 28, TextAnchor.MiddleLeft);
|
|
CreateText(root.transform, "Txt_Right", new Vector2(520, 340), new Vector2(700, 60), 28, TextAnchor.MiddleRight);
|
|
CreateHpBar(root.transform, "LeftHp", new Vector2(-520, 285));
|
|
CreateHpBar(root.transform, "RightHp", new Vector2(520, 285));
|
|
CreateText(root.transform, "Txt_Help", new Vector2(0, -420), new Vector2(900, 50), 22, TextAnchor.MiddleCenter)
|
|
.text = "A/D move S guard J light K heavy";
|
|
|
|
return root;
|
|
}
|
|
|
|
private T Find<T>(string name) where T : Component
|
|
{
|
|
Transform child = FindChild(_root.transform, name);
|
|
return child == null ? null : child.GetComponent<T>();
|
|
}
|
|
|
|
private HudText FindText(string name)
|
|
{
|
|
Transform child = FindChild(_root.transform, name);
|
|
return child == null ? HudText.Empty : new HudText(child.GetComponent<Text>(), child.GetComponent<TMP_Text>());
|
|
}
|
|
|
|
private static Transform FindChild(Transform root, string name)
|
|
{
|
|
if (root.name == name)
|
|
return root;
|
|
|
|
for (int i = 0; i < root.childCount; i++)
|
|
{
|
|
Transform found = FindChild(root.GetChild(i), name);
|
|
if (found != null)
|
|
return found;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static Text CreateText(Transform parent, string name, Vector2 anchoredPosition, Vector2 size, int fontSize, TextAnchor alignment)
|
|
{
|
|
var go = new GameObject(name);
|
|
go.transform.SetParent(parent, false);
|
|
var rect = go.AddComponent<RectTransform>();
|
|
rect.sizeDelta = size;
|
|
rect.anchoredPosition = anchoredPosition;
|
|
|
|
var text = go.AddComponent<Text>();
|
|
text.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
text.fontSize = fontSize;
|
|
text.alignment = alignment;
|
|
text.color = Color.white;
|
|
return text;
|
|
}
|
|
|
|
private static void CreateHpBar(Transform parent, string name, Vector2 anchoredPosition)
|
|
{
|
|
var bg = new GameObject("Img_" + name + "Bg");
|
|
bg.transform.SetParent(parent, false);
|
|
var bgRect = bg.AddComponent<RectTransform>();
|
|
bgRect.sizeDelta = new Vector2(700, 30);
|
|
bgRect.anchoredPosition = anchoredPosition;
|
|
var bgImage = bg.AddComponent<Image>();
|
|
bgImage.color = new Color(0.12f, 0.12f, 0.12f, 0.85f);
|
|
|
|
var fill = new GameObject("Img_" + name + "Fill");
|
|
fill.transform.SetParent(bg.transform, false);
|
|
var fillRect = fill.AddComponent<RectTransform>();
|
|
fillRect.anchorMin = new Vector2(0, 0);
|
|
fillRect.anchorMax = new Vector2(1, 1);
|
|
fillRect.offsetMin = Vector2.zero;
|
|
fillRect.offsetMax = Vector2.zero;
|
|
var fillImage = fill.AddComponent<Image>();
|
|
fillImage.type = Image.Type.Filled;
|
|
fillImage.fillMethod = Image.FillMethod.Horizontal;
|
|
fillImage.color = name == "LeftHp" ? new Color(0.18f, 0.8f, 0.42f) : new Color(0.9f, 0.23f, 0.22f);
|
|
}
|
|
|
|
private static void SetFill(Image image, float value)
|
|
{
|
|
if (image != null)
|
|
image.fillAmount = Mathf.Clamp01(value);
|
|
}
|
|
|
|
private readonly struct HudText
|
|
{
|
|
public static readonly HudText Empty = new HudText(null, null);
|
|
|
|
private readonly Text _legacy;
|
|
private readonly TMP_Text _tmp;
|
|
|
|
public HudText(Text legacy, TMP_Text tmp)
|
|
{
|
|
_legacy = legacy;
|
|
_tmp = tmp;
|
|
}
|
|
|
|
public void Set(string value)
|
|
{
|
|
if (_legacy != null)
|
|
_legacy.text = value;
|
|
if (_tmp != null)
|
|
_tmp.text = value;
|
|
}
|
|
}
|
|
}
|
|
}
|