最近海涛大神分享了一个用于Unity的游戏服务器RPGDemoServer,基于.net开发.目前开始记录学习.
文章目录
总体框架
服务器端
客户端
此框架比较庞大,应用于rpg项目基本业务是足够了.
服务器通讯部分主要是上面Net包,里面有注册,登陆,背包,好友系统,副本,技能,组队等模块.
它是以线程驱动NGUI的形式进行的,新版本的unity默认是无法运行的.
但是根据我的需求,目前精简到下面就够了,有些留在后面研究:
上面有注册模块,还有聊天模块.后面也许会加入组队模块.
服务器是如何开始的
新建一个服务器
ServerStart ss = new ServerStart(9999);
ss.Decode = MessageEncoding.Decode;
ss.Encode = MessageEncoding.Encode;
ss.LengthDecode = LengthEncoding.Decode;
ss.LengthEncode = LengthEncoding.Encode;
ss.HandlerCenter =new MessageHandlerCenter();
ss.Start(1234);
Console.WriteLine("服务启动成功");
这看起来是一个基本的服务器实例化代码
9999 是最大连接数.
1234 代表端口号.
中间有几行看起来是加密和解密,还有消息处理的地方.
基于事件驱动
public override void ClientClose(UserToken userToken, string error)
{
Console.WriteLine("有客户端断开连接!");
try
{
_mapHandler.ClientClose(userToken);
}
finally
{
_userHandler.ClientClose(userToken);
_loginHandler.ClientClose(userToken);
}
}
public override void ClientConnect(UserToken userToken)
{
Console.WriteLine("有客户端连接进来了!");
}
我很喜欢这种事件驱动.
public override void MessageReceive(UserToken userToken, object message)
{
SocketModel socketModel = message as SocketModel;
if(socketModel!=null)
switch (socketModel.Type)
{
case Protocol.TYPE_LOGIN:
_loginHandler.MessageReceive(userToken, socketModel);
break;
case Protocol.TYPE_USER:
_userHandler.MessageReceive(userToken, socketModel);
break;
case Protocol.TYPE_MAP:
_mapHandler.MessageReceive(userToken,socketModel);
break;
case Protocol.TYPE_RAID:
_raidHandler.MessageReceive(userToken,socketModel);
break;
case Protocol.TYPE_CHAT:
_chatHandler.MessageReceive(userToken,socketModel);
break;
}
}
我不太喜欢这种以二进制的形式来通讯,这样就无法直接通过调试工具进行查看和测试.
注册模块和登陆模块
public void MessageReceive(UserToken userToken, SocketModel message)
{
switch (message.Command)
{
case LoginProtocol.LOGIN_CREQ:
Console.WriteLine("登陆");
Login(userToken, message.GetMessage<AccountDTO>());
break;
case LoginProtocol.REG_CREQ:
Console.WriteLine("注册");
Reg(userToken, message.GetMessage<AccountDTO>());
break;
case LoginProtocol.OFFLINE_CREQ:
ClientClose(userToken);
break;
}
}
public void ClientClose(UserToken userToken)
{
ExecutorPool.Instance.Executor(delegate ()
{
_accountBiz.Close(userToken);
});
}
private void Login(UserToken userToken ,AccountDTO value)
{
ExecutorPool.Instance.Executor(delegate()
{
int result = _accountBiz.AccountLogin(userToken, value.Account, value.Password);
Console.WriteLine("登陆结果=" + result);
Write(userToken,Protocol.TYPE_LOGIN,0,LoginProtocol.LOGIN_SRES,result);
});
}
private void Reg(UserToken userToken ,AccountDTO value)
{
ExecutorPool.Instance.Executor(delegate ()
{
int result = _accountBiz.CreateAccount(userToken, value.Account, value.Password);
Console.WriteLine("注册结果="+ result);
Write(userToken, Protocol.TYPE_LOGIN, 0, LoginProtocol.REG_SRES, result);
});
}
里面是怎么判断的 ,就不进去看了,目前只要结果就行了.
新建一个客户端
基础模块
我新建了一个unity项目来尝试和服务端进行通讯测试,基本的项目如下:
global 是全局项目,用来存储一些信息.
NetManager是用来跟服务器通讯的工具.我加入了登陆/注册,聊天模块.
NetIO
主要socket代码都在里面,应该是在NetManagerUtil 的update 里被激活的.如果没有连上服务器,貌似就在假死状态,所以这里可以做下调整.
注册/登陆模块
using UnityEngine;
using System.Collections;
using GameProtocol;
using GameProtocol.Auto;
public class LoginHandler : MonoBehaviour {
public delegate void loginEventHandler(int ms);
public event loginEventHandler LoginEvent;
public delegate void RegEventHandler(int ms);
public event RegEventHandler RegEvent;
public void MessageReceive(SocketModel socketModel)
{
switch (socketModel.Command)
{
case 0:
Debug.Log("登陆信息");
LoginAccount(socketModel.GetMessage<int>());
break;
case 2:
Debug.Log("注册信息");
RegAccount(socketModel.GetMessage<int>());
break;
}
}
private void RegAccount(int result)
{
switch (result)
{
case -1:
Debug.Log("账号重复!");
if (RegEvent != null)
{
RegEvent(0);
}
break;
case 0:
if (RegEvent != null)
{
RegEvent(1);
}
Debug.Log("注册成功!");
break;
}
}
private void LoginAccount(int result)
{
switch (result)
{
case 0:
Global.Instance.IsOnline = true;
Debug.Log("登陆成功");
if(LoginEvent!=null)
{
LoginEvent (1);
}
break;
case -1:
if (LoginEvent != null)
{
LoginEvent(0);
}
Debug.Log("账号不存在!");
break;
case -2:
if (LoginEvent != null)
{
LoginEvent(2);
}
Debug.Log("账号在线!");
break;
case -3:
if (LoginEvent != null)
{
LoginEvent(3);
}
Debug.Log("账号或密码错误!");
break;
case -4:
if (LoginEvent != null)
{
LoginEvent(4);
}
Debug.Log("账号密码为空!");
break;
}
}
}
我给它加了注册事件还有登陆事件.
NetManager
using UnityEngine;
using System.Collections;
using GameProtocol;
using GameProtocol.Auto;
public class NetManagerUtil : MonoBehaviour
{
private LoginHandler _loginHandler;
private ChatHandler _chatHandler;
void Start()
{
DontDestroyOnLoad(this.gameObject);
_loginHandler = this.GetComponent<LoginHandler>();
_loginHandler.LoginEvent += handleLoginEvent;
_loginHandler.RegEvent += handleRegEvent;
_chatHandler = this.GetComponent<ChatHandler>();
zhuce();
}
private void handleLoginEvent(int result)
{
Debug.Log("登陆结果="+result);
}
private void handleRegEvent(int result)
{
Debug.Log("注册结果=" + result);
if(result==1)
{
login();
}
}
void zhuce()
{
// yield return new WaitForSeconds(2f);
AccountDTO accountDto = new AccountDTO();
accountDto.Account = "abc";
accountDto.Password = "123";
this.Write(Protocol.TYPE_LOGIN, 0, LoginProtocol.REG_CREQ, accountDto);
Global.Instance.Account = "abc";
Global.Instance.Password = "123";
}
void login()
{
AccountDTO accountDto = new AccountDTO();
accountDto.Account = Global.Instance.Account;
accountDto.Password = Global.Instance.Password;
this.Write(Protocol.TYPE_LOGIN, 0, LoginProtocol.LOGIN_CREQ, accountDto);
}
void Update()
{
while (NetIO.Instance.MessageList.Count > 0)
{
SocketModel socketModel = NetIO.Instance.MessageList[0];
StartCoroutine("MessageReceive", socketModel);
NetIO.Instance.MessageList.RemoveAt(0);
}
}
private void MessageReceive(SocketModel socketModel)
{
switch (socketModel.Type)
{
case 0:
_loginHandler.MessageReceive(socketModel);
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
_chatHandler.MessageReceive(socketModel);
break;
}
}
}
目前加入了自动注册和登陆,现在看来一切结果都在预期之中.
开始聊天
聊天失败
我先在登陆结果的地方直接输入聊天内容,服务器是收到了,但是客户端并没有收到
private void handleLoginEvent(int result)
{
Debug.Log("登陆结果="+result);
if (result == 1)
{
ChatDTO dto = new ChatDTO(Global.Instance.UserDto, "世上没有一件工作不辛苦,没有一处人事不复杂。不要随意发脾气,谁都不欠你的。学会低调,取舍间必有得失,不用太计较。学着踏实而务实,越努力越幸运。当一个人有了足够的内涵和物质做后盾,人生就会变得底气十足。若是美好,叫做精彩,若是糟糕,叫做经历。——互勉", ChatType.System, null);
this.Write(Protocol.TYPE_CHAT, 0, ChatProtocol.CHAT_TALK_CREQ, dto);
}
}
以下是聊天模块:
public void MessageReceive(SocketModel socketModel)
{
switch (socketModel.Command)
{
case 1:
break;
case 2:
ChatDTO dto = socketModel.GetMessage<ChatDTO>();
switch (dto.Type)
{
case ChatType.System:
SystemChatList.Add(dto);
Debug.Log("System="+dto.PlayerDto.Name+":"+ dto.ChatValue);
break;
case ChatType.Team:
string text = string.Format("[ff0000]【队伍】[-] [ffff00]{0}[-] :{1}", dto.PlayerDto.Name,
dto.ChatValue);
Debug.Log("Team=" + dto.PlayerDto.Name + ":" + dto.ChatValue);
/* if (BottomEnumPanel.Instance != null)
{
BottomEnumPanel.Instance.TextList.Clear();
BottomEnumPanel.Instance.TextList.Add(text);
}*/
TeamChatList.Add(dto);
// CreateChatItem(1, dto);
break;
case ChatType.World:
string value = string.Format("[ff0000]【世界】[-] [ffff00]{0}[-] :{1}", dto.PlayerDto.Name,
dto.ChatValue);
/* if (BottomEnumPanel.Instance != null)
{
BottomEnumPanel.Instance.TextList.Clear();
BottomEnumPanel.Instance.TextList.Add(value);
}*/
WorldChatList.Add(dto);
//CreateChatItem(0,dto);
break;
}
AllChatList.Add(dto);
break;
}
}
以上并没有被触发.
服务端没有角色信息
private void Talk(UserToken userToken , ChatDTO dto)
{
switch (dto.Type)
{
case ChatType.World:
Console.WriteLine("世界频道");
break;
case ChatType.System:
int[] playerIds = _userBiz.GetAllOnlinePlayerId(userToken);
Console.WriteLine("系统频道");
Console.WriteLine("playerIds length="+ playerIds.Length);
for (int i=0;i<playerIds.Length;i++)
{
Console.WriteLine("id="+playerIds[i]);
}
this.Write(playerIds, Protocol.TYPE_CHAT,0,ChatProtocol.CHAT_TALK_BRO, dto);
break;
case ChatType.Team:
Console.WriteLine("组队频道");
this.Write(dto.TeamId, Protocol.TYPE_CHAT, 0, ChatProtocol.CHAT_TALK_BRO, dto);
break;
}
Console.WriteLine("文字内容="+dto.ChatValue);
}
检查这里,原来playerIds length=0;
新建一个角色
应该是因为没有角色,所以才没有办法发给客户端.那还是加入角色模块吧,先新建一个:
private void addUser()
{
UserDTO userDto = new UserDTO();
userDto.Name = "战士";
userDto.JobId = 0;
this.Write(Protocol.TYPE_USER, 0, UserProtocol.Create_CREQ, userDto);
}
private void handleLoginEvent(int result)
{
Debug.Log("登陆结果="+result);
if (result == 1)
{
addUser();
}
}
UserHandler下改成这样:
...
Debug.Log("socketModel.Command="+ socketModel.Command);
switch (socketModel.Command)
{
case 1:
//创建角色
Debug.Log("创建角色");
Create(socketModel.GetMessage<bool>());
break;
case 3:
//获取角色信息
Debug.Log("获取角色信息");
GetPlayerIdList(socketModel.GetMessage<List<int>>());
break;
case 5:
//获取角色数据
Debug.Log("获取角色数据");
_userDtos = socketModel.GetMessage<List<UserDTO>>();
for(int i=0;i<_userDtos.Count;i++)
{
UserDTO userD = _userDtos[i];
Debug.Log("name="+ userD.Name);
Debug.Log("id=" + userD.Id);
Debug.Log("job=" + userD.JobId);
}
//UIMgr.Instance.Destory(PanelType.SelectPlayerPanel);
// UIMgr.Instance.ShowPanel(PanelType.SelectPlayerPanel, _userDtos);
break;
case 13:
Global.Instance.UserDto = socketModel.GetMessage<UserDTO>();
if (OnPlayerInfoChange != null)
OnPlayerInfoChange();
break;
...
运行结果:
新建角色成功.再来试试聊天吧.
成功收到信息
_userHandler.OnPlayerCreate += userCreated;
private void userCreated()
{
Debug.Log("角色新建成功");
talk();
}