点击蓝字关注我哦
那么通过前面的几篇文章学习,相信各位对于socket编程有一定的了解了,所以本篇开始,通过做一个真正的联网功能.来告诉各位网络游戏编程中遇到的问题以及如何解决.
乱斗游戏(一)
那么游戏很简单,就是用户登录,可以攻击其他玩家,然后最后剩下的就赢了.其实,别看功能简单,但是网络中做的操作可一点也不简单.那么我们一点一点的开发,一点一点的前进.
首先,我们自己搭个简单的游戏,这个功能不用太复杂,打开客户端就自动连上服务器,随机点生成一个玩家,鼠标点击地面,玩家移动到该位置,点击鼠标右键,即可发起攻击,每个玩家默认100滴血,谁掉线,或者血先扣完,表示失败,最后的玩家即为胜利者.
首先我们搭个场景,因为此操作过于简单,不想看的可以直接往后翻到网络部分.我们首先要刷个地形,新建一个地形.如果不想浪费时间刷地形的,新建一个plane也行.如果是新建了地形按照你自己的设计,刷出山脉河流.导入unity标准资源包,给地形贴上好看的纹理.
然后将地面的tag设置为:”Terrain”,再将摄像机调整到一个比较好看的角度.
然后继续导入标准资源包中的人物模型,
其中的Ethan可以作为我们的Player
接下来我们就来设计控制玩家的脚本.首先我们写一个human的父类:BaseHuman,这个类派生出玩家控制,与网络控制.那么,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseHuman : MonoBehaviour {
protected bool isMoving = false;
private Vector3 TargetPosition;
public float speed = 1.2f;
private Animator animator;
public string desc = "";
public void MoveTo(Vector3 pos)
{
TargetPosition = pos;
isMoving = true;
animator.SetBool("isMoving", true);
}
public void MoveUpdate()
{
if (isMoving == false)
return;
Vector3 pos = Vector3.MoveTowards(transform.position, TargetPosition, speed * Time.deltaTime);
transform.LookAt(TargetPosition);
if (Vector3.Distance(transform.position, TargetPosition) < 0.05f)
{
isMoving = false;
animator.SetBool("isMoving", false );
}
}
// Use this for initialization
public void Start () {
animator = transform.GetComponent<Animator>();
}
// Update is called once per frame
public void Update () {
MoveUpdate();
}
}
此脚本作为player的父类,包含了一些公共方法,player表示一个是玩家自己控制的玩家一个是网络上控制的玩家.也许你会有疑问,为甚会有网络上控制的功能,其实,网络传输过来的数据用来驱动同一场景中的玩家的player.也许这里有点绕,看到后面你就会明白的.
接下来设计角色的动画状态机.首先新建一个Animation Controller,重命名为:HumanAniCtrl,放在Ani文件夹下,挂载在Ethan上,这样使得项目更具有条理性.
接下来给player加两个状态,一个是idle,一个是run,通过bool的改变来驱动变化.
通过 设置一个isMoving.
到此为止,我们就完成了一个player的制作,接下来就把做好的Ethan做成一个prefab,首先要断开它与原来的Ethan的联系:点击场景中的Ethan,点击菜单栏中的GameObject->Break Prefab Instance ,即可,然后再将Ethan拖拽到Prrfabs文件夹中,这样做也是为了条理性.
然后我们制作一个玩家在本地控制的一个功能,新建一个脚本:CtrlHuman,放在Scripts文件夹下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CtrlHuman : BaseHuman {
private new void Start()
{
base.Start();
}
new 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.Equals("Terrain"))
{
MoveTo(hit.point);
}
}
}
}
然后,我们测试一下,可以实现点击地面的时候,Ethan跑到鼠标点击的位置.
下面就顺便把网络驱动的player也写了吧,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class syncHuman : BaseHuman {
// Use this for initialization
new void Start () {
base.Start();
}
// Update is called once per frame
new void Update () {
base.Update();
}
}
这里我们先只搭个框架.方法暂时空着.
通讯协议
那么什么是通讯协议呢?按照标准的解释,一般是这样的:通讯双方约定数据传输的格式,通讯双方必须遵守,那么简单点来说,就是通讯就是你们约定一个暗号,加入通过这个暗号你能直到对方在说什么,那么就说明这个通讯协议是你们在用的.否则就不对.
那么这里我们采用字符串,消息名和消息体用”|”隔开:
消息名|参数1,参数2,参数3....
举个例子,加入是进入场景的协议,那么发送的时候就是这样:
Enter|127.0.0.1:8889,10,20,15
Enter代表这是有玩家进来了,127.0.0.1:8889代表的是具体的玩家IP地址,而后面的3个数字就是玩家在场景中初始位置.
这个时候就可以把unity先放到一边,开始改写服务器端的功能:
首先是上面说到的,根据通讯协议,分割客户端传过来的字符串,理解其中的意思,比如客户端传过来:Move|127.0.0.1:554,10,2,30,
那么根据通讯协议可知,客户端127.0.0.1:554 Move到了10,2,30的位置.
那么只要在规定其他的协议,即可完成通讯.
客户端处理数据操作如下:
首先是网络的基本工具类:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
public class NetManager : MonoBehaviour {
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.Add(msgName, 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)
{
//socket
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//connect
socket.Connect(ip, Port);
//BeginReceive
socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallBack, socket);
}
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 (Exception ex )
{
Debug.Log("Socket ,接收失败" + ex);
}
}
public static void Send(string SendStr)
{
if (socket == null)
return;
if (!socket.Connected)
return;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(SendStr);
socket.Send(sendBytes);
}
public 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];
foreach (var item in listeners)
{
Debug.Log(item.Key);
}
Debug.Log(msgName);
if (listeners.ContainsKey(msgName))
{
listeners[msgName](msgArgs);
}
}
}
场景中挂载的:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour {
public GameObject humanPrefab;
public BaseHuman myHuman;
public Dictionary<string, BaseHuman> otherHumans;
// Use this for initialization
void Start()
{
NetManager.AddListener("Enter", OnEnter);
NetManager.AddListener("Move", OnMove);
NetManager.AddListener("Leave", OnLeave);
NetManager.Connect("127.0.0.1", 8888);
GameObject go = Instantiate(humanPrefab) as GameObject;
float x = Random.Range(-5, 5);
float z = Random.Range(-5, 5);
go.transform.position = new Vector3(x, 0, z);
myHuman = go.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);
}
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 go = Instantiate(humanPrefab)as GameObject;
go.transform.position = new Vector3(x, y, z);
go.transform.eulerAngles = new Vector3(0, euly, 0);
BaseHuman h = go.AddComponent<syncHuman>();
h.desc = desc;
otherHumans.Add(desc, h);
}
void OnMove(string msg)
{
Debug.Log("OnMove" + msg);
}
void OnLeave(string msg)
{
Debug.Log("OnLeave" + msg);
}
// Update is called once per frame
void Update () {
NetManager.Update();
}
}
然后改造服务器端:服务中改动的很小,就是把传入的字符串修改一下即可.
string sendStr = recvStr;
byte[] sendBytes = Encoding.Default.GetBytes(sendStr);
运行之后即可看到第二个客户端开启的时候,会发现只有后进入的客户端更新了,而先前的客户端没有更新,那么这个是因为客户端还没有收到其他玩家上线的消息.后续我们会一点一点解决
本案例的客户端与服务器源码下载方法:公众号回复 服务器开发 即可.
…END…
技术群:添加小编微信并备注进群
小编微信:mm1552923
公众号:dotNet编程大全