Chat Refactor + Spawn Points Y Axis Fix

This commit is contained in:
pelpanagiotis
2026-01-16 08:19:14 +02:00
parent bc6989bf05
commit 71f1523dc7
63 changed files with 17493 additions and 1491 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ RectTransform:
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 9142752550085889673}
- {fileID: 7755110316114242192}
m_Father: {fileID: 2002316872831918386}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -138,6 +139,142 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: -20, y: -20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1329269310395619394
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6833653566965825464}
- component: {fileID: 575314324789154401}
- component: {fileID: 7342339693531116023}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6833653566965825464
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1329269310395619394}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 0.61, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 9142752550085889673}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &575314324789154401
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1329269310395619394}
m_CullTransparentMesh: 1
--- !u!114 &7342339693531116023
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1329269310395619394}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Send
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4281479730
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 24
m_fontSizeBase: 24
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 20.7
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &1699830911550741300
GameObject:
m_ObjectHideFlags: 0
@@ -166,7 +303,7 @@ RectTransform:
m_GameObject: {fileID: 1699830911550741300}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_LocalScale: {x: 0.9957322, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2484816357132094706}
@@ -176,7 +313,7 @@ RectTransform:
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 575, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
m_Pivot: {x: 0.02, y: 0.5}
--- !u!222 &4119829714474046246
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -277,10 +414,10 @@ MonoBehaviour:
m_OnEndEdit:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 9035039066971338182}
m_TargetAssemblyTypeName: ChatBehaviour, Assembly-CSharp
m_MethodName: Send
m_Mode: 1
- m_Target: {fileID: 5419200451138107557}
m_TargetAssemblyTypeName: ChatUI, Assembly-CSharp
m_MethodName: OnEndEdit
m_Mode: 5
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
@@ -363,7 +500,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -251.10033, y: -119.50798}
m_AnchoredPosition: {x: -251.1008, y: -119.50808}
m_SizeDelta: {x: 550, y: 0}
m_Pivot: {x: 0.05, y: 0.04}
--- !u!222 &7358741870202837761
@@ -633,7 +770,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Enter text...
m_text: Enter message...
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
@@ -668,7 +805,7 @@ MonoBehaviour:
m_fontSizeMax: 72
m_fontStyle: 2
m_HorizontalAlignment: 1
m_VerticalAlignment: 256
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
@@ -724,6 +861,139 @@ MonoBehaviour:
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 1
--- !u!1 &5809875039645542854
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 9142752550085889673}
- component: {fileID: 6289523614918362705}
- component: {fileID: 8905872634035799237}
- component: {fileID: 4523868746972842606}
m_Layer: 5
m_Name: SendButton
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!224 &9142752550085889673
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5809875039645542854}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.65958, y: 1.5413, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6833653566965825464}
m_Father: {fileID: 749413481453130584}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 515.8, y: -1262.8}
m_SizeDelta: {x: 160, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6289523614918362705
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5809875039645542854}
m_CullTransparentMesh: 1
--- !u!114 &8905872634035799237
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5809875039645542854}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &4523868746972842606
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5809875039645542854}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 8905872634035799237}
m_OnClick:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 5419200451138107557}
m_TargetAssemblyTypeName: ChatUI, Assembly-CSharp
m_MethodName: SendMessage
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!1 &6611872676632210334
GameObject:
m_ObjectHideFlags: 0
@@ -823,8 +1093,8 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 2002316872831918386}
- component: {fileID: 9035039066971338182}
- component: {fileID: 2251569389423392976}
- component: {fileID: -52462571427488525}
- component: {fileID: 5419200451138107557}
m_Layer: 0
m_Name: ChatUI
m_TagString: Untagged
@@ -848,25 +1118,7 @@ Transform:
- {fileID: 749413481453130584}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &9035039066971338182
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7297740156781135662}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3e2cbf076f9d6ec41b63cd8c0fe4c17d, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::ChatBehaviour
syncDirection: 0
syncMode: 0
syncInterval: 0
chatText: {fileID: 3732663597380374314}
inputField: {fileID: 5078773697465256974}
canvas: {fileID: 49657406320274459}
--- !u!114 &2251569389423392976
--- !u!114 &-52462571427488525
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -883,6 +1135,22 @@ MonoBehaviour:
serverOnly: 0
visibility: 0
hasSpawned: 0
--- !u!114 &5419200451138107557
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7297740156781135662}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 527a619416f29bd4c8493418ac26af76, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::ChatUI
chatHistory: {fileID: 3732663597380374314}
scrollbar: {fileID: 1421155399902046049}
chatMessage: {fileID: 5078773697465256974}
sendButton: {fileID: 4523868746972842606}
--- !u!1 &7685879014975046634
GameObject:
m_ObjectHideFlags: 0
@@ -1003,7 +1271,7 @@ MonoBehaviour:
m_TargetGraphic: {fileID: 1544255096311300969}
m_HandleRect: {fileID: 1116689992204639187}
m_Direction: 2
m_Value: 0
m_Value: 1
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:
@@ -1567,7 +1835,7 @@ MonoBehaviour:
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 256
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1bd1c6a1fe528fe40b52e12b7b782147
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c4e279b532045d549a7ebe512fafb6a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 112000000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

8
Assets/Scripts/Chat.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f78075137756caf45a39abfa2eab72d9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,165 @@
using Mirror;
using Mirror.Examples.Chat;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChatAuthenticator : NetworkAuthenticator
{
readonly HashSet<NetworkConnectionToClient> connectionsPendingDisconnect = new HashSet<NetworkConnectionToClient>();
internal static readonly HashSet<string> playerNames = new HashSet<string>();
[Header("Client Username")]
public string playerName;
#region Messages;
public struct AuthRequestMessage : NetworkMessage
{
public string authUsername;
}
public struct AuthResponseMessage : NetworkMessage
{
public byte code;
public string message;
}
#endregion
#region Server
[UnityEngine.RuntimeInitializeOnLoadMethod]
static void ResetStatics()
{
playerNames.Clear();
}
public override void OnStartServer()
{
NetworkServer.RegisterHandler<AuthRequestMessage>(OnAuthRequestMessage, false);
}
public override void OnStopServer()
{
NetworkServer.UnregisterHandler<AuthRequestMessage>();
}
public override void OnServerAuthenticate(NetworkConnectionToClient conn)
{
}
public void OnAuthRequestMessage(NetworkConnectionToClient conn, AuthRequestMessage msg)
{
Debug.Log($"Authentication Request Username: {msg.authUsername}");
if (connectionsPendingDisconnect.Contains(conn)) return;
if (!playerNames.Contains(msg.authUsername))
{
playerNames.Add(msg.authUsername);
conn.authenticationData = msg.authUsername;
AuthResponseMessage authResponseMessage = new AuthResponseMessage
{
code = 100,
message = "Success"
};
conn.Send(authResponseMessage);
ServerAccept(conn);
}
else
{
connectionsPendingDisconnect.Add(conn);
AuthResponseMessage authResponseMessage = new AuthResponseMessage
{
code = 200,
message = "Username already in use... try again"
};
conn.Send(authResponseMessage);
conn.isAuthenticated = false;
StartCoroutine(DelayedDisconnect(conn, 1f));
}
}
IEnumerator DelayedDisconnect(NetworkConnectionToClient conn, float waitTime)
{
yield return new WaitForSeconds(waitTime);
ServerReject(conn);
yield return null;
connectionsPendingDisconnect.Remove(conn);
}
#endregion
#region Client
public void SetPlayerName(string username)
{
playerName = username;
if (LoginUI.instance != null && LoginUI.instance.errorText != null)
{
LoginUI.instance.errorText.text = string.Empty;
LoginUI.instance.errorText.gameObject.SetActive(false);
}
}
public override void OnStartClient()
{
NetworkClient.RegisterHandler<AuthResponseMessage>(OnAuthResponseMessage, false);
}
public override void OnStopClient()
{
NetworkClient.UnregisterHandler<AuthResponseMessage>();
}
public override void OnClientAuthenticate()
{
NetworkClient.Send(new AuthRequestMessage { authUsername = playerName });
}
public void OnAuthResponseMessage(AuthResponseMessage msg)
{
if (msg.code == 100)
{
Debug.Log($"Authentication Response: {msg.code} {msg.message}");
ClientAccept();
}
else
{
Debug.LogError($"Authentication Response: {msg.code} {msg.message}");
NetworkManager.singleton.StopHost();
LoginUI.instance.errorText.text = msg.message;
LoginUI.instance.errorText.gameObject.SetActive(true);
}
}
#endregion
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 23712bd1913985b419020af1e3c9479f

View File

@@ -0,0 +1,97 @@
using UnityEngine;
using Mirror;
using System.Collections.Generic;
[AddComponentMenu("")]
public class ChatNetworkManager : NetworkManager
{
[SerializeField] private GameObject chatUIPrefab;
// Server-side authoritative store of chat messages
private readonly List<string> chatMessages = new();
// Server-side map of connections to player names (moved from the old ChatUI)
internal static readonly Dictionary<NetworkConnectionToClient, string> connNames = new();
public void SetHostname(string hostname)
{
networkAddress = hostname;
}
public override void OnServerDisconnect(NetworkConnectionToClient conn)
{
if (conn.authenticationData != null)
ChatAuthenticator.playerNames.Remove((string)conn.authenticationData);
connNames.Remove(conn);
base.OnServerDisconnect(conn);
}
public override void OnClientDisconnect()
{
base.OnClientDisconnect();
// Destroy ChatUI instance when disconnecting
ChatUI existingChatUI = FindFirstObjectByType<ChatUI>();
if (existingChatUI != null)
{
Destroy(existingChatUI.gameObject);
}
// Show LoginUI again
if (LoginUI.instance != null)
{
LoginUI.instance.gameObject.SetActive(true);
LoginUI.instance.usernameInput.text = "";
LoginUI.instance.usernameInput.ActivateInputField();
}
}
public override void OnClientConnect()
{
base.OnClientConnect();
Debug.Log("ChatNetworkManager: OnClientConnect called");
// Spawn ChatUI prefab locally when client successfully connects
if (chatUIPrefab == null)
{
Debug.LogError("ChatNetworkManager: ChatUI prefab is not assigned in the inspector!");
return;
}
// Check if ChatUI already exists to avoid duplicates
ChatUI existingChatUI = FindFirstObjectByType<ChatUI>();
if (existingChatUI == null)
{
GameObject chatUIInstance = Instantiate(chatUIPrefab);
if (chatUIInstance == null)
{
Debug.LogError("ChatNetworkManager: Failed to instantiate ChatUI prefab!");
return;
}
chatUIInstance.SetActive(true);
}
// Hide LoginUI when successfully connected
if (LoginUI.instance != null)
{
LoginUI.instance.gameObject.SetActive(false);
}
}
[Server]
public void AddServerMessage(string message)
{
if (string.IsNullOrWhiteSpace(message)) return;
chatMessages.Add(message);
}
[Server]
public string[] GetHistory()
{
return chatMessages.ToArray();
}
}

View File

@@ -0,0 +1,107 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
// Client-only UI. DO NOT add a NetworkIdentity to this prefab.
public class ChatUI : MonoBehaviour
{
[Header("UI Elements")]
[SerializeField] private TextMeshProUGUI chatHistory;
[SerializeField] private Scrollbar scrollbar;
[SerializeField] private TMP_InputField chatMessage;
[SerializeField] private Button sendButton;
internal static string localPlayerName;
private readonly List<string> lines = new();
private Player localPlayer;
void Awake()
{
if (chatMessage != null)
{
chatMessage.onValueChanged.AddListener(ToggleButton);
chatMessage.onSubmit.AddListener(OnInputSubmit);
}
}
// Called by Player.OnStartLocalPlayer to connect the UI to the player's network methods.
public void Initialize(Player player)
{
localPlayer = player;
}
public void SetHistory(string[] history)
{
lines.Clear();
if (history != null && history.Length > 0)
lines.AddRange(history);
RefreshVisual();
}
// Called by Player.RpcReceiveChat / Player.TargetReceiveHistory
public void AddMessage(string message)
{
lines.Add(message);
if (chatHistory != null)
chatHistory.text += message + "\n";
StartCoroutine(ScrollNextFrame());
}
IEnumerator ScrollNextFrame()
{
yield return null;
yield return null;
if (scrollbar != null)
scrollbar.value = 0;
}
void RefreshVisual()
{
if (chatHistory == null) return;
chatHistory.text = string.Empty;
foreach (var l in lines)
chatHistory.text += l + "\n";
StartCoroutine(ScrollNextFrame());
}
public void ToggleButton(string input)
{
if (sendButton != null)
sendButton.interactable = !string.IsNullOrWhiteSpace(input);
}
private void OnInputSubmit(string input)
{
if (!string.IsNullOrWhiteSpace(input))
SendMessage();
}
public void SendMessage()
{
if (localPlayer == null)
{
Debug.LogWarning("ChatUI: local player not set; cannot send chat.");
return;
}
if (string.IsNullOrWhiteSpace(chatMessage?.text)) return;
localPlayer.SendChatToServer(chatMessage.text.Trim());
chatMessage.text = string.Empty;
chatMessage.ActivateInputField();
}
void OnDestroy()
{
if (chatMessage != null)
chatMessage.onValueChanged.RemoveListener(ToggleButton);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 527a619416f29bd4c8493418ac26af76

View File

@@ -0,0 +1,61 @@
using UnityEngine;
using UnityEngine.UI;
using Mirror;
using TMPro;
public class LoginUI : MonoBehaviour
{
[Header("UI Elements")]
[SerializeField] internal TMP_InputField usernameInput;
[SerializeField] internal Button clientButton;
[SerializeField] internal Text errorText;
public static LoginUI instance;
string originalNetworkAddress;
void Awake()
{
instance = this;
}
void Start()
{
if (string.IsNullOrWhiteSpace(NetworkManager.singleton.networkAddress))
NetworkManager.singleton.networkAddress = "localhost";
originalNetworkAddress = NetworkManager.singleton.networkAddress;
}
void Update()
{
if (string.IsNullOrWhiteSpace(NetworkManager.singleton.networkAddress))
NetworkManager.singleton.networkAddress = originalNetworkAddress;
}
public void ToggleButton(string username)
{
clientButton.interactable = !string.IsNullOrWhiteSpace(username);
}
public void OnClientButton()
{
var username = usernameInput.text.Trim();
if (string.IsNullOrWhiteSpace(username))
{
if (errorText != null) { errorText.text = "Enter a username"; errorText.gameObject.SetActive(true); }
return;
}
// set the name on the ChatAuthenticator (if present)
var auth = NetworkManager.singleton.GetComponent<ChatAuthenticator>();
if (auth != null)
auth.SetPlayerName(username);
else
Debug.LogWarning("LoginUI: ChatAuthenticator not found on NetworkManager.");
// start client
NetworkManager.singleton.StartClient();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c2e50c9434c920c44ad6d25327815d5c

View File

@@ -0,0 +1,88 @@
using UnityEngine;
using Mirror;
// Networked player. This is the authority object used to run Commands.
public class Player : NetworkBehaviour
{
[SyncVar(hook = nameof(OnNameChanged))]
public string playerName;
public override void OnStartServer()
{
playerName = (string)connectionToClient.authenticationData;
}
public override void OnStartLocalPlayer()
{
// initialize local UI
var ui = FindFirstObjectByType<ChatUI>();
if (ui != null)
{
ui.Initialize(this);
ChatUI.localPlayerName = playerName;
}
// request history from server
if (isLocalPlayer)
CmdRequestHistory();
}
void OnNameChanged(string oldName, string newName)
{
if (isLocalPlayer)
ChatUI.localPlayerName = newName;
}
// Called by the local ChatUI to send a message
public void SendChatToServer(string message)
{
if (!isLocalPlayer) return;
if (string.IsNullOrWhiteSpace(message)) return;
CmdSendChat(message);
}
// Runs on server: store message and broadcast to all clients
[Command]
private void CmdSendChat(string message)
{
var manager = NetworkManager.singleton as ChatNetworkManager;
manager?.AddServerMessage($"{playerName}: {message}");
RpcReceiveChat(playerName, message);
}
// Runs on all clients: update local ChatUI
[ClientRpc]
private void RpcReceiveChat(string senderName, string message)
{
var ui = FindFirstObjectByType<ChatUI>();
if (ui == null) return;
var prettyMessage = senderName == ChatUI.localPlayerName ?
$"<color=red>{senderName}:</color> {message}" :
$"<color=blue>{senderName}:</color> {message}";
ui.AddMessage(prettyMessage);
}
// Request history from server; server will reply with TargetReceiveHistory
[Command]
private void CmdRequestHistory()
{
var manager = NetworkManager.singleton as ChatNetworkManager;
if (manager == null) return;
var history = manager.GetHistory();
TargetReceiveHistory(connectionToClient, history);
}
// Sent only to the requesting client
[TargetRpc]
private void TargetReceiveHistory(NetworkConnection target, string[] history)
{
var ui = FindFirstObjectByType<ChatUI>();
if (ui == null) return;
ui.SetHistory(history);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b3732139ba57ef74daf2a04c11b62cfa

View File

@@ -1,25 +0,0 @@
using Mirror;
using UnityEngine;
public class ChatNetworkManager : NetworkManager
{
[SerializeField] private GameObject chatPrefab;
public override void OnClientConnect()
{
base.OnClientConnect();
// no client-side Instantiate here when using server-spawned approach
}
public override void OnServerAddPlayer(NetworkConnectionToClient conn)
{
// keep default player spawn
base.OnServerAddPlayer(conn);
if (chatPrefab != null)
{
var chatInstance = Instantiate(chatPrefab);
NetworkServer.Spawn(chatInstance, conn);
}
}
}

View File

@@ -78,7 +78,7 @@ PrefabInstance:
addedObject: {fileID: 7395102181367272335}
- targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: e0ac844e563d55c4a8ff510e5409eeae, type: 3}
insertIndex: -1
addedObject: {fileID: 8610705006518060865}
addedObject: {fileID: 2187101703061017507}
m_SourcePrefab: {fileID: 100100000, guid: e0ac844e563d55c4a8ff510e5409eeae, type: 3}
--- !u!4 &8077244700045459333 stripped
Transform:
@@ -177,7 +177,7 @@ MonoBehaviour:
rotationSensitivity: 0.01
positionPrecision: 0.01
scalePrecision: 0.01
--- !u!114 &8610705006518060865
--- !u!114 &2187101703061017507
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -186,10 +186,10 @@ MonoBehaviour:
m_GameObject: {fileID: 8886714697230705983}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6dab1be56f31ff44889b274b8f565102, type: 3}
m_Script: {fileID: 11500000, guid: b3732139ba57ef74daf2a04c11b62cfa, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SpawnScript
m_EditorClassIdentifier: Assembly-CSharp::Player
syncDirection: 0
syncMode: 0
syncInterval: 0
chatPrefab: {fileID: 7297740156781135662, guid: 87017431da5fdd442b694135dde0a74b, type: 3}
playerName: