文章目录

  • 第一版角色类Human
    • BaseHuman
    • CtrlHuman
    • SyncHuman
  • 通信协议
  • 消息队列
  • NetManager类
  • 加入网络功能
    • 消息处理
  • 第二版角色类
    • CtrlHuman
    • SyncHuman
    • Main(客户端)
  • 服务端的处理
    • MsgHandler
    • EventHandler
    • MainClass(服务端)
  • 总结一下

这章主要是实现一个大乱斗的游戏,搭场景部分省略,人物动作状态机省略。

第一版角色类Human

BaseHuman

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BaseHuman : MonoBehaviour {//是否正在移动internal bool isMoving = false;//移动目标点private Vector3 targetPosition;//移动速度public float speed = 1.2f;//动画组件private Animator animator;//是否正在攻击internal bool isAttacking = false;internal float attackTime = float.MinValue;//描述public string desc = "";//移动到某处public void MoveTo(Vector3 pos){targetPosition = pos;isMoving = true;animator.SetBool("isMoving", true);}//移动Updatepublic void MoveUpdate(){if(isMoving == false) {return;}Vector3 pos = transform.position;transform.position = Vector3.MoveTowards(pos, targetPosition, speed*Time.deltaTime);transform.LookAt(targetPosition);if(Vector3.Distance(pos, targetPosition) < 0.05f){isMoving = false;animator.SetBool("isMoving", false);}}//攻击动作public void Attack(){isAttacking = true;attackTime = Time.time;animator.SetBool("isAttacking", true);}//攻击Updatepublic void AttackUpdate(){if(!isAttacking) return;if(Time.time - attackTime < 1.2f) return;isAttacking = false;animator.SetBool("isAttacking", false);}// Use this for initializationinternal void Start () {animator = GetComponent<Animator>();}// Update is called once per frameinternal void Update () {MoveUpdate();AttackUpdate();}
}

BaseHuman作为基类,这部分主要实现的逻辑是,给定一个目标地点,那么人物就会朝目标地点移动,不涉及任何网络部分。

CtrlHuman

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CtrlHuman : BaseHuman {// Use this for initializationnew void Start () {base.Start();}// Update is called once per framenew void Update () {base.Update();//移动if(Input.GetMouseButtonDown(0)){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;Physics.Raycast(ray, out hit);if (hit.collider.tag == "Terrain"){MoveTo(hit.point);} }}
}

暂时也不涉及网络部分,主要逻辑是鼠标点击到Terrain,玩家就会向点击的地方移动。

SyncHuman

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SyncHuman : BaseHuman {// Use this for initializationnew void Start () {base.Start();}// Update is called once per framenew void Update () {base.Update();}
}

暂时不做逻辑处理。

通信协议

目前的通信协议采用字符串协议,形式如下
消息名|参数1,参数2,参数3,...
例如Move|127.0.0.1:1234,10,0,8可以代表127.0.0.1:1234这位用户向(10,0,8)坐标移动。

消息队列

将通信记录保存下来,采用数据结构List<String>,主要是在Update里面读取消息处理。

NetManager类

辅助方法类,便于客户端对网络的操作。
主要提供三个主要接口

  • Connect 发起连接
  • AddListener 消息监听
  • Send 发送消息给服务端
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;
using System;public static class NetManager {//定义套接字static Socket socket;//接收缓冲区static byte[] readBuff = new byte[1024]; //委托类型public delegate void MsgListener(String str);//监听列表private static Dictionary<string, MsgListener> listeners = new Dictionary<string, MsgListener>();//消息列表static List<String> msgList = new List<string>();//添加监听public static void AddListener(string msgName, MsgListener listener){listeners[msgName] = listener;}//获取描述public static string GetDesc(){if(socket == null) return "";if(!socket.Connected) return "";return socket.LocalEndPoint.ToString();}//连接public static void Connect(string ip, int port){//Socketsocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);//Connectsocket.Connect(ip, port);//BeginReceivesocket.BeginReceive( readBuff, 0, 1024, 0,ReceiveCallback, socket);}//Receive回调private static void ReceiveCallback(IAsyncResult ar){try {Socket socket = (Socket) ar.AsyncState;int count = socket.EndReceive(ar);string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);msgList.Add(recvStr);socket.BeginReceive( readBuff, 0, 1024, 0,ReceiveCallback, socket);}catch (SocketException ex){Debug.Log("Socket Receive fail" + ex.ToString());}}//点击发送按钮public static void Send(string sendStr){if(socket == null) return;if(!socket.Connected)return;byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);}//Send回调private static void SendCallback(IAsyncResult ar){try {Socket socket = (Socket) ar.AsyncState;//int count = socket.EndSend(ar);}catch (SocketException ex){Debug.Log("Socket Send fail" + ex.ToString());}}//Updatepublic static void Update(){if(msgList.Count <= 0)return;String msgStr = msgList[0];msgList.RemoveAt(0);string[] split = msgStr.Split('|');string msgName = split[0];string msgArgs = split[1];//监听回调;if(listeners.ContainsKey(msgName)){listeners[msgName](msgArgs);}}
}

再解释一下上面的代码,首先用到了delegate,不解释。用private static Dictionary<string, MsgListener> listeners = new Dictionary<string, MsgListener>();表示了字符串和要调用函数之间的映射关系,这个功能很强大,在Update函数中的最后一个判断就用到了这个映射关系

if(listeners.ContainsKey(msgName)){listeners[msgName](msgArgs);}

客户端从服务端接收消息解析之后,比如Move|127.0.0.1:1234,10,0,8解析之后得到的msgName = split[0],实际得到的就是Move这个字符串,然后listeners[msgName]就代表了一个delegate,再给delegate传递参数msgArgs,相当于给相应的函数传递参数,下面更详细说一下过程。

 public static void AddListener(string msgName, MsgListener listener){listeners[msgName] = listener;}

AddListener,相当于事先自定了字符串到委托函数的映射,比如一开始调用了NetManager.AddListener("Move",OnMove),那么字符串MoveOnMove函数就存在着映射关系,这时候调用这一行代码listeners[msgName](msgArgs);msgName实际上就是MovemsgArgs实际上就是127.0.0.1:1234,10,0,8,而listeners[msgName]就是listeners["Move"],也就是OnMove函数,所以最后结果就是调用了OnMove(127.0.01:1234,10,0,8)函数。那么就实现了从服务端接收一个字符串,就在客户端调用相应函数的功能,方便服务端给客户端发消息。
最后图解一下整个过程。

加入网络功能

最直白的实现的效果是,有多个客户端同时启动,当你移动的时候,别人也可以看到你在移动,当你攻击的时候,相应的逻辑也正确,攻击传入的参数是方向,而不是一个玩家。

消息处理

因为要显示别的玩家,所以用一个数据结构来存储其他玩家public Dictionary<string, BaseHuman> otherHumans = new Dictionary<string, BaseHuman>();
客户端主要处理六个信息,玩家一开始会发送Enter消息告知服务端自己的位置以及角度,再发一个空的List消息用来查询玩家信息。

  • Enter 接受服务端的信息,类似于Enter|127.0.0.1:4564,3,0,5,0一个消息的名称,加上玩家的唯一表示,加上位置的信息,再加上一个欧拉角,判断如果不是自己,那么就利用信息,生成一个GameObject,并且在otherHumans中加入他。
//客户端
void OnEnter (string msgArgs) {Debug.Log("OnEnter " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);float eulY = float.Parse(split[4]);//是自己if(desc == NetManager.GetDesc())return;//添加一个角色GameObject obj = (GameObject)Instantiate(humanPrefab);obj.transform.position = new Vector3(x, y, z);obj.transform.eulerAngles = new Vector3(0, eulY, 0);BaseHuman h = obj.AddComponent<SyncHuman>();h.desc = desc;otherHumans.Add(desc, h);}
//服务端
public static void MsgEnter(ClientState c, string msgArgs){//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);float eulY = float.Parse(split[4]);//赋值c.hp = 100;c.x = x;c.y = y;c.z = z;c.eulY = eulY;//广播string sendStr = "Enter|" + msgArgs;foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}
  • List 查询所有玩家,所以消息中包含多个值,类似List|127.0.0.1:4564,3,0,5,0,100,127.0.0.1:4578,4,0,9,0,100这里多了一个参数用作HP。如果不是自己的话,就生成一个GameObject,并且放到otherHumans中。
//客户端
void OnList (string msgArgs) {Debug.Log("OnList " + msgArgs);//解析参数string[] split = msgArgs.Split(',');int count = (split.Length-1)/6;for(int i = 0; i < count; i++){string desc = split[i*6+0];float x = float.Parse(split[i*6+1]);float y = float.Parse(split[i*6+2]);float z = float.Parse(split[i*6+3]);float eulY = float.Parse(split[i*6+4]);int hp = int.Parse(split[i*6+5]);//是自己if(desc == NetManager.GetDesc())continue;//添加一个角色GameObject obj = (GameObject)Instantiate(humanPrefab);obj.transform.position = new Vector3(x, y, z);obj.transform.eulerAngles = new Vector3(0, eulY, 0);BaseHuman h = obj.AddComponent<SyncHuman>();h.desc = desc;otherHumans.Add(desc, h);}}
//服务端
public static void MsgList(ClientState c, string msgArgs){string sendStr = "List|";foreach (ClientState cs in MainClass.clients.Values){sendStr+=cs.socket.RemoteEndPoint.ToString() + ",";sendStr+=cs.x.ToString() + ",";sendStr+=cs.y.ToString() + ",";sendStr+=cs.z.ToString() + ",";sendStr+=cs.eulY.ToString() + ",";sendStr+=cs.hp.ToString() + ",";}MainClass.Send(c, sendStr);}
  • Move 处理移动逻辑,消息类似 Move|127.0.0.1:4564,3.0.5,如果是其他玩家,就让其他玩家移动。因为本地会自己处理自己的移动逻辑,不需要服务端的介入。
//客户端
void OnMove (string msgArgs) {Debug.Log("OnMove " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);//移动if(!otherHumans.ContainsKey(desc))return;BaseHuman h = otherHumans[desc];Vector3 targetPos = new Vector3(x, y, z);h.MoveTo(targetPos);}
//服务端
public static void MsgMove(ClientState c, string msgArgs){//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);//赋值c.x = x;c.y = y;c.z = z;//广播string sendStr = "Move|" + msgArgs;foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}
  • Leave 玩家退出或者下线,消息类似···Leave|127.0.0.1:4564,如果是其他玩家,则销毁其他玩家,并且从字典中删除。
//客户端void OnLeave (string msgArgs) {Debug.Log("OnLeave " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];//删除if(!otherHumans.ContainsKey(desc))return;BaseHuman h = otherHumans[desc];Destroy(h.gameObject);otherHumans.Remove(desc);}
//服务端
public static void OnDisconnect(ClientState c){string desc = c.socket.RemoteEndPoint.ToString();string sendStr = "Leave|" + desc + ",";foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}
  • Attack 攻击 消息类似Attack|127.0.0.1:4564,45最后一个参数表示攻击方向,如果是其他玩家的话,则让其他玩家攻击,本地会处理自己的攻击逻辑。还有一个Hit消息是发送给服务端的,由于做的游戏比较简单,暂时没有血条的UI,所以只要服务端知道人物的HP就可以了,不必返还给客户端,所以客户端也就没有Hit的协议接受。Attack是玩家攻击,只要右击了就可以认为是在攻击,而Hit是被攻击到了,要在Attack的前提下再加上判断条件.
//客户端void OnAttack (string msgArgs) {Debug.Log("OnAttack " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float eulY = float.Parse(split[1]);//攻击动作if(!otherHumans.ContainsKey(desc))return;SyncHuman h = (SyncHuman)otherHumans[desc];h.SyncAttack(eulY);}
//服务端public static void MsgAttack(ClientState c, string msgArgs){//广播string sendStr = "Attack|" + msgArgs;foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}
//服务端
public static void MsgHit(ClientState c, string msgArgs){//解析参数string[] split = msgArgs.Split(',');string attDesc = split[0];string hitDesc = split[1];//被攻击ClientState hitCS = null;foreach (ClientState cs in MainClass.clients.Values){if(cs.socket.RemoteEndPoint.ToString() == hitDesc)hitCS = cs;}if(hitCS == null) return;hitCS.hp -= 25;if(hitCS.hp <= 0){string sendStr = "Die|" + hitCS.socket.RemoteEndPoint.ToString();foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}}
  • Die 玩家死亡,消息类似Die|127.0.0.1:4664,如果是其他玩家的话,则删除该角色。
//客户端
void OnDie (string msgArgs) {Debug.Log("OnAttack " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string attDesc = split[0];string hitDesc = split[0];//自己死了if(hitDesc == myHuman.desc){Debug.Log("Game Over");return;}//死了if(!otherHumans.ContainsKey(hitDesc))return;SyncHuman h = (SyncHuman)otherHumans[hitDesc];h.gameObject.SetActive(false);}

服务端的处理在MsgHit中处理了。

第二版角色类

BaseHuman没有变化

CtrlHuman

现在需要在移动和攻击的时候发出消息给服务端

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CtrlHuman : BaseHuman {// Use this for initializationnew void Start () {base.Start();}// Update is called once per framenew void Update () {base.Update();//移动if(Input.GetMouseButtonDown(0)){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;Physics.Raycast(ray, out hit);if (hit.collider.tag == "Terrain"){MoveTo(hit.point);//发送协议string sendStr = "Move|";sendStr += NetManager.GetDesc()+ ",";sendStr += hit.point.x + ",";sendStr += hit.point.y + ",";sendStr += hit.point.z + ",";NetManager.Send(sendStr);} }//攻击if(Input.GetMouseButtonDown(1)){if(isAttacking) return;if(isMoving) return;Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;Physics.Raycast(ray, out hit);if(hit.collider.tag != "Terrain") return;transform.LookAt(hit.point);Attack();//发送协议string sendStr = "Attack|";sendStr += NetManager.GetDesc()+ ",";sendStr += transform.eulerAngles.y + ",";NetManager.Send(sendStr);//攻击判定Vector3 lineEnd = transform.position + 0.5f*Vector3.up;Vector3 lineStart = lineEnd + 20*transform.forward;if(Physics.Linecast(lineStart, lineEnd, out hit)){GameObject hitObj = hit.collider.gameObject;if(hitObj == gameObject)return;SyncHuman h = hitObj.GetComponent<SyncHuman>();if(h == null)return;sendStr = "Hit|";sendStr += NetManager.GetDesc()+ ",";sendStr += h.desc + ",";NetManager.Send(sendStr);}}}
}

SyncHuman

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SyncHuman : BaseHuman {// Use this for initializationnew void Start () {base.Start();}// Update is called once per framenew void Update () {base.Update();}public void SyncAttack(float eulY){transform.eulerAngles = new Vector3(0, eulY, 0);Attack();}}

Main(客户端)

这是客户端的主函数,将之前提到的都整合在一起形成完整的逻辑

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Main : MonoBehaviour {//人物模型预设public GameObject humanPrefab;//人物列表public BaseHuman myHuman;public Dictionary<string, BaseHuman> otherHumans = new Dictionary<string, BaseHuman>();void Start () {//网络模块NetManager.AddListener("Enter", OnEnter);NetManager.AddListener("List", OnList);NetManager.AddListener("Move", OnMove);NetManager.AddListener("Leave", OnLeave);NetManager.AddListener("Attack", OnAttack);NetManager.AddListener("Die", OnDie);NetManager.Connect("127.0.0.1", 8888);//添加一个角色GameObject obj = (GameObject)Instantiate(humanPrefab);float x = Random.Range(-5, 5);float z = Random.Range(-5, 5);obj.transform.position = new Vector3(x, 0, z);myHuman = obj.AddComponent<CtrlHuman>();myHuman.desc = NetManager.GetDesc();//发送协议Vector3 pos = myHuman.transform.position;Vector3 eul = myHuman.transform.eulerAngles;string sendStr = "Enter|";sendStr += NetManager.GetDesc()+ ",";sendStr += pos.x + ",";sendStr += pos.y + ",";sendStr += pos.z + ",";sendStr += eul.y + ",";NetManager.Send(sendStr);NetManager.Send("List|");}void Update(){NetManager.Update();}void OnEnter (string msgArgs) {Debug.Log("OnEnter " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);float eulY = float.Parse(split[4]);//是自己if(desc == NetManager.GetDesc())return;//添加一个角色GameObject obj = (GameObject)Instantiate(humanPrefab);obj.transform.position = new Vector3(x, y, z);obj.transform.eulerAngles = new Vector3(0, eulY, 0);BaseHuman h = obj.AddComponent<SyncHuman>();h.desc = desc;otherHumans.Add(desc, h);}void OnList (string msgArgs) {Debug.Log("OnList " + msgArgs);//解析参数string[] split = msgArgs.Split(',');int count = (split.Length-1)/6;for(int i = 0; i < count; i++){string desc = split[i*6+0];float x = float.Parse(split[i*6+1]);float y = float.Parse(split[i*6+2]);float z = float.Parse(split[i*6+3]);float eulY = float.Parse(split[i*6+4]);int hp = int.Parse(split[i*6+5]);//是自己if(desc == NetManager.GetDesc())continue;//添加一个角色GameObject obj = (GameObject)Instantiate(humanPrefab);obj.transform.position = new Vector3(x, y, z);obj.transform.eulerAngles = new Vector3(0, eulY, 0);BaseHuman h = obj.AddComponent<SyncHuman>();h.desc = desc;otherHumans.Add(desc, h);}}void OnMove (string msgArgs) {Debug.Log("OnMove " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);//移动if(!otherHumans.ContainsKey(desc))return;BaseHuman h = otherHumans[desc];Vector3 targetPos = new Vector3(x, y, z);h.MoveTo(targetPos);}void OnLeave (string msgArgs) {Debug.Log("OnLeave " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];//删除if(!otherHumans.ContainsKey(desc))return;BaseHuman h = otherHumans[desc];Destroy(h.gameObject);otherHumans.Remove(desc);}void OnAttack (string msgArgs) {Debug.Log("OnAttack " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float eulY = float.Parse(split[1]);//攻击动作if(!otherHumans.ContainsKey(desc))return;SyncHuman h = (SyncHuman)otherHumans[desc];h.SyncAttack(eulY);}void OnDie (string msgArgs) {Debug.Log("OnAttack " + msgArgs);//解析参数string[] split = msgArgs.Split(',');string attDesc = split[0];string hitDesc = split[0];//自己死了if(hitDesc == myHuman.desc){Debug.Log("Game Over");return;}//死了if(!otherHumans.ContainsKey(hitDesc))return;SyncHuman h = (SyncHuman)otherHumans[hitDesc];h.gameObject.SetActive(false);}}

服务端的处理

MsgHandler

专门用来处理消息

using System;public class MsgHandler
{public static void MsgEnter(ClientState c, string msgArgs){//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);float eulY = float.Parse(split[4]);//赋值c.hp = 100;c.x = x;c.y = y;c.z = z;c.eulY = eulY;//广播string sendStr = "Enter|" + msgArgs;foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}public static void MsgList(ClientState c, string msgArgs){string sendStr = "List|";foreach (ClientState cs in MainClass.clients.Values){sendStr+=cs.socket.RemoteEndPoint.ToString() + ",";sendStr+=cs.x.ToString() + ",";sendStr+=cs.y.ToString() + ",";sendStr+=cs.z.ToString() + ",";sendStr+=cs.eulY.ToString() + ",";sendStr+=cs.hp.ToString() + ",";}MainClass.Send(c, sendStr);}public static void MsgMove(ClientState c, string msgArgs){//解析参数string[] split = msgArgs.Split(',');string desc = split[0];float x = float.Parse(split[1]);float y = float.Parse(split[2]);float z = float.Parse(split[3]);//赋值c.x = x;c.y = y;c.z = z;//广播string sendStr = "Move|" + msgArgs;foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}public static void MsgAttack(ClientState c, string msgArgs){//广播string sendStr = "Attack|" + msgArgs;foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}public static void MsgHit(ClientState c, string msgArgs){//解析参数string[] split = msgArgs.Split(',');string attDesc = split[0];string hitDesc = split[1];//被攻击ClientState hitCS = null;foreach (ClientState cs in MainClass.clients.Values){if(cs.socket.RemoteEndPoint.ToString() == hitDesc)hitCS = cs;}if(hitCS == null) return;hitCS.hp -= 25;if(hitCS.hp <= 0){string sendStr = "Die|" + hitCS.socket.RemoteEndPoint.ToString();foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}}
}

EventHandler

用来处理事件,其他可以合并,这么做应该是为了以后更好的代码风格吧。

using System;public class EventHandler
{public static void OnDisconnect(ClientState c){string desc = c.socket.RemoteEndPoint.ToString();string sendStr = "Leave|" + desc + ",";foreach (ClientState cs in MainClass.clients.Values){MainClass.Send(cs, sendStr);}}
}

MainClass(服务端)

服务端也是要用到和客户端类似的,根据字符串调用函数的操作,只不过这里没有用委托,而是用的反射机制,关于委托和反射机制的区别以及用法,可以参考这里。

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;public class ClientState
{public Socket socket; public byte[] readBuff = new byte[1024]; public int hp = -100;public float x = 0;public float y = 0;public float z = 0;public float eulY = 0;
}class MainClass
{//监听Socketpublic static Socket listenfd;//客户端Socket及状态信息public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();public static void Main (string[] args){//Socketlistenfd = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);//BindIPAddress ipAdr = IPAddress.Parse("127.0.0.1");IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);listenfd.Bind(ipEp);//Listenlistenfd.Listen(0);Console.WriteLine("[服务器]启动成功");//checkReadList<Socket> checkRead = new List<Socket>();//主循环while(true){//填充checkRead列表checkRead.Clear();checkRead.Add(listenfd); foreach (ClientState s in clients.Values){checkRead.Add(s.socket);}//selectSocket.Select(checkRead, null, null, 1000);//检查可读对象foreach (Socket s in checkRead){if(s == listenfd){ReadListenfd(s);}else{ReadClientfd(s);}}}}//读取Listenfdpublic static void ReadListenfd(Socket listenfd){Console.WriteLine("Accept");Socket clientfd = listenfd.Accept();ClientState state = new ClientState();state.socket = clientfd;clients.Add(clientfd, state);}//读取Clientfdpublic static bool ReadClientfd(Socket clientfd){ClientState state = clients[clientfd];//接收int count = 0;try{count = clientfd.Receive(state.readBuff);}catch(SocketException ex){MethodInfo mei =  typeof(EventHandler).GetMethod("OnDisconnect");object[] ob = {state};mei.Invoke(null, ob);clientfd.Close();clients.Remove(clientfd);Console.WriteLine("Receive SocketException " + ex.ToString());return false;}//客户端关闭if(count <= 0){MethodInfo mei =  typeof(EventHandler).GetMethod("OnDisconnect");object[] ob = {state};mei.Invoke(null, ob);clientfd.Close();clients.Remove(clientfd);Console.WriteLine("Socket Close");return false;}//消息处理string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);string[] split = recvStr.Split('|');Console.WriteLine("Recv " + recvStr);string msgName = split[0];string msgArgs = split[1];string funName = "Msg" + msgName;MethodInfo mi =  typeof(MsgHandler).GetMethod(funName);object[] o = {state, msgArgs};mi.Invoke(null, o);return true;}//发送public static void Send(ClientState cs, string sendStr){byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);cs.socket.Send(sendBytes);}}

总结一下

举个攻击的例子,说一下完整的流程。一开始玩家A连接到了服务端,那么就会向服务端发送Enter,服务端接受到消息之后,会向玩家A发送Enter消息,但是由于是自己,并不处理,这时候玩家A再发送List消息,服务端的List便会添加玩家A的信息,然后发给所有的客户端,目前只有玩家A,由于HP是在服务端处理的,所以玩家A会知道自己血量。这时候玩家B也连上了服务端,向服务端发送Enter消息,那么服务端就会转发Enter消息,所以玩家A就知道了玩家B的存在,并且在玩家A的屏幕上显示了玩家B,但是玩家B不知道玩家A的存在,所以发送了List请求,这时候就拿到了玩家A的信息。并且屏幕上也会显示玩家A的位置。假设这时候玩家A右击了玩家B,实际上判定并不是是否点击了玩家B,而是是否点到了地板,点的时候会发送Attack消息,本地播放攻击动画,然后服务端接收到Attack,会向所有的客户端广播消息,玩家A本身接收到消息之后不会处理,而玩家B接收到消息之后会处理,所以现在玩家B也可以看到玩家A播放攻击动画了。如果玩家B是站在玩家A的正前方,并且本地判定为攻击到了玩家B,那么玩家A就会发出Hit消息,服务端接收到Hit消息之后,会在后台默默的扣除玩家B的血量,并不广播,所以如果这时候在本地打印血量的时候,是不会有变化的,但是服务端会记录,当玩家B的血条为0时,服务端会广播Die消息,玩家A会看到玩家B消失了,而玩家B会直接显示Game Over,所以说玩家B的直接死亡是服务端发送消息,并且玩家B的客户端接收之后才触发的,由于血量是在服务端计算的,所以不经过服务端处理玩家A是打不死玩家B的。

实践出真知:大乱斗游戏相关推荐

  1. Unity3D网络游戏实战——实践出真知:大乱斗游戏

    前言 这一章是教我们做一个大乱斗游戏.但是书中的代码有些前后不一致导致运行错误,如果你也碰到了这样的情况,可以参考我的代码 我们要完成的主要有以下这些事 左键操控角色行走 右键操控角色攻击 受到攻击掉 ...

  2. ★「C++游戏」BattleOfPhantom:大乱斗游戏升级版

    (原创) 目前正在不断更新! ★ 一款超级有趣的大乱斗游戏,包含多种游戏模式,支持双人联机. 离线情况下也可以与多个(或一群)机器玩家进行疯狂的对战. 直接上图 使用C++ with EGE图形库编写 ...

  3. 怎么开发联机小游戏_Q飞机游戏:空战吃鸡大乱斗游戏!好玩的联机Q飞机对战小游戏...

    20000+游戏爱好者已加入我们! 带你发现好游戏! <Q飞机>游戏小程序好玩吗? <Q飞机>小游戏怎么玩? 只有你想不到, 没有我找不到的好游戏! 「良心好游戏推荐」 搜罗了 ...

  4. 新闻上的文本分类:机器学习大乱斗 王岳王院长 王岳王院长 5 个月前 目标 从头开始实践中文短文本分类,记录一下实验流程与遇到的坑 运用多种机器学习(深度学习 + 传统机器学习)方法比较短文本分类处

    新闻上的文本分类:机器学习大乱斗 王岳王院长 5 个月前 目标 从头开始实践中文短文本分类,记录一下实验流程与遇到的坑 运用多种机器学习(深度学习 + 传统机器学习)方法比较短文本分类处理过程与结果差 ...

  5. 计算机社团嘉年华的游戏,社团嘉年华,游戏大乱斗!

    原标题:社团嘉年华,游戏大乱斗! 一年一度倍受期待的社团嘉年华在周二圆满结束 大家肯定还对那些有趣的游戏念念不忘吧! 如果你没能及时参加,没关系 喇叭会带着你去回顾社团嘉年华那些精彩的活动! 立夏的五 ...

  6. 微信游戏脑力大乱斗92一个计算机,微信脑力大乱斗答案大全_全关卡图文攻略_软吧...

    脑力大乱斗是一款非常益智的小程序游戏,大家通过微信就可以直接打开无需下载.这里小编为大家提供的是所有关卡的过关技巧帮助小伙伴们了解这个游戏的脑洞,在碰上难题的时候协助大家顺利过关! [游戏入口] 在微 ...

  7. 怪兽大乱斗:进入苹果推荐的Creator独立游戏

    吃豆人是80.90后一款记忆深刻的游戏,躲避怪兽,吃掉金币!紧张.刺激.节奏明快,快乐了多少童年.向经典致敬,基于Cocos Creator制作.接入原生SDK.手动绑定jsb的吃豆玩法,全面升级的像 ...

  8. C++游戏——小胎大乱斗

    小胎大乱斗! 预告:PVP 模式 教程有一点儿问题!血量为负数,一进去就扣钱------教程里面的字看看就行了,看完就退出重新以"不要教程"模式开始!(笑哭) 不过有个bug 会碰 ...

  9. 小兵大乱斗服务器维修吗,这个游戏有点意思《小兵大乱斗》最新评测来袭

    原标题:这个游戏有点意思<小兵大乱斗>最新评测来袭 <小兵大乱斗>上线前的终极测试,在12月26日终于圆满的落下了帷幕. 此次测试版本,增加了全新玩法:天赋系统.积累战斗经验, ...

最新文章

  1. uboot启动linux,uboot中ramdisk启动linux的两种方法
  2. linux /dev/null 21,/dev/null 21 21 与 的意思
  3. 20、DELETE:删除数据
  4. java线程锁机制_多线程之锁机制
  5. 科大星云诗社动态20201119
  6. c++ 中的 cin.get()函数
  7. 【lucene】lucene自定义评分
  8. unity怪物攻击玩家减血_怪物猎人发布15周年 — 回顾历代封面怪之三大传奇怪物...
  9. linux通过xrander添加分辨率,使用X11,XDamage,XRender和其他技巧保留屏幕内容的QPixmap副本...
  10. python中用来占位的语句是_MySQL / Python-gt;语句中占位符的语法错误?
  11. python实现sorted函数_Python sorted函数及用法
  12. 高级Java程序员必备:《IDEA问题库》常见问题及解决方案,提升开发效率2(JAVA 小虚竹)
  13. 如何快速掌握一门新的技术?
  14. 沙黾农:买股票就买这家上市公司的股票
  15. Matlab绘制树形图
  16. 周总结20170925学习C,接触GMM和ML
  17. TensorFlow之设备(device)详解
  18. shp转.osm.pbf
  19. MYSQL数据库系统第4次实验 单表查询
  20. python想要保存QQ邮箱富途的附件

热门文章

  1. 直播观看指南|SOFA 五周年,Live Long and Prosper!
  2. SQL 更新语句:Update ... set ... from ...
  3. 超详细快速入门JavaScript详解(一)
  4. 图解HTTP四:返回结果的 HTTP 状态码
  5. IE8 base64 编码 解码
  6. redis设计与实现-数据库篇
  7. Windows XP 下安装SQL SERVER 2005问题汇总
  8. Word文档中文繁体简体的转换
  9. Java IO模型:BIO、NIO、AIO讲解
  10. e的lnx为什么等于x