Files
test/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgCharacter.cs
T
2026-03-10 10:08:00 +07:00

551 lines
24 KiB
C#

// Port of C++ CDlgCharacter (DlgCharacter.cpp / DlgCharacter.h)
// Character information panel: stats, attributes, equipment bonuses, attribute point allocation.
using System;
using System.Collections.Generic;
using System.Text;
using BrewMonster.Network;
using BrewMonster.Scripts;
using BrewMonster.Scripts.Managers;
using PerfectWorld.Scripts.Managers;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
namespace BrewMonster.UI
{
public class DlgCharacter : AUIDialog
{
private const int INTERAL_LONG = 618;
private const int INTERAL_SHORT = 100;
#region UI References (same names as C++)
[Header("Basic Info")]
[SerializeField] private TextMeshProUGUI m_pTxt_CharName;
[SerializeField] private TextMeshProUGUI m_pTxt_CharLevel;
[SerializeField] private TextMeshProUGUI m_pTxt_Profession;
[SerializeField] private TextMeshProUGUI m_pTxt_Faction;
[SerializeField] private TextMeshProUGUI m_pTxt_Partner;
[SerializeField] private TextMeshProUGUI m_pTxt_ExpCurrent;
[SerializeField] private TextMeshProUGUI m_pTxt_ExpRequire;
[SerializeField] private TextMeshProUGUI m_pTxt_Distinction;
[SerializeField] private TextMeshProUGUI m_pTxt_xz;
[Header("Resources")]
[SerializeField] private TextMeshProUGUI m_pTxt_HP;
[SerializeField] private TextMeshProUGUI m_pTxt_MP;
[SerializeField] private TextMeshProUGUI m_pTxt_SP;
[SerializeField] private TextMeshProUGUI m_pTxt_Point;
[Header("Attributes")]
[SerializeField] private TextMeshProUGUI m_pTxt_Str;
[SerializeField] private TextMeshProUGUI m_pTxt_Agi;
[SerializeField] private TextMeshProUGUI m_pTxt_Vit;
[SerializeField] private TextMeshProUGUI m_pTxt_Int;
[Header("Combat Stats")]
[SerializeField] private TextMeshProUGUI m_pTxt_Attack;
[SerializeField] private TextMeshProUGUI m_pTxt_AtkSpeed;
[SerializeField] private TextMeshProUGUI m_pTxt_Definition;
[SerializeField] private TextMeshProUGUI m_pTxt_Evade;
[SerializeField] private TextMeshProUGUI m_pTxt_MoveSpeed;
[SerializeField] private TextMeshProUGUI m_pTxt_MgcAttack;
[SerializeField] private TextMeshProUGUI m_pTxt_Critical;
[SerializeField] private TextMeshProUGUI m_pTxt_PhyDefense;
[SerializeField] private TextMeshProUGUI m_pTxt_MgcDefense;
[SerializeField] private TextMeshProUGUI m_pTxt_AttackLevel;
[SerializeField] private TextMeshProUGUI m_pTxt_DefenseLevel;
[SerializeField] private TextMeshProUGUI m_pTxt_CrtPower;
[SerializeField] private TextMeshProUGUI m_pTxt_SoulPower;
[Header("Hints / Additional")]
[SerializeField] private TextMeshProUGUI m_pNew_PhyDefense;
[SerializeField] private TextMeshProUGUI m_pNew_MgcDefense;
[SerializeField] private TextMeshProUGUI m_pNew_AttackLevel;
[SerializeField] private TextMeshProUGUI m_pNew_DefenseLevel;
[SerializeField] private TextMeshProUGUI m_pTxt_Force;
[SerializeField] private TextMeshProUGUI m_pTxt_Title;
[SerializeField] private TextMeshProUGUI m_pTxt_Realm;
[SerializeField] private TextMeshProUGUI m_pTxt_Vigour;
[SerializeField] private TextMeshProUGUI m_pTxt_Stealth;
[SerializeField] private TextMeshProUGUI m_pTxt_Antistealth;
[SerializeField] private TextMeshProUGUI m_pTxt_Penetration;
[SerializeField] private TextMeshProUGUI m_pTxt_Resilience;
[SerializeField] private TextMeshProUGUI m_pNew_Vigour;
[SerializeField] private TextMeshProUGUI m_pNew_SoulPower;
[SerializeField] private TextMeshProUGUI m_pNew_Penetration;
[SerializeField] private TextMeshProUGUI m_pNew_Resilience;
[Header("Character Portrait")]
[SerializeField] private RawImage m_pImg_Char;
[Tooltip("Optional. Pivot transform to rotate (Y) when dragging on portrait. E.g. character model or camera rig.")]
[SerializeField] private Transform m_pPortraitPivot;
[Header("Realm Progress")]
[SerializeField] private Slider m_pPro_RealmExp;
[Header("Buttons")]
[SerializeField] private Button m_pBtn_AddStr;
[SerializeField] private Button m_pBtn_AddAgi;
[SerializeField] private Button m_pBtn_AddVit;
[SerializeField] private Button m_pBtn_AddInt;
[SerializeField] private Button m_pBtn_MinusStr;
[SerializeField] private Button m_pBtn_MinusAgi;
[SerializeField] private Button m_pBtn_MinusVit;
[SerializeField] private Button m_pBtn_MinusInt;
[SerializeField] private Button m_pBtn_Confirm;
[SerializeField] private Button m_pBtn_Reset;
[SerializeField] private Button m_pBtn_Cancel;
[SerializeField] private Button m_pBtn_Force;
[SerializeField] private Button m_pBtn_TitleList;
[SerializeField] private Button m_pBtn_Book;
#endregion
#region State (same as C++)
private int m_nStatusPtUsed;
private ROLEEXTPROP_BASE m_repBase;
private int m_nMouseLastX;
private int m_nMouseOffset;
private int m_nMouseOffsetThis;
private float m_dwStartTime;
private float m_dwLastTime;
private int m_iIntervalTime;
private Button m_pButtonPress;
private bool m_bAdd;
private static readonly Color ColorBonus = new Color(0f, 1f, 0f);
private static readonly Color ColorNormal = Color.white;
private string m_cachedHostName = "";
#endregion
public override void OnEnable()
{
base.OnEnable();
EventBus.Subscribe<CECHostPlayer.InfoHostPlayer>(OnInfoHostPlayer);
}
public override void OnDisable()
{
EventBus.Unsubscribe<CECHostPlayer.InfoHostPlayer>(OnInfoHostPlayer);
base.OnDisable();
}
private void OnInfoHostPlayer(CECHostPlayer.InfoHostPlayer e)
{
m_cachedHostName = e.NameHostPlayer ?? "";
}
public override void Awake()
{
base.Awake();
SetName("Win_Character");
m_nMouseOffset = 0;
m_nMouseOffsetThis = 0;
m_iIntervalTime = INTERAL_LONG;
m_dwLastTime = 0;
m_pButtonPress = null;
ResetPoints();
}
public override void Start()
{
base.Start();
WireButtons();
WirePortraitDrag();
}
private void WirePortraitDrag()
{
if (m_pImg_Char == null) return;
var go = m_pImg_Char.gameObject;
var receiver = go.GetComponent<DlgCharacterPortraitDragReceiver>();
if (receiver == null) receiver = go.AddComponent<DlgCharacterPortraitDragReceiver>();
receiver.Init(this);
}
/// <summary>Called by portrait drag receiver. deltaX in screen space; rotation clamped to [-70,70] degrees.</summary>
public void OnPortraitDragDelta(float deltaX)
{
m_nMouseOffset += (int)deltaX;
m_nMouseOffset = Mathf.Clamp(m_nMouseOffset, -70, 70);
m_nMouseOffsetThis = m_nMouseOffset;
if (m_pPortraitPivot != null)
m_pPortraitPivot.localRotation = Quaternion.Euler(0f, m_nMouseOffset, 0f);
}
public override void Show(bool value)
{
m_bShow = value;
base.Show(value);
}
private void WireButtons()
{
if (m_pBtn_AddStr != null) m_pBtn_AddStr.onClick.AddListener(() => OnAddAttr(m_pBtn_AddStr, 0)); // Str
if (m_pBtn_AddAgi != null) m_pBtn_AddAgi.onClick.AddListener(() => OnAddAttr(m_pBtn_AddAgi, 1));
if (m_pBtn_AddVit != null) m_pBtn_AddVit.onClick.AddListener(() => OnAddAttr(m_pBtn_AddVit, 2)); // Vit maps to 3 in C++ iFourPro
if (m_pBtn_AddInt != null) m_pBtn_AddInt.onClick.AddListener(() => OnAddAttr(m_pBtn_AddInt, 3)); // Int/Energy
if (m_pBtn_MinusStr != null) m_pBtn_MinusStr.onClick.AddListener(OnMinusStr);
if (m_pBtn_MinusAgi != null) m_pBtn_MinusAgi.onClick.AddListener(OnMinusAgi);
if (m_pBtn_MinusVit != null) m_pBtn_MinusVit.onClick.AddListener(OnMinusVit);
if (m_pBtn_MinusInt != null) m_pBtn_MinusInt.onClick.AddListener(OnMinusInt);
if (m_pBtn_Confirm != null) m_pBtn_Confirm.onClick.AddListener(OnCommand_confirm);
if (m_pBtn_Reset != null) m_pBtn_Reset.onClick.AddListener(OnCommand_reset);
if (m_pBtn_Cancel != null) m_pBtn_Cancel.onClick.AddListener(OnCommand_CANCEL);
if (m_pBtn_Force != null) m_pBtn_Force.onClick.AddListener(OnCommand_force);
if (m_pBtn_TitleList != null) m_pBtn_TitleList.onClick.AddListener(OnCommand_Title);
if (m_pBtn_Book != null) m_pBtn_Book.onClick.AddListener(OnCommand_ReincarnationBook);
}
public override bool Render()
{
RefreshHostDetails();
return base.Render();
}
public override void Update()
{
OnTick();
base.Update();
}
private void OnTick()
{
if (m_pButtonPress == null) return;
if (!m_pButtonPress.gameObject.activeInHierarchy) { m_pButtonPress = null; return; }
float now = Time.realtimeSinceStartup * 1000f;
if (now - m_dwLastTime >= m_iIntervalTime)
{
if (m_bAdd)
PropertyAdd(m_pButtonPress);
else
PropertyMinus(m_pButtonPress);
m_dwLastTime += m_iIntervalTime;
if (m_iIntervalTime == INTERAL_LONG)
m_iIntervalTime = INTERAL_SHORT;
}
}
private void OnAddAttr(Button btn, int kind)
{
PropertyAdd(btn);
// Don't start continuous loop - only increment once per click
m_pButtonPress = null;
}
private void OnMinusStr() { PropertyMinus(m_pBtn_MinusStr); }
private void OnMinusAgi() { PropertyMinus(m_pBtn_MinusAgi); }
private void OnMinusVit() { PropertyMinus(m_pBtn_MinusVit); }
private void OnMinusInt() { PropertyMinus(m_pBtn_MinusInt); }
public override void ResetPoints()
{
m_nStatusPtUsed = 0;
m_repBase = new ROLEEXTPROP_BASE(false);
m_repBase.vitality = 0;
m_repBase.energy = 0;
m_repBase.strength = 0;
m_repBase.agility = 0;
}
private void PropertyAdd(Button btn)
{
CECHostPlayer host = GetHostPlayer();
if (host == null) return;
ROLEBASICPROP rbp = host.GetBasicProps();
if (m_nStatusPtUsed >= rbp.iStatusPt) return;
if (btn == m_pBtn_AddStr) { m_repBase.strength++; m_nStatusPtUsed++; }
else if (btn == m_pBtn_AddAgi) { m_repBase.agility++; m_nStatusPtUsed++; }
else if (btn == m_pBtn_AddVit) { m_repBase.vitality++; m_nStatusPtUsed++; }
else if (btn == m_pBtn_AddInt) { m_repBase.energy++; m_nStatusPtUsed++; }
}
private void PropertyMinus(Button btn)
{
if (btn == m_pBtn_MinusStr && m_repBase.strength > 0) { m_repBase.strength--; m_nStatusPtUsed--; }
else if (btn == m_pBtn_MinusAgi && m_repBase.agility > 0) { m_repBase.agility--; m_nStatusPtUsed--; }
else if (btn == m_pBtn_MinusVit && m_repBase.vitality > 0) { m_repBase.vitality--; m_nStatusPtUsed--; }
else if (btn == m_pBtn_MinusInt && m_repBase.energy > 0) { m_repBase.energy--; m_nStatusPtUsed--; }
}
private void OnCommand_confirm()
{
UnityGameSession.c2s_CmdSetStatusPts(
m_repBase.vitality, m_repBase.energy,
m_repBase.strength, m_repBase.agility);
ResetPoints();
// Request fresh ext props so host player gets updated values from server; UI will refresh when response arrives
UnityGameSession.c2s_SendCmdGetExtProps();
}
private void OnCommand_reset()
{
ResetPoints();
}
private void OnCommand_CANCEL()
{
CloseDialogue();
}
private void OnCommand_force()
{
var pForce = m_pAUIManager?.GetDialog("Win_Force");
if (pForce != null) pForce.Show(!pForce.IsShow());
}
private void OnCommand_Title()
{
var pDlg = GetGameUIMan()?.GetDialog("Win_TitleList");
if (pDlg != null) pDlg.Show(!pDlg.IsShow());
}
private void OnCommand_ReincarnationBook()
{
CECHostPlayer host = GetHostPlayer();
if (host == null) return;
var pDlg = m_pAUIManager?.GetDialog("Win_ReincarnationBook");
if (pDlg != null && host.GetReincarnationCount() > 0) pDlg.Show(!pDlg.IsShow());
}
private void RefreshHostDetails()
{
if (!gameObject.activeSelf) return;
CECHostPlayer host = GetHostPlayer();
if (host == null) return;
ROLEBASICPROP rbp = host.GetBasicProps();
ROLEEXTPROP rep = host.GetExtendProps();
var gameRun = EC_Game.GetGameRun();
if (gameRun == null) return;
string charName = host.GetName();
if (string.IsNullOrEmpty(charName)) charName = m_cachedHostName;
if (string.IsNullOrEmpty(charName))
{
var ri = UnityGameSession.Instance?.GetRoleInfo();
if (ri?.name != null && ri.name.ByteArray != null && ri.name.ByteArray.Length > 0)
charName = Encoding.Unicode.GetString(ri.name.ByteArray);
}
SetText(m_pTxt_CharName, charName ?? "");
SetText(m_pTxt_CharLevel, rbp.iLevel.ToString());
SetText(m_pTxt_Profession, gameRun.GetProfName(host.GetProfession()));
int idFaction = host.GetFactionID();
string factionName = idFaction <= 0 ? GetStringFromTable(251) : "";
var factionMan = EC_Game.GetFactionMan();
if (idFaction > 0 && factionMan != null)
{
var fi = factionMan.GetFaction((uint)idFaction, false);
if (fi != null) factionName = fi.m_szName ?? "";
}
SetText(m_pTxt_Faction, factionName);
int spouse = 0; // TODO: host.GetSpouse() when exposed; use gameRun.GetPlayerName(spouse, false) when that API exists
if (spouse != 0)
{
string spouseName = ""; // TODO: gameRun.GetPlayerName(spouse, false)
SetText(m_pTxt_Partner, spouseName ?? "");
}
else
SetText(m_pTxt_Partner, GetStringFromTable(786));
int[] iFourPro = CalcEquipmentBonus(host);
SetText(m_pTxt_ExpCurrent, rbp.iExp.ToString());
SetText(m_pTxt_ExpRequire, host.GetLevelUpExp(rbp.iLevel).ToString());
int reputation = 0; // TODO: host.GetReputation() when exposed
SetText(m_pTxt_Distinction, reputation.ToString());
SetText(m_pTxt_xz, GetGameUIMan()?.GetStringFromTable(1001 + rbp.iLevel2) ?? "");
SetText(m_pTxt_HP, $"{rbp.iCurHP}/{rep.bs.max_hp}");
SetText(m_pTxt_MP, $"{rbp.iCurMP}/{rep.bs.max_mp}");
SetText(m_pTxt_SP, rbp.iSP.ToString());
SetText(m_pTxt_Point, (rbp.iStatusPt - m_nStatusPtUsed).ToString());
int strVal = rep.bs.strength + m_repBase.strength;
SetText(m_pTxt_Str, strVal.ToString());
SetColor(m_pTxt_Str, iFourPro[0] != 0 ? ColorBonus : ColorNormal);
int agiVal = rep.bs.agility + m_repBase.agility;
SetText(m_pTxt_Agi, agiVal.ToString());
SetColor(m_pTxt_Agi, iFourPro[1] != 0 ? ColorBonus : ColorNormal);
int vitVal = rep.bs.vitality + m_repBase.vitality;
SetText(m_pTxt_Vit, vitVal.ToString());
SetColor(m_pTxt_Vit, iFourPro[3] != 0 ? ColorBonus : ColorNormal);
int energyVal = rep.bs.energy + m_repBase.energy;
SetText(m_pTxt_Int, energyVal.ToString());
SetColor(m_pTxt_Int, iFourPro[2] != 0 ? ColorBonus : ColorNormal);
SetText(m_pTxt_Attack, $"{rep.ak.DamageLow}-{rep.ak.DamageHigh}");
string atkSpeedUnit = GetGameUIMan()?.GetStringFromTable(279) ?? "/s";
float atkSpeed = rep.ak.AttackSpeed != 0 ? 1f / (rep.ak.AttackSpeed * 0.05f) : 0f;
SetText(m_pTxt_AtkSpeed, $"{atkSpeed:F2} {atkSpeedUnit}");
SetText(m_pTxt_Definition, rep.ak.Attack.ToString());
SetText(m_pTxt_Evade, rep.df.armor.ToString());
string moveUnit = GetGameUIMan()?.GetStringFromTable(280) ?? "m/s";
SetText(m_pTxt_MoveSpeed, $"{rep.mv.run_speed:F1} {moveUnit}");
SetText(m_pTxt_MgcAttack, $"{rep.ak.DamageMagicLow}-{rep.ak.DamageMagicHigh}");
SetText(m_pTxt_Critical, $"{rbp.iCritRate}%");
int nLevel = rbp.iLevel;
float fReduce = DefenceToPercent(rep.df.defense, nLevel);
SetText(m_pTxt_PhyDefense, rep.df.defense.ToString());
SetText(m_pNew_PhyDefense, string.Format(GetGameUIMan()?.GetStringFromTable(490) ?? "{0} {1}", nLevel, fReduce));
if (m_pTxt_MgcDefense != null || m_pNew_MgcDefense != null)
{
int nSum = 0;
var parts = new System.Collections.Generic.List<string>();
for (int i = 0; i < GameConstants.NUM_MAGICCLASS; i++)
{
nSum += rep.df.resistance[i];
float r = DefenceToPercent(rep.df.resistance[i], nLevel);
parts.Add(string.Format(GetGameUIMan()?.GetStringFromTable(491 + i) ?? "", rep.df.resistance[i], nLevel, r));
}
if (m_pTxt_MgcDefense != null) SetText(m_pTxt_MgcDefense, (nSum / GameConstants.NUM_MAGICCLASS).ToString());
if (m_pNew_MgcDefense != null) SetText(m_pNew_MgcDefense, string.Join("\n", parts));
}
SetText(m_pTxt_AttackLevel, rbp.iAtkDegree.ToString());
SetText(m_pTxt_DefenseLevel, rbp.iDefDegree.ToString());
SetText(m_pTxt_CrtPower, $"{rbp.iCritDamageBonus + 200}%");
int soulPower = 0; // TODO: host.GetSoulPower() when exposed
SetText(m_pTxt_SoulPower, soulPower >= 0 ? soulPower.ToString() : "-");
string soulHint = GetStringFromTable(8135);
if (soulPower > 0 && host.GetProfession() == (int)PROFESSION.PROF_MONK)
{
soulHint = GetStringFromTable(8136) + "\n"
+ string.Format(GetStringFromTable(8130), soulPower) + "\n"
+ string.Format(GetStringFromTable(8131), (int)Mathf.Floor(soulPower * 0.08f * (1 + 0.01f * rbp.iAtkDegree))) + "\n"
+ string.Format(GetStringFromTable(8132), (int)Mathf.Floor(soulPower * 0.0002f + 1)) + "\n"
+ string.Format(GetStringFromTable(8133), soulPower * 0.0006f) + "\n"
+ string.Format(GetStringFromTable(8134), (int)Mathf.Floor(soulPower * 0.5f));
}
SetText(m_pNew_SoulPower, soulHint);
SetText(m_pTxt_Stealth, rbp.iInvisibleDegree.ToString());
SetText(m_pTxt_Antistealth, rbp.iAntiInvisibleDegree.ToString());
SetText(m_pTxt_Penetration, rbp.iPenetration.ToString());
SetText(m_pNew_Penetration, string.Format(GetStringFromTable(9380), (int)(100 * rbp.iPenetration * 3 / (float)(rbp.iPenetration + 300))));
SetText(m_pTxt_Resilience, rbp.iResilience.ToString());
SetText(m_pNew_Resilience, string.Format(GetStringFromTable(9381), nLevel, (int)(100 * rbp.iResilience / (float)(rbp.iResilience + nLevel))));
int vigourRealm = 0;
int realmLevel = host.GetRealmLevel();
if (m_pTxt_Realm != null) m_pTxt_Realm.gameObject.SetActive(realmLevel > 0);
if (m_pPro_RealmExp != null) m_pPro_RealmExp.gameObject.SetActive(realmLevel > 0);
if (realmLevel > 0)
{
SetText(m_pTxt_Realm, GetRealmDisplayName(realmLevel));
if (m_pPro_RealmExp != null)
{
int requireExp = 0; // TODO: host.GetRealmRequireExp() when exposed
if (requireExp > 0)
{
int realmExp = 0; // TODO: host.GetRealmExp() when exposed
m_pPro_RealmExp.value = Mathf.Clamp01((float)Math.Max(realmExp, 0) / requireExp);
SetText(m_pNew_Vigour, string.Format(GetGameUIMan()?.GetStringFromTable(11164) ?? "", realmExp, requireExp));
}
else
{
m_pPro_RealmExp.value = 1f;
}
}
}
SetText(m_pTxt_Vigour, rbp.iVigour.ToString());
SetText(m_pNew_Vigour, string.Format(GetStringFromTable(11146), vigourRealm));
int forceId = host.GetForce();
if (m_pBtn_Force != null) m_pBtn_Force.interactable = forceId > 0;
if (m_pTxt_Force != null)
SetText(m_pTxt_Force, ""); // TODO: force name when GetForceMgr/GetForceData exposed
if (m_pTxt_Title != null)
SetText(m_pTxt_Title, ""); // TODO: title when GetCurrentTitle/GetTitleConfig exposed
if (m_pBtn_Book != null) m_pBtn_Book.interactable = host.GetReincarnationCount() > 0;
}
private static void SetText(TextMeshProUGUI t, string s)
{
if (t != null) t.text = s ?? "";
}
private static void SetColor(TextMeshProUGUI t, Color c)
{
if (t != null) t.color = c;
}
private static float DefenceToPercent(int def, int nLevel)
{
float f = 100f * def / (def + 40f * nLevel - 25f);
return Mathf.Min(f, 95f);
}
private string GetRealmDisplayName(int realmLevel)
{
if (realmLevel <= 0) return "";
string s = GetGameUIMan()?.GetStringFromTable(11000 + realmLevel);
return s ?? "";
}
private int[] CalcEquipmentBonus(CECHostPlayer host)
{
int[] iFourPro = { 0, 0, 0, 0 };
var inv = host?.GetEquipment();
if (inv == null) return iFourPro;
int equipSlots = InventoryConst.SIZE_ALL_EQUIPIVTR;
for (int i = 0; i < equipSlots; i++)
{
var item = inv.GetItem(i);
if (item is EC_IvtrEquip equ && equ.Props != null)
{
int n = equ.Props.Count;
for (int j = 0; j < n; j++)
{
var pro = equ.Props[j];
int byPropType = pro.Type; // use Type as prop type; map via GetItemExtPropType if needed
int val = (pro.Params != null && pro.Params.Length > 0) ? pro.Params[0] : 0;
if (byPropType >= 41 && byPropType <= 44)
iFourPro[byPropType - 41] += val;
else if (byPropType >= 106 && byPropType <= 108)
iFourPro[byPropType - 106] += val;
else if (byPropType >= 95 && byPropType <= 98)
iFourPro[byPropType - 95] += val;
else if (byPropType >= 128 && byPropType <= 131)
{
int[] order = { 3, 0, 1, 2 };
iFourPro[order[byPropType - 128]] += val;
}
}
}
}
return iFourPro;
}
}
/// <summary>Forwards drag on the portrait RawImage to DlgCharacter for rotation.</summary>
public class DlgCharacterPortraitDragReceiver : MonoBehaviour, IBeginDragHandler, IDragHandler
{
private DlgCharacter m_dlg;
public void Init(DlgCharacter dlg) { m_dlg = dlg; }
public void OnBeginDrag(PointerEventData eventData) { }
public void OnDrag(PointerEventData eventData)
{
if (m_dlg != null)
m_dlg.OnPortraitDragDelta(eventData.delta.x);
}
}
}