Add player profile system

This commit is contained in:
ud18010
2026-07-31 17:39:52 +08:00
parent 786b7ffff9
commit bece0a4d2c
16 changed files with 6618 additions and 0 deletions
@@ -41,6 +41,7 @@ namespace XGame
private MiniGameStageIsolation stageIsolation;
private LobbyWorldController lobbyWorld;
private WorldChatModule worldChat;
private PlayerProfileModule playerProfile;
private readonly List<GameInfoMsg> games = new List<GameInfoMsg>();
private readonly List<NetMessage> pendingGameMessages = new List<NetMessage>();
private readonly HashSet<int> canceledMatchRequestIds = new HashSet<int>();
@@ -129,6 +130,7 @@ namespace XGame
lobbyActive = false;
lobbyWorld?.SendLeave();
CloseWorldChat();
ClosePlayerProfile();
lobbyNet = null;
socket?.close();
socket = null;
@@ -315,6 +317,8 @@ namespace XGame
private void EnterLobby()
{
CloseLoginPanel();
SyncPlayerProfile();
EnsurePlayerProfileModule();
OpenLobby();
ConnectLobby();
}
@@ -886,6 +890,40 @@ namespace XGame
}
}
private void EnsurePlayerProfileModule()
{
if (playerProfile == null)
{
playerProfile = gameObject.GetComponent<PlayerProfileModule>();
if (playerProfile == null)
{
playerProfile = gameObject.AddComponent<PlayerProfileModule>();
}
}
if (!playerProfile.IsOpen)
{
XWCoroutine.StartCo(playerProfile.OpenAsync());
}
}
private void ClosePlayerProfile()
{
if (playerProfile != null)
{
playerProfile.Close();
}
}
private void SyncPlayerProfile()
{
int pid = GetLobbyPlayerId();
PlayerProfileStore.SetSessionIdentity(
GetLobbyPlayerName(),
pid.ToString(CultureInfo.InvariantCulture),
pid <= 0 ? 0 : pid % 8 + 1,
PlayerRoleType.Adventurer);
}
private static string GetLobbyPlayerName()
{
string account = XData.GetString(LoginAccountKey, string.Empty);
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 15830dfd44b61334f8fb32f2e70da201
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,68 @@
using System;
using UnityEngine;
namespace XGame
{
public enum PlayerRoleType
{
Adventurer = 0,
Warrior = 1,
Mage = 2,
Archer = 3
}
[Serializable]
public sealed class PlayerProfileData
{
public string Nickname = "Player";
public string Uid = "0";
public int AvatarId;
public PlayerRoleType RoleType = PlayerRoleType.Adventurer;
public int Gold;
public int Diamond;
public int Lv = 1;
public int Exp;
public int Hp = 100;
public int HpMax = 100;
public int Mp = 50;
public int MpMax = 50;
public int Ad = 10;
public int Ap = 5;
public float AtkSpeed = 1f;
public float MoveSpeed = 4f;
public int RequiredExp => Mathf.Max(100, Lv * 100);
public float Experience01 => Mathf.Clamp01((float)Mathf.Max(0, Exp) / RequiredExp);
public static PlayerProfileData CreateDefault()
{
PlayerProfileData data = new PlayerProfileData();
data.Normalize();
return data;
}
public PlayerProfileData Clone()
{
return (PlayerProfileData)MemberwiseClone();
}
public void Normalize()
{
Nickname = string.IsNullOrWhiteSpace(Nickname) ? "Player" : Nickname.Trim();
Uid = string.IsNullOrWhiteSpace(Uid) ? "0" : Uid.Trim();
AvatarId = Mathf.Max(0, AvatarId);
Gold = Mathf.Max(0, Gold);
Diamond = Mathf.Max(0, Diamond);
Lv = Mathf.Max(1, Lv);
Exp = Mathf.Max(0, Exp);
HpMax = Mathf.Max(1, HpMax);
MpMax = Mathf.Max(1, MpMax);
Hp = Mathf.Clamp(Hp, 0, HpMax);
Mp = Mathf.Clamp(Mp, 0, MpMax);
Ad = Mathf.Max(0, Ad);
Ap = Mathf.Max(0, Ap);
AtkSpeed = Mathf.Max(0.01f, AtkSpeed);
MoveSpeed = Mathf.Max(0.01f, MoveSpeed);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b80a1b74b02355444aefdcc7c580468b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,247 @@
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UObject = UnityEngine.Object;
namespace XGame
{
public sealed class PlayerProfileModule : MonoBehaviour
{
private const string HudPrefabPath = "Assets/Game/Art/UI/Prefab/UI_PlayerProfileHud.prefab";
private const string PanelPrefabPath = "Assets/Game/Art/UI/Prefab/UI_PlayerProfilePanel.prefab";
private GameObject hudView;
private GameObject detailView;
private bool openingHud;
private bool openingDetail;
private int openVersion;
private TMP_Text hudNicknameText;
private TMP_Text hudLevelText;
private TMP_Text hudExpText;
private RectTransform hudExpFill;
private RectTransform hudExpTrack;
private void OnEnable()
{
PlayerProfileStore.Changed += OnProfileChanged;
}
private void OnDisable()
{
PlayerProfileStore.Changed -= OnProfileChanged;
}
public bool IsOpen => hudView != null;
public IEnumerator OpenAsync()
{
if (hudView != null || openingHud)
{
RefreshAll(PlayerProfileStore.Current);
yield break;
}
openingHud = true;
int version = ++openVersion;
UObject loaded = null;
yield return XResLoader.coLoadRes(HudPrefabPath, typeof(GameObject), obj => loaded = obj);
openingHud = false;
if (version != openVersion)
{
yield break;
}
GameObject prefab = loaded as GameObject;
if (prefab == null)
{
Debug.LogError("[PlayerProfileModule] load HUD prefab failed: " + HudPrefabPath);
yield break;
}
hudView = Instantiate(prefab);
UIManager.AttachTo(UILayer.HUD, hudView);
CacheHudControls();
BindHudEvents();
RefreshHud(PlayerProfileStore.Current);
}
public void Close()
{
openVersion++;
openingHud = false;
openingDetail = false;
if (hudView != null)
{
Destroy(hudView);
}
if (detailView != null)
{
Destroy(detailView);
}
hudView = null;
detailView = null;
hudNicknameText = null;
hudLevelText = null;
hudExpText = null;
hudExpFill = null;
hudExpTrack = null;
}
private void OnProfileChanged(PlayerProfileData profile)
{
RefreshAll(profile);
}
private void RefreshAll(PlayerProfileData profile)
{
RefreshHud(profile);
RefreshDetail(profile);
}
private void CacheHudControls()
{
if (hudView == null)
{
return;
}
hudNicknameText = FindComponent<TMP_Text>(hudView, "Txt_Nickname");
hudLevelText = FindComponent<TMP_Text>(hudView, "Txt_Level");
hudExpText = FindComponent<TMP_Text>(hudView, "Txt_Exp");
hudExpTrack = FindRect(hudView, "Img_ExpBg");
hudExpFill = FindRect(hudView, "Img_ExpFill");
}
private void BindHudEvents()
{
Button button = FindComponent<Button>(hudView, "Btn_Profile");
if (button == null)
{
return;
}
button.onClick.RemoveAllListeners();
button.onClick.AddListener(OpenDetail);
}
private void RefreshHud(PlayerProfileData profile)
{
if (hudView == null)
{
return;
}
profile = profile ?? PlayerProfileStore.Current;
if (hudNicknameText != null) hudNicknameText.text = profile.Nickname;
if (hudLevelText != null) hudLevelText.text = "Lv." + profile.Lv;
if (hudExpText != null) hudExpText.text = profile.Exp + "/" + profile.RequiredExp;
if (hudExpTrack != null && hudExpFill != null)
{
float width = Mathf.Max(0f, hudExpTrack.rect.width);
hudExpFill.sizeDelta = new Vector2(width * profile.Experience01, hudExpFill.sizeDelta.y);
}
}
private void OpenDetail()
{
if (detailView != null || openingDetail)
{
RefreshDetail(PlayerProfileStore.Current);
return;
}
XWCoroutine.StartCo(CoOpenDetail());
}
private IEnumerator CoOpenDetail()
{
openingDetail = true;
UObject loaded = null;
yield return XResLoader.coLoadRes(PanelPrefabPath, typeof(GameObject), obj => loaded = obj);
openingDetail = false;
GameObject prefab = loaded as GameObject;
if (prefab == null)
{
Debug.LogError("[PlayerProfileModule] load detail prefab failed: " + PanelPrefabPath);
yield break;
}
detailView = Instantiate(prefab);
UIManager.AttachTo(UILayer.Dialog, detailView);
BindDetailEvents();
RefreshDetail(PlayerProfileStore.Current);
}
private void BindDetailEvents()
{
BindCloseButton("Btn_Close");
BindCloseButton("Img_Mask");
}
private void BindCloseButton(string nodeName)
{
Button button = FindComponent<Button>(detailView, nodeName);
if (button == null)
{
return;
}
button.onClick.RemoveAllListeners();
button.onClick.AddListener(CloseDetail);
}
private void CloseDetail()
{
if (detailView != null)
{
Destroy(detailView);
}
detailView = null;
}
private void RefreshDetail(PlayerProfileData profile)
{
if (detailView == null)
{
return;
}
profile = profile ?? PlayerProfileStore.Current;
SetText("Txt_Nickname", profile.Nickname);
SetText("Txt_Uid", "UID: " + profile.Uid);
SetText("Txt_RoleType", profile.RoleType.ToString());
SetText("Txt_Gold", profile.Gold.ToString());
SetText("Txt_Diamond", profile.Diamond.ToString());
SetText("Txt_Lv", profile.Lv.ToString());
SetText("Txt_Exp", profile.Exp + "/" + profile.RequiredExp);
SetText("Txt_Hp", profile.Hp + "/" + profile.HpMax);
SetText("Txt_Mp", profile.Mp + "/" + profile.MpMax);
SetText("Txt_Ad", profile.Ad.ToString());
SetText("Txt_Ap", profile.Ap.ToString());
SetText("Txt_AtkSpeed", profile.AtkSpeed.ToString("0.##"));
SetText("Txt_MoveSpeed", profile.MoveSpeed.ToString("0.##"));
}
private void SetText(string nodeName, string value)
{
TMP_Text text = FindComponent<TMP_Text>(detailView, nodeName);
if (text != null)
{
text.text = value ?? string.Empty;
}
}
private static RectTransform FindRect(GameObject root, string nodeName)
{
Transform t = root == null ? null : root.transform.FindTransform(nodeName);
return t == null ? null : t.GetComponent<RectTransform>();
}
private static T FindComponent<T>(GameObject root, string nodeName) where T : Component
{
Transform t = root == null ? null : root.transform.FindTransform(nodeName);
return t == null ? null : t.GetComponent<T>();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b19aceb62ce32574ab5918471bc49d8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,43 @@
using System;
namespace XGame
{
public static class PlayerProfileStore
{
private static PlayerProfileData current = PlayerProfileData.CreateDefault();
public static event Action<PlayerProfileData> Changed;
public static PlayerProfileData Current => current.Clone();
public static void Replace(PlayerProfileData profile)
{
current = profile == null ? PlayerProfileData.CreateDefault() : profile.Clone();
current.Normalize();
Changed?.Invoke(Current);
}
public static void Update(Action<PlayerProfileData> updater)
{
PlayerProfileData next = Current;
updater?.Invoke(next);
Replace(next);
}
public static void ResetToDefault()
{
Replace(PlayerProfileData.CreateDefault());
}
public static void SetSessionIdentity(string nickname, string uid, int avatarId = 0, PlayerRoleType roleType = PlayerRoleType.Adventurer)
{
Update(profile =>
{
profile.Nickname = nickname;
profile.Uid = uid;
profile.AvatarId = avatarId;
profile.RoleType = roleType;
});
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87850bdd4bcb84c4a953202d393f0003
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: