基于C#的游戏服务开发(四)

42de93ea67ac291fb381345a86a8ac96.png

点击蓝字关注我哦

4712c0f8dc6db2e00847539baff504c1.png

5aa4e613ccd7878b638d90f3817afd1f.png

那么通过前面的几篇文章学习,相信各位对于socket编程有一定的了解了,所以本篇开始,通过做一个真正的联网功能.来告诉各位网络游戏编程中遇到的问题以及如何解决.

b887b957361a2fd6b2f8b36a96251a33.gif

c85d53dc8240830b9d4a97c3f3991867.png

乱斗游戏(一)

0e500f78128a092a29448b32dc6213d0.png

那么游戏很简单,就是用户登录,可以攻击其他玩家,然后最后剩下的就赢了.其实,别看功能简单,但是网络中做的操作可一点也不简单.那么我们一点一点的开发,一点一点的前进.

首先,我们自己搭个简单的游戏,这个功能不用太复杂,打开客户端就自动连上服务器,随机点生成一个玩家,鼠标点击地面,玩家移动到该位置,点击鼠标右键,即可发起攻击,每个玩家默认100滴血,谁掉线,或者血先扣完,表示失败,最后的玩家即为胜利者.

首先我们搭个场景,因为此操作过于简单,不想看的可以直接往后翻到网络部分.我们首先要刷个地形,新建一个地形.如果不想浪费时间刷地形的,新建一个plane也行.如果是新建了地形按照你自己的设计,刷出山脉河流.导入unity标准资源包,给地形贴上好看的纹理.

896caef5f4002d189c6c8c0929c1388b.png

然后将地面的tag设置为:”Terrain”,再将摄像机调整到一个比较好看的角度.

fdda981f9afdad4676e027509871c172.png

然后继续导入标准资源包中的人物模型,

28655b82968e532f31676486b4c41806.png

其中的Ethan可以作为我们的Player

79388cedfe2e4fa47b051313ebb03796.png

接下来我们就来设计控制玩家的脚本.首先我们写一个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上,这样使得项目更具有条理性.

743255c309f586e42d3c2a554e9fc1eb.png

接下来给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编程大全      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值