34.消息面板的显示

首先的话,我们创建我们的MessagePanel脚本,这里我们提供了创建,显示,隐藏三个方法,并且的话我们这个面板脚本的控制是交给Uimanager进行管理的

publicclassMessagePanel : BasePanel {

private Texttext;

privatefloat showTime = 1f;

publicoverridevoid OnEnter()

{   //这个是用来创建消息面板的

base.OnEnter();

text = this.GetComponent<Text>();

text.enabled = false;

uiMng.InjectMsg(this);

}

publicvoid ShowMessage(string data)

{

text.text = data;

//将alpha值重新设置为1

text.enabled = true;

text.color = Color.white;

//一秒种后隐藏这个方法

Invoke("HideMessage", showTime);

}

publicvoid HideMessage()

{

//这个是用来修改text的Alpha值使得它的显示为隐藏

text.CrossFadeAlpha(0, 1, false);

}

}

因为所有的Ui面板都是用UiManager进行管理的,因此我们提供在父类提供一个方法来获取到Uimanager

public UIManager UIManager

{

//提供一个set方法可以进行赋值

set { uiMng= value; }

}

然后在这个实例化面板的方法里,当我们获得到对应的面板的时候,就会对它进行赋值,让UiManager进行管理

privateBasePanel GetPanel(UIPanelType panelType)

{

if(panelDict == null)

{

panelDict = new Dictionary<UIPanelType,BasePanel>();

}

//BasePanel panel;

//panelDict.TryGetValue(panelType, outpanel);//TODO

BasePanel panel = panelDict.TryGet(panelType);

if (panel== null)

{

//如果找不到,那么就找这个面板的prefab的路径,然后去根据prefab去实例化面板

//stringpath;

//panelPathDict.TryGetValue(panelType,out path);

string path = panelPathDict.TryGet(panelType);

GameObject instPanel =GameObject.Instantiate(Resources.Load(path)) as GameObject;

instPanel.transform.SetParent(CanvasTransform,false);

//实例化对应面板后我们要给这个面板提供UiManager这个值

instPanel.GetComponent<BasePanel>().UIManager = this;

panelDict.Add(panelType,instPanel.GetComponent<BasePanel>());

return instPanel.GetComponent<BasePanel>();

}

然后我们在UIManager里提供两个方法,来引用和操控我们的MessagePanel

publicvoid ShowMessage(string msg)

{

if(messagePanel == null)

{

Debug.Log("无法显示该面板");

return;

}

messagePanel.ShowMessage(msg);

}

privatevoid HideMessage()

{

messagePanel.HideMessage();

}

publicvoidInjectMsg(MessagePanel messagePanel)

{

//当面板被创建的时候,注入这个Panel

messagePanel = this.messagePanel;

}

如果其他的模块也想要用这个提示错误信息的话,我们只要在GameFacade里提供这样一个接口方法来调用Uimanager里的ShowMessage即可

publicvoid ShowMessage(string msg)

{

uIManager.ShowMessage(msg);

}

35.这里我们来开发我们的UI界面

这里实现的是点击登录弹出登录框,这个的话,首先,我们用UIManager来创建出我们的消息面板和登录面板

publicoverridevoid OnInit()

{

base.OnInit();

PushPanel(UIPanelType.Start);

PushPanel(UIPanelType.Message);

}

然后的话,我们修改我们的StartPanel,给登录注册上点击创建登录页面的按钮

publicclassStartPanel : BasePanel {

private ButtonloginButton;

privateAnimator btnAnimator;

publicoverridevoid OnEnter()

{

base.OnEnter();

gameObject.SetActive(true);

loginButton = transform.Find("LoginButton").GetComponent<Button>();

btnAnimator = transform.Find("LoginButton").GetComponent<Animator>();

loginButton.onClick.AddListener(OnClick);

}

publicvoid OnClick()

{

uiMng.PushPanel(UIPanelType.Login);

}

然后给我们的登录页面添加一个关闭功能即可,这里我们使用DoTween控制页面的切换

publicclassLoginPanel : BasePanel {

private ButtoncloseButton;

// Use this for initialization

publicoverridevoid OnEnter()

{

base.OnEnter();

gameObject.SetActive(true);

transform.DOScale(1, 0.4f);

transform.DOLocalMove( new Vector3(35,0,0), 0.4f);

closeButton = transform.Find("Exit").GetComponent<Button>();

closeButton.onClick.AddListener(OnCloseClick);

}

public  void OnCloseClick()

{

uiMng.PopPanel();

transform.DOScale(0, 0.4f);

//transform.DOLocalMove(new Vector3(1000,0, 0), 0.4f).OnComplete(()=>uiMng.PopPanel());

}

publicoverridevoid OnExit()

{

base.OnExit();

gameObject.SetActive(false);

}

}

36.创建两个数据表用来管理我们的信息

一个用户表,包含id,用户名,密码

一个战机表,包含id,用户id,总局数,胜利局数

这里我们创建这样两个数据表,战绩表的用户id,作为用户表id的外键相关联

37.登录面板检测你是否输入了用户名或者密码

首先,我们获得一下两个输出域

userIf = transform.Find("userName/InputField").GetComponent<InputField>();

passwordIf = transform.Find("password/InputField").GetComponent<InputField>();

然后我们注册一下登录按钮

loginButton = transform.Find("LoginButton").GetComponent<Button>();

loginButton.onClick.AddListener(OnLoginClick);

根据输出域判断消息并且决定消息面板显示什么信息

publicvoid OnLoginClick()

{

string msg = "";

if (string.IsNullOrEmpty(userIf.text))

{

msg += "用户名为空";

}

if (string.IsNullOrEmpty(passwordIf.text))

{

msg += "密码为空";

}

if (msg !=null)

{

uiMng.ShowMessage(msg);

return;

}

Debug.Log(msg + "登录按钮被点击了");

}

1.  修改让requestCode指定对应的ActionCode

这里我们要知道,我们是通过RequestCode来找到对应的Controller,通过ActionCode区别Request

1.  首先的话,我们修改BaseRequest,子类继承的时候都要修改他们的值

publicclassBaseRequest : MonoBehaviour {

protectedRequestCode requestCode = RequestCode.None;

protected ActionCodeactionCode = ActionCode.None;

2.  修改RequestManager,我们是通过RequestCode获得对应的ActionCode

publicclassBaseRequest : MonoBehaviour {

protected RequestCoderequestCode = RequestCode.None;

protectedActionCode actionCode = ActionCode.None;

3.  修改GameFacade中对应的获得Request的方法

publicvoid AddRequest(ActionCode actionCode,BaseRequestbaseRequest)

{

requestManager.AddRequest(actionCode,baseRequest);

}

publicvoid RemoveRequest(ActionCode actionCode)

{

requestManager.RemoveRequest(actionCode);

}

publicvoid HandleResponse(ActionCode actionCode, string data)

{

requestManager.HandleResponse(actionCode, data);

}

4.  修改服务器端的各种RequestCode,这里的话只要根据报错改就可以了,还是很简单的,目的就是每个RequestCode用ActionCode区分

2.  客户端向服务器端发起登录请求

首先,我们要给RequestCode和ActionCode提供几个code

publicenumRequestCode

{

None,

User

}

publicenumActionCode

{

None,

Login,

Regist

}

然后我们重新生成一下,把dll放到plugins下,这个一般是提前设计好,因为是案例,所以会将框架修改一下

这里的话我们做的是客户端发送登录请求,于是我们创建一个LoginRequest

void Start()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.User;

actionCode = ActionCode.Login;

}

publicvoid SendRequest(string username,string password)

{

string data =username + "," +password;

base.SendRequest(data);

}

指定好它对应的requestCode和ActionCode,然后调用ClientManager里的SendMessage方法,这个方法的调用我们通过GameFacade作为中介,而GameFacade的持有,我们放在父类BaseRequest,这里还要修改一下GameFacade,使它持有一下ClientManager里的SendMessage,这里代码就不贴了,和之前的一样

protectedGameFacade gameFacade;

publicvirtualvoid Awake()

{

GameFacade._instance.AddRequest(actionCode, this);

gameFacade = GameFacade._instance;

}

3.  添加用户的Dao层和Model层来进行校验

在Model层里我们给它提供用户属性

namespace游戏服务器.Model

{

classUser

{

//这里我们设置它的几个属性

public User(int id,string username,string password)

{

this.id = id;

this.username =username;

this.password =password;

}

publicint id { get; set; }

publicstring username { get; set; }

publicstring password { get; set; }

}

}

在Dao层的话,我们提供它的解析方法

using System;

usingSystem.Collections.Generic;

using System.Linq;

usingSystem.Text;

usingSystem.Threading.Tasks;

usingMySql.Data.MySqlClient;

using游戏服务器.Model;

namespace游戏服务器.Dao

{

classUserDao

{

//提供校验方法,一个model对应一个Dao

public UserVerifyUser(MySqlConnection conn,string username,string password)

{

MySqlCommand cmd = new MySqlCommand("select * from user where username=@username andpassword=@password", conn);

//插值作为连接查询

MySqlDataReader mySqlDataReader = null;

try

{

mySqlDataReader =cmd.ExecuteReader();

cmd.Parameters.AddWithValue("username",username);

cmd.Parameters.AddWithValue("password",password);

if (mySqlDataReader.Read())

{

int id = mySqlDataReader.GetInt32("id");

User user = new User(id, username,password);

return user;

}

else

{

returnnull;

}

}

catch(Exception e)

{

Console.WriteLine("读取用户信息出现异常" + e);

}

finally

{

mySqlDataReader.Close();

}

returnnull;

}

}

创建后我们在Controller层进行控制Dao层方法的调用即可

4.  让服务器端对我们的客户端登录请求做出响应

首先我们在Common类里提供一个枚举类型的方法判断是否正确返回信息

publicenumReturnCode

{

Success,

Fail

}

然后我们回到我们的UserController里判断我们是否登录成功

首先我们要用spilt方法分割用户名和密码,再用上一节写的方法判断是不是正确的用户名和密码,再返回对应的判断即可,这里要获取conn,要给Client提供一个构造方法获取,这里的话就不写了

publicstring Login(string data,Clientclient,Sever server)

{

string[] str = data.Split(',');

User user =userDao.VerifyUser(client.MysqlConn, str[0], str[1]);

if (user == null)

{

return ((int)(ReturnCode.Fail)).ToString();

}

else

{

return ((int)(ReturnCode.Success)).ToString();

}

}

5.  客户端响应服务器做出的回应

这里我们回到unity客户端,这里的话,服务器发送了信息,需要客户端做出响应,我们在LoginRequest对它做出响应,将服务器返回的ReturnCode进行转型,然后调用LoginPanel对UI进行反应

publicoverridevoid OnResponse(string data)

{

ReturnCode returnCode = (ReturnCode)(int.Parse(data));

loginPanel.OnResponse(returnCode);

}

这里的OnResponse,会对你的returnCode进行反应,当然前提是连接了数据库

publicvoidOnResponse(ReturnCode returnCode )

{

if(returnCode == ReturnCode.Success)

{

//判断登录成功,进入房间列表

}

else

{

uiMng.ShowMessage("登录失败,用户名或者密码不正确");

}

}

6.  测试登录效果

在测试登录效果的时候,不得不说,我碰到了很多的障碍,这也是之前没有debug的原因,这里讲述一下

1.  在loginRequest中调用MessagePanel下的ShowMessage时,因为是异步调用,所以无法修改,解决办法嘛,就是调用Unity自带的Update方法进行传值,

publicvoid Update()

{

//因为这个BasePanel是继承自MonBehaviour,所以我们能调用Update方法

if(message != null)

{

ShowMessage(message);

message = null;

}

}

publicvoid ShowMessage(string data)

{

//将alpha值重新设置为1

text.enabled = true;

text.text = data;

text.CrossFadeAlpha(1, 0.2f, false);

//一秒种后隐藏这个方法

Invoke("HideMessage", showTime);

}

publicvoid ShowMessageSync(string msg)

{

//异步调用显示信息的方法,因为想要修改unity组件是不能直接修改

message = msg;

1.      }

2.  这个是之前的代码问题,我在client里面回调信息时候,写错了clientSocket的传值方式

我把clientSocket=this.clientSocket写成了this.clientSocket=clientSocket,不得不说,这一块造成了很大的困扰,因为Bug很隐晦,看了很久,所幸最后还是找出来了,这里提示了我修改的重要性,当然也因此重温了一遍服务器和客户端的信息派发机制

3.  接下来就很简单了,我们手动给数据库添加一条用户名密码做校验就行,当然记得select数据的时候要先注入值再查询,之前这一块没有做出来,导致Bug,这里也标记一下

4.  那么到这里,我们的登录功能也就算完成了

7.  注册页面的一些Ui调整

这里的话比较简单,就是一个弹出页面的操作,我就贴一下代码了

publicclassRegistPanel : BasePanel {

private ButtoncloseButton;

publicoverridevoid OnEnter()

{

base.OnEnter();

this.gameObject.SetActive(true);

transform.DOScale(1, 0.4f);

closeButton = transform.Find("Exit").GetComponent<Button>();

closeButton.onClick.AddListener(OnCloseButtonClick);

}

publicvoidOnCloseButtonClick()

{

transform.DOScale(0, 0.4f);

uiMng.PopPanel();

}

publicoverridevoid OnExit()

{

base.OnExit();

this.gameObject.SetActive(false);

}

}

8.  发送信息到服务器端

这里的话我们创建一个RegisterRequest来处理注册的请求

也是持有我们的注册页面,然后发送请求,根据ReturnCode判断是否正确

publicclassRegisterRequest : BaseRequest {

privateRegistPanel registPanel;

publicoverridevoid Awake()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.User;

actionCode = ActionCode.Regist;

registPanel = GetComponent<RegistPanel>();

base.Awake();

}

publicvoid SendRequest(string username, string password)

{

string data =username + "," +password;

base.SendRequest(data);

}

publicoverridevoid OnResponse(string data)

{

ReturnCode returnCode = (ReturnCode)(int.Parse(data));

}

然后我们修改一下我们的RegisterPanel做注册校验和发送数据

publicvoid Start()

{

userInfo = transform.Find("userName/InputField/Text").GetComponent<Text>();

passwordInfo = transform.Find("password/InputField/Text").GetComponent<Text>();

rePasswordInfo = transform.Find("RePassword/InputField/Text").GetComponent<Text>();

registerRequest = this.GetComponent<RegisterRequest>();

}

publicoverridevoid OnEnter()

{

base.OnEnter();

this.gameObject.SetActive(true);

transform.DOScale(1, 0.4f);

closeButton = transform.Find("Exit").GetComponent<Button>();

closeButton.onClick.AddListener(OnCloseButtonClick);

registerButton = transform.Find("RegeistButton").GetComponent<Button>();

registerButton.onClick.AddListener(OnRegisterClick);

}

publicvoidOnCloseButtonClick()

{

transform.DOScale(0, 0.4f);

uiMng.PopPanel();

}

publicvoid OnRegisterClick()

{

string msg = "";

if (string.IsNullOrEmpty(userInfo.text)){

msg += "用户名不能为空";

}

if (string.IsNullOrEmpty(passwordInfo.text))

{

msg += "/n密码不能为空";

}

if(passwordInfo.text != rePasswordInfo.text)

{

msg += "密码和重复密码不一致";

}

if (msg !=null)

{

uiMng.ShowMessage(msg);

}

//进行注册请求发送信息到服务器端

registerRequest.SendRequest(userInfo.text, passwordInfo.text);

}

publicoverridevoid OnExit()

{

base.OnExit();

this.gameObject.SetActive(false);

}

}

9.  使服务器对于客户端的注册请求做响应

这里的话,当我们想要对客户端的注册做出响应,这里牵扯到的Controller和Dao就是UserController和UserDao了,一个负责调用Dao里的方法进行检验和注册,一个来书写注册方法

UserController

publicstring Regist(string data, Clientclient, Sever server)

{

string[] str = data.Split(',');

string username = str[0];

string password = str[1];

bool res = userDao.GetUserByUsername(client.MysqlConn,username);

if (res)

{

Console.WriteLine("用户名已经存在");

return ((int)(ReturnCode.Fail)).ToString();

}

userDao.RegisterUser(client.MysqlConn, username, password);

return ((int)(ReturnCode.Success)).ToString();

}

publicvoid RegisterUser(MySqlConnectionconn, stringusername, stringpassword)

{

//插值作为连接查询

try

{

MySqlCommand cmd = new MySqlCommand("insert into user where username=@username adnpassword=@password", conn);

cmd.Parameters.AddWithValue("username",username);

cmd.Parameters.AddWithValue("password",password);

//执行插入语句

cmd.ExecuteNonQuery();

}

catch (Exception e)

{

Console.WriteLine("插入用户信息出现异常" + e);

}

}

10.创建声音的模块AudioMannager

我们将在AudioMannager里来管理所有我们的声音,这里的话,我们先持有所有的声音并且为我们的游戏添加背景音乐

publicclassAudioManager : BaseManager {

//重写基类对于facade的调用

publicAudioManager(GameFacade facade) : base(facade) { }

//接下来的话,我们将在这个类里面,持有所有的声音

privateconststring sound_prepath = "Sounds/";

privateconststring sound_alert = "Alert";

privateconststring sound_Bgfast = "Bg(fast)";

privateconststring sound_Bgmoderate = "Bg(moderate)";

privateconststring sound_ButtonClick ="ButtonClick";

privateconststring sound_Miss="Miss";

privateconststring sound_ShootPerson ="ShootPerson";

privateconststring sound_Timer = "Timer";

privateAudioSource bgAudioSource;

privateAudioSource normalAudioSource;

publicoverridevoid OnInit()

{

//在这里我们初始化我们的音乐

base.OnInit();

GameObject audioPlay = new GameObject("AudioSource(GameObject)");

bgAudioSource= audioPlay.AddComponent<AudioSource>();

normalAudioSource = audioPlay.AddComponent<AudioSource>();

bgAudioSource.clip = LoadClip(sound_prepath, sound_Bgfast);

PlaySound(bgAudioSource, true);

}

publicAudioClip LoadClip(stringprepath,stringsoundName)

{

//指定类型后在AudioClip中进行加载

returnResources.Load<AudioClip>(prepath + soundName);

}

publicvoidPlaySound(AudioSource audioSource,bool isLoop=false)

{

audioSource.Play();

audioSource.volume = 0.2f;

audioSource.loop = isLoop;

}

}

11.添加按键声音

这里的话,我们通过GameFacade实现对AudioMannager的调用,然后的话我们处理点击事件都是在UiPanel下调用的,这样的话,我们通过GameFacde中介,然后给BasePanel写一个点击播放音乐的方法,这样,各个子panel就都能播放声音了

1.提供两个方法,分别播放循环的音乐和不循环的音乐

publicvoid PlayBgSound(string soundName)

{

bgAudioSource.clip= LoadClip(sound_prepath, soundName);

PlaySound(bgAudioSource, true,0.2f);

}

publicvoid PlayNormalSound(string soundName)

{

normalAudioSource.clip = LoadClip(sound_prepath, soundName);

PlaySound(normalAudioSource, false,1f);

}

2.  在GameFacade里调用

publicvoid PlayBgsound(string soundName)

{

audioManager.PlayBgSound(soundName);

}

publicvoid PlayNormalSound(string soundName)

{

audioManager.PlayNormalSound(soundName);

}

3.在创建UIPanel的时候注入GameFacade的值

instPanel.GetComponent<BasePanel>().Facade= facade;

3.  在BasePanel里创建播放点击声音的方法,设置GameFacade的值

protectedGameFacade facade;

public GameFacade Facade

{

set

{

this.facade = value;

}

}

4.  接下来在各个面板调用即可

12.让我们点击注册时候直接弹出面板

这一块的话我们做的和之前弹出其他面板思路是一样,将RoomList做成Prefab,然后修改我们的panelType添加上roomlist,当然同时我们也要修改一下我们的的json文件里的路径保证我们的PanelType能被读取到

当然这里有个问题,我们的登录是异步处理,而不是在主线程中处理,这里的话和之前的ShowMessage是一样的,那我们解决办法也和那个类似,我们更改PanelType也放在异步中进行处理即可

1.我们给UiManager的父类BaseManager里实现一个方法Update

2.这个交互我们放在GameFacade里去实现,因为我们这个更改Panel要在主线程中进行

publicvoid UpdateMannager()

{

audioManager.Update();

cameraManager.Update();

requestManager.Update();

playerManager.Update();

clientManager.Update();

uIManager.Update();

}

3. 这样我们就能在Uimanager里重写我们的Update方法来执行了,在里面修改我们的PanelType,调用这个异步方法

publicoverridevoid Update()

{

base.Update();

if(panelTypeToPush != UIPanelType.None)

{

PushPanel(panelTypeToPush);

panelTypeToPush = UIPanelType.None;

}

}

publicvoidPushPanelSync(UIPanelType panelType)

{

panelTypeToPush = panelType;

}

4.这样的话,我们就可以在LoginPanel里的OnResPonse,对是否登录成功,做出响应了

publicvoidOnResponse(ReturnCode returnCode )

{

if(returnCode == ReturnCode.Success)

{

//判断登录成功,进入房间列表

uiMng.PushPanelSync(UIPanelType.RoomList);

}

else

{

uiMng.ShowMessageSync("登录失败,用户名或者密码不正确");

}

}

13.服务器查询用户信息返回给客户端

这里的话我们仿照一下UserDao的写法就行,实际就是对result表通过userid进行查询,再让服务器在登录成功时转发给客户端

1.  创建Model层的Result类

classResult

{

//这里我们设置返回Result的几个属性

public Result(int id, int userid, int totalcount,int wincount)

{

this.Id = id;

this.UserInd = userid;

this.Wincount = wincount;

this.Totalcount = totalcount;

}

publicint Id { get; set; }

publicint UserInd { get; set; }

publicint Totalcount { get; set; }

publicint Wincount { get; set; }

}

2.  创建Dao层的ResultDao,执行查询表的操作并且返回查询结果

public ResultVerifyResult(MySqlConnection conn,int userid)

{

//插值作为连接查询

MySqlDataReader mySqlDataReader = null;

try

{

MySqlCommand cmd = new MySqlCommand("select * from result where userid=@userid", conn);

cmd.Parameters.AddWithValue("userid",userid);

mySqlDataReader =cmd.ExecuteReader();

if (mySqlDataReader.Read())

{

int id = mySqlDataReader.GetInt32("id");

int totalcount = mySqlDataReader.GetInt32("total_count");

int wincount = mySqlDataReader.GetInt32("win_count");

//Useruser = new User(id, username, password);

Result result = new Result(id, userid,totalcount, wincount);

return result;

}

else

{

returnnull;

}

}

catch (Exception e)

{

Console.WriteLine("校验用户结果出现异常" + e);

}

finally

{

if (mySqlDataReader != null)

mySqlDataReader.Close();

}

returnnull;

}

3.在登录成功后,向客户端返回信息

publicstring Login(string data,Clientclient,Sever server)

{

Console.Write("开始校验");

string[] str = data.Split(',');

User user =userDao.VerifyUser(client.MysqlConn, str[0], str[1]);

if (user == null)

{

return ((int)(ReturnCode.Fail)).ToString();

}

else

{

//这里我们登录以后不仅要打开房间列表,还要将用户名字和用户的胜利场数,总场数等信息传递过去

//return((int)(ReturnCode.Success)).ToString();

Result res = resultDao.VerifyResult(client.MysqlConn,user.Id);

returnstring.Format("{0},{1},{2},{3}", ((int)(ReturnCode.Success)).ToString(), user.Username,res.Totalcount, res.Wincount);

}

}

14.存储服务器端向客户端发送的数据

1.  我们收到数据是在LoginRequest中的OnResponse中获得的,这里我们将它进行解析就可以获得到服务器中传递的各个数值,在这里我们会将收到的信息存储在PlayerManager里,因为数据比较少,我们就用这个来保存了

publicoverridevoid OnResponse(string data)

{

//这里的话就是返回我们服务器端发送回来的查询数据

string[] strs= data.Split(',');

ReturnCode returnCode = (ReturnCode)(int.Parse(strs[0]));

if(returnCode == ReturnCode.Success)

{

string username = strs[1];

int totalcount = (int.Parse(strs[2]));

int wincount = (int.Parse(strs[3]));

UserData ud = new UserData(username,totalcount, wincount);

gameFacade.SetUserData(ud);

}

loginPanel.OnResponse(returnCode);

}

2.  这里我们创建一个User的Model来存放User的信息

publicclassUserData

{

publicUserData(stringusername,inttotalCount,intwinCount)

{

this.Username = username;

this.Totalcount = totalCount;

this.Wincount = winCount;

}

publicstring Username { get; privateset; }

publicint Totalcount { get; privateset; }

publicint Wincount { get; privateset; }

}

3.  然后我们用PlayerManager来存放这些数据

privateUserData userData;

public PlayerManager(GameFacade facade) : base(facade) { }

publicUserData UserData

{

set

{

this.userData = value;

}

get

{

return userData;

}

1.      }

4.  当然我们还需要在GameFacade里提供两个方法,来作为PlayerManager和LoginRequest的接口

publicvoidSetUserData(UserData userData)

{

playerManager.UserData = userData;

}

publicUserData GetUserData()

{

returnplayerManager.UserData;

1.      }

15.让RoomList显示用户信息

这里的话比较简单,我们只要获取到PlayerManager里存储的UserData再进行赋值即可

publicvoid ShowUser()

{

UserData ud = facade.GetUserData();

transform.Find("BattleRes/username").GetComponent<Text>().text= ud.Username;

transform.Find("BattleRes/totalGame").GetComponent<Text>().text= ud.Totalcount.ToString();

transform.Find("BattleRes/winGame").GetComponent<Text>().text= ud.Wincount.ToString();

}

然后在OnEnter里进行调用即可

16.将房间列表做成prefab使它在加载的时候会更新它的信息

classRoomList:MonoBehaviour

{

public Button joinButton;

public Text userName;

public Text totalCount;

public Text winCount;

publicvoid Start()

{

joinButton.onClick.AddListener(JoinRoom);

}

publicvoid JoinRoom()

{

}

publicvoid SetRoomInfo(string username,int totalcount,int wincount)

{

userName.text = username;

totalCount.text = "总局数"+totalcount.ToString();

winCount.text = "胜利局数"+wincount.ToString();

}

17.动态创建我们的房间列表

这里的话我们需要获得到列表的宽度来计算layout的宽度,这样的话当我们的房间加入的时候,就会自动进行排版和布局了

privateVerticalLayoutGroup roomGroup;

privateGameObject roomListPrefab;

publicvoid Start()

{

roomGroup = transform. Find("RoomList/ScrollRect/layOut").GetComponent<VerticalLayoutGroup>();

roomListPrefab = Resources.Load("UIPanel/room") as GameObject;

}

publicvoid CreateRoom( int count)

{

for(int i = 0; i < count; i++)

{

GameObject roomPrefab =GameObject.Instantiate(roomListPrefab);

roomPrefab.transform.SetParent(roomGroup.transform);

roomPrefab.transform.localScale= Vector3.one;

}

int roomCounts =GetComponentsInChildren<RoomList>().Length;

Vector2 size =roomGroup.GetComponent<RectTransform>().sizeDelta;

roomGroup.GetComponent<RectTransform>().sizeDelta = new Vector2(size.x,roomCounts * (roomListPrefab.GetComponent<RectTransform>().sizeDelta.y +roomGroup.spacing))

18.创建我们的房间列表

这里的话比较简单,我就描述一下逻辑

1.  创建如上图的UI

2.  创建RoomPanel,修改UiPanlType和它的json文件,做成prefab,在创建房间那里调用UImng,PushPanel完成对房间的加载

3.  退出按钮的话调用Ui框架下的OnEnter和OnExit就可以处理房间加载和消失的一些事件了,这里我控制退出就是直接gameObject.setActive(true);当然从需求上是解决了我的问题,如果有兴趣的话可以用DoTween做一些动画什么的,不要让切换变得那么枯燥,我这里是从学习的角度,就不多提了。

19.发起创建房间请求并且在服务器保存信息

这里的话比较简单,我们要理清楚逻辑,就不难

1.  我们创建一个CreateRoomRequest,这个用来处理向服务器发起请求,在这里我们指定了它的requestCode和actionCode后,就可以调用父类两个方法来处理发送和响应了

classCreateRoomRequest:BaseRequest

{

privateRoomPanel roomPanel;

//在这个request里我们需要在服务器创建我们的房间

publicoverridevoid Awake()

{

//指定我们的requestCode

requestCode = RequestCode.Room;

actionCode = ActionCode.CreateRoom;

roomPanel =GetComponent<RoomPanel>();

base.Awake();

}

publicoverridevoid SendRequest()

{

//向服务器发起请求创建房间列表

base.SendRequest("r");

}

publicoverridevoid OnResponse(string data)

{

//返回成功得判断

base.OnResponse(data);

ReturnCode returnCode =(ReturnCode)(int.Parse(data));

if (returnCode == ReturnCode.Success)

{

UserData ud =gameFacade.GetUserData();

roomPanel.SetUsernameSync(ud.Username,ud.Totalcount, ud.Wincount);

}

}

1.      }

2.  接下来我们处理服务器端,我们在severs里创建一个Room,并且在Controller里面创建一个RoomController来处理客户端的请求,当我们创建房间时,我们将发起创建的客户端就指定为本地客户端了,因此,我们在severs里创建我们的Room,将对应的客户端保存起来,当然这里我们创建的Controller要放在BaseController里进行保存

classRoomController:BaseController

{

publicRoomController()

{

requestCode = RequestCode.Room;

}

//给定一个方法,用来创建房间

publicstring CreateRoom(string data, Clientclient, Sever server)

{

server.CreateRoom(client);

Console.WriteLine("创建房间");

return ((int)(ReturnCode.Success)).ToString();

}

1.      }

classRoom

{

privateList<Client> roomClientlist = new List<Client>();

privateRoomState state = RoomState.WaitingJoin;

publicvoid AddRoom(Clientclient)

{

roomClientlist.Add(client);

}

1.      }

这是Severs端的方法,创建房间,将它保存起来

publicvoid CreateRoom(Clientclient)

{

Room room = new Room();

room.AddRoom(client);

roomList.Add(room);

1.          }

2.  然后的话我们就要在我们的RoomPanel里对我们的一些Ui信息进行更新了,RoomPanel里的方法会被CreateRoomRequest进行调用,当然这里的信息更新是异步更新,所以我们要在Update里修改我们的Ui信息,毕竟ui只能在主线程里被修改

privatevoid Update()

{

if (ud != null)

{

SetUsername(ud.Username,ud.Totalcount, ud.Wincount);

ClearUsername();

ud = null;

}

1.          }

publicvoid SetUsernameSync(string username, int totalGame, int winGame)

{

ud = new UserData(username, totalGame, winGame);

}

publicvoid SetUsername(string username, int totalGame, int winGame)

{

localUsername.text = username;

localTotalgame.text =totalGame.ToString();

localWingame.text =winGame.ToString();

}

publicvoid ClearUsername()

{

ememyUsername.text = "";

ememyTotalgame.text = "等待玩家加入";

ememyWingame.text = "";

}

20.服务器获得客户端加入的房间的信息并且返回给客户端的房间列表

1.我们创建一个Request来处理我们对房间信息的获取

//这个是客户端处理服务器发送的房间列表信息的request

publicoverridevoid Awake()

{

requestCode = RequestCode.Room;

actionCode = ActionCode.ListRoom;

base.Awake();

}

publicoverridevoid SendRequest()

{

base.SendRequest("r");

}

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

1.          }

2.  修改服务器端,首先我们需要给RoomController里提供一个方法ListRoom,它用来返回客户端请求的信息,看到下面的代码,首先,我们需要获得服务器端的roomList列表,因此我们需要给它提供一个方法来获得它的列表,然后我们需要在登录的时候传递给我们的服务器客户端的用户名和战绩,这样我们才可以动态更新我们的客户端的房间信息,然后我们用StringBuilder来进行组拼字符串存储信息并且返回

publicstring ListRoom(string data, Clientclient, Sever server)

{

StringBuilder sb = new StringBuilder();

foreach(Room room in server.GetRoomList())

{

if (room.IsWaitingForJoin())

{

sb.Append(room.GetHouseUserData() + "|");

}

}

if (sb == null)

{

sb.Append("0");

}

else

{

sb.Remove(sb.Length - 1, 1);

}

return sb.ToString();

}

3.  首先我们给client提供一个setUser和getUser,用来存储我们用户的信息,然后在登陆时调用setUser

publicvoid SetUserData(Useruser,Result result)

{

this.user = user;

this.result = result;

}

publicstring GetUserData()

{

return user.Username + "," + result.Totalcount + "," + result.Wincount;

1.          }

4在Sever端返回房间列表

publicList<Room> GetRoomList()

{

return roomList;

}

5.Room类里提供一个获取用户信息和判断是否等待两个方法,然后的话,按照上面说的,就可以给客户端返回房主信息了

publicstring GetHouseUserData()

{

return roomClientlist[0].GetUserData();

}

publicbool IsWaitingForJoin()

{

return state == RoomState.WaitingJoin;

}.

21.客户端接收服务器返回的信息创建房间列表

1.  我们处理返回信息的是在OnResponse方法里执行,在这里我们解析了服务器发送过来的数据,并且进行异步更新我们的UI信息

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

string[] roomArray = data.Split('|');

List<UserData> ud = new List<UserData>();

if (data == "0") return;

foreach (string room in roomArray)

{

string[] strs = room.Split(',');

ud.Add(new UserData( strs[0], int.Parse(strs[1]), int.Parse(strs[2])));

}

roomListPanel.CreateRoomSync(ud);

}

2.  我们来处理我们的异步更新房间列表的方法,这里其实和之前的异步操作是一样的,但是我们添加一个清理房间列表的方法,每次刷新的时候,我们都要更新我们房间列表,这里房间列表的信息是根据服务器返回的UserData类型的信息,我们再调用Roomlist下面的setInfo就可以更新我们的ui了,当然更新是要放在update下面执行的

publicvoid CreateRoomSync(List<UserData>udList)

{

this.udList = udList;

}

publicvoid CreateRoom(List<UserData> udList )

{

RoomList[] roomList =roomGroup.GetComponentsInChildren<RoomList>();

foreach (RoomList item in roomList)

{

Debug.Log("销毁房间");

item.DestorySelf();

}

int count = udList.Count;

for (int i = 0; i < count; i++)

{

GameObject roomPrefab =GameObject.Instantiate(roomListPrefab);

roomPrefab.transform.SetParent(roomGroup.transform);

roomPrefab.transform.localScale= Vector3.one;

UserData ud = udList[i];

roomPrefab.GetComponent<RoomList>().SetRoomInfo(ud.Username,ud.Totalcount,ud.Wincount);

}

int roomCounts =GetComponentsInChildren<RoomList>().Length;

Vector2 size =roomGroup.GetComponent<RectTransform>().sizeDelta;

roomGroup.GetComponent<RectTransform>().sizeDelta= new Vector2(size.x,roomCounts * (roomListPrefab.GetComponent<RectTransform>().sizeDelta.y +roomGroup.spacing));

1.          }

2.  这里是RoomList下面的两个方法,销毁自身和设置信息

publicvoid SetRoomInfo(string username,int totalcount,int wincount)

{

userName.text = username;

totalCount.text = "总局数"+totalcount.ToString();

winCount.text = "胜利局数"+wincount.ToString();

}

publicvoid DestorySelf()

{

GameObject.Destroy(this.gameObject);

Debug.Log("销毁了自身");

1.          }

22.修改创建房间的时机并且修改一下传递房间的信息

1.这里为了保证我们加入房间的唯一性,当我们点击加入房间的时候,我们应该将id一起传递进来,所以我们需要修改一下我们的UserData和setRoomInfo,我们房间的id就是创建者的id了,因为具有唯一性,所以可以用来区分

publicUserData(int id,string username, int totalCount, int winCount)

{

this.Username = username;

this.Id = id;

this.Totalcount = totalCount;

this.Wincount = winCount;

}

publicvoid SetRoomInfo(int id,string username,int totalcount,intwincount,RoomListPanel panel)

{

this.id = id;

userName.text = username;

totalCount.text = "总局数"+totalcount.ToString();

winCount.text = "胜利局数"+wincount.ToString();

this.panel = panel;

}

publicvoid CreateRoom(List<UserData> udList )

{

RoomList[] roomList =roomGroup.GetComponentsInChildren<RoomList>();

foreach (RoomList item in roomList)

{

Debug.Log("销毁房间");

item.DestorySelf();

}

int count = udList.Count;

for (int i = 0; i < count; i++)

{

GameObject roomPrefab =GameObject.Instantiate(roomListPrefab);

roomPrefab.transform.SetParent(roomGroup.transform);

roomPrefab.transform.localScale= Vector3.one;

UserData ud = udList[i];

roomPrefab.GetComponent<RoomList>().SetRoomInfo(ud.Id,ud.Username,ud.Totalcount,ud.Wincount,this);

2.  修改我们创建房间的时机,因为我们发起获得房间信息的时候应该是在点击创建房间的时候,因此我们需要将CreateRoomRequest放在我们的RoomListPanel下面,以防止重复申请,所以我们需要修改一下我们的CreateRoomRequest,使得它在外界设置它的RoomPanel,以防止被重复调用

23.处理房间的关闭

当我们的客户端断开服务器连接的时候,我们需要关闭我们的房间,并且将它移除出我们的roomList,这里牵扯到三个脚本之间的关系

1.在sever端创建移除房间的方法

publicvoid RemoveRoom(Roomroom)

{

if (roomList != null || room != null)

{

roomList.Remove(room);

}

}

2在room端,每次我们田间上房间后,我们都要指定上房主就是该房间,并且在关闭的时候我们要根据客户端移除该房间

publicvoid AddRoom(Clientclient)

{

roomClientlist.Add(client);

client.room = this;

}

publicvoid Close(Clientclient)

{

if(client==roomClientlist[0])

sever.RemoveRoom(this);

else

{

roomClientlist.Remove(client);

}

}

3.  在client端,当房间不为空的时候,当我们关闭时,就要移除该房间

publicvoid Close()

{

ConnHelper.CloseConnect(sqlConnection);

if (clientSocket != null)

{

clientSocket.Close();

sever.RemoverClient(this);

}

if (room != null)

{

room.Close(this);

}

}

24.处理加入房间的请求和服务器端的处理

首先我们需要添加一个新的Request来处理加入房间的请求了,当然这里我们先指定好它的requestCode和ActionCode了

publicoverridevoid Awake()

{

requestCode = RequestCode.Room;

actionCode = ActionCode.JoinRoom;

base.Awake();

}

publicvoid SendRequest(int id)

{

base.SendRequest(id.ToString());

}

publicoverridevoid OnResponse(string data)

{

}

接下来我们来处理我们的服务器端了,首先,我们在RoomController里创建我们的JoinRoom方法,这里的joinRoom会根据我们的id找到对应的room然后返回数据了,这里的话牵扯到以下几个脚本

publicstring JoinRoom(string data, Clientclient, Sever server)

{

int id = int.Parse(data);

//然后我们就需要在sever端根据id找到room

Room room = server.GetRoomById(id);

if (room == null)

{

return ((int)(ReturnCode.NotFound)).ToString();

}

elseif (room.IsWaitingForJoin()== false)

{

return ((int)(ReturnCode.Fail)).ToString();

}

else

{

string roomData = room.GetRoomUserData();

return  ((int)(ReturnCode.Success)).ToString()+"-"+roomData;

}

}

第一, 我们要在sever下提供一个脚本叫做getRoomByid,在这里我们会遍历我们的roomList,如果有匹配的id,代表找到了对应的房间,然后我们要怎么获得id呢?w

public RoomGetRoomById(int id)

{

foreach (Room room in roomList)

{

if (room.GetId()== id)

{

return room;

}

}

returnnull;

}

我们需要在room处加入一个脚本,当房间列表大于零的时候我们就要返回它的id了,然后我们的id要从roomClientList中获取,也就是我们的创建者的信息,

publicint GetId()

{

if (roomClientlist.Count> 0)

{

return roomClientlist[0].GetId();

}

else

{

return -1;

}

因此,我们要修改一下我们的client脚本,这里比较简单,直接返回用户的id即可

publicint GetId()

{

return user.Id;

}

然后我们找到房间后还要获得到创建者的信息,也就是战绩和名字,这里我们提供一个方法,通过遍历所有加入房间的客户端,然后组拼起所有获得的房主信息返回给我们发起请求的客户端

publicstring GetRoomUserData()

{

StringBuilder sb = new StringBuilder();

foreach(Client client in roomClientlist)

{

sb.Append(client.GetUserData()+ "|");

}

if (sb.Length > 0)

{

sb.Remove(sb.Length - 1, 1);

}

return sb.ToString();

}

25.在客户端处理加入房间的请求

这里首先我们要在joinRequest里来处理我们的从服务器收到的数据

在这里我们根据回传的returnCode来决定我们的操作,我们会调用我们RoomListPanel下的方法,对数据进行更新

publicoverridevoid OnResponse(string data)

{

string[] strs = data.Split('-');

ReturnCode returnCode =(ReturnCode)(int.Parse(strs[0]));

UserData ud1 = null;

UserData ud2 = null;

if (returnCode == ReturnCode.Success)

{

//如果判断加入成功,我们就要将取得的用户信息传递给房间列表

string[] userdataArray = strs[1].Split('|');

ud1 = new UserData(userdataArray[0]);

ud2 = new UserData(userdataArray[1]);

}

roomListPanel.OnJoinResponse(returnCode,ud1, ud2);

}

在这里我们用个switch来判断回调的code,如果成功的话,我们就会调用roompanel下的setData对我们的Ui面板进行更新

publicvoidOnJoinResponse(ReturnCode returnCode,UserData ud1,UserData ud2)

{

switch (returnCode)

{

case ReturnCode.NotFound:

uiMng.ShowMessageSync("房间不存在");

break;

case ReturnCode.Fail:

uiMng.ShowMessageSync("房间已满");

break;

case ReturnCode.Success:

BasePanelroomPanel=uiMng.PushPanel(UIPanelType.Room);

(roomPanel asRoomPanel).SetUserData(ud1, ud2);

break;

}

26.对于Debug的一些记录

在处理房间加入的时候,碰到了很多bug,所幸最后都解决了

1.加入房间是listRoomRequest中进行房间的重复加载,之前调用错了,当然,这个是小问题

2.BaseRequest里可能因为调用顺序的不同产生没有赋值的问题,这个的话我们用get方法构造一下它使他直接被赋值即可

protected GameFacade facade

{

get

{

if (_facade == null)

{

_facade = GameFacade._instance;

}

return _facade;

}

}

4.  我们申请房间列表的时候要遍历此时存储在服务器的客户端数量,因此来决定我们实例化房间列表的数量,这里也要修改一下

if (data!= "0")

{

string[] roomArray = data.Split('|');

foreach (string room in roomArray)

{

string[] strs = room.Split(',');

ud.Add(new UserData(int.Parse(strs[0]),strs[1], int.Parse(strs[2]),int.Parse(strs[3])));

}

1.              }

27.处理当其他人加入时候更新房间的请求

这一块功能只要理清楚逻辑就不难,首先我们要让服务器向我们的客户端广播放假加入这个消息,我们要处理的就是joinRoom这个方法

也就是当我们加入的时候就会广播方法,而这个广播我们在room里处理,也就是房间信息由room里面的userClient来决定

publicstring JoinRoom(string data, Clientclient, Sever server)

{

int id = int.Parse(data);

//然后我们就需要在sever端根据id找到room

Room room = server.GetRoomById(id);

if (room == null)

{

return ((int)(ReturnCode.NotFound)).ToString();

}

elseif (room.IsWaitingForJoin()== false)

{

return ((int)(ReturnCode.Fail)).ToString();

}

else

{

room.AddRoom(client);

string roomData = room.GetRoomUserData();

//在这里我们就可以更新不是房主的ui界面了

room.BroadCastMessgae(client,ActionCode.UpdateRoom, roomData);

return  ((int)(ReturnCode.Success)).ToString()+"-"+roomData;

}

}

在room的这个方法里我们会遍历我们的客户端列表,并且将消息发送给所有非房主的客户端

publicvoidBroadCastMessgae(Client excludeClient,ActionCode actionCode,string data)

{

foreach (Client client in roomClientlist)

{

if (client != excludeClient)

{

sever.SendMessage(client,actionCode, data);

}

}

}

接下来我们就要处理我们的客户端了,我们加入一个UpdataRoomRequest,用来处理服务器返回的数据即可

private RoomPanelroomPanel;

publicoverridevoid Awake()

{

requestCode = RequestCode.Room;

actionCode = ActionCode.UpdateRoom;

roomPanel =GetComponent<RoomPanel>();

base.Awake();

}

publicoverridevoid OnResponse(string data)

{

UserData ud1 = null;

UserData ud2 = null;

//如果判断加入成功,我们就要将取得的用户信息传递给房间列表

string[] userdataArray = data.Split('|');

ud1 = new UserData(userdataArray[0]);

ud2 = new UserData(userdataArray[1]);

roomPanel.SetUserData(ud1, ud2);

}

28.处理当房间退出时候房间信息的更新

1.  我们创建一个QuitRoomRequest来处理我们退出请求

private RoomPanelroomPanel;

publicoverridevoid Awake()

{

requestCode = RequestCode.Room;

actionCode = ActionCode.QuitRoom;

roomPanel =GetComponent<RoomPanel>();

base.Awake();

}

publicoverridevoid SendRequest()

{

base.SendRequest("r");

}

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

ReturnCode returnCode =(ReturnCode)(int.Parse(data));

if (returnCode == ReturnCode.Success)

{

//改变ui面板,弹出房间列表

roomPanel.OnExitClickResponse();

}

1.          }

2.  在服务器端,我们移除我们的房间,并且向所有的客户端广播我们的信息

publicstring QuitRoom(string data, Clientclient, Sever server)

{

//这里我们处理离开房间的请求,首先,我们要判断是否是房主

if (client.IsHouserOwner())

{

//如果是房主的话,就要销毁房间,退出房间列表

return ((int)(ReturnCode.NotFound)).ToString();

}

else

{

//如果是其他用户的话,我们就要移除这个客户端,并且向所有其他房间广播

Room room = client.room;

client.Room.RemoveClient(client);

room.BroadCastMessgae(client,ActionCode.UpdateRoom, room.GetRoomUserData());

return ((int)(ReturnCode.Success)).ToString();

}

1.          }

2.  我们修改一下直接ui更新的判断,并且在roomPanel类里面调用这个request即可

29.处理房主的退出

这里我们做一下微调即可,我们修改一下我们的Room,提供一个方法Close,当此时为房主时候,移除即可

publicvoid Close(Clientclient)

{

if(client==roomClientlist[0])

sever.RemoveRoom(this);

else

{

roomClientlist.Remove(client);

}

}

然后这里QuitRoom进行判断,如果此时是房主点击退出,我们就会广播其他客户端退出该房间,然后关闭我们的客户端

publicstring QuitRoom(string data, Clientclient, Sever server)

{

Room room = client.room;

//这里我们处理离开房间的请求,首先,我们要判断是否是房主

if (client.IsHouserOwner())

{

//如果是房主的话,就要销毁房间,退出房间列表

room.BroadCastMessgae(client,ActionCode.QuitRoom, ((int)(ReturnCode.Success)).ToString());

room.Close(client);

return ((int)(ReturnCode.Success)).ToString();

}

else

{

//如果是其他用户的话,我们就要移除这个客户端,并且向所有其他房间广播

client.Room.RemoveClient(client);

room.BroadCastMessgae(client,ActionCode.UpdateRoom, room.GetRoomUserData());

return ((int)(ReturnCode.Success)).ToString();

}

}

30.除了开始游戏的请求

这里比较简单,因为和之前类似,我就贴贴代码了

1.创建StartGameRequest

classStartGameRequest:BaseRequest

{

privateRoomPanel roomPanel;

publicoverridevoid Awake()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.Game;

actionCode = ActionCode.StartGame;

roomPanel =GetComponent<RoomPanel>();

base.Awake();

}

publicoverridevoid SendRequest()

{

base.SendRequest("r");

}

publicoverridevoid OnResponse(string data)

{

ReturnCode returnCode =(ReturnCode)(int.Parse(data));

roomPanel.OnStartGameResponse(returnCode);

}

2添加GameController和GameManager,一个是保存服务器处理Game类的方法,一个是客户端保存Game有关的数据

publicGameController()

{

requestCode = RequestCode.Game;

}

publicstring StartGame(string data, Clientclient, Sever server)

{

if (client.IsHouserOwner())

{

return ((int)(ReturnCode.Success)).ToString();

}

else

{

return ((int)(ReturnCode.Fail)).ToString();

}

}

}

3.  在roomPanel的回复响应那里根据returnCode进行判断

开始游戏就显示倒计时界面,不然就弹出不是房主

publicvoidOnStartGameResponse(ReturnCode returnCode)

{

if (returnCode == ReturnCode.Success)

{

//开始游戏

uiMng.PushPanelSync(UIPanelType.Game);

}

else

{

uiMng.ShowMessageSync("你不是房主");

}

1.          }

67.计时器的制作并且将计时信息同步到服务器

因为现实原因,客户端之间总会产生延迟,因此,我们要将计时放在服务器端进行,同步向客户端发送信息并且更新我们的UI,因此我们只要在点击开始游戏后向客户端广播信息即可

1.这个是我们的计时器面板

publicvoid Start()

{

timeText = transform.Find("Text").GetComponent<Text>();

timeText.gameObject.SetActive(false);

showTimeRequest = this.GetComponent<ShowTimeRequest>();

}

publicvoid ShowTime(int time)

{

timeText.gameObject.SetActive(true);

timeText.text = time.ToString();

timeText.transform.localScale =Vector3.one;

Color textAlpha = timeText.color;

textAlpha.a = 1;

timeText.color = textAlpha;

timeText.transform.DOScale(2,0.3f).SetDelay(0.3f);

timeText.DOFade(0,0.3f).SetDelay(0.3f);

facade.PlayNormalSound(AudioManager.sound_alert);

}

publicvoid ShowTimeSync(int time)

{

this.time = time;

}

privatevoid Update()

{

if (time > -1)

{

ShowTime(time);

time = -1;

}

}

}

2.这是我们的Request请求

classShowTimeRequest:BaseRequest

{

privateGamePanel gamePanel;

publicoverridevoid Awake()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.Game;

actionCode = ActionCode.ShowTime;

gamePanel =GetComponent<GamePanel>();

base.Awake();

}

publicoverridevoid OnResponse(string data)

{

int time = int.Parse(data);

gamePanel.ShowTimeSync(time);

}

}

3.这是服务器端处理发送时间的方法

publicstring StartGame(string data, Clientclient, Sever server)

{

if (client.IsHouserOwner())

{

client.room.StartTimmer();

return ((int)(ReturnCode.Success)).ToString();

}

else

{

return ((int)(ReturnCode.Fail)).ToString();

}

}

publicstring ShowTime(string data, Clientclient, Sever server)

{

int time = int.Parse(data);

return time.ToString();

}

}

31.控制人物移动

这里的话移动的脚本本身没什么写的,关键是注意一点,也就是我们这里控制的是我们动画状态机的Forward数值的变化,返回的res,决定了我们是平移还是奔跑

classPlayerMove: MonoBehaviour

{

privatefloat speed = 30f;

private Animator anim;

publicvoid Awake()

{

anim =GetComponent<Animator>();

}

publicvoid Update()

{

float h = Input.GetAxis("Horizontal");

float v = Input.GetAxis("Vertical");

transform.Translate(new Vector3(h, 0, v) *speed*Time.deltaTime, Space.World);

transform.rotation =Quaternion.LookRotation(new Vector3(h, 0, v));

float res = Mathf.Max(Mathf.Abs(h), Mathf.Abs(v));

anim.SetFloat("Forward", res);

}

1.      }

32.控制人物的攻击,这里的话,就是修改一下人物的动画播放状态,并且根据鼠标点击到自身的射线确定箭的位置

privateAnimator anim;

privateTransform handTransfrom;

publicGameObject arrowPrefab;

private Vector3shootDir;

privatevoid Awake()

{

//获得手的位置,将箭实例化在那里

anim =GetComponent<Animator>();

handTransfrom = transform.Find("Bip001/Bip001 Pelvis/Bip001 Spine/Bip001Neck/Bip001 L Clavicle/Bip001 L UpperArm/Bip001 L Forearm/Bip001 L Hand");

}

privatevoid Update()

{

if (anim.GetCurrentAnimatorStateInfo(0).IsName("Grounded"))

{

if (Input.GetMouseButtonDown(0))

{

RaycastHit hit;

Ray ray =Camera.main.ScreenPointToRay(Input.mousePosition);

bool isCollier = Physics.Raycast(ray, out hit);

if (isCollier)

{

//Vector3 point = hit.point;

//anim.SetTrigger("Attack");

//Shoot(point);

Vector3 targetPoint =hit.point;

targetPoint.y =transform.position.y;

shootDir = targetPoint- transform.position;

transform.rotation =Quaternion.LookRotation(shootDir);

anim.SetTrigger("Attack");

Shoot(targetPoint);

// Invoke("Shoot", 0.1f);

}

}

}

}

privatevoid Shoot(Vector3point)

{

point.y = transform.position.y;

Vector3 dir = point -transform.position;

GameObject.Instantiate(arrowPrefab,handTransfrom.position, Quaternion.LookRotation(dir));

}

1.      }

33.保存角色数据并且用PlayerManager来进行管理

我们的角色数据,包括弓箭,角色,角色类型

classRoleData

{

public  RoleType RoleType { get; privateset; }

public  GameObjectArrowPrefab { get; privateset; }

publicGameObject RolePrefab { get; privateset; }

publicRoleData(RoleType roleType,string arrowPath,string rolePath)

{

this.RoleType = roleType;

this.ArrowPrefab = Resources.Load(arrowPath) as GameObject;

this.RolePrefab = Resources.Load(rolePath) as GameObject;

}

}

我们的PlayerManager来进行管理

private UserData userData;

publicPlayerManager(GameFacade facade) : base(facade) { }

privateDictionary<RoleType, RoleData> roleDictionary = new Dictionary<RoleType, RoleData>();

publicUserData UserData

{

set

{

this.userData = value;

}

get

{

return userData;

}

}

publicvoidInitPlayerDictionary()

{

roleDictionary.Add(RoleType.Blue, new RoleData(RoleType.Blue, "Arrow_BLUE", "Hunter_BLUE"));

roleDictionary.Add(RoleType.Red, new RoleData(RoleType.Red, "Arrow_RED", "Hunter_RED"));

}

34.控制相机跟随并且在CameraManager里提供漫游和跟随切换的方法

1.跟随方法

public Transform targetTransform;

privatefloat smoothing = 2f;

private Vector3offset = newVector3(11.59f, 21.34f, -18.19f);

privatevoid Update()

{

transform.position= Vector3.Lerp(transform.position, targetTransform.position + offset,smoothing * Time.deltaTime);

transform.LookAt(targetTransform);

}

2.  漫游和切换的方法

这里我解释一下脚本,在这里我们调用切换到跟随的方法时候,我们得将漫游禁用掉,然后将目标设置到我们的角色,当然在此之前我们需要保存我们相机此时漫游到的位置,以保证下次切换视角相机在那个位置,并且当我们的相机动画完成后才会进行摄像机的跟随,这样就避免了跟随脚本会导致摄像机切换不流畅

然后在接下里启用漫游方法里,我们只要调用刚刚存放的摄像机位置,接着运动可以,当然要先禁用掉我们的跟随方法,不然就会出现位置跳转的bug

publicCameraManager(GameFacade facade) : base(facade) { }

//在这个脚本里,我们要管理摄像机的一些方法,比如说动画的切换

privateAnimator animator;

private FollowTarget followTarget;

privateGameObject cameraGameObject;

private Vector3oringalPosition;

private Vector3oringalRotation;

publicoverridevoid OnInit()

{

base.OnInit();

//在这里的话,我们会对一些组件进行初始化

cameraGameObject = Camera.main.gameObject;

animator = cameraGameObject.GetComponent<Animator>();

followTarget = cameraGameObject.GetComponent<FollowTarget>();

}

publicvoid FollowTarget()

{

//这里的话我们会将摄像机视角由漫游转变为关注目标

animator.enabled = false;

oringalPosition = cameraGameObject.transform.position;

oringalRotation = cameraGameObject.transform.eulerAngles;

Quaternion targetRotation =Quaternion.LookRotation(followTarget.targetTransform.position);

cameraGameObject.transform.DORotateQuaternion(targetRotation,1f).OnComplete(delegate ()

{

followTarget.enabled = true;

});

}

publicvoid CameraWalkAround()

{

followTarget.enabled = false;

cameraGameObject.transform.DOMove(oringalPosition,1f);

cameraGameObject.transform.DORotate(oringalRotation, 1f).OnComplete(delegate ()

{

animator.enabled = true;

});

}

35.修改PlayerManager完成角色的创建

这里首先我们要修改一下我们的roleData,创建角色信息的时候将角色的生成位置也保存起来,然后遍历我们的角色字典将角色实例化出来即可

publicoverridevoid OnInit()

{

base.OnInit();

playerTransform = GameObject.Find("RolePosition").transform;

}

publicvoid InitPlayerDictionary()

{

//在这里我们还要实例化出来我们的角色

roleDictionary.Add(RoleType.Blue, newRoleData(RoleType.Blue, "Arrow_BLUE", "Hunter_BLUE",playerTransform.Find("Position1")));

roleDictionary.Add(RoleType.Red, newRoleData(RoleType.Red, "Arrow_RED", "Hunter_RED", playerTransform.Find("Position2")));

}

publicvoid SpaswnPlayer()

{

foreach (RoleData rd in roleDictionary.Values)

{

GameObject.Instantiate(rd.RolePrefab, rd.SpawnPosition,Quaternion.identity);

}

}

36.服务器分配当前客户端控制的物体

我们分配角色是在哪里进行的呢?CreateRoomRequest和JoinRoomRequest这两个请求之中,我们在这里返回我们的角色类型,Create时候就返回我们的房间创建者蓝色角色,而join的时候就返回我们的红色角色给其他客户端,接下来的话,我们就要在PlayerManger来根据我们的创建的角色是不是当前角色来保存我们的角色类型了

publicvoid SpaswnPlayer()

{

foreach(RoleData rd inroleDictionary.Values)

{

GameObject go= GameObject.Instantiate(rd.RolePrefab, rd.SpawnPosition,Quaternion.identity);

if (rd.RoleType == currentRoleType)

{

currentGameObject = go;

}

}

}

当然了,我们也要在façade里提供一个getCurrentGamObject和setCurrentType,方便其他类来设置它们的角色类型和获得当前控制的角色

publicvoidSetCurrentRoleType(RoleType rd)

{

playerManager.SetCurrentRoleType(rd);

}

publicGameObject GetCurrentGameObjcet()

{

returnplayerManager.GetCurrentGameObjcet();

}

37.控制游戏开始后视野跟随

这里的话我们需要调用两个,第一,是CamerManager里的followTarget,第二,是PlayerManager里的GetCurrentRole,获得到当前的玩家

这个是遍历我们的角色字典,然后根据字典的信息实例化出来角色

publicvoid SpaswnPlayer()

{

foreach(RoleData rd inroleDictionary.Values)

{

GameObject go=GameObject.Instantiate(rd.RolePrefab, rd.SpawnPosition, Quaternion.identity);

if (rd.RoleType == currentRoleType)

{

currentGameObject = go;

}

}

}

这个是我们的房间面板的开始游戏点击响应,如果点击确定,就会调用façade里的方法,创建我们的游戏物体并且让它跟随。

publicvoidOnStartGameResponse(ReturnCode returnCode)

{

if (returnCode == ReturnCode.Success)

{

//开始游戏

uiMng.PushPanelSync(UIPanelType.Game);

facade.EnterPlayingSync();

}

else

{

uiMng.ShowMessageSync("你不是房主");

}

}

38.为我们的角色添加控制脚本

这里说一下思路

1.  我们开始控制是在倒计时结束完成后添加,所以我们会在倒计时结束后向所有客户端广播一次startPlay,然后再给我们的角色添加控制脚本,下面是牵涉到的脚本

这个是向客户端广播消息

2.    publicvoid RunTime()

3.          {

4.              for(int i = 3; i > 0; i--)

5.              {

6.                  Thread.Sleep(1000);

7.                  BroadCastMessgae(null,ActionCode.ShowTime, i.ToString());

8.

9.

10.

11.            }

12.            Console.WriteLine("计时结束");

13.            BroadCastMessgae(null,ActionCode.StartPlay, "r");

14.        }

这个是响应服务器发起的请求开始添加脚本

privateRoomPanel roomPanel;

publicoverridevoid Awake()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.Game;

actionCode = ActionCode.StartGame;

roomPanel =GetComponent<RoomPanel>();

base.Awake();

}

publicoverridevoid SendRequest()

{

base.SendRequest("r");

}

publicoverridevoid OnResponse(string data)

{

ReturnCode returnCode =(ReturnCode)(int.Parse(data));

roomPanel.OnStartGameResponse(returnCode);

1.          }

这里是我们的客户端根据roleType和roleData添加我们的脚本,并且指定对应的prefab然后这个方法会在façade里提供以便于其他脚本调用

privateRoleData GetRoleData(RoleType roleType)

{

RoleData rd;

roleDictionary.TryGetValue(roleType, out rd);

return rd;

}

publicvoid AddComponentController()

{

PlayerAttack playerAttack=currentGameObject.AddComponent<PlayerAttack>();

PlayerMove playerMove=currentGameObject.AddComponent<PlayerMove>();

PlayerInfo playerInfo =currentGameObject.GetComponent<PlayerInfo>();

RoleData rd =GetRoleData(playerInfo.roleType);

playerAttack.arrowPrefab = rd.ArrowPrefab;

Debug.Log("添加了脚本");

1.      }

39.创建游戏物体管理我们的位置信息并且将它发送给服务器端,再由服务器端转发给其他客户端

这里的话我们创建一个新的requet叫做MoveRequest,然后在每次开始游戏,添加完控制脚本后就会创建一个游戏物体来实时更新游戏数据,这里的话我就把我们的游戏物体控制脚本贴一下吧

这里的话我们将组拼好的字符串发送给我们的服务器端,通过-和,进行分割,那么服务器怎么处理我们下节再讲

classMoveRequest:BaseRequest

{

publicoverridevoid Awake()

{

requestCode = RequestCode.Game;

actionCode = ActionCode.Move;

base.Awake();

}

public  void SendRequest(float x,float y,float z,float rotationx,float rotationy,float rotationz,float forward)

{

string data = string.Format("{0},{1},{2}-{3},{4},{5}-{6}", x, y, z, rotationx, rotationy, rotationz, forward);

base.SendRequest(data);

}

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

}

}

40.服务器端向其他客户端发送消息

这里的话我们希望将位置信息同步给其他客户端,由这个MoveRequest来处理,首先我们要获得这个当前角色和他身上的PlayerMove组件,因此我们提供一个方法

publicvoidSetLocalPlayer(Transform playerTransform, PlayerMove playerMove)

{

this.playerTransform = playerTransform;

this.playerMove = playerMove;

}

然后我们在创建完主角,添加完脚本和request之后来设置它的值

publicvoid CreateMoveSync()

{

moveController = new GameObject("MoveController");

moveController.AddComponent<MoveRequest>().SetLocalPlayer(currentGameObject.transform,currentGameObject.GetComponent<PlayerMove>());

}

设置完值以后我们就可以向服务器发送数据了,发送的次数我们可以用InvokeRepeating来确定

privatevoid Start()

{

InvokeRepeating("SetPlayerSync", 0, 1f/ syncRepeating);

}

privatevoid SetLocalSync()

{

SendRequest(playerTransform.position.x, playerTransform.position.y,playerTransform.position.z,

playerTransform.eulerAngles.x,playerTransform.eulerAngles.y, playerTransform.eulerAngles.z,playerMove.forward);

}

然后我们在服务器的GameController下的move方法来分发传递过来的数据

publicstring Room(string data, Clientclient, Sever server)

{

Room room = client.room;

room.BroadCastMessgae(client,ActionCode.Move, data);

returnnull;

}

41.在其他客户端同步位置信息

这里的话核心思路就是一个,将本地位置信息组拼,发送给其他的客户端,其他客户端再解析,获得到当前控制的角色,并且将信息同步给该角色即可

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

//这里我们要分发的数据是我们的位置信息,我们的旋转角度,我们动画的forward

string[] strs = data.Split('|');

Pos = Parse(strs[0]);

eularAngles = Parse(strs[1]);

forward = float.Parse(strs[2]);

isSetRemotePlayer = true;

}

private Vector3Parse(string data )

{

//这个脚本里,我们要分割那些以逗号为间隔的脚本

string[] strs = data.Split(',');

float x = float.Parse( strs[0]);

float y = float.Parse(strs[1]);

float z = float.Parse(strs[2]);

returnnew Vector3(x, y, z);

}

publicvoidSetRemotePlayerSync()

{

remoteTransform.transform.position= Pos;

remoteTransform.transform.eulerAngles =eularAngles;

remoteAnimator.SetFloat("Forward",forward);

}

publicoverridevoid Awake()

{

requestCode = RequestCode.Game;

actionCode = ActionCode.Move;

base.Awake();

}

privatevoid Update()

{

if (isSetRemotePlayer)

{

SetRemotePlayerSync();

isSetRemotePlayer = false;

}

}

我们获得角色自然是放在PlayerManager来执行

publicvoid CreateMoveSync()

{

moveController = new GameObject("MoveController");

moveController.AddComponent<MoveRequest>().SetRemotePlayer(remotePlayerGameObject.transform,remotePlayerGameObject.GetComponent<Animator>()).SetLocalPlayer(currentGameObject.transform,currentGameObject.GetComponent<PlayerMove>());

}

publicvoid SpaswnPlayer()

{

foreach(RoleData rd inroleDictionary.Values)

{

GameObject go= GameObject.Instantiate(rd.RolePrefab, rd.SpawnPosition,Quaternion.identity);

if (rd.RoleType == currentRoleType)

{

currentGameObject = go;

}

else

{

remotePlayerGameObject = go;

}

}

}

42.服务器转发箭的位置信息

首先我们将箭的实例化放在PlayerManager进行管理,然后我们每次射击的时候,就会将我们的箭的各种信息同步给其他客户端,因此和同步位置信息一样,这里我们创建一个ShootRequest来控制信息发送和处理

classShootRequest:BaseRequest

{

publicoverridevoid Awake()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.Game;

actionCode = ActionCode.Shoot;

base.Awake();

}

publicvoidSendRequest(RoleType rt,Vector3 pos,Vector3 rotation)

{

string data = string.Format("{0}|{1},{2},{3}|{4},{5},{6}", rt, pos.x, pos.y, pos.z, rotation.x, rotation.y,rotation.z);

base.SendRequest(data);

}

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

}

}

然后同样的我们在服务器端直接将这个信息转发给其他客户端即可

publicstring Shoot(string data, Clientclient, Sever server)

{

if (client.room != null)

{

client.room.BroadCastMessgae(client, ActionCode.Shoot, data);

}

returnnull;

}

43.在客户端同步我们箭的位置信息

首先要解析我们接收到的服务器端的数据

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

//这里接收到数据,我们就要将数据进行解析,并且传递给PlayerManager去创建

string[] strs = data.Split('|');

roleType = (RoleType)(int.Parse(strs[0]));

pos = UnityTools.Parse(strs[1]);

rotation=UnityTools.Parse(strs[2]);

//调用playerManager里的RemoteArrow去创建

playerManager.RemoteShoot(roleType,pos, rotation);

1.          }

然后我们每次射箭的时候就实例化箭然后发起请求,然后接收到请求就会在其他客户端实例化这个箭并且根据解析的数据设置箭的位置

publicvoid Shoot(GameObject arrowPrefab,TransformhandTransform,Vector3 shootDir)

{

GameObject.Instantiate(arrowPrefab, handTransform.position,Quaternion.LookRotation(shootDir));

shootRequest.SendRequest(arrowPrefab.GetComponent<ArrowFly>().roleType,handTransform.position, shootDir);

}

publicvoidRemoteShoot(RoleType roleType, Vector3 handPos, Vector3 shootDir)

{

GameObject arrowPrefab = GetRoleData(roleType).ArrowPrefab;

Transform transform =arrowPrefab.GetComponent<Transform>().transform;

transform.position = handPos;

transform.eulerAngles = shootDir;

}

44.创建箭爆炸的特效

这里比较简单,我就概述一下

1.  创建一个Partile System,这个可以简单的做一些特效,这里的话各位可以根据个人需求制作不同的效果

2.  我们修改一下RoleData,让PlayerManager在实例化人物数据的时候,把爆炸特效也复制给对应的物体

publicRoleData(RoleType roleType,string arrowPath,string rolePath,string explositionPath,Transform spawnPosition)

{

this.RoleType = roleType;

this.ArrowPrefab = Resources.Load(FIXED_PRE+ arrowPath) as GameObject;

this.RolePrefab = Resources.Load(FIXED_PRE+ rolePath) as GameObject;

this.ExplosionPrefab = Resources.Load(FIXED_PRE +explositionPath) asGameObject;

ArrowPrefab.GetComponent<ArrowFly>().arrowEffect= ExplosionPrefab;

this.SpawnPosition = spawnPosition.position;

1.          }

2.  在playerManager里面书写一下路径就可以了,当然检测碰撞啊,销毁什么的,用unity

自带的OnTriggerEnter检测一下再判断实例化位置并且销毁即可,那么到这里,爆炸特效也算做完了

45.播放爆炸音效

这里就一句话概括吧,在创建物体时添加tag,然后检测碰撞时根据tag调用façade里事先写好的播放音效的方法

46.将我们的伤害发送给我们的服务器端

我们处理碰撞是在ArrowFly里的OntreggerEnter来进行

这里经过两层判断,第一层是否本地发射,这里我们可以在射击的时候讲本地射击设置为true,第二个判断是是否碰撞到的是自身,这个我们可以给playerInfo一个属性是否自身,并且在实例化的时候设置,这样我们获取到碰撞物体的时候就可以根据它挂在的playerInfo判断是否是自身

privatevoidOnTriggerEnter(Collider collider)

{

//实例化爆炸特效

if (collider.tag == "Player")

{

GameFacade._instance.PlayNormalSound(AudioManager.sound_ShootPerson);

//在这里我们需要进行一个判断,当这个攻击是本地发出的并且攻击到的是敌方玩家,我们就造成伤害

if (isLocalAttack)

{

bool isLocalPlayer =collider.GetComponent<PlayerInfo>().isLocalPlayer;

if (isLocalPlayer == false)

{

//我们就要发送我们的伤害了

GameFacade._instance.TakeDamage(UnityEngine.Random.Range(10,20));

}

}

}

,接下来我们就要向其他客户端发送伤害数据,这个发送我们新建一个TakeDamage来进行处理,我们直接转发数据就可以,那么服务器处理我们下一节讲

publicoverridevoid Awake()

{

//指定好LoginRequest中的requestCode和actionCode

requestCode = RequestCode.Game;

actionCode = ActionCode.TakeDamage;

base.Awake();

}

publicvoid SendRequest(int damage)

{

base.SendRequest(damage.ToString());

}

47.在服务器端处理收到伤害

这里我们新建一个类TakeDamage来处理对于伤害的转发

我们在这里会调用room里的TakeDamage来进行判断

publicstring TakeDamage(string data, Clientclient, Sever server)

{

//这里我们来处理收到伤害后对于游戏角色的处理

int damage = int.Parse(data);

Room room = client.Room;

if (room == null) returnnull;

room.TakeDamage(damage, client);

returnnull;

}

首先我们需要在client类里初始化一下我们人物的血量还有判断是否死亡

privateint hp;

publicint HP

{

get;set;

}

publicbool TakeCost(int damage)

{

HP -= damage;

hp = HP;

if (hp <= 0) returntrue;

elsereturnfalse;

}

publicbool IsDie()

{

return hp <= 0;

}

然后我们在room里处理收到伤害的方法,我们每次扣血会调用client里的方法,如果死亡的话我们就会遍历所有的客户端并且向所有的客户端发送死亡的消息并且关闭掉我们的游戏

publicvoid TakeDamage(int damage,ClientexcludeClient)

{

foreach(Client client in roomClientlist)

{

if (client != excludeClient)

{

if (client.TakeCost(damage))

{

isDie = true;

}

}

}

if (isDie == false) return;

//处理死亡结果

foreach(Client client in roomClientlist)

{

if (client.IsDie())

{

client.SendMessage(ActionCode.GameOver, ((int)ReturnCode.Fail).ToString());

}

else

{

client.SendMessage(ActionCode.GameOver, ((int)ReturnCode.Success).ToString());

}

}

Close();

}

48.客户端对于伤害进行响应并且显示胜利失败的画面

这个ui部分我就不讲了,这里也比较简单,我们创建一个GameOverRequest,上节我们向客户端发送了我们的胜利失败信息,然后我们在GamePanel处理胜利失败消息的显示

classGameOverRequest:BaseRequest

{

privateGamePanel gamePanel;

privatebool isGameOver = false;

privateReturnCode returnCode;

publicoverridevoid Awake()

{

actionCode = ActionCode.GameOver;

gamePanel =GetComponent<GamePanel>();

base.Awake();

}

privatevoid Update()

{

if (isGameOver)

{

gamePanel.OnGameOverResponse(returnCode);

isGameOver = false;

}

}

publicoverridevoid OnResponse(string data)

{

base.OnResponse(data);

//在这里我们会根据我们客户端发送过来的信息决定游戏是胜利还是失败

returnCode = (ReturnCode)(int.Parse(data));

isGameOver = true;

}

}

这个就是响应的方法了,会把画面显示出来

publicvoidOnGameOverResponse(ReturnCode returnCode)

{

if (returnCode == ReturnCode.Fail)

{

failBtn.gameObject.SetActive(true);

}

else

{

successBtn.gameObject.SetActive(true);

}

}

49.在客户端处理游戏结果

嗯,在这里笔者差不多debug了两小时改之前的问题,这里就权且记一下思路了,游戏胜利后我们就要点击胜利按钮和失败按钮pop一下我们的panel,然后删除游戏物体,清空我们的request就行,这个GamerOver自然是注册在我们的胜利和失败按钮的点击事件上了

publicvoid GameOver()

{

//   private GameObject moveController;

//private ShootRequest shootRequest;

//private GameObjectremotePlayerGameObject;

//private GameObject currentGameObject;

GameObject.Destroy(moveController);

GameObject.Destroy(currentGameObject);

GameObject.Destroy(remotePlayerGameObject);

takeDamageRequest = null;

shootRequest = null;

}

50.在服务器更新我们的战绩信息

正所谓把大象装进冰箱,三步即可,第一,在游戏胜利的时候发起更新数据的请求

第二, 在client类里提供根据胜利失败操作数据的方法,第三,在resultDao里提供操作数据的方法

1.

foreach(Client client in roomClientlist)

{

if (client.IsDie())

{

//首先我们确定一下,我们是在这里更新我们的战绩信息,也就是我们会根据我们的结果的胜败发起更新数据库的请求,而数据库的更新

//我们会在client端进行,获得到我们的resultDao的信息,执行操作

client.UpdateResult(false);

client.SendMessage(ActionCode.GameOver, ((int)ReturnCode.Fail).ToString());

Console.WriteLine("发送死亡消息");

}

else

{

client.UpdateResult(true);

client.SendMessage(ActionCode.GameOver, ((int)ReturnCode.Success).ToString());

}

}

2.

publicvoid UpdateResult(bool isVictory)

{

result.Totalcount++;

//这里我们根据我们的bool值判断是怎么添加

if (isVictory == false)

{

//代表失败了,我们就totalCount+1,winCount不加一,而这个更新的信息我们怎么获取呢,我们首先获得查询的数据也就是result

//在这里我们的的result已经在登录的时候就被设置了,因此我们可以直接修改它的值,然后将这个值用查询语句插入进

}

else

{

result.Wincount++;

}

resultDao.UpdateResult(sqlConnection,result);

}

3.

try

{

MySqlCommand cmd = null;

if (result.Id <= -1)

{

//这个代表不存在,我们要进行插入操作

cmd = new MySqlCommand("insert into result settotal_count=@total_count,win_count=@win_count,userid=@userid", conn);

}

else

{

//存在的话我们就做更新操作

cmd = new MySqlCommand("update result settotal_count=@total_count,win_count=@win_count where userid=@userid", conn);

}

//我们进行插值操作

cmd.Parameters.AddWithValue("total_count",result.Totalcount);

cmd.Parameters.AddWithValue("win_count",result.Wincount);

cmd.Parameters.AddWithValue("userid",result.UserId);

cmd.ExecuteNonQuery();

if (result.Id <= -1)

{

//如果此时id还为-1我们就在数据库种检测是否存在,如果存在的话我们就更新它的id防止重复操作

Result tempResult =VerifyResult(conn, result.UserId);

result.Id = tempResult.Id;

}

}

catch (Exception e)

{

Console.WriteLine("更新战绩出现异常" + e);

}

51.在客户端处理战绩的更新

在这里的话比较简单,我们就在服务器更新的同时,向客户端发送一个request要求更新数据,然后这个操作我们放在PlayerManager来更新我们的UserData,因为roomListPanel就是根据我们的UserData来决定我们的面板数据,然后更新面板的操作我们放在我们的UpdateRequest来进行即可

1.  发送消息

这里的result是在数据库更新完后的result

string data = string.Format("{0},{1}",result.Totalcount, result.Wincount);

SendMessage(ActionCode.UpdateResult, data);

2.这里是我们的updateRequest里面的请求了,还有一些是playerManager里设置更新userDao和façade里提供接口,因为比较简单并且重复,这里就不赘述了

privateRoomListPanel roomListPanel;

privatebool isUpdateResult=false;

privateint totalCount;

privateint winCount;

publicoverridevoid Awake()

{

actionCode =ActionCode.UpdateResult;

roomListPanel =GetComponent<RoomListPanel>();

base.Awake();

}

privatevoid Update()

{

if (isUpdateResult)

{

roomListPanel.ShowUser();

isUpdateResult = false;

}

}

publicoverridevoid OnResponse(string data)

{

string[] strs= data.Split(',');

totalCount = int.Parse(strs[0]);

winCount = int.Parse(strs[1]);

Facade.UpdateResult(totalCount,winCount);

isUpdateResult = true;

}

52.做一个退出游戏的优化

这里咱也是讲一下思路1.在GameControoler提供一个QuitBattle的方法算作退出

这里的话会向客户端广播退出消息并且关闭,然后创建一个request叫做QuitBattleRequest,在GamePanel下点击退出时发送请求并且调用之前的结束游戏方法即可

classQuitBattleRequest:BaseRequest

{

privatebool isQuitBattle = false;

privateGamePanel gamePanel;

publicoverridevoid Awake()

{

requestCode = RequestCode.Game;

actionCode = ActionCode.QuitBattle;

gamePanel = GetComponent<GamePanel>();

base.Awake();

}

publicoverridevoid SendRequest()

{

base.SendRequest("r");

}

privatevoid Update()

{

if (isQuitBattle)

{

Debug.Log("退出被调用");

gamePanel.OnExitResponse();

isQuitBattle = false;

}

}

publicoverridevoid OnResponse(string data)

{

isQuitBattle = true;

}

}

privatevoid OnExitClick()

{

Debug.Log("点击退出按钮");

quitBattleRequest.SendRequest();

}

publicvoid OnExitResponse()

{

OnResultClick();

}

53.关于数据库的备份还原

这里就记一句话了,Mysql里的dataBaseExport能够解析出一个sql文件,导入的时候再导入这个sql文件即可

54.最后的最后改一下我们的服务器配置

这里的话我学习了一下,我就看了看怎么样配置公网的思路,其他就不多说了,这里以阿里云为例

1.  我们在阿里云购买一个服务器,它会提供两个ip地址,一个公网ip地址,一个私网地址,公网地址我们设置在clientManager,私网地址我们设置在clientManager里面,然后我们设置一下我们的安全组,这里的话我们入就是从6688的端口进行的,面向ip就是0.0.0.0/1,tcp连接

2.  我们设置好我们的ip地址后我们把我们vs的服务器端生成一下,然后放在我们的远程服务器,这里我们购买好以后,设置好我们呢服务器的账户名和密码,然后用mstsc就可以打开远程服务器的连接,在这里,我们安装两个1.mysql,并且将我们本地的sql文件导入到那边去解析,这里要安装的只是核心功能的mysql,包括一个sever和core的就行2.我们服务器端发布的那个exe文件,我们在远程服务器去打开它,然后不要关掉,只关掉我们的连接就行,这样,我们相当于在远程服务器永久的打开了我们的服务器端,接下来的话我们就可以用客户端去连接这个服务器了

游戏开发,丛林战争3相关推荐

  1. 游戏开发-丛林战争制作2

    20.如何解析我们客户端的信息并且交给ControllerManager来进行处理 首先是Message类,参数分别是数据的长度和一个回掉的函数委托,用来重复解析数据,这里的话前四个是数据长度,后面4 ...

  2. unity网络实战开发(丛林战争)-前期知识准备(012-UI框架开发)

    使用工具:VS2017,Unity2017.3,DoTween插件 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解 ...

  3. Siki_Unity_4-4_丛林战争_Socket/TCP网络游戏开发

    Unity 4-4 丛林战争(Socket/TCP网络游戏开发) 任务1:素材.演示.Prerequisite 使用c#的有关TCP的底层API进行服务器端的开发(直接通过socket进行通信) 功能 ...

  4. 游戏制作-联网对战丛林战争制作笔记(一)

    开篇先说明,这个游戏制作也是我跟随别人的教程制作的游戏,因此想要了解更多的内容可以去看siki老师的视频,我这里做笔记的目的有两个,一个是帮助喜欢看文字版教程的朋友进一步的学习,一个是保存自己在学习中 ...

  5. unity网络实战开发(丛林战争)-正式开发阶段(014-游戏客户端与服务器端连接搭建)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,我已经把服务器端的框架进行了搭建,接下来, ...

  6. 网络游戏《丛林战争》开发与学习之(一):网络编程的基础知识

    <丛林战争>是一款完整的网络游戏案例,运用U3D开发客户端,Socket开发服务端,其中涉及到了网络编程.数据库和Unity的功能实现,之前通过U3D开发了一个单机游戏<黑暗之光&g ...

  7. unity网络实战开发(丛林战争)-正式开发阶段(013-游戏服务器端框架搭建)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下游戏服务器端的开发. 01-项目目录结构 ...

  8. 《C++游戏开发》笔记十四 平滑过渡的战争迷雾(二) 实现:真正的迷雾来了

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9712321 作者:七十一雾央 新浪微博:http:// ...

  9. 《C++游戏开发》笔记十三 平滑过渡的战争迷雾(一) 原理:Warcraft3地形拼接算法...

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9611887 作者:七十一雾央 新浪微博:http:// ...

  10. 《C++游戏开发》笔记十二 战争迷雾:初步实现

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9475979 作者:七十一雾央 新浪微博:http:// ...

最新文章

  1. 转:MySQL性能优化神器Explain使用分析
  2. ssh登录虚拟机上的linux
  3. python time.time和time.clock_Python中time.clock和 time.time的对比探究
  4. MySQL连不上,报Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'
  5. 微信现金红包接口实现红包发放
  6. redis java客户端配置,Java的Redis客户端选择-jedis与Lettuce
  7. 机器学习:SVM训练,SMO算法描述,启发式选择样本或变量
  8. loopback的作用
  9. hiredis封装事务示例
  10. 全局使用dva dispatch
  11. 导出文件_一招解决PDF文件导出图片
  12. 深度强化学习在时序数据压缩中的应用--ICDE 2020收录论文
  13. win10计算机错误代码,Win10错误代码:0xc00000f 解决方案
  14. Android FrameLayout和AbsoluteLayout示例教程
  15. datax运行无法加载主类
  16. springboot项目访问jsp文件
  17. CCF ChinaSoft 2022预告丨形式化方法工业应用前沿分论坛 暨中科国创高可信联合上海控安新品发布...
  18. 通过RSRP和SINR判断LTE信号质量
  19. 今天开通了CNSD博客
  20. 【Spark2运算效率】第四节 影响生产集群运算效率的原因之数据倾斜

热门文章

  1. Linux C高阶(14)C语言宏定义你所不知道的事
  2. 计算机房改造简报,某单位机房改造方案
  3. 计算机专业必备的英语词汇,计算机专业必备英语词汇
  4. 爆款的诞生:《胡闹厨房2》的多人游戏模式解决方案
  5. 分辨mqtt在线与离线_最全视频下载方案,100%下载所有在线视频!
  6. 详解朴素贝叶斯分类算法
  7. Matlab:绘制琼斯矩阵的偏振图像
  8. AD7799称重系统
  9. 《工程电磁场》学习笔记4-时变电磁场
  10. 采访:来自于公众号的干货资料,助力兄弟斩获36W年薪岗!