新建参考

This commit is contained in:
2026-04-14 02:21:58 +08:00
commit 2ef540c7e5
148 changed files with 36460 additions and 0 deletions

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

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

View File

@@ -0,0 +1,94 @@
using UnityEngine;
namespace GameSDK
{
/// <summary>
/// UI面板基类支持淡入淡出动画
/// </summary>
public class BasePanel : MonoBehaviour
{
protected CanvasGroup canvasGroup;
public float fadeDuration = 0.25f;
private float fadeTarget = -1f;
private float fadeTimer = 0f;
private float fadeFrom = 0f;
private System.Action fadeCallback;
/// <summary>
/// 确保 CanvasGroup 存在lazy init应对 inactive 面板)
/// </summary>
protected void EnsureCanvasGroup()
{
if (canvasGroup == null)
{
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
}
protected virtual void Awake()
{
EnsureCanvasGroup();
}
protected virtual void Update()
{
if (fadeTarget < 0f) return;
fadeTimer += Time.deltaTime;
float t = Mathf.Clamp01(fadeTimer / fadeDuration);
canvasGroup.alpha = Mathf.Lerp(fadeFrom, fadeTarget, t);
if (t >= 1f)
{
canvasGroup.alpha = fadeTarget;
if (fadeTarget > 0f)
canvasGroup.blocksRaycasts = true;
System.Action cb = fadeCallback;
fadeTarget = -1f;
fadeCallback = null;
cb?.Invoke();
}
}
public virtual void OnEnter()
{
gameObject.SetActive(true);
EnsureCanvasGroup();
canvasGroup.blocksRaycasts = true;
StartFade(1f);
}
public virtual void OnPause()
{
StartFade(0f, () => gameObject.SetActive(false));
}
public virtual void OnResume()
{
gameObject.SetActive(true);
EnsureCanvasGroup();
canvasGroup.blocksRaycasts = true;
StartFade(1f);
}
public virtual void OnExit()
{
StartFade(0f, () => gameObject.SetActive(false));
}
private void StartFade(float targetAlpha, System.Action onComplete = null)
{
EnsureCanvasGroup();
fadeFrom = canvasGroup.alpha;
fadeTarget = targetAlpha;
fadeTimer = 0f;
fadeCallback = onComplete;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DitNvH+qASrZ3xHma0cii5/gjrwR7HYv1c/TA4orpBJNbH1O5TmEVGk=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,98 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace GameSDK
{
/// <summary>
/// 修改密码面板
/// </summary>
public class ChangePasswordPanel : BasePanel
{
[Header("UI References")]
public TMP_InputField usernameInput;
public TMP_InputField oldPasswordInput;
public TMP_InputField newPasswordInput;
public TMP_InputField confirmPasswordInput;
public Button confirmBtn;
public Button backBtn;
public TMP_Text tipText;
private bool initialized;
private void OnEnable()
{
if (initialized) return;
initialized = true;
confirmBtn.onClick.AddListener(OnConfirmClick);
backBtn.onClick.AddListener(OnBackClick);
}
private void OnConfirmClick()
{
string username = usernameInput.text.Trim();
string oldPassword = oldPasswordInput.text.Trim();
string newPassword = newPasswordInput.text.Trim();
string confirmPassword = confirmPasswordInput.text.Trim();
if (string.IsNullOrEmpty(username))
{
ShowTip("请输入账号");
return;
}
if (string.IsNullOrEmpty(oldPassword))
{
ShowTip("请输入原密码");
return;
}
if (string.IsNullOrEmpty(newPassword))
{
ShowTip("请输入新密码");
return;
}
if (newPassword.Length < 6)
{
ShowTip("新密码长度不能少于6位");
return;
}
if (newPassword != confirmPassword)
{
ShowTip("两次输入的新密码不一致");
return;
}
if (oldPassword == newPassword)
{
ShowTip("新密码不能与旧密码相同");
return;
}
// TODO: 接入实际修改密码接口
Debug.Log($"[SDK] 修改密码请求: 账号={username}");
ShowTip("提交中...");
// 模拟成功
Debug.Log("[SDK] 密码修改成功");
SDKUIManager.Instance.PopPanel();
}
private void OnBackClick()
{
SDKUIManager.Instance.PopPanel();
}
private void ShowTip(string msg)
{
tipText.text = msg;
}
public override void OnEnter()
{
base.OnEnter();
usernameInput.text = "";
oldPasswordInput.text = "";
newPasswordInput.text = "";
confirmPasswordInput.text = "";
tipText.text = "";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: C3pLs3moAH5Toh0op4hx6ZzPWLQttg1SE4L3LcF6s/cMS5Zd1Y8Mi9E=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,513 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace GameSDK
{
/// <summary>
/// 登录面板miHoYo风格布局
/// 支持[ExecuteInEditMode],在编辑器模式下也能预览布局效果
/// </summary>
[ExecuteInEditMode]
public class LoginPanel : BasePanel
{
[Header("UI References")]
public TMP_InputField usernameInput;
public TMP_InputField passwordInput;
public Button loginBtn;
public Button registerBtn;
public Button changePasswordBtn;
public Button guestLoginBtn;
public TMP_Text tipText;
// 运行时创建的UI元素引用
private GameObject subtitleObj; // 副标题 "TECH OTAKUS SAVE THE WORLD"
private GameObject closeBtnObj; // 关闭按钮 X
private GameObject agreementRowObj; // 协议勾选行
private bool initialized;
#if UNITY_EDITOR
private int lastLayoutVersion = 0;
#endif
protected override void Awake()
{
base.Awake();
InitLayout();
// 仅在运行模式下绑定按钮事件(编辑模式不需要)
if (Application.isPlaying)
BindButtons();
}
#if UNITY_EDITOR
/// <summary>
/// 编辑器模式下脚本加载、Inspector值变化、对象选中时都会触发
/// 确保在Scene视图中也能看到正确布局
/// </summary>
private void OnValidate()
{
// 避免在非播放模式且对象未激活时执行Awake不会触发的情况
if (!Application.isPlaying && gameObject.activeInHierarchy)
{
// 延迟一帧执行,确保所有序列化字段已就绪
UnityEditor.EditorApplication.delayCall += () =>
{
if (this != null && gameObject != null)
InitLayout();
};
}
}
/// <summary>
/// 右键菜单:手动刷新布局
/// </summary>
[UnityEditor.MenuItem("CONTEXT/LoginPanel/刷新布局")]
private static void RefreshLayout(UnityEditor.MenuCommand command)
{
var panel = (LoginPanel)command.context;
if (panel != null)
{
panel.initialized = false; // 强制重新初始化
panel.InitLayout();
UnityEditor.EditorUtility.SetDirty(panel);
}
}
#endif
/// <summary>
/// 初始化miHoYo风格布局每次Awake调用确保Play模式下修改也生效
/// </summary>
private void InitLayout()
{
var card = transform.Find("Card");
if (card == null)
{
Debug.LogError("[LoginPanel] Card not found!");
return;
}
var cardRt = card.GetComponent<RectTransform>();
// === 1. 标题: miHoYo + 副标题 ===
SetupTitle(card);
// === 2. 输入框去掉标签更新placeholder ===
SetupInputFields(card);
// === 3. 按钮文字与样式 ===
SetupButtons(card);
// === 4. 隐藏游客登录按钮 ===
var guestBtn = card.Find("游客登录Btn");
if (guestBtn != null) guestBtn.gameObject.SetActive(false);
// === 5. 提示文字位置 ===
var tip = card.Find("TipText");
if (tip != null)
{
var tipRt = tip.GetComponent<RectTransform>();
tipRt.anchorMin = new Vector2(0.5f, 0.5f);
tipRt.anchorMax = new Vector2(0.5f, 0.5f);
tipRt.pivot = new Vector2(0.5f, 0.5f);
tipRt.sizeDelta = new Vector2(300f, 30f);
tipRt.anchoredPosition = new Vector2(0f, -175f);
}
// === 6. 创建协议勾选行 ===
SetupAgreementRow(card);
// === 7. 创建关闭按钮(X) ===
SetupCloseButton(card);
}
#region
private void SetupTitle(Transform card)
{
var titleTf = card.Find("Title");
if (titleTf == null) return;
var titleRt = titleTf.GetComponent<RectTransform>();
titleRt.anchorMin = new Vector2(0.5f, 0.5f);
titleRt.anchorMax = new Vector2(0.5f, 0.5f);
titleRt.pivot = new Vector2(0.5f, 0.5f);
titleRt.sizeDelta = new Vector2(380f, 50f);
titleRt.anchoredPosition = new Vector2(0f, 220f);
var titleTxt = titleTf.GetComponent<TMP_Text>();
if (titleTxt != null)
{
titleTxt.text = "miHoYo";
titleTxt.fontSize = 28f;
titleTxt.fontStyle = FontStyles.Bold;
titleTxt.alignment = TextAlignmentOptions.Center;
}
// 副标题
subtitleObj = EnsureChild(card, "Subtitle", "Title");
if (subtitleObj != null)
{
var subRt = subtitleObj.GetComponent<RectTransform>();
ResetRectTransform(subRt,
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
new Vector2(0.5f, 0.5f), new Vector2(380f, 20f),
new Vector2(0f, 185f));
var subTxt = subtitleObj.GetComponent<TextMeshProUGUI>();
if (subTxt == null)
{
subTxt = subtitleObj.AddComponent<TextMeshProUGUI>();
subTxt.font = GetFont(card);
}
subTxt.text = "TECH OTAKUS SAVE THE WORLD";
subTxt.fontSize = 10f;
subTxt.color = new Color(0.6f, 0.6f, 0.6f);
subTxt.alignment = TextAlignmentOptions.Center;
}
}
private void SetupInputFields(Transform card)
{
string accKey = "账号Group";
string pwdKey = "密码Group";
// 账号输入框
var accGroup = card.Find(accKey);
if (accGroup != null)
{
// 隐藏标签
var label = accGroup.Find("Label");
if (label != null) label.gameObject.SetActive(false);
// 调整Group和InputField尺寸
var grpRt = accGroup.GetComponent<RectTransform>();
grpRt.anchorMin = new Vector2(0.5f, 0.5f);
grpRt.anchorMax = new Vector2(0.5f, 0.5f);
grpRt.pivot = new Vector2(0.5f, 0.5f);
grpRt.sizeDelta = new Vector2(380f, 55f);
grpRt.anchoredPosition = new Vector2(0f, 125f);
var inputField = accGroup.Find("InputField");
if (inputField != null)
{
var inputRt = inputField.GetComponent<RectTransform>();
inputRt.sizeDelta = new Vector2(360f, 50f);
inputRt.anchoredPosition = Vector2.zero;
var placeholder = inputField.Find("Placeholder")?.GetComponent<TMP_Text>();
if (placeholder != null) placeholder.text = "输入手机号/邮箱";
var inputImg = inputField.GetComponent<Image>();
if (inputImg != null) inputImg.color = new Color(0.95f, 0.96f, 0.97f, 1f);
}
}
// 密码输入框
var pwdGroup = card.Find(pwdKey);
if (pwdGroup != null)
{
var label = pwdGroup.Find("Label");
if (label != null) label.gameObject.SetActive(false);
var grpRt = pwdGroup.GetComponent<RectTransform>();
grpRt.anchorMin = new Vector2(0.5f, 0.5f);
grpRt.anchorMax = new Vector2(0.5f, 0.5f);
grpRt.pivot = new Vector2(0.5f, 0.5f);
grpRt.sizeDelta = new Vector2(380f, 55f);
grpRt.anchoredPosition = new Vector2(0f, 60f);
var inputField = pwdGroup.Find("InputField");
if (inputField != null)
{
var inputRt = inputField.GetComponent<RectTransform>();
inputRt.sizeDelta = new Vector2(360f, 50f);
inputRt.anchoredPosition = Vector2.zero;
var placeholder = inputField.Find("Placeholder")?.GetComponent<TMP_Text>();
if (placeholder != null) placeholder.text = "输入密码";
var inputImg = inputField.GetComponent<Image>();
if (inputImg != null) inputImg.color = new Color(0.95f, 0.96f, 0.97f, 1f);
}
}
}
private void SetupButtons(Transform card)
{
// 登录按钮 -> 进入游戏(放在输入框下方)
var loginBtnTf = card.Find("登录Btn");
if (loginBtnTf != null)
{
var btnRt = loginBtnTf.GetComponent<RectTransform>();
btnRt.sizeDelta = new Vector2(380f, 50f);
btnRt.anchoredPosition = new Vector2(0f, 0f);
var btnImg = loginBtnTf.GetComponent<Image>();
if (btnImg != null) btnImg.color = new Color(0.18f, 0.22f, 0.28f); // 深灰底色
var loginTxt = loginBtnTf.Find("Text")?.GetComponent<TMP_Text>();
if (loginTxt != null)
{
loginTxt.text = "进入游戏";
loginTxt.fontSize = 20f;
loginTxt.fontStyle = FontStyles.Bold;
loginTxt.color = Color.white;
}
}
// 注册按钮 -> 没有账号?立即注册 (橙色文字链接)
var regBtnTf = card.Find("注册账号Btn");
if (regBtnTf != null)
{
var regRt = regBtnTf.GetComponent<RectTransform>();
regRt.sizeDelta = new Vector2(180f, 36f);
regRt.anchoredPosition = new Vector2(-90f, -55f);
var regImg = regBtnTf.GetComponent<Image>();
if (regImg != null) regImg.enabled = false; // 无背景
var regTxt = regBtnTf.Find("Text")?.GetComponent<TMP_Text>();
if (regTxt != null)
{
regTxt.text = "没有账号?立即注册";
regTxt.fontSize = 14f;
regTxt.color = new Color(1f, 0.75f, 0f); // 橙色
}
}
// 修改密码按钮 -> 忘记密码 (灰色文字链接)
var pwdBtnTf = card.Find("修改密码Btn");
if (pwdBtnTf != null)
{
var pwdRt = pwdBtnTf.GetComponent<RectTransform>();
pwdRt.sizeDelta = new Vector2(100f, 36f);
pwdRt.anchoredPosition = new Vector2(130f, -55f);
var pwdImg = pwdBtnTf.GetComponent<Image>();
if (pwdImg != null) pwdImg.enabled = false; // 无背景
var pwdTxt = pwdBtnTf.Find("Text")?.GetComponent<TMP_Text>();
if (pwdTxt != null)
{
pwdTxt.text = "忘记密码";
pwdTxt.fontSize = 14f;
pwdTxt.color = new Color(0.55f, 0.45f, 0.35f); // 灰棕色
}
}
}
private void SetupAgreementRow(Transform card)
{
agreementRowObj = EnsureChild(card, "AgreementRow", "TipText");
if (agreementRowObj == null) return;
var rowRt = agreementRowObj.GetComponent<RectTransform>();
rowRt.anchorMin = new Vector2(0.5f, 0.5f);
rowRt.anchorMax = new Vector2(0.5f, 0.5f);
rowRt.pivot = new Vector2(0.5f, 0.5f);
rowRt.sizeDelta = new Vector2(380f, 30f);
rowRt.anchoredPosition = new Vector2(200f, -95f);
// === Toggle复选框 ===
var toggleGo = EnsureChild(agreementRowObj.transform, "Toggle", null);
var trt = toggleGo.GetComponent<RectTransform>();
ResetRectTransform(trt, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f),
new Vector2(0f, 0.5f), new Vector2(20f, 20f), new Vector2(-190f, 0f));
var tgl = toggleGo.GetComponent<Toggle>();
if (tgl == null) tgl = toggleGo.AddComponent<Toggle>();
tgl.isOn = true;
var bgImg = toggleGo.GetComponent<Image>();
if (bgImg == null) bgImg = toggleGo.AddComponent<Image>();
bgImg.color = new Color(0.9f, 0.9f, 0.9f);
// Checkmark仅在不存在时创建
if (toggleGo.transform.Find("Checkmark") == null)
{
var chk = new GameObject("Checkmark");
chk.transform.SetParent(toggleGo.transform, false);
var crt = chk.AddComponent<RectTransform>();
crt.anchorMin = Vector2.zero;
crt.anchorMax = Vector2.zero;
crt.sizeDelta = new Vector2(18f, 18f);
crt.anchoredPosition = Vector2.zero;
crt.pivot = new Vector2(0.5f, 0.5f);
var cki = chk.AddComponent<Image>();
cki.color = new Color(0.15f, 0.55f, 1f); // 蓝色勾
tgl.graphic = cki;
}
// === 协议文本 ===
var txtGo = EnsureChild(agreementRowObj.transform, "AgreeText", "Toggle");
var txtRt = txtGo.GetComponent<RectTransform>();
ResetRectTransform(txtRt, new Vector2(0f, 0.5f), new Vector2(1f, 0.5f),
new Vector2(0.5f, 0.5f), new Vector2(340f, 25f), new Vector2(10f, 0f));
var agreeTxt = txtGo.GetComponent<TextMeshProUGUI>();
if (agreeTxt == null)
{
agreeTxt = txtGo.AddComponent<TextMeshProUGUI>();
agreeTxt.font = GetFont(card);
}
agreeTxt.text = "已同意《用户协议》和《隐私政策》";
agreeTxt.fontSize = 13f;
agreeTxt.color = new Color(0.65f, 0.65f, 0.65f);
agreeTxt.alignment = TextAlignmentOptions.Left;
}
private void SetupCloseButton(Transform card)
{
closeBtnObj = EnsureChild(card, "CloseBtn", null, insertAtFront: true);
if (closeBtnObj == null) return;
var cRt = closeBtnObj.GetComponent<RectTransform>();
ResetRectTransform(cRt, new Vector2(1f, 1f), new Vector2(1f, 1f),
new Vector2(0.5f, 0.5f), new Vector2(30f, 30f), new Vector2(-20f, -20f));
if (closeBtnObj.GetComponent<Button>() == null)
closeBtnObj.AddComponent<Button>().onClick.AddListener(() => Debug.Log("[LoginPanel] 关闭"));
var cImg = closeBtnObj.GetComponent<Image>();
if (cImg == null) cImg = closeBtnObj.AddComponent<Image>();
cImg.color = new Color(0.3f, 0.3f, 0.3f);
// X 文字(始终重置位置)
var xTf = EnsureChild(closeBtnObj.transform, "XText", null);
var xRt = xTf.GetComponent<RectTransform>();
ResetRectTransform(xRt, Vector2.zero, Vector2.zero,
new Vector2(0.5f, 0.5f), new Vector2(30f, 30f), Vector2.zero);
var xtxt = xTf.GetComponent<TextMeshProUGUI>();
if (xtxt == null)
{
xtxt = xTf.AddComponent<TextMeshProUGUI>();
xtxt.font = GetFont(card);
}
xtxt.text = "\u00D7"; // × 符号
xtxt.fontSize = 18f;
xtxt.color = Color.white;
xtxt.alignment = TextAlignmentOptions.Center;
}
#endregion
#region
/// <summary>确保子对象存在不存在则创建。insertAfterName指定在哪个兄弟之后插入</summary>
private GameObject EnsureChild(Transform parent, string name, string insertAfterName = null, bool insertAtFront = false)
{
var existing = parent.Find(name)?.gameObject;
if (existing != null) return existing;
var go = new GameObject(name);
go.transform.SetParent(parent, false);
go.AddComponent<RectTransform>(); // 确保有RT组件
// 设置插入位置
if (insertAtFront)
go.transform.SetAsFirstSibling();
else if (!string.IsNullOrEmpty(insertAfterName))
{
var afterTf = parent.Find(insertAfterName);
if (afterTf != null)
go.transform.SetSiblingIndex(afterTf.GetSiblingIndex() + 1);
}
return go;
}
/// <summary>完全重置RectTransform的所有属性解决编辑模式下新建对象RT默认值异常的问题</summary>
private static void ResetRectTransform(RectTransform rt, Vector2 anchorMin, Vector2 anchorMax,
Vector2 pivot, Vector2 sizeDelta, Vector2 anchoredPosition)
{
rt.anchorMin = anchorMin;
rt.anchorMax = anchorMax;
rt.pivot = pivot;
rt.sizeDelta = sizeDelta;
rt.anchoredPosition = anchoredPosition;
// 注意不要设offsetMin/offsetMax它们会覆盖上面设置的sizeDelta和anchoredPosition
}
private TMP_FontAsset GetFont(Transform card)
{
var titleTxt = card.Find("Title")?.GetComponent<TMP_Text>();
if (titleTxt != null && titleTxt.font != null)
return titleTxt.font;
// fallback: 从全局资源找SimHei_SDF
var font = Resources.Load<TMP_FontAsset>("Fonts/SimHei_SDF");
if (font != null) return font;
return null;
}
private void BindButtons()
{
if (initialized) return;
initialized = true;
if (loginBtn != null) loginBtn.onClick.AddListener(OnLoginClick);
if (registerBtn != null) registerBtn.onClick.AddListener(OnRegisterClick);
if (changePasswordBtn != null) changePasswordBtn.onClick.AddListener(OnChangePasswordClick);
if (guestLoginBtn != null) guestLoginBtn.onClick.AddListener(OnGuestLoginClick);
}
#endregion
private void OnLoginClick()
{
string username = usernameInput.text.Trim();
string password = passwordInput.text.Trim();
if (string.IsNullOrEmpty(username))
{
ShowTip("请输入账号");
return;
}
if (string.IsNullOrEmpty(password))
{
ShowTip("请输入密码");
return;
}
// TODO: 接入实际登录接口
Debug.Log($"[SDK] 登录请求: 账号={username}, 密码={password}");
ShowTip("登录中...");
// 模拟登录成功
SDKManager.Instance.LoginSuccess(username);
SDKUIManager.Instance.PopPanel();
SDKUIManager.Instance.ShowPanel<RealNameAuthPanel>();
}
private void OnRegisterClick()
{
SDKUIManager.Instance.ShowPanel<RegisterPanel>();
}
private void OnChangePasswordClick()
{
SDKUIManager.Instance.ShowPanel<ChangePasswordPanel>();
}
private void OnGuestLoginClick()
{
Debug.Log("[SDK] 游客登录");
SDKManager.Instance.LoginSuccess("Guest_" + Random.Range(1000, 9999));
SDKUIManager.Instance.PopPanel();
SDKUIManager.Instance.ShowPanel<RealNameAuthPanel>();
}
private void ShowTip(string msg)
{
tipText.text = msg;
}
public override void OnEnter()
{
base.OnEnter();
usernameInput.text = "";
passwordInput.text = "";
tipText.text = "";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: Xi5NtHn4VH3blNa2yzsMjhHddSjv4pAjXWVdcpNdQC2XBAzFvtweUEI=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace GameSDK
{
/// <summary>
/// 实名认证面板
/// </summary>
public class RealNameAuthPanel : BasePanel
{
[Header("UI References")]
public TMP_InputField realNameInput;
public TMP_InputField idCardInput;
public Button submitBtn;
public Button skipBtn;
public TMP_Text tipText;
private bool initialized;
private void OnEnable()
{
if (initialized) return;
initialized = true;
submitBtn.onClick.AddListener(OnSubmitClick);
skipBtn.onClick.AddListener(OnSkipClick);
}
private void OnSubmitClick()
{
string realName = realNameInput.text.Trim();
string idCard = idCardInput.text.Trim();
if (string.IsNullOrEmpty(realName))
{
ShowTip("请输入真实姓名");
return;
}
if (realName.Length < 2)
{
ShowTip("姓名格式不正确");
return;
}
if (string.IsNullOrEmpty(idCard))
{
ShowTip("请输入身份证号码");
return;
}
if (!ValidateIdCard(idCard))
{
ShowTip("身份证号码格式不正确");
return;
}
// TODO: 接入实际实名认证接口
Debug.Log($"[SDK] 实名认证请求: 姓名={realName}, 身份证={MaskIdCard(idCard)}");
ShowTip("认证中...");
// 模拟认证成功
Debug.Log("[SDK] 实名认证成功");
CloseSDK();
}
private void OnSkipClick()
{
Debug.Log("[SDK] 跳过实名认证");
CloseSDK();
}
private void CloseSDK()
{
// 关闭SDK界面
Debug.Log($"[SDK] 用户: {SDKManager.Instance.CurrentUser} 已进入游戏");
// 实际项目中可销毁SDK UI的Canvas
var canvas = GetComponentInParent<Canvas>();
if (canvas != null) canvas.gameObject.SetActive(false);
}
private bool ValidateIdCard(string idCard)
{
if (idCard.Length != 18) return false;
for (int i = 0; i < 17; i++)
{
if (!char.IsDigit(idCard[i])) return false;
}
char last = idCard[17];
return char.IsDigit(last) || last == 'X' || last == 'x';
}
private string MaskIdCard(string idCard)
{
if (idCard.Length != 18) return idCard;
return idCard.Substring(0, 4) + "**********" + idCard.Substring(14);
}
private void ShowTip(string msg)
{
tipText.text = msg;
}
public override void OnEnter()
{
base.OnEnter();
realNameInput.text = "";
idCardInput.text = "";
tipText.text = "";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: CC8f5nytWnn9BcO0gURVQqpFdbjumhEuvsiDMG8qioTNJ0qqjNAOgio=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,90 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace GameSDK
{
/// <summary>
/// 注册面板
/// </summary>
public class RegisterPanel : BasePanel
{
[Header("UI References")]
public TMP_InputField usernameInput;
public TMP_InputField passwordInput;
public TMP_InputField confirmPasswordInput;
public Button registerBtn;
public Button backBtn;
public TMP_Text tipText;
private bool initialized;
private void OnEnable()
{
if (initialized) return;
initialized = true;
registerBtn.onClick.AddListener(OnRegisterClick);
backBtn.onClick.AddListener(OnBackClick);
}
private void OnRegisterClick()
{
string username = usernameInput.text.Trim();
string password = passwordInput.text.Trim();
string confirmPassword = confirmPasswordInput.text.Trim();
if (string.IsNullOrEmpty(username))
{
ShowTip("请输入账号");
return;
}
if (username.Length < 4 || username.Length > 20)
{
ShowTip("账号长度需在4-20个字符之间");
return;
}
if (string.IsNullOrEmpty(password))
{
ShowTip("请输入密码");
return;
}
if (password.Length < 6)
{
ShowTip("密码长度不能少于6位");
return;
}
if (password != confirmPassword)
{
ShowTip("两次输入的密码不一致");
return;
}
// TODO: 接入实际注册接口
Debug.Log($"[SDK] 注册请求: 账号={username}");
ShowTip("注册中...");
// 模拟注册成功
Debug.Log("[SDK] 注册成功");
SDKUIManager.Instance.PopPanel();
}
private void OnBackClick()
{
SDKUIManager.Instance.PopPanel();
}
private void ShowTip(string msg)
{
tipText.text = msg;
}
public override void OnEnter()
{
base.OnEnter();
usernameInput.text = "";
passwordInput.text = "";
confirmPasswordInput.text = "";
tipText.text = "";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: B3IavSKrVC/xXwDniptviVynL8MsgiJ9ofPr3YVmz8oOpeVzqg2331A=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
using UnityEngine;
namespace GameSDK
{
/// <summary>
/// SDK 入口脚本,挂到游戏中的任意 GameObject 即可调用
/// 使用示例:
/// 1. 在场景中创建空物体,挂载此脚本
/// 2. 代码中调用 GameSDK.SDKInit.Show();
/// </summary>
public class SDKInit : MonoBehaviour
{
[Header("SDK 配置")]
[Tooltip("SDK 服务器地址(后续接入用)")]
public string serverUrl = "https://sdk.example.com/api";
/// <summary>
/// 显示 SDK 登录界面
/// </summary>
public static void Show()
{
if (SDKManager.Instance == null)
{
Debug.LogWarning("[SDK] SDKRoot 未初始化,请确认场景中包含 SDK UI 对象");
return;
}
SDKUIManager.Instance.ShowPanel<LoginPanel>();
}
/// <summary>
/// 关闭 SDK 所有界面
/// </summary>
public static void Hide()
{
if (SDKUIManager.Instance != null)
SDKUIManager.Instance.CloseAll();
}
/// <summary>
/// 获取当前登录用户名(未登录返回 null
/// </summary>
public static string GetCurrentUser()
{
return SDKManager.Instance != null ? SDKManager.Instance.CurrentUser : null;
}
/// <summary>
/// 是否已登录
/// </summary>
public static bool IsLoggedIn()
{
return SDKManager.Instance != null && SDKManager.Instance.IsLoggedIn;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: C3MZvSukVHizZ2Xlg61Vg0e80CArhJH0zTs2e4vcoPRa7HPCDrBnEYw=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,92 @@
using UnityEngine;
using UnityEngine.Events;
namespace GameSDK
{
/// <summary>
/// SDK 主入口,单例模式
/// </summary>
public class SDKManager : MonoBehaviour
{
public static SDKManager Instance { get; private set; }
public event UnityAction OnLoginSuccess;
public event UnityAction OnLogout;
public bool IsLoggedIn { get; private set; }
public string CurrentUser { get; private set; }
[Tooltip("是否在启动时自动弹出登录面板")]
public bool autoShowLogin = true;
private bool panelsRegistered = false;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
// 确保整个 SDK Canvas 不被销毁
var canvas = GetComponentInParent<Canvas>();
if (canvas != null)
DontDestroyOnLoad(canvas.gameObject);
}
private void Start()
{
// Start 在所有 Awake 之后执行,此时 SDKUIManager.Instance 一定已初始化
AutoRegisterPanels();
if (autoShowLogin && panelsRegistered)
{
SDKUIManager.Instance.ShowPanel<LoginPanel>();
}
}
/// <summary>
/// 自动发现 PanelContainer 下所有面板并注册
/// </summary>
private void AutoRegisterPanels()
{
var panelContainer = transform.Find("PanelContainer");
if (panelContainer == null)
{
Debug.LogError("[SDK] PanelContainer not found under SDKRoot!");
return;
}
for (int i = 0; i < panelContainer.childCount; i++)
{
var child = panelContainer.GetChild(i);
var panel = child.GetComponent<BasePanel>();
if (panel != null)
{
SDKUIManager.Instance.RegisterPanel(child.name, panel);
}
}
panelsRegistered = true;
}
public void LoginSuccess(string username)
{
IsLoggedIn = true;
CurrentUser = username;
Debug.Log($"[SDK] 用户登录成功: {username}");
OnLoginSuccess?.Invoke();
}
public void Logout()
{
IsLoggedIn = false;
CurrentUser = null;
Debug.Log("[SDK] 用户已退出登录");
OnLogout?.Invoke();
SDKUIManager.Instance.CloseAll();
SDKUIManager.Instance.ShowPanel<LoginPanel>();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: Cn9MtS2oWy+bFhfAOMM1oD+1reNzjDJRmxSCRfACvUKJSbwYqqBjfHs=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
using System.Collections.Generic;
using UnityEngine;
namespace GameSDK
{
/// <summary>
/// UI面板管理器负责面板的显示/隐藏切换
/// </summary>
public class SDKUIManager : MonoBehaviour
{
public static SDKUIManager Instance { get; private set; }
private Dictionary<string, BasePanel> panelDict = new Dictionary<string, BasePanel>();
private Stack<BasePanel> panelStack = new Stack<BasePanel>();
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
/// <summary>
/// 显示指定类型的面板,并压入栈(当前面板会暂停)
/// </summary>
public void ShowPanel<T>() where T : BasePanel
{
string panelName = typeof(T).Name;
if (panelDict.TryGetValue(panelName, out BasePanel panel))
{
// 暂停当前面板(不弹栈)
if (panelStack.Count > 0)
{
BasePanel topPanel = panelStack.Peek();
if (topPanel != panel) topPanel.OnPause();
}
// 显示新面板并压栈
panel.OnEnter();
panelStack.Push(panel);
}
else
{
Debug.LogError($"[SDK] 面板未注册: {panelName}");
}
}
/// <summary>
/// 关闭当前面板并返回上一个面板
/// </summary>
public void PopPanel()
{
if (panelStack.Count > 0)
{
BasePanel top = panelStack.Pop();
top.OnExit();
}
if (panelStack.Count > 0)
{
BasePanel peek = panelStack.Peek();
peek.OnResume();
}
}
/// <summary>
/// 注册面板
/// </summary>
public void RegisterPanel(string panelName, BasePanel panel)
{
if (!panelDict.ContainsKey(panelName))
{
panelDict.Add(panelName, panel);
// 注册时直接隐藏面板(面板此时 inactiveCanvasGroup 可能还不存在)
panel.gameObject.SetActive(false);
}
}
/// <summary>
/// 关闭所有面板并显示指定面板(清空栈后重新 Push
/// </summary>
public void ShowOnlyPanel<T>() where T : BasePanel
{
// 清空栈并关闭所有面板
while (panelStack.Count > 0)
{
BasePanel top = panelStack.Pop();
top.OnExit();
}
ShowPanel<T>();
}
/// <summary>
/// 关闭整个SDK UI
/// </summary>
public void CloseAll()
{
while (panelStack.Count > 0)
{
BasePanel top = panelStack.Pop();
top.OnExit();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: XHwb53ipVXOeCFvcXVvr99Q8CzMIfKRxt5q3gXGZGNAq9DoBpMwJrGg=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;
namespace GameSDK
{
/// <summary>
/// UI 视觉增强工具 - 为按钮添加高亮色、为 InputField 添加选中边框效果
/// 挂载到 SDKRoot 上即可自动生效
/// </summary>
public class SDKUIStyle : MonoBehaviour
{
[Header("颜色配置")]
public Color buttonHoverColor = new Color(0.9f, 0.95f, 1f, 1f);
public Color buttonPressedColor = new Color(0.7f, 0.8f, 0.9f, 1f);
public Color inputSelectedColor = new Color(0.3f, 0.7f, 1f, 1f);
public Color inputNormalColor = new Color(0.85f, 0.85f, 0.85f, 1f);
public float transitionDuration = 0.15f;
private void Start()
{
ApplyStyles();
}
private void ApplyStyles()
{
// 为所有按钮添加颜色过渡
var buttons = transform.GetComponentsInChildren<Button>(true);
foreach (var btn in buttons)
{
if (btn.colors.highlightedColor == Color.white && btn.colors.pressedColor == Color.white)
{
var colors = btn.colors;
colors.highlightedColor = buttonHoverColor;
colors.pressedColor = buttonPressedColor;
colors.fadeDuration = transitionDuration;
btn.colors = colors;
}
}
// 为所有 InputField 添加选中边框效果
var inputs = transform.GetComponentsInChildren<TMP_InputField>(true);
foreach (var input in inputs)
{
var img = input.targetGraphic as Image;
if (img != null)
{
img.color = inputNormalColor;
}
input.onSelect.AddListener((s) =>
{
if (input.targetGraphic is Image im)
im.color = inputSelectedColor;
});
input.onDeselect.AddListener((s) =>
{
if (input.targetGraphic is Image im)
im.color = inputNormalColor;
});
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DXoesimlU3PWXWWFMByKN5/o5J6iWJee2pJBNAiJHoGUTMhvguMxjkw=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: