fix: polish lobby chat and joystick

This commit is contained in:
ud18010
2026-07-28 17:23:11 +08:00
parent 48aed26725
commit 053a95b4fb
11 changed files with 176 additions and 9 deletions
@@ -36,7 +36,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 208, y: 158} m_AnchoredPosition: {x: 260, y: 220}
m_SizeDelta: {x: 148, y: 148} m_SizeDelta: {x: 148, y: 148}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!225 &8720693099983328153 --- !u!225 &8720693099983328153
@@ -0,0 +1,88 @@
using System;
using System.Reflection;
using NUnit.Framework;
using TMPro;
using UnityEditor;
using UnityEngine;
using XGame;
using XWorld.Framework.Protocol;
public sealed class WorldChatModuleTests
{
private GameObject view;
private GameObject host;
[TearDown]
public void TearDown()
{
if (view != null)
{
UnityEngine.Object.DestroyImmediate(view);
}
if (host != null)
{
UnityEngine.Object.DestroyImmediate(host);
}
}
[Test]
public void EndEditingInput_SubmitsTrimmedWorldChat()
{
WorldChatModule module = CreateBoundModule();
TMP_InputField input = view.transform.FindTransform("InputHitArea").GetComponent<TMP_InputField>();
string sent = null;
module.OnSendWorldChat = text => sent = text;
input.text = " hello world ";
input.onEndEdit.Invoke(input.text);
Assert.That(sent, Is.EqualTo("hello world"));
Assert.That(input.text, Is.Empty);
}
[Test]
public void ReceivedMessage_UpdatesCollapsedPreview()
{
WorldChatModule module = CreateBoundModule();
TMP_Text collapsedText = view.transform.FindTransform("Txt_CollapsedPlaceholder").GetComponent<TMP_Text>();
module.HandleWorldChatMessage(new WorldChatMessageMsg
{
PlayerId = 2,
PlayerName = "Bob",
Text = "hello world"
});
Assert.That(collapsedText.text, Is.EqualTo("Bob: hello world"));
}
private WorldChatModule CreateBoundModule()
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Game/Art/UI/Prefab/UI_CommonChat.prefab");
Assert.That(prefab, Is.Not.Null);
view = UnityEngine.Object.Instantiate(prefab);
host = new GameObject("WorldChatModuleTestHost");
WorldChatModule module = host.AddComponent<WorldChatModule>();
module.Initialize(1, _ => { });
SetPrivateField(module, "view", view);
InvokePrivate(module, "CacheControls");
InvokePrivate(module, "BindEvents");
return module;
}
private static void SetPrivateField(object target, string name, object value)
{
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(field, Is.Not.Null);
field.SetValue(target, value);
}
private static void InvokePrivate(object target, string name)
{
MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null);
method.Invoke(target, Array.Empty<object>());
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f953f6437b2d4314b8c8c77d531223e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -3,6 +3,7 @@
"rootNamespace": "", "rootNamespace": "",
"references": [ "references": [
"XWorld.Link", "XWorld.Link",
"XWorld.Framework.Shared",
"Unity.TextMeshPro", "Unity.TextMeshPro",
"GUID:119d45324677fde49923efc4ea54b313" "GUID:119d45324677fde49923efc4ea54b313"
], ],
@@ -584,6 +584,7 @@ namespace XGame
lobbyWorld?.HandleFrame(frame); lobbyWorld?.HandleFrame(frame);
break; break;
case FrameworkOpcode.WorldChatMessage: case FrameworkOpcode.WorldChatMessage:
Debug.Log("[CSharpClientApp] recv WorldChatMessage bytes=" + (frame.Payload?.Length ?? 0));
worldChat?.HandleWorldChatMessage(WorldChatMessageMsg.Decode(frame.Payload)); worldChat?.HandleWorldChatMessage(WorldChatMessageMsg.Decode(frame.Payload));
break; break;
} }
@@ -701,13 +702,25 @@ namespace XGame
private void SendWorldChat(string text) private void SendWorldChat(string text)
{ {
text = (text ?? string.Empty).Trim(); text = (text ?? string.Empty).Trim();
if (text.Length == 0 || lobbyNet == null) if (text.Length == 0)
{ {
return; return;
} }
if (lobbyNet == null)
{
Debug.LogWarning("[CSharpClientApp] drop WorldChatSend: lobbyNet is null");
return;
}
if (!lobbyNet.IsConnected)
{
Debug.LogWarning("[CSharpClientApp] drop WorldChatSend: lobbyNet is disconnected");
return;
}
byte[] payload = new WorldChatSendMsg { Text = text }.Encode();
Debug.Log("[CSharpClientApp] send WorldChatSend bytes=" + payload.Length);
lobbyNet.SendFramework( lobbyNet.SendFramework(
FrameworkOpcode.WorldChatSend, FrameworkOpcode.WorldChatSend,
new WorldChatSendMsg { Text = text }.Encode()); payload);
} }
private void CloseWorldChat() private void CloseWorldChat()
@@ -40,7 +40,7 @@ namespace XGame
private const float DirectionChangeDot = 0.98f; private const float DirectionChangeDot = 0.98f;
private const float JoystickRadius = 74f; private const float JoystickRadius = 74f;
private const float JoystickKnobRadius = 28f; private const float JoystickKnobRadius = 28f;
private const float JoystickPadding = 34f; private const float JoystickPadding = 100.0f;
private const float JoystickDeadZone = 0.18f; private const float JoystickDeadZone = 0.18f;
private const float JoystickAcquireRadiusMultiplier = 1.6f; private const float JoystickAcquireRadiusMultiplier = 1.6f;
private const float CameraMinDistance = 3.5f; private const float CameraMinDistance = 3.5f;
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6508ec0f8203b6d4e868b5a74194a926
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -24,10 +24,13 @@ namespace XGame
private TMP_InputField inputField; private TMP_InputField inputField;
private TMP_Text inputPlaceholder; private TMP_Text inputPlaceholder;
private TMP_Text inputValue; private TMP_Text inputValue;
private TMP_Text collapsedPreviewText;
private ScrollRect scrollMessages; private ScrollRect scrollMessages;
private bool opening; private bool opening;
private int openVersion; private int openVersion;
private int localPlayerId; private int localPlayerId;
private string collapsedDefaultText;
private string lastCollapsedPreview;
private readonly Queue<GameObject> messageRows = new Queue<GameObject>(); private readonly Queue<GameObject> messageRows = new Queue<GameObject>();
public Action<string> OnSendWorldChat; public Action<string> OnSendWorldChat;
@@ -89,7 +92,10 @@ namespace XGame
inputField = null; inputField = null;
inputPlaceholder = null; inputPlaceholder = null;
inputValue = null; inputValue = null;
collapsedPreviewText = null;
scrollMessages = null; scrollMessages = null;
collapsedDefaultText = null;
lastCollapsedPreview = null;
opening = false; opening = false;
} }
@@ -128,6 +134,12 @@ namespace XGame
scrollMessages = FindComponent<ScrollRect>("Scroll_Messages"); scrollMessages = FindComponent<ScrollRect>("Scroll_Messages");
inputPlaceholder = FindComponent<TMP_Text>("Txt_InputPlaceholder"); inputPlaceholder = FindComponent<TMP_Text>("Txt_InputPlaceholder");
inputValue = FindComponent<TMP_Text>("Txt_InputValue"); inputValue = FindComponent<TMP_Text>("Txt_InputValue");
collapsedPreviewText = FindComponent<TMP_Text>("Txt_CollapsedPlaceholder");
if (collapsedPreviewText != null && string.IsNullOrEmpty(collapsedDefaultText))
{
collapsedDefaultText = collapsedPreviewText.text;
}
RefreshCollapsedPreview();
EnsureInputField(); EnsureInputField();
} }
@@ -142,6 +154,8 @@ namespace XGame
{ {
inputField.onSubmit.RemoveListener(OnInputSubmitted); inputField.onSubmit.RemoveListener(OnInputSubmitted);
inputField.onSubmit.AddListener(OnInputSubmitted); inputField.onSubmit.AddListener(OnInputSubmitted);
inputField.onEndEdit.RemoveListener(OnInputEndEdit);
inputField.onEndEdit.AddListener(OnInputEndEdit);
} }
} }
@@ -259,6 +273,11 @@ namespace XGame
SendCurrentInput(); SendCurrentInput();
} }
private void OnInputEndEdit(string text)
{
SendCurrentInput();
}
private void SendCurrentInput() private void SendCurrentInput()
{ {
string text = inputField == null ? string.Empty : inputField.text; string text = inputField == null ? string.Empty : inputField.text;
@@ -271,6 +290,7 @@ namespace XGame
{ {
text = text.Substring(0, MaxInputLength); text = text.Substring(0, MaxInputLength);
} }
Debug.Log("[WorldChatModule] send world chat length=" + text.Length);
OnSendWorldChat?.Invoke(text); OnSendWorldChat?.Invoke(text);
ClearInput(); ClearInput();
FocusInput(); FocusInput();
@@ -326,6 +346,28 @@ namespace XGame
{ {
StartCoroutine(ScrollToBottomNextFrame()); StartCoroutine(ScrollToBottomNextFrame());
} }
SetCollapsedPreview(message, isSelf);
}
private void SetCollapsedPreview(WorldChatMessageMsg message, bool isSelf)
{
string name = isSelf ? "\u6211" : message.PlayerName;
if (string.IsNullOrWhiteSpace(name))
{
name = "\u73A9\u5BB6";
}
lastCollapsedPreview = name + ": " + (message.Text ?? string.Empty);
RefreshCollapsedPreview();
}
private void RefreshCollapsedPreview()
{
if (collapsedPreviewText != null)
{
collapsedPreviewText.text = string.IsNullOrEmpty(lastCollapsedPreview)
? (collapsedDefaultText ?? string.Empty)
: lastCollapsedPreview;
}
} }
private IEnumerator ScrollToBottomNextFrame() private IEnumerator ScrollToBottomNextFrame()
@@ -6,6 +6,6 @@ MonoImporter:
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0
icon: {instanceID: 0} icon: {instanceID: 0}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:
+1 -1
View File
@@ -25,7 +25,7 @@
"anchorMin": [0, 0], "anchorMin": [0, 0],
"anchorMax": [0, 0], "anchorMax": [0, 0],
"pivot": [0.5, 0.5], "pivot": [0.5, 0.5],
"anchoredPosition": [208, 158], "anchoredPosition": [260, 220],
"sizeDelta": [148, 148] "sizeDelta": [148, 148]
}, },
"components": [ "components": [
+5 -1
View File
@@ -221,6 +221,7 @@ namespace XWorld.Server.Gateway
{ {
if (!_sessions.IsConnected(pid)) if (!_sessions.IsConnected(pid))
{ {
_logger.Warn($"world chat ignored pid={pid} reason=not_connected");
return; return;
} }
@@ -228,6 +229,7 @@ namespace XWorld.Server.Gateway
string text = (req.Text ?? string.Empty).Trim(); string text = (req.Text ?? string.Empty).Trim();
if (text.Length == 0) if (text.Length == 0)
{ {
_logger.Warn($"world chat ignored pid={pid} reason=empty");
return; return;
} }
if (text.Length > MaxWorldChatTextLength) if (text.Length > MaxWorldChatTextLength)
@@ -246,7 +248,9 @@ namespace XWorld.Server.Gateway
ServerTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() ServerTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
}; };
byte[] encoded = msg.Encode(); byte[] encoded = msg.Encode();
foreach (int targetPid in _sessions.ConnectedPlayerIds()) IReadOnlyList<int> targets = _sessions.ConnectedPlayerIds();
_logger.Info($"world chat pid={pid} name={name} length={text.Length} targets={targets.Count}");
foreach (int targetPid in targets)
{ {
SendFramework(targetPid, FrameworkOpcode.WorldChatMessage, encoded); SendFramework(targetPid, FrameworkOpcode.WorldChatMessage, encoded);
} }