目标
- 玩家进入游戏后自动登录
如果本地存有账号,则使用本地账号登录;如果本地不存在账号,则创建1个账号并登录,自动设置1个默认的玩家昵称。 - 玩家可在游戏中切换登录账号
将一个合法账号复制到相应输入框中 - 玩家可在游戏中修改名称
- 玩家可在任意处(PC、移动端等)打开游戏,使用同一账号登录。
下面是实现了上述目标后的效果示意:

实现思路
实现LoginSys,提供如下功能
- 在游戏开始时自动登录
- 输入账号重新登录
- 设置玩家昵称
- 本地保存账号
实现IdSys,提供如下功能
- 随机生成1个账号
账号 = x位字母+y位数字+z位校验位
目标是尽量生成不重复的账号(但是实际还是有可能重复的) - 判断给定的账号是否是有效账号
相关代码
LoginSys.cs 登录相关
using System.IO;
using System;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using PlayFab.ClientModels;
public class LoginSys : MonoBehaviour
{
private string _loginUserId;
public string UserId { get { return _loginUserId; } }
[SerializeField]
private Text _userNickNameText;
public string UserName { get { return _userNickNameText.text; } }
public string CustomId
{
get
{
if (!PlayerPrefs.HasKey(nameof(CustomId)))
{
PlayerPrefs.SetString(nameof(CustomId), CreateCustomId());
}
return PlayerPrefs.GetString(nameof(CustomId));
}
private set
{
PlayerPrefs.SetString(nameof(CustomId), value);
}
}
public event Action<string> RefreshPlayerNickNameEvent;
private IdSys _idSys;
// 生成玩家Id
private string CreateCustomId()
{
var id = _idSys.GenerateId();
return id;
}
private void Start()
{
Init();
}
private void Update()
{
MyUpdate();
}
private void OnDestroy()
{
MyDestroy();
}
private void Init()
{
_idSys = new IdSys();
Login();
}
private void MyUpdate()
{
if (Input.GetKeyDown(KeyCode.Space))
{
PlayerPrefs.DeleteKey(nameof(CustomId));
Debug.Log("清除账号信息!");
}
}
private void MyDestroy()
{
_idSys?.MyDestroy();
_idSys = null;
}
private void Login()
{
var request = new LoginWithCustomIDRequest { CustomId = CustomId, CreateAccount = true };
PlayFab.PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure);
}
private void OnLoginSuccess(LoginResult result)
{
Debug.Log("登录成功:" + CustomId);
_loginUserId = result.PlayFabId;
// 设置玩家昵称
// 如果是新账号,需创建玩家昵称。
if (result.NewlyCreated)
{
SetNickName(CreateDefaultNickName());
Debug.Log("新账号,创建玩家昵称");
}
else
{
GetNickName(RefreshNickName);
Debug.Log("老账号,获取玩家昵称");
}
}
private void OnLoginFailure(PlayFab.PlayFabError error)
{
Debug.LogError(error.GenerateErrorReport());
}
// 生成默认玩家昵称
private string CreateDefaultNickName()
{
int digitCnt = 5;
return "Player_" + _loginUserId.Substring(_loginUserId.Length - digitCnt, digitCnt);
}
// 设置玩家昵称
private void SetNickName(string nickName, Action successfulAction = null, Action failAction = null)
{
PlayFab.PlayFabClientAPI.UpdateUserTitleDisplayName(new UpdateUserTitleDisplayNameRequest
{
DisplayName = nickName
},
result =>
{
RefreshNickName(nickName);
successfulAction?.Invoke();
Debug.Log($"设置玩家昵称成功!{result.DisplayName}");
},
error =>
{
failAction?.Invoke();
Debug.LogError($"设置玩家昵称失败!{ error.GenerateErrorReport()}");
}
);
}
private void GetNickName(Action<string> afterGetAction)
{
string nickName = string.Empty;
PlayFab.PlayFabClientAPI.GetPlayerProfile(new GetPlayerProfileRequest(),
result =>
{
nickName = result.PlayerProfile.DisplayName;
Debug.Log($"获取玩家昵称成功!{nickName}");
afterGetAction?.Invoke(nickName);
},
error =>
{
Debug.LogError($"获取玩家昵称失败!{error.GenerateErrorReport()}");
}
);
}
private void RefreshNickName(string nickName)
{
RefreshPlayerNickNameEvent?.Invoke(nickName);
_userNickNameText.text = nickName;
}
// 验证是否是合法的CustomId
private bool IsValidCustomId(string id)
{
return _idSys.IsValidId(id);
}
// 尝试重新登录(通过输入账号)
public bool TryRelogin(string anotherCustomId)
{
bool ret = false;
if (IsValidCustomId(anotherCustomId))
{
CustomId = anotherCustomId;
Login();
ret = true;
}
return ret;
}
public void ChangeNickName(string newNickName, Action successfulAction, Action failAction)
{
SetNickName(newNickName, successfulAction, failAction);
}
}
IdSys.cs 账号相关
using System.Text;
using System.Data;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IdSys
{
private Char[] _letterArray;
private Char[] _numArray;
// 有效Id的结构:x个小写字母+y个数字+z个加校验数字
private const int LetterCnt = 5;
private const int NumCnt = 3;
private const int CheckNumCnt = 2;
public IdSys()
{
var ids = new List<Char>();
for (char i = 'a'; i <= 'z'; i++)
{
ids.Add(i);
}
_letterArray = ids.ToArray();
_numArray = new Char[10];
for (int i = 0; i < _numArray.Length; i++)
{
_numArray[i] = char.Parse(i.ToString());
}
}
public void MyDestroy()
{
_letterArray = null;
_numArray = null;
}
private string CharArrayToString(Char[] chars)
{
StringBuilder sb = new StringBuilder();
foreach (var c in chars)
{
sb.Append(c.ToString());
}
return sb.ToString();
}
// validStr:只包含小写字母和数字
private bool TryToValidStr(string str, out string validStr)
{
str = str.ToLower();
bool ret = true;
validStr = string.Empty;
StringBuilder tempSb = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
var s = str[i];
if (IsValidChar(s))
{
tempSb.Append(s);
}
else
{
ret = false;
break;
}
}
if (ret)
{
validStr = tempSb.ToString();
}
return ret;
}
// validChar:是小写字母或数字
private bool IsValidChar(Char c)
{
return IsLetter(c) || IsNum(c);
}
private bool IsLetter(Char c)
{
return Array.IndexOf(_letterArray, c) >= 0;
}
private bool IsNum(Char c)
{
return Array.IndexOf(_numArray, c) >= 0;
}
private bool IsLetterStr(string str)
{
return IsTargetTypeStr(str, IsLetter);
}
private bool IsNumStr(string str)
{
return IsTargetTypeStr(str, IsNum);
}
private bool IsTargetTypeStr(string str, Func<Char, bool> checkFunc)
{
bool ret = true;
foreach (var s in str)
{
if (!checkFunc(s))
{
ret = false;
break;
}
}
return ret;
}
private int CharToNum(Char c)
{
var index = 0;
if (IsLetter(c))
{
index = Array.IndexOf(_letterArray, c);
}
else if (IsNum(c))
{
index = Array.IndexOf(_numArray, c);
}
return index;
}
// 验证是否是合法Id
public bool IsValidId(string idStr)
{
var isValid = true;
if (idStr.Length != LetterCnt + NumCnt + CheckNumCnt)
{
isValid = false;
}
else if (!TryToValidStr(idStr, out string newIdStr))
{
isValid = false;
}
else
{
var letterStr = newIdStr.Substring(0, LetterCnt);
var numStr = newIdStr.Substring(LetterCnt, NumCnt);
var checkNumStr = newIdStr.Substring(LetterCnt + NumCnt, CheckNumCnt);
isValid = IsLetterStr(letterStr)
&& IsNumStr(numStr)
&& checkNumStr == GetCheckNumStr(letterStr + numStr, CheckNumCnt);
}
return isValid;
}
// 生成1个Id(几位字母、几位数字、几位校验)
public string GenerateId()
{
var sb = new StringBuilder();
for (int i = 0; i < LetterCnt; i++)
{
sb.Append(_letterArray[UnityEngine.Random.Range(0, _letterArray.Length)]);
}
for (int i = 0; i < NumCnt; i++)
{
sb.Append(_numArray[UnityEngine.Random.Range(0, _numArray.Length)]);
}
var checkNumStr = GetCheckNumStr(sb.ToString(), CheckNumCnt);
sb.Append(checkNumStr);
return sb.ToString();
}
// 获取n位校验数字
private string GetCheckNumStr(string str, int cnt)
{
var checkNumStr = string.Empty;
if (TryToValidStr(str, out string validStr))
{
int num = 0;
for (int i = 0; i < validStr.Length; i++)
{
num += CharToNum(validStr[i]) * i * 10;
}
checkNumStr = num.ToString();
if (checkNumStr.Length < cnt)
{
checkNumStr = checkNumStr.PadLeft(cnt, '0');
}
else if (checkNumStr.Length > cnt)
{
checkNumStr = checkNumStr.Substring(checkNumStr.Length - cnt);
}
}
return checkNumStr;
}
}
PlayerInfoWnd.cs 测试用:显示和修改昵称、显示和输入(以便重新登录)账号
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
// 玩家信息窗口:显示玩家账号和昵称,可修改玩家账号和昵称。
public class PlayerInfoWnd : MonoBehaviour
{
[SerializeField]
private LoginSys _loginSys;
[SerializeField]
private GameObject _wndGo;
[SerializeField]
private InputField _customIdInputField;
[SerializeField]
private InputField _nickNameInputField;
[SerializeField]
private Button _closeBtn;
[SerializeField]
private GameObject _successTips;
[SerializeField]
private GameObject _failTips;
private string _curName;
private void Start()
{
Init();
}
private void OnDestroy()
{
MyDestroy();
}
private void Init()
{
Close();
_loginSys.RefreshPlayerNickNameEvent += OnRefreshPlayerNickName;
_customIdInputField.onEndEdit.AddListener(OnEndEditCustomId);
_nickNameInputField.onEndEdit.AddListener(OnEndEditNickName);
_closeBtn.onClick.AddListener(OnClickCloseBtn);
}
private void MyDestroy()
{
if (_loginSys != null)
{
_loginSys.RefreshPlayerNickNameEvent -= OnRefreshPlayerNickName;
}
_customIdInputField?.onEndEdit.RemoveListener(OnEndEditCustomId);
_nickNameInputField?.onEndEdit.RemoveListener(OnEndEditNickName);
_closeBtn?.onClick.RemoveListener(OnClickCloseBtn);
}
public void Open()
{
_customIdInputField.SetTextWithoutNotify(_loginSys.CustomId);
_wndGo.SetActive(true);
}
private void Close()
{
HideAllTips();
_wndGo.SetActive(false);
}
private void OnClickCloseBtn()
{
Close();
}
private void OnEndEditCustomId(string id)
{
if (id != _loginSys.CustomId)
{
bool isSuccess = _loginSys.TryRelogin(id);
ShowTips(isSuccess);
if (!isSuccess)
{
_customIdInputField.SetTextWithoutNotify(_loginSys.CustomId);
}
}
}
private void OnEndEditNickName(string name)
{
_loginSys.ChangeNickName(name, () =>
{
ShowTips(true);
},
() =>
{
ShowTips(false);
_nickNameInputField.SetTextWithoutNotify(_loginSys.UserName);
});
}
private void OnRefreshPlayerNickName(string name)
{
_nickNameInputField.SetTextWithoutNotify(name);
}
private void HideAllTips()
{
_successTips.SetActive(false);
_failTips.SetActive(false);
}
private void ShowTips(bool isSuccess)
{
var go = isSuccess ? _successTips : _failTips;
go.SetActive(true);
MonoSys.Instance.DelayCall(2f, () => go.SetActive(false));
}
}
其他说明
-
Unity打包Webgl会出现移动端点击InputField无法唤起输入法窗口的情况,需通过以下方式解决:将WebGLInput组件添加到InputField所在的GameObject上。
kou-yeung/WebGLInput: IME for Unity WebGL -
PlayFab的UnityPackage的下载链接无法打开,需通过以下方式解决
国内访问raw.github…被拒绝的解决方法
网友评论