Unity3D网络游戏实战|05 整体设计
服务端架构
总体框架
- 处理玩家数据
- 存储玩家数据
一个简单的服务器架构图如下
模块划分
- 网络层:连接客户端,解析协议
- 消息层:属于游戏逻辑层,不同的消息肯定会有不同的逻辑处理
- 数据层:访问数据库进行数据存储
JSON的编码和解码
服务端有两个地方会涉及编码和解码,一是处理消息时,和客户端一样需要编码和解码,二是在玩家存储数据时,还需要编码和解码。设计一个名为PlayerData
的数据,里面包含需要存储的信息,如金币等级等,不会直接操作数据库,而是访问这个数据类,在必要的时候直接把这个数据类存储到数据库中
添加协议文件
将客户端的部分脚本直接移植到服务端
区别在于服务度不能使用客户端的Json编码器,因为那是Unity提供的工具,服务端肯定是不能直接用的(除非你把DLL拷贝过来)
引用System.web.Extensions
使用.NET提供的JavaScriptJsonSerializer来序列化Json,需要添加DLL
修改MsgBase
将编码和解码都替换为新的DLL提供的方法
namespace Framework
{
public abstract class MsgBase
{
// 用于序列化、反序列JSON对象
private JavaScriptSerializer serializer = new JavaScriptSerializer();
/// <summary>
/// 编码
/// </summary>
/// <param name="msgBase">协议</param>
/// <returns></returns>
public static byte[] Encode(MsgBase msgBase)
{
var str = serializer.Serialize(msgBase);
return System.Text.Encoding.UTF8.GetBytes(str);
}
/// <summary>
/// 解码
/// </summary>
/// <param name="protoName">协议名称</param>
/// <param name="bytes">协议数据</param>
/// <param name="offset">偏移</param>
/// <param name="count">长度</param>
/// <returns></returns>
public static MsgBase Decode(string protoName, byte[] bytes, int offset, int count)
{
var str = System.Text.Encoding.UTF8.GetString(bytes, offset, count);
return (MsgBase) serializer.Deserialize(str, Type.GetType($"Framework.{protoName},Assembly-CSharp"));
}
}
}
网络模块
ClientState
和每一个客户端都需要维护一个Socket连接
namespace Framework
{
public class ClientState
{
public Socket socket;
public ByteArray readBuff = new ByteArray();
}
}
开启监听和多路复用
一个用于监听的listenfd
一个管理客户端状态的列表clients
一个用于多路复用的checkRead
namespace Framework
{
public class NetManager
{
//监听Socket
public static Socket listenfd;
//客户端Socket及状态信息
public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
//Select的检查列表
static List<Socket> checkRead = new List<Socket>();
}
}
开启服务器的接口如下
namespace Framework
{
public class NetManager
{
/// <summary>
/// 开启服务器
/// </summary>
/// <param name="listenPort"></param>
public static void StartLoop(string ip,int listenPort)
{
// Socket
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 绑定IP
IPAddress ipAdr = IPAddress.Parse(ip);
IPEndPoint ipEp = new IPEndPoint(ipAdr, listenPort);
listenfd.Bind(ipEp);
// 开始监听
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
//循环
while (true)
{
ResetCheckRead(); //重置checkRead
Socket.Select(checkRead, null, null, 1000);
//检查可读对象
for (int i = checkRead.Count - 1; i >= 0; i--)
{
Socket s = checkRead[i];
if (s == listenfd)
{
// 处理服务器消息
}
else
{
// 不理客户端你消息
}
}
// 超时处理
}
}
/// <summary>
/// 重置并填充多路复用列表
/// </summary>
private static void ResetCheckRead()
{
checkRead.Clear();
checkRead.Add(listenfd);
foreach (var s in clients.Values)
checkRead.Add(s.socket);
}
}
}
处理监听消息
也就是连接请求
namespace Framework
{
public class NetManager
{
/// <summary>
/// 处理监听消息(客户端请求连接)
/// </summary>
/// <param name="listenfd"></param>
public static void ReadListenfd(Socket listenfd)
{
try
{
Socket clientfd = listenfd.Accept();
Console.WriteLine($"[客户端连接]{clientfd.RemoteEndPoint}");
ClientState state = new ClientState {socket = clientfd};
clients.Add(clientfd, state);
}
catch (SocketException ex)
{
Console.WriteLine("Accept fail" + ex.ToString());
}
}
}
}
处理客户端消息
如果缓冲区剩余空间不够那么会先尝试释放
然后开始正式接受数据
namespace Framework
{
public class NetManager
{
/// <summary>
/// 处理客户端消息
/// </summary>
/// <param name="clientfd"></param>
public static void ReadClientfd(Socket clientfd)
{
var state = clients[clientfd];
var readBuff = state.readBuff;
// 接收
// 缓冲区不够,清除,若依旧不够,只能返回
// 缓冲区长度只有1024,单条协议超过缓冲区长度时会发生错误,根据需要调整长度
int count = 0;
if (readBuff.Remain <= 0)
{
readBuff.MoveBytes();
if (readBuff.Remain <= 0)
{
Console.WriteLine("接受数据错误,消息长度超过最大上限");
Close(state);
return;
}
}
try
{
count = clientfd.Receive(readBuff.Bytes, readBuff.WriteIndex, readBuff.Remain, 0);
}
catch (SocketException ex)
{
Console.WriteLine("Receive SocketException " + ex.ToString());
Close(state);
return;
}
// 客户端关闭
if (count <= 0)
{
Console.WriteLine("Socket Close " + clientfd.RemoteEndPoint.ToString());
Close(state);
return;
}
// 消息处理
readBuff.WriteIndex += count;
//处理二进制消息
OnReceiveData(state);
//移动缓冲区
readBuff.CheckAndMoveBytes();
}
}
}
关闭连接
利用反射触发消息,然后关闭客户端连接
/// <summary>
/// 关闭连接
/// </summary>
/// <param name="state"></param>
public static void Close(ClientState state)
{
// 事件分发
MethodInfo method = typeof(EventHandler).GetMethod("OnDisconnect");
object[] ob = {state};
method.Invoke(null, ob);
// 关闭
state.socket.Close();
clients.Remove(state.socket);
}
处理协议
总体和客户端类似
namespace Framework
{
public class NetManager
{
/// <summary>
/// 数据处理
/// </summary>
/// <param name="state"></param>
public static void OnReceiveData(ClientState state)
{
ByteArray readBuffer = state.readBuff;
// 消息长度
if (readBuffer.Length <= 2)
return;
// 获取消息体长度
int readIdx = readBuffer.ReadIndex;
byte[] bytes = readBuffer.Bytes;
Int16 bodyLength = (Int16) ((bytes[readIdx + 1] << 8) | bytes[readIdx]);
if (readBuffer.Length < bodyLength)
return;
readBuffer.ReadIndex += 2;
// 解析协议名
int nameCount = 0;
string protoName = MsgBase.DecodeName(readBuffer.Bytes, readBuffer.ReadIndex, out nameCount);
if (protoName == "")
{
Console.WriteLine("OnReceiveData MsgBase.DecodeNamefail");
Close(state);
}
readBuffer.ReadIndex += nameCount;
// 解析协议体
int bodyCount = bodyLength - nameCount;
MsgBase msgBase = MsgBase.Decode(protoName, readBuffer.Bytes, readBuffer.ReadIndex, bodyCount);
readBuffer.ReadIndex += bodyCount;
readBuffer.CheckAndMoveBytes();
// 分发消息
MethodInfo method = typeof(SysMsgHandler).GetMethod(protoName);
object[] o = {state, msgBase};
Console.WriteLine("[Receive]" + protoName);
if (method != null)
method.Invoke(null, o);
else
Console.WriteLine("OnReceiveData Invoke fail " + protoName);
// 继续读取消息
if (readBuffer.Length > 2)
OnReceiveData(state);
}
}
}
Timer
定时器方法
/// <summary>
/// 定时器
/// </summary>
public static void Timer()
{
// 消息分发
MethodInfo mei = typeof(EventHandler).GetMethod("OnTimer");
object[] ob = {};
mei.Invoke(null, ob);
}
发送协议
namespace Framework
{
public class NetManager
{
/// <summary>
/// 发送数据
/// </summary>
/// <param name="cs"></param>
/// <param name="msg"></param>
public static void Send(ClientState cs, MsgBase msg)
{
// 状态判断
if (cs == null)
return;
if (!cs.socket.Connected)
return;
// 数据编码
byte[] nameBytes = MsgBase.EncodeName(msg);
byte[] bodyBytes = MsgBase.Encode(msg);
int len = nameBytes.Length + bodyBytes.Length;
byte[] sendBytes = new byte[2 + len];
// 组装长度
sendBytes[0] = (byte) (len % 256);
sendBytes[1] = (byte) (len / 256);
// 组装名字
Array.Copy(nameBytes, 0, sendBytes, 2, nameBytes.Length);
// 组装消息体
Array.Copy(bodyBytes, 0, sendBytes, 2 + nameBytes.Length, bodyBytes.Length);
// 为简化代码,不设置回调
try
{
cs.socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, null, null);
}
catch (SocketException ex)
{
Console.WriteLine("Socket Close on BeginSend" + ex.ToString());
}
}
}
}
测试
添加功能协议
public partial class MsgHandler
{
public static void MsgMove(ClientState c, MsgBase msgBase)
{
var msg = msgBase as MsgMove;
msg.x++;
NetManager.Send(c, msg);
}
public static void MsgPing(ClientState c, MsgBase msgBase)
{
Console.WriteLine("MsgPing");
}
}
添加事件协议
public static void OnDisconnect(ClientState c)
{
Console.WriteLine("Event Close");
}
客户端监听
void Start()
{
NetManager.AddMsgListener("MsgMove",(msg =>
{
var move = msg as MsgMove;
Debug.Log($"Move {move.x} {move.y} {move.z}");
}));
connect.onClick.AddListener((() =>
{
NetManager.Connect("127.0.0.1", 8765);
}));
move.onClick.AddListener((() =>
{
var msg = new MsgMove();
msg.x = 1;
msg.y = 2;
msg.z = 3;
NetManager.Send(msg);
}));
}
客户端结果:
服务端结果:
心跳机制
在ClientState中定义一个字段用于记录心跳时间
namespace Framework
{
public class ClientState
{
public long lastPingTime = 0;
}
}
在NetManager中设置好服务器接受PING的间隔,最好和客户端一致
namespace Framework
{
public static class NetManager
{
private static long pingInterval = 30;
}
}
时间戳
1970年1月1日至今的秒数
namespace Framework
{
public static class NetManager
{
/// <summary>
/// 获取时间戳
/// </summary>
/// <returns></returns>
public static long GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds);
}
}
}
然后在客户端建立连接时,刷新客户端的时间戳
namespace Framework
{
public static class NetManager
{
/// <summary>
/// 处理监听消息(客户端请求连接)
/// </summary>
/// <param name="listenfd"></param>
public static void ReadListenfd(Socket listenfd)
{
...
ClientState state = new ClientState {socket = clientfd,lastPingTime = GetTimeStamp()};
...
}
}
}
回应PING协议
namespace Framework
{
public partial class MsgHandler
{
public static void MsgPing(ClientState c, MsgBase msgBase)
{
Console.WriteLine("MsgPing");
c.lastPingTime = NetManager.GetTimeStamp();
MsgPong msgPong = new MsgPong();
NetManager.Send(c, msgPong);
}
}
}
超时处理
当服务器很久没有收到消息时,可以认为已经断开连接了,在服务器的定时事件OnTimer中编写心跳处理的机制
不能在遍历的同时删除,所以每次只断开一个连接就return了
namespace Framework
{
public partial class MsgHandler
{
public static void OnTimer()
{
CheckPing();
}
/// <summary>
/// Ping检查
/// </summary>
public static void CheckPing()
{
// 现在的时间戳
long timeNow = NetManager.GetTimeStamp();
// 遍历,删除
foreach (ClientState s in NetManager.clients.Values)
{
if (timeNow - s.lastPingTime > NetManager.pingInterval * 4)
{
Console.WriteLine("Ping Close " + s.socket.RemoteEndPoint.ToString());
NetManager.Close(s);
return;
}
}
}
}
}
测试
让服务器的Interval设置为2,客户端的Interval设置为比较大的一个值比如30
玩家数据结构
ClientState
目前只包含了服务器所需要的一些数据,还需要添加玩家数据
PlayerManager
管理所有Player数据的一个管理器
namespace Framework
{
public class PlayerManager
{
// 玩家列表
public static Dictionary<string, Player> players = new Dictionary<string, Player>();
/// <summary>
/// 玩家是否在线
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool IsOnline(string id)
{
return players.ContainsKey(id);
}
/// <summary>
/// 玩家列表
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static Player GetPlayer(string id)
{
if (players.ContainsKey(id))
return players[id];
return null;
}
/// <summary>
/// 添加玩家
/// </summary>
/// <param name="id"></param>
/// <param name="player"></param>
public static void AddPlayer(string id, Player player)
{
players.Add(id, player);
}
/// <summary>
/// 移除玩家
/// </summary>
/// <param name="id"></param>
public static void RemovePlayer(string id)
{
players.Remove(id);
}
}
}
NetManager存储所有客户端状态
PlayerManager存储所有玩家数据
MySql
略(记得装个Navicat可视化)
数据库模块
连接数据库
需要添加引用MySql.Data.dll
namespace Framework
{
public class DbManager
{
public static MySqlConnection mysql;
//连接mysql数据库
public static bool Connect(string db, string ip, int port, string user, string pw)
{
// 创建MySqlConnection对象
mysql = new MySqlConnection();
// 连接参数
mysql.ConnectionString = $"Database={db};Data Source = {ip};port = {port};User Id = {user};Password = {pw}";
// 连接
try
{
mysql.Open();
Console.WriteLine("[数据库]connect success");
return true;
}
catch (Exception e)
{
Console.WriteLine("[数据库]connect fail, " + e.Message);
return false;
}
}
}
}
用Navicat新建一个连接
再新建一个数据库
然后可以测试一下了
namespace Server
{
class Program
{
private const string DB = "playerdata";
private const string DB_IP = "127.0.0.1";
private const int DB_PORT = 3306;
private const string DB_USER = "root";
private const string DB_PW = "415753";
static void Main(string[] args)
{
DbManager.Connect(DB, DB_IP, DB_PORT, DB_USER, DB_PW);
}
}
}
防止依赖注入
简单来说就是防止玩家名称和SQL语句一致
加一个函数进行合法性检查
namespace Framework
{
public class DbManager
{
/// <summary>
/// 合法性检查
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsSafeString(string str)
{
return !Regex.IsMatch(str, @"[-|;|,|\/|\(|\)|\[|\]|\}|\{|%|@|\*|!|\']");
}
}
}
IsAccountExist
账号是否存在的判断
namespace Framework
{
public class DbManager
{
/// <summary>
/// 账号是否存在
/// </summary>
/// <param name="id">张海ID</param>
/// <returns></returns>
public static bool IsAccountExist(string id)
{
// 防SQL注入
if (!IsSafeString(id))
return false;
// SQL语句
string s = $"select * from account where account = '{id}';";
// 查询
try
{
var cmd = new MySqlCommand(s, mysql);
var dataReader = cmd.ExecuteReader();
var hasRows = dataReader.HasRows;
dataReader.Close();
return !hasRows;
}
catch (Exception e)
{
Console.WriteLine("[数据库]IsSafeString err, " + e.Message);
return false;
}
}
}
}
账号注册
namespace Framework
{
public class DbManager
{
/// <summary>
/// 注册账号
/// </summary>
/// <param name="account"></param>
/// <param name="pw"></param>
/// <returns></returns>
public static bool Register(string account, string pw)
{
// 防SQL注入
if (!IsSafeString(account))
{
Console.WriteLine("[数据库]Register fail, id not safe");
return false;
}
if (!IsSafeString(pw))
{
Console.WriteLine("[数据库]Register fail, pw not safe");
return false;
}
// 能否注册
if (!IsAccountExist(account))
{
Console.WriteLine("[数据库]Register fail, id exist");
return false;
}
// 写入数据库User表
string sql = $"insert into account set account= '{account}',password= '{pw}'";
try
{
MySqlCommand cmd = new MySqlCommand(sql, mysql);
cmd.ExecuteNonQuery();
return true;
}
catch (Exception e)
{
Console.WriteLine("[数据库]Register fail " + e.Message);
return false;
}
}
}
}
用Navicat新建一张表
测试一下
namespace Server
{
class Program
{
private const string DB = "playerdata";
private const string DB_IP = "127.0.0.1";
private const int DB_PORT = 3306;
private const string DB_USER = "root";
private const string DB_PW = "415753";
static void Main(string[] args)
{
if (DbManager.Connect(DB, DB_IP, DB_PORT, DB_USER, DB_PW))
{
DbManager.Register("415753928", "415753");
}
}
}
}
CheckPassword
登录时服务端程序需要检测玩家输入的用户名和密码是否正确
namespace Framework
{
public partial class DbManager
{
/// <summary>
/// 账号验证
/// </summary>
/// <param name="account"></param>
/// <param name="pw"></param>
/// <returns></returns>
public static bool CheckPassword(string account, string pw)
{
if (!IsSafeString(account))
{
Console.WriteLine("[数据库]CheckPassword Err,非法账号");
return false;
}
if (!IsSafeString(pw))
{
Console.WriteLine("[数据库]CheckPassword Err,非法密码");
return false;
}
// 查询
var sql = $"select * from account where account = '{account}' and pw = '{pw}';";
try
{
var cmd = new MySqlCommand(sql, mysql);
var dataReader = cmd.ExecuteReader();
bool hasRows = dataReader.HasRows;
dataReader.Close();
return hasRows;
}
catch (Exception e)
{
Console.WriteLine("[数据库]CheckPassword Err:" + e.Message);
return false;
}
}
}
}
记事本
一个简单的记事本程序,拥有注册、登录、在线记事本,三个功能
- MsgRegister 注册协议
- MsgLogin 登录协议
- MsgGetText 获取文本协议
- MsgSaveText 修改文本协议
登录注册协议
namespace Framework
{
/// <summary>
/// 注册协议
/// </summary>
public class MsgRegister : MsgBase
{
public override string protoName => "MsgRegister";
// 结果
public int result;
// 账号
public string account;
// 密码
public string password;
}
/// <summary>
/// 登录协议
/// </summary>
public class MsgLogin : MsgBase
{
public override string protoName => "MsgLogin";
// 结果
public int result = 0;
// 账号
public string account = "";
// 密码
public string password = "";
}
/// <summary>
/// 强制下线
/// </summary>
public class MsgKick : MsgBase
{
public override string protoName => "MsgKick";
// 结果
public int reason = 0;
}
}
记事本协议
namespace Framework
{
/// <summary>
/// 获取记事本内容
/// </summary>
public class MsgGetText : MsgBase
{
public override string protoName => "MsgGetText";
public int result;
// 服务端回
public string text = "";
// 账号
public string account;
}
/// <summary>
/// 保存记事本内容
/// </summary>
public class MsgSaveText : MsgBase
{
public override string protoName => "MsgSaveText";
// 结果
public int result = 0;
// 账号
public string account;
// 客户端发
public string text = "";
}
}
注册功能
namespace Framework
{
public partial class MsgHandler
{
public static void MsgRegister(ClientState c, MsgBase msgBase)
{
var msg = msgBase as MsgRegister;
if (msg == null)
return;
// 注册成功
msg.result = DbManager.Register(msg.account, msg.password) ? 1 : 0;
NetManager.Send(c, msg);
}
}
}
登录功能
namespace Framework
{
public partial class MsgHandler
{
public static void MsgLogin(ClientState c, MsgBase msgBase)
{
var msg = msgBase as MsgLogin;
if (msg == null)
return;
// 密码校验
if (!DbManager.CheckPassword(msg.account, msg.password))
{
msg.result = 1;
NetManager.Send(c, msg);
return;
}
// 不允许再次登录
if (c.isLogin)
{
msg.result = 2;
NetManager.Send(c, msg);
return;
}
//返回协议
msg.result = 0;
NetManager.Send(c, msg);
}
}
}
获取文本功能
新建一张表
namespace Framework
{
public partial class MsgHandler
{
public static void MsgGetText(ClientState c, MsgBase msgBase)
{
var msg = (MsgGetText) msgBase;
if (msg == null)
return;
if (!c.isLogin)
return;
// 获取text
DbManager.GetNotePad(msg);
NetManager.Send(c, msg);
}
}
}
数据库的对应方法
using System;
using MySql.Data.MySqlClient;
namespace Framework
{
public partial class DbManager
{
public static void GetNotePad(MsgGetText msg)
{
msg.text = "";
// 防SQL注入
if (!IsSafeString(msg.account))
{
msg.result = 1;
return;
}
// 查询
try
{
var cmd = mysql.CreateCommand();
cmd.CommandText = $"select * from notepad where account = '{msg.account}';";
using (var read = cmd.ExecuteReader())
{
if (!read.HasRows)
{
msg.result = 2;
}
else
{
read.Read();
msg.result = 0;
msg.text = read.GetString("text");
}
}
}
catch (Exception e)
{
Console.WriteLine("[数据库]语法错误:" + e.Message);
}
}
}
}
保存文本功能
namespace Framework
{
public partial class MsgHandler
{
public static void MsgSaveText(ClientState c, MsgBase msgBase)
{
MsgSaveText msg = (MsgSaveText)msgBase;
if(msg == null)
return;
// 获取text
DbManager.SaveNotePad(msg);
NetManager.Send(c, msg);
}
}
}
数据库对应操作
using System;
using MySql.Data.MySqlClient;
namespace Framework
{
public partial class DbManager
{
public static void SaveNotePad(MsgSaveText msg)
{
// 防SQL注入
if (!IsSafeString(msg.account))
{
msg.result = 1;
return;
}
// 查询
try
{
var cmd = new MySqlCommand($"select * from notepad where account = '{msg.account}';", mysql);
using (var read = cmd.ExecuteReader())
{
// 不存在那么新增
if (!read.HasRows)
{
var add =
new MySqlCommand($"insert into notepad set account = '{msg.account}',text ='{msg.text}';", mysql);
add.ExecuteNonQuery();
}
// 存在那么修改
else
{
var update =
new MySqlCommand($"update notepad set text = '{msg.text}' where account = '{msg.account}';", mysql);
update.ExecuteNonQuery();
}
}
}
catch (Exception e)
{
Console.WriteLine("[数据库]语法错误:" + e.Message);
}
}
}
}
测试一下
基本框架如下
public class Test : MonoBehaviour
{
public InputField account;
public InputField password;
public Button login;
public Button register;
public InputField text;
public Button get;
public Button save;
void Start()
{
NetManager.Connect("127.0.0.1", 8765);
}
private void Update()
{
NetManager.Update();
}
}
登录
public class Test : MonoBehaviour
{
void Start()
{
...
login.onClick.AddListener((() =>
{
if (string.IsNullOrEmpty(account.text) || string.IsNullOrEmpty(password.text))
return;
var msg = new MsgLogin() {account = account.text, password = password.text};
NetManager.Send(msg);
}));
NetManager.AddMsgListener("MsgLogin",(msgBase =>
{
var msg = msgBase as MsgLogin;
if (msg == null)
return;
switch (msg.result)
{
case 0:
accountText = msg.account;
break;
case 1:
Debug.Log("账号或密码错误");
break;
case 2:
Debug.Log("不允许重复登录");
break;
}
}));
}
}
注册
public class Test : MonoBehaviour
{
void Start()
{
...
register.onClick.AddListener((() =>
{
if (string.IsNullOrEmpty(account.text) || string.IsNullOrEmpty(password.text))
return;
var msg = new MsgRegister() {account = account.text, password = password.text};
NetManager.Send(msg);
}));
NetManager.AddMsgListener("MsgRegister",(msgBase =>
{
var msg = msgBase as MsgRegister;
switch (msg.result)
{
case 1:
Debug.Log("注册失败");
break;
}
}));
}
}
获取
public class Test : MonoBehaviour
{
void Start()
{
...
get.onClick.AddListener((() =>
{
var msg = new MsgGetText() {account = accountText};
NetManager.Send(msg);
}));
NetManager.AddMsgListener("MsgGetText", (msgBase) =>
{
var msg = msgBase as MsgGetText;
switch (msg.result)
{
case 0:
text.text = msg.text;
break;
case 1:
Debug.Log("账号非法");
break;
case 2:
Debug.Log("没有数据");
break;
case 3:
Debug.Log("没有登录");
break;
}
});
}
}
保存
public class Test : MonoBehaviour
{
void Start()
{
...
save.onClick.AddListener((() =>
{
if (string.IsNullOrEmpty(text.text))
return;
var msg = new MsgSaveText() {account = accountText,text = text.text};
NetManager.Send(msg);
}));
NetManager.AddMsgListener("MsgSaveText", (msgBase) =>
{
var msg = msgBase as MsgSaveText;
switch (msg.result)
{
case 1:
Debug.Log("非法账号");
break;
case 3:
Debug.Log("没有登录");
break;
}
});
}
}