fix: polish lobby chat and joystick
This commit is contained in:
@@ -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:
|
||||||
+42
@@ -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()
|
||||||
@@ -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": [
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user