这篇演示应用通讯协议
演示从登录界面进入
打开数据库SSMS;启动服务器
从Loading场景进入
输入已经注册过的账号和密码
//这里的登录按钮有切换场景的逻辑(在Cilent中
void OnLogin(Result result, string message)
{
if (result == Result.Success)
{
//登录成功进入角色选择
//MessageBox.Show("登录成功,准备角色选择"+message,"提示",message,"提示",MessageBoxType.Information);
//切换到CharSelect场景
SceneManager.Instance.LoadScene("CharSelect");
}
else
MessageBox.Show(message, "错误", MessageBoxType.Error);
}
//这里的Scene Manager需要挂在场景的一个空物体上才能初始化
//由于我们要从Loading场景转换到CharSelect场景;在Edit的Build里面把这两个场景勾上
//成功登进去了友友们!
角色创建
给开启冒险按钮添加事件
OnClickCreate
角色名称!=null校验
创建角色需要让服务器知道角色名称和职业;传入两个参数
public void OnClickCreate()
{
if(string.IsNullOrEmpty(this.charName.text))
{
MessageBox.Show("请输入角色名称");
return;
//不能把空消息发给服务器
}
//启用协议创建角色
UserService.Instance.SendCharacterCreate(this.charName.text, this.charClass);
}
在客服端添加发送
添加UserService:SendCharacterCreate服务
//用来发给服务器角色创建的信息
//发给服务器的所以这里是Request请求
//在NetMessageRequest里面包含了所需的请求访问器
public void SendCharacterCreate(string charName, CharacterClass cls)
{
Debug.LogFormat("SendCharacterCreate::charName :{0} cls:{1}", charName, cls);
NetMessage message = new NetMessage();
message.Request = new NetMessageRequest();
message.Request.createChar = new UserCreateCharacterRequest();
message.Request.createChar.Name = charName;
message.Request.createChar.Class = cls;
//这下面的都跟其他消息发送服务相同
if (this.connected && NetClient.Instance.Connected)
{
this.pendingMessage = null;
NetClient.Instance.SendMessage(message);
}
else
{
this.pendingMessage = message;
}
}
添加OnUserCreateCharacter接收服务
//响应结束后通过这个回馈给UI层
//接收
void OnUserCreateCharacter(object sender, UserCreateCharacterResponse response)
{
Debug.LogFormat("OnUserCreateCharacter:{0} [{1}]", response.Result, response.Errormsg);
if(response.Result == Result.Success)
{
Models.User.Instance.Info.Player.Characters.Clear();
Models.User.Instance.Info.Player.Characters.AddRange(response.Characters);
}
//创建成功之后把返回一个角色列表;把角色填充到角色列表中
if (this.OnCharacterCreate != null)
{//通过事件通知UI 层
this.OnCharacterCreate(response.Result, response.Errormsg);
}
}
void OnCharacterCreate(Result result,string message)
{
if(result== Result.Success)
InitCharacterSelect(true);
else MessageBox.Show(message,"错误",MessageBoxType.Error);
}
添加订阅OnUserCreateCharacter
接收方法必须要订阅才能有效
这里订阅的是角色创建响应的协议:UserCreateCharacterResponse
监听接收服务事件
在Dispose也加//订阅和取消订阅成对出现
OnCharacterCreate在Game面板上显示通知
显示角色创建是否成功
public UnityEngine.Events.UnityAction<Result, string> OnCharacterCreate;
void OnCharacterCreate(Result result,string message)
{
if(result== Result.Success)
InitCharacterSelect(true);
else MessageBox.Show(message,"错误",MessageBoxType.Error);
}
//UnityAction是Unity的一种事件;可以传两个参数
//如果有订阅了创建成功的消息,在OnUserCreateCharacter中判断是否为空
//在角色创建面板启动订阅
启动订阅的位置
在UICharacterSelect脚本挂的角色选择页面上需要得到角色创建是否成功的消息
因此在角色选择的面板打开时就启动订阅:
void Start()
{
// DataManager.Instance.Load();
InitCharacterSelect(true);//初始化当前点击选择的角色
UserService.Instance.OnCharacterCreate = OnCharacterCreate;
}
//注意等号左边的OnCharacterCreate在UserService中,是UnityAction
//右边的在UICharacterSelect中
创建成功:InitCharacterSelect打开选择角色面板
public void InitCharacterSelect(bool init)
{
panelCreate.SetActive(false);
panelSelect.SetActive(true);
}
逻辑是创建成功返回角色选择面板
在服务端接收
订阅OnCreateCharacter
请求协议下的创建角色事件OnCreateCharacter
public UserService()
{
//创建一个分发器的单例然后执行订阅;这里订阅登录消息,这是登陆消息的处理器:UserLoginRequest//协议
MessageDistributer<NetConnection<NetSession>>.Instance.Subscribe<UserLoginRequest>(this.OnLogin);
MessageDistributer<NetConnection<NetSession>>.Instance.Subscribe<UserRegisterRequest>(this.OnRegister);
MessageDistributer<NetConnection<NetSession>>.Instance.Subscribe<UserCreateCharacterRequest>(this.OnCreateCharacter);
}
OnCreateCharacter
void OnCreateCharacter(NetConnection<NetSession> sender,UserCreateCharacterRequest request)
{
Log.InfoFormat("UserCreateCharacterRequest: Name:{0} Class:{1}", request.Name, request.Class);
//TCharacter是提前建好的一个类,里面有关于Character的所有字段的访问器
TCharacter character = new TCharacter()
{//创建新角色要把客户端传进来的信息填充
Name = request.Name,
Class = (int)request.Class,//这里加一个强制转换;数据库不允许枚举值
TID=(int)request.Class,
//mapID:第一次出现的时候要出现在那张地图;哪个位置
MapID=1,//主城;某次登录需要用到上一次的位置
MapPosX=5000,
MapPosY=4000,
MapPosZ=820,//这是地图的中心位置;单位是cm
};
DBService.Instance.Entities.Characters.Add(character);
//前面OnLogin里创建了用户User
sender.Session.User.Player.Characters.Add(character);
//对数据库做的任何增删改查的操作之后都要保存
DBService.Instance.Entities.SaveChanges();
//准备要发送客户端的信息
NetMessage message = new NetMessage();
message.Response = new NetMessageResponse();
message.Response.createChar = new UserCreateCharacterResponse();
//回发消息吧结果告诉用户;创建是否成功
message.Response.createChar.Result = Result.Success;
message.Response.createChar.Errormsg = "None";
byte[] data = PackageHandler.PackMessage(message);
sender.SendData(data, 0, data.Length);
}
创建了新的角色
创建新表!
//T开头的类代表EF框架使用的entity映射的对象//对应一个数据表里的对象
//TCharacter是提前建好的一个类,里面有关于Character的所有字段的访问器
TCharacter character = new TCharacter()
{//创建新角色要把客户端传进来的信息填充
Name = request.Name,
Class = (int)request.Class,//这里加一个强制转换;数据库不允许枚举值
TID=(int)request.Class,
//mapID:第一次出现的时候要出现在那张地图;哪个位置
MapID=1,//主城;某次登录需要用到上一次的位置
MapPosX=5000,
MapPosY=4000,
MapPosZ=820,//这是地图的中心位置;单位是cm
};
把刚建的新表加上
//Session会话 是内存中的角色对象;每次登录的用户信息会保存在Session中;
//创建新角色的同时更新Session,就不需要重新拉一个DB
DBService.Instance.Entities.Characters.Add(character);
sender.Session.User.Player.Characters.Add(character);
//对数据库做的任何增删改查的操作之后都要保存
DBService.Instance.Entities.SaveChanges();
填充信息,打包
//准备要发送客户端的信息
NetMessage message = new NetMessage();
message.Response = new NetMessageResponse();
message.Response.createChar = new UserCreateCharacterResponse();
//回发消息吧结果告诉用户;创建是否成功
message.Response.createChar.Result = Result.Success;
message.Response.createChar.Errormsg = "None";
byte[] data = PackageHandler.PackMessage(message);
sender.SendData(data, 0, data.Length);
完整的OnCreateCharacter
void OnCreateCharacter(NetConnection<NetSession> sender,UserCreateCharacterRequest request)
{
Log.InfoFormat("UserCreateCharacterRequest: Name:{0} Class:{1}", request.Name, request.Class);
//TCharacter是提前建好的一个类,里面有关于Character的所有字段的访问器
TCharacter character = new TCharacter()
{//创建新角色要把客户端传进来的信息填充
Name = request.Name,
Class = (int)request.Class,//这里加一个强制转换;数据库不允许枚举值
TID=(int)request.Class,
//mapID:第一次出现的时候要出现在那张地图;哪个位置
MapID=1,//主城;某次登录需要用到上一次的位置
MapPosX=5000,
MapPosY=4000,
MapPosZ=820,//这是地图的中心位置;单位是cm
};
DBService.Instance.Entities.Characters.Add(character);
sender.Session.User.Player.Characters.Add(character);
//对数据库做的任何增删改查的操作之后都要保存
DBService.Instance.Entities.SaveChanges();
//准备要发送客户端的信息
NetMessage message = new NetMessage();
message.Response = new NetMessageResponse();
message.Response.createChar = new UserCreateCharacterResponse();
//回发消息吧结果告诉用户;创建是否成功
message.Response.createChar.Result = Result.Success;
message.Response.createChar.Errormsg = "None";
byte[] data = PackageHandler.PackMessage(message);
sender.SendData(data, 0, data.Length);
}
//关于角色名称输入框支持中文
在InputFiled中选择
演示创建角色
登陆->创建角色按钮->选择弓箭手;输入名字->开启游戏按钮:
页面会回到SelectCharacter;但是创建的角色没有显示在Srcoll view中//这部分业务未做
成功发到服务端://Game Server中
//在服务器面板打印出了
客户端也接收到了响应://Client中
//在日志中打印了
我画了一个信息传输示意图//或许不完全正确
角色成功被我们创建在数据库中了;现在需要把创建的角色刷新显示在选择角色的页面
切换已创建角色(按钮)状态
首先绑定CharInfo
//content是角色列表;Char Info是一个角色信息绑定到UICharacterSelect的脚本上
通过脚本初始化:角色创建成功;登录时服务端发来角色列表用到CharInfo更新
角色列表填充InitCharacterSelect
角色创建后需要刷新选择页面
在InitCharacterSelect中获取并初始化charInfo
给动态创建的Button添加监听事件:选择角色
public void InitCharacterSelect(bool init)
{
panelCreate.SetActive(false);
panelSelect.SetActive(true);
if(init)
{//把原来有的销毁
foreach(var old in uiChars)
{
Destroy(old);
}
uiChars.Clear();
//登录成功后信息会到charInfo
//User中会包含Player的角色信息;登录或创建角色成功,User中的charInfo会更新
for(int i=0;i<User.Instance.Info.Player.Characters.Count;i++)
{//遍历所有角色
GameObject go = Instantiate(uiCharInfo, this.uiCharList);
// charInfo 根节点滚动区
//初始化设置名字
UICharInfo charInfo=go.GetComponent<UICharInfo>();
charInfo.info = User.Instance.Info.Player.Characters[i];
//Button初始化因为Button在charInfo上
Button button=go.GetComponent<Button>();
int idx = i;
//这里的Button是动态创建的;因此对点击事件添加监听器
button.onClick.AddListener(() =>
{
OnSelectCharacter(idx);//执行选择角色
});
uiChars.Add(go);
go.SetActive(true);
}
}
}
这里的方法是把原来uiChars列表里面的对象全部销毁;又把(可能包括新加进来的角色)全部角色都创建一个
选中后角色框高亮
UICharInfo.cs
//UICharInfo是CharIfo(每个创建出来的角色一个框)的脚本;里面有每个显示框需要的字段
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UICharInfo : MonoBehaviour {
public SkillBridge.Message.NCharacterInfo info;
public Text charClass;
public Text charName;
public Image highlight;
public bool Selected
{//某个节点是否被选中
get { return highlight.IsActive(); }//被选中
set
{
highlight.gameObject.SetActive(value);
}
}
// Use this for initialization
void Start () {
if(info!=null)
{
this.charClass.text = this.info.Class.ToString();
this.charName.text = this.info.Name;
}
}
}
OnSelectCharacter(int idx)
public void OnSelectCharacter(int idx)
{
this.selectCharacterIdx = idx;
//知道当前的索引
var cha = User.Instance.Info.Player.Characters[idx];
//按下按钮打印当前选择的已创建的角色信息
Debug.LogFormat("Select Char:[{0}]{1}[{2}]", cha.Id, cha.Name, cha.Class);
User.Instance.CurrentCharacter = cha;
characterView.CurrectCharacter = ((int)cha.Class - 1);
//点击的显示高亮,其他的不显示
for (int i = 0; i < User.Instance.Info.Player.Characters.Count; i++)
{
//获取当前点击事件的对象
UICharInfo ci = this.uiChars[i].GetComponent<UICharInfo>();
ci.Selected = (idx==i);
}
}
//关于创建角色后角色列表的角色都消失了(未解决
服务器创建角色后没有把角色列表返回给客户端
总结!角色创建逻辑
Service层是处理对UI事件的订阅;对网络消息的订阅和收发响应;
UserService则是所有和用户相关的网络通讯;和一些基本的逻辑
//UICharacterSelect中
->OnClickCreate()点击创建按钮时把信息发给服务端
//UserService中
->SendCharacterCreate(在UserService中添加)构建消息发送//使用的是createChar协议
->OnUserCreateCharacter处理创建角色的返回//这样才知道角色创建之后是否成功
//UICharacterSelect中
->OnSelectCharacter切换按钮的状态
->打开选择角色面板或创建角色出现角色列表;InitCharacterSelect
//关于Session
当不同消息从给客户端发到服务端时,通过Session知当前接收到的是谁,用户是谁,以及拿到其他的消息
//关于端口
在Game Server中的Init初始端口8000
但服务器要做配置:
int Port = Properties.Settings.Default.ServerPort;
network = new NetService();
network.Init(Port);
这种配置在GameServer项目属性中
点击设置:
在这里面加完配置保存过后,会自动生成这个命名空间:Properties.Settings.Default.
生成的配置在这个里面
最终会体现在App.Config 中
//关于SQLServer配置管理器,可以在SQLServer服务中看SQL Server是否运行//保证服务器一定启动
//EF访问数据库(未解决
一种对象关系映射器;
在创建新角色时,用特定类New一个对象;通过实体类访问数据库:把新建角色放进DBService
//TCharacter是提前建好的一个类,里面有关于Character的所有字段的访问器
TCharacter character = new TCharacter()
{//创建新角色要把客户端传进来的信息填充
Name = request.Name,
Class = (int)request.Class,//这里加一个强制转换;数据库不允许枚举值
TID=(int)request.Class,
//mapID:第一次出现的时候要出现在那张地图;哪个位置
MapID=1,//主城;某次登录需要用到上一次的位置
MapPosX=5000,
MapPosY=4000,
MapPosZ=820,//这是地图的中心位置;单位是cm
};
DBService.Instance.Entities.Characters.Add(character);
sender.Session.User.Player.Characters.Add(character);
//对数据库做的任何增删改查的操作之后都要保存
DBService.Instance.Entities.SaveChanges();
创建角色后角色被放进数据库,显示已创建角色列表,需要在找到对应的用户创建的所有角色;
//登录成功后信息会到charInfo
//User中会包含Player的角色信息;登录或创建角色成功,User中的charInfo会更新
for(int i=0;i<User.Instance.Info.Player.Characters.Count;i++)
{//遍历所有角色
GameObject go = Instantiate(uiCharInfo, this.uiCharList);
// charInfo 根节点滚动区
//初始化
UICharInfo charInfo=go.GetComponent<UICharInfo>();
charInfo.info = User.Instance.Info.Player.Characters[i];
//Button初始化因为Button在charInfo上
Button button=go.GetComponent<Button>();
int idx = i;
button.onClick.AddListener(() => {
OnSelectCharacter(idx);
//add;点击已创建角色显示动画
//characterView.CurrentCharacter = (int)charInfo.info.Class-1;
});
uiChars.Add(go);
go.SetActive(true);
}