Unity与Node后端-SocketIO通信(二)

前言:

承接前面的内容,继续学习Unity与nodejs通信

 

 

 

 

 

 


发送聊天

上一次问我们实现了成功登陆游戏并且实例化一个游戏对象

现在,我们开始发送聊天内容

新建ChatView.cs脚本,然后在脚本中给发送消息和显示消息两个UI挂载对应的脚本类名

public class ChatView : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        transform.Find("ShowMessage").gameObject.AddComponent<ShowMessage>();
        transform.Find("SendMessage").gameObject.AddComponent<SendMessage>();
    }
}

我们先来完成SendMessage.cs的内容

public class SendMessage : MonoBehaviour
{
    void Start()
    {
        string message = "";
        //发送消息的输入框
        InputField input = transform.Find("InputField").GetComponent<InputField>();
        input.onEndEdit.AddListener((text) =>
        {
            message = text;
        });
        input.text = "";
        //发送消息的按钮
        transform.Find("Send").GetComponent<Button>()
            .onClick.AddListener(() =>
            {
                JSONObject json = new JSONObject(JSONObject.Type.OBJECT);
                json.AddField("user", 0);
                json.AddField("message", message);
                NetWorkMgr.Instance.Emit(Keys.Chat,json);
                input.text = "";
            });
    }
}

我们封装json中会加入它的user,但是现在先没有写,用0代替

客户端先写成这样,我们回服务端

#StartGame.js
var chatRoom=require('./Chatroom')
let players={}
module.exports.InintStartGame=function(socket,playID){
    socket.on(keys.InitGameComplete,function(data){
        //获取当前的player对象
        let player={
            id:playID,
            position:{x:0,y:0,z:0},
            rotation:{x:0,y:0,z:0}
        }
        chatRoom.InitChatRoom(socket)
        //将player对象保存到数组当中
        players[playID]=player
        //告诉老对象当前登录对象的信息
        socket.broadcast.emit(keys.Spawn,player)
        //告诉当前登录对象,已在线对象的信息
        for(let player in players){
            //不再发送自己了,避免重复
            if(player.id!=playID)
                socket.emit(keys.Spawn,players[player])
        }
    })
}


#Chatroom.js
module.exports.InitChatRoom=function(socket){
    socket.on(keys.Chat,(data)=>{
        //给自己发送这个消息
        socket.emit(keys.ReceiveChat,data)
        //给其余人发送这个消息
        socket.broadcast.emit(keys.ReceiveChat,data)
    })
}

然后我们在回头客户端,处理keys.ReceiveChat消息

public class ShowMessage : ViewBase
{
    private Transform _content;
    private void Start()
    {
        _content = transform.Find("content/back");
    }
    protected override void AddEventLintener()
    {
        NetWorkMgr.Instance.AddListener(Keys.ReceiveChat, showChat);
    }

    protected override void RemoveEventLintener()
    {
        NetWorkMgr.Instance.RemoveListener(Keys.ReceiveChat, showChat);
    }
    private void showChat(SocketIOEvent data) {
        string id = data.data["id"].ToString();
        string message = data.data["message"].ToString();
        SpawnItem().Init(id, message);
    }
    private ChatItem SpawnItem()
    {
        GameObject prefab = Resources.Load<GameObject>(Paths.ChatItem);
        GameObject item = Instantiate(prefab, _content);
        return item.AddComponent<ChatItem>();
    }
}
#ShowMessage.cs
public class ShowMessage : ViewBase
{
    private Transform _content;
    private void Start()
    {
        _content = transform.Find("content");
    }
    protected override void AddEventLintener()
    {
        NetWorkMgr.Instance.AddListener(Keys.ReceiveChat, showChat);
    }

    protected override void RemoveEventLintener()
    {
        NetWorkMgr.Instance.RemoveListener(Keys.ReceiveChat, showChat);
    }
    private void showChat(SocketIOEvent data) {
        string id = data.data["id"].ToString();
        string message = data.data["message"].ToString();
        SpawnItem().Init(id, message);
    }
    private ChatItem SpawnItem()
    {
        GameObject prefab = Resources.Load<GameObject>(Paths.ChatItem);
        GameObject item = Instantiate(prefab, _content);
        return item.AddComponent<ChatItem>();
    }
}

SpawnItem方法中导入的预制体是一个UI组件,下面有三个文本,结构如下

Name、Time、Content都挂载着ContentSizeFitter来使大小可以自动适应。

然后

#ChatItem.cs
public class ChatItem : MonoBehaviour
{
    public void Init(string id,string message)
    {
        RectTransform nameTrans = transform.Find("Name").GetComponent<RectTransform>();
        RectTransform TimeTrans = transform.Find("Time").GetComponent<RectTransform>();
        RectTransform ContentTrans = transform.Find("Content").GetComponent<RectTransform>();
        SetTextContent(nameTrans, id);
        SetTextContent(ContentTrans, message);
        SetTextContent(TimeTrans, DateTime.Now.ToString("HH:mm:ss"));
    }

    private void SetTextContent(Transform trans, string content)
    {
        //给对应位置写对应文字
        trans.GetComponent<Text>().text=content;
    }
}

重写一下ChatItem.cs

public class ChatItem : MonoBehaviour
{
    public void Init(string id,string message)
    {
        RectTransform nameTrans = transform.Find("Name").GetComponent<RectTransform>();
        RectTransform TimeTrans = transform.Find("Time").GetComponent<RectTransform>();
        RectTransform ContentTrans = transform.Find("Content").GetComponent<RectTransform>();
        SetTextContent(nameTrans, id);
        SetTextContent(ContentTrans, message);
        SetTextContent(TimeTrans, DateTime.Now.ToString("HH:mm:ss"));

        SetHeight(nameTrans, ContentTrans);
    }

    private void SetTextContent(Transform trans, string content)
    {
        //给对应位置写对应文字
        trans.GetComponent<Text>().text=content;
    }
    private void SetHeight(RectTransform nameTrans, RectTransform ContentTrans) {
        StartCoroutine(wait(nameTrans, ContentTrans));
    }
    private IEnumerator wait(RectTransform nameTrans, RectTransform ContentTrans) {
        //挂起一帧后才会有高度偏差
        yield return null;
        float height = 0;
        height += Math.Abs(nameTrans.sizeDelta.y);
        height += Math.Abs(ContentTrans.sizeDelta.y);
        RectTransform self = GetComponent<RectTransform>();
        print(height);
        self.sizeDelta = new Vector2(self.sizeDelta.x,height+40);  
    }
}

这个协程的作用是让背景跟随输入文字的多少实现自适应,预制体要注意:

  • Name、Time、Content要挂载 Content Size Fitter ,这样输入多了才会自动换行全部显示出来。
  • Name、Time、Content的锚点一定要左右对住边,如果涉及到了上下方向,自适应就会出现问题

下面我做了一些修改(躺了几个大坑)

到现在,我们来整理一下Game场景的目录结构

Chat下面由ShowMessage和SendMessage,其中ShowMessage下面是一个UI——Scroll View,挂在组件:

Scroll View下面我把Scroll Bar都删了,现在只有一个ViewPort(滚动视野),上面挂载着Mask来挡住content的内容

最后是content,承载聊天内容的面板,我们的预制体就是会生成在它下面,它挂载着Grid Layout Group,使得我们生成的聊天内容按照一行一个的网格布局。

最后我们改进一下代码

#ChatItem.cs
public class ChatItem : MonoBehaviour
{
    public void Init(string id,string message,Action callback)
    {
        RectTransform nameTrans = transform.Find("Name").GetComponent<RectTransform>();
        RectTransform TimeTrans = transform.Find("Time").GetComponent<RectTransform>();
        RectTransform ContentTrans = transform.Find("Content").GetComponent<RectTransform>();
        SetTextContent(nameTrans, id);
        SetTextContent(ContentTrans, message);
        SetTextContent(TimeTrans, DateTime.Now.ToString("HH:mm:ss"));

        SetHeight(nameTrans, ContentTrans,callback);
    }

    private void SetTextContent(Transform trans, string content)
    {
        //给对应位置写对应文字
        trans.GetComponent<Text>().text=content;
    }
    private void SetHeight(RectTransform nameTrans, RectTransform ContentTrans,Action callback) {
        StartCoroutine(wait(nameTrans, ContentTrans,callback));
    }
    private IEnumerator wait(RectTransform nameTrans, RectTransform ContentTrans,Action callback) {
        //挂起一帧后才会有高度偏差
        yield return null;
        float height = 0;
        height += Math.Abs(nameTrans.sizeDelta.y);
        height += Math.Abs(ContentTrans.sizeDelta.y);
        RectTransform self = GetComponent<RectTransform>();
        print(height);
        self.sizeDelta = new Vector2(self.sizeDelta.x,height+50);

        if (callback != null) {
            callback();
        }
    }
}
#ShowMessage.cs
public class ShowMessage : ViewBase
{
    private ScrollRect _scrollRect;
    private Transform _content;
    private void Start()
    {
        _content = transform.Find("Scroll View/Viewport/Content");
        _scrollRect = GetComponentInChildren<ScrollRect>();
    }
    protected override void AddEventLintener()
    {
        NetWorkMgr.Instance.AddListener(Keys.ReceiveChat, showChat);
    }

    protected override void RemoveEventLintener()
    {
        NetWorkMgr.Instance.RemoveListener(Keys.ReceiveChat, showChat);
    }
    private void showChat(SocketIOEvent data) {
        string id = data.data["id"].ToString();
        string message = data.data["message"].ToString();
        SpawnItem().Init(id, message, UpdateContentPosition);
    }
    private ChatItem SpawnItem()
    {
        GameObject prefab = Resources.Load<GameObject>(Paths.ChatItem);
        GameObject item = Instantiate(prefab, _content);
        return item.AddComponent<ChatItem>();
    }
    private void UpdateContentPosition() {
        //刷新Canvas,避免排列造成画面抖动
        Canvas.ForceUpdateCanvases();
        //更改标准位置,从而使得视图罩住content的下部分
        //实现实时显示最新的消息的效果
        _scrollRect.verticalNormalizedPosition = 0;
    }
}

按照siki学院老师的说法,verticalNormalizedPosition = 0 这句的作用就是:

这样,我们就实现了消息的实时更新

 


更新信息

聊天信息

上面的步骤让我们实现了可以基本进行实时聊天了,接下来我们将人物的更多信息传给客户端。首先定义一个PlayData类来存储本地Player的一些属性与一些特征

public class PlayerData
{
    public static string ID { get; set; };
}

定义了ID,我们在LoginView.cs中初始化

    private void LoginResult(SocketIOEvent data) {
        //如果该方法被调用,说明node端向C#端发送了key为login的信息
        //data是一个SocketIOEvent对象的json属性,通过下标得到值
        if (Util.GetBoolFromJson(data.data["result"]))
        {
            //同步加载
            SceneManager.LoadScene("Game");
            PlayerData.ID = data.data["user"].ToString();
        }
        else {
            Debug.Log("Login lose");
        }
    }

并且在SendMessage.cs中,就可以指定好

public class SendMessage : MonoBehaviour
{
    void Start()
    {
        string message = "";
        //发送消息的输入框
        InputField input = transform.Find("InputField").GetComponent<InputField>();
        input.onEndEdit.AddListener((text) =>
        {
            message = text;
        });
        input.text = "";
        //发送消息的按钮
        transform.Find("Send").GetComponent<Button>()
            .onClick.AddListener(() =>
            {
                JSONObject json = new JSONObject(JSONObject.Type.OBJECT);
                json.AddField("id", PlayerData.ID);
                json.AddField("message", message);
                NetWorkMgr.Instance.Emit(Keys.Chat,json);
                input.text = "";
            });
    }
}

然后我们测试,会发现:哎,居然不显示用户名

问题出在哪里呢?我在发送消息按钮上绑定了一个打印封装的json的命令,会发现,它给id封装了两个双引号,所以node端没有办法解析到内容。

这个问题其实是获取json数据常见的一种问题

我这里的解决方式采用了一个插件——LitJson

LitJson是一个开源项目,比较小巧轻便,安装也很简单,在Unity里只需要把LitJson.dll放到Plugins文件夹下,并在代码的最开头添加 “Using LitJson”就可以了。简单来说,LitJson的用途是实现Json和代码数据之间的转换,一般用于从服务器请求数据,得到返回的Json后进行转换从而在代码里可以访问。

github上有源代码,我还准备了直接编译好的dll,提取码:7sat

将dll放到Unity的Plugins目录即可

好的,我们重新修改一下代码

#LoginView.cs
    private void LoginResult(SocketIOEvent data) {
        //如果该方法被调用,说明node端向C#端发送了key为login的信息
        //data是一个SocketIOEvent对象的json属性,通过下标得到值
        if (Util.GetBoolFromJson(data.data["result"]))
        {
            JsonData Json = JsonMapper.ToObject(data.data.ToString());
            PlayerData.ID = Json["user"].ToString();
            SceneManager.LoadScene("Game");

        }
        else {
            Debug.Log("Login lose");
        }
    }
#ShowMessage.cs
    private void showChat(SocketIOEvent data) {
        JsonData Json = JsonMapper.ToObject(data.data.ToString());
        string id = Json["id"].ToString();
        string message = Json["message"].ToString();
        SpawnItem().Init(id, message, UpdateContentPosition);
    }

移动信息

我们计划将移动方式为玩家点击某处从而移动,所以,我们需要导航系统,烘焙一下导航网格,还要给角色挂载寻路代理。

给Util工具类添加三个方法方便后期转换值

    public static JSONObject VectorToJson(Vector3 pos) {
        JSONObject json = new JSONObject(JSONObject.Type.OBJECT);
        json.AddField("x",pos.x);
        json.AddField("y", pos.y);
        json.AddField("z", pos.z);
        return json;
    }
    public static Vector3 JsonToVector(JSONObject json)
    {
        //.f可以直接获得float的值
        return new Vector3(json["x"].f, json["y"].f, json["z"].f);
    }
    public static string GetID(SocketIOEvent data) {
        //这里的获取ID方法,只能拆json包中键为id的
        JsonData Json = JsonMapper.ToObject(data.data.ToString());
        return  Json["id"].ToString();
    }

我们新建一个类,用来检测玩家的点击来让角色行走,目前写入如下代码,将人物移动信息发送给服务端(注意,这个类不能添加在预制体角色身上,因为应该玩家只可以控制自己的角色,我在StartGameView.cs中的Start方法里添加了这样一句 gameObject.AddComponent<PlayerClick>(); ,将它挂载到了主相机上,注意相机的标签要选对)

public class PlayerClick : MonoBehaviour
{
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit)) {
                HitGround(hit);
            }
        }
    }
    private void HitGround(RaycastHit hit) {
        //封装json
        JSONObject json = new JSONObject(JSONObject.Type.OBJECT);
        json.AddField("id", PlayerData.ID);
        Vector3 playerPos = PlayerSpawner.Instance
                        .GetPlayer(PlayerData.ID)
                        .transform.position;
        json.AddField("StartPos", Util.VectorToJson(playerPos));
        json.AddField("TargetPos", Util.VectorToJson(hit.point));
        NetWorkMgr.Instance.Emit(Keys.Move, json);
    }
}

回到服务端,修改startGame.js,我们来处理服务端接收Keys.Move

var chatRoom=require('./Chatroom')

let players={}

module.exports.InintStartGame=function(socket,playID){
    //获取当前的player对象
    let player={
       id:playID,
       position:{x:0,y:0,z:0},
       rotation:{x:0,y:0,z:0}
    }

    socket.on(keys.InitGameComplete,function(data){
        chatRoom.InitChatRoom(socket)
        //将player对象保存到数组当中
        players[playID]=player
        //告诉老对象当前登录对象的信息
        socket.broadcast.emit(keys.Spawn,player)
        //告诉当前登录对象,已在线对象的信息
        for(let player in players){
            //不再发送自己了,避免重复
            if(player.id!=playID)
                socket.emit(keys.Spawn,players[player])
        }
    })
    socket.on(keys.Move,function(data){
        player.position.x=data.StartPos.x;
        player.position.y=data.StartPos.y;
        player.position.z=data.StartPos.z;
        //给客户端发送
        socket.emit(keys.Move,data);
        //给所有客户端广播
        socket.broadcast.emit(keys.Move,data);
    })
}

然后我们再回到客户端,添加一个脚本Move挂载给角色来接收服务端发送的keys.Move

在写Move之前,我们先进行这样的处理,在StartGameView.cs的生成Player中,给生成的Player添加组件PlayerView,并调用它的init方法

    private void spawnPlayer(SocketIOEvent data) {
        //新建角色
        string id = Util.GetID(data);
        var player = PlayerSpawner.Instance.SpawnPlayer(id);
        //PlayView这个组件会负责给Player添加其他组件
        player.AddComponent<PlayView>().Init(id);
    }

playerview的内容如下

public class PlayView : MonoBehaviour
{  
    public  void Init(string ID)
    {
        gameObject.AddComponent<Move>();
        Data data=gameObject.AddComponent<Data>();
        data.ID = ID;
    }
}

文件Data.cs的内容如下

public class Data : MonoBehaviour
{
    public string ID { get; set; }
}

好,我们来写Move.cs文件

public class Move : ViewBase
{
    //这也是一个视图类

    private NavMeshAgent _agent;

    private void Start()
    {
        _agent = GetComponent<NavMeshAgent>();
    }

    protected override void AddEventLintener()
    {
        NetWorkMgr.Instance.AddListener(Keys.Move, OnMove);
    }

    protected override void RemoveEventLintener()
    {
        NetWorkMgr.Instance.RemoveListener(Keys.Move, OnMove);
    }
    private void OnMove(SocketIOEvent data)
    {
        //Util的GetID可以得到json中id键对应的值
        string id = Util.GetID(data);
        Data ownData = GetComponent<Data>();
        //判断是生成自己还是处理别人
        if (id == ownData.ID) {
            //获取PlayerClick.cs中封装的起始/目标位置
            transform.position = Util.JsonToVector(data.data["StartPos"]);
            _agent.SetDestination(Util.JsonToVector(data.data["TargetPos"]));
        }
    }
}

到这里,我们应该就可以实现基本人物移动了

缕一下:PlayerClick将用户id、起始位置、目标位置(用户点击位置)发送给了服务端的InintStartGame中的监听,这个监听将消息转发给了所有客户端。

客户端收到消息后,进行处理。

动画

下面为了丰富游戏效果,我们先添加一下动画,这个相信大家应该都会的。

我这里是添加了一个混合树

动画的播放由变量distance决定

在PlayerView中再让它自动添加一个组件——AniController

    public  void Init(string ID)
    {
        gameObject.AddComponent<Move>();
        Data data=gameObject.AddComponent<Data>();
        gameObject.AddComponent<AniController>();
        data.ID = ID;
    }

AniController的内容如下

public class AniController : MonoBehaviour
{
    private Animator _animator;
    private NavMeshAgent _agent;

    private int _timer;

    private int _distanceID = Animator.StringToHash("distance");

    void Start()
    {
        _animator = GetComponent<Animator>();
        _agent = GetComponent<NavMeshAgent>();
        _timer = 0;
    }

    void Update()
    {
        //每20帧检测一次,限制执行频率
        if (_timer > 20)
        {
            //通过与目标位置距离来设置状态机的变量distance
            _animator.SetFloat(_distanceID, _agent.remainingDistance);
            _timer = 0;
        }
        _timer++;
    }
}

 

镜头跟随

然后我们添加一下镜头跟随效果

新建脚本FollowPlayer

public class FollowPlayer : MonoBehaviour
{
    //处理相机对人物的跟随
    public Transform Player;
    public float rotateSpeed = 5;
    private bool isRotating = false;       //标志
    private Vector3 offsetPosition;  //一个位置偏移
    private void Start()
    {
        //相机朝向人物位置
        transform.LookAt(Player.transform.position);
        //记录人物与相机的位置偏移
        offsetPosition = transform.position - Player.position;
    }
    private void Update()
    {
        //永远保持偏移距离
        transform.position = Player.transform.position + offsetPosition;

        RotateView();
    }

    void RotateView()
    {
        //Input.GetAxis("Mouse X");  //得到鼠标在水平方向的滑动
        //Input.GetAxis("Mouse Y");  //得到鼠标在竖直方向的滑动
        if (Input.GetMouseButtonDown(1))  //按下鼠标右键
        {
            isRotating = true;
        }
        if (Input.GetMouseButtonUp(1))
        {
            isRotating = false;
        }
        if (isRotating)
        {
            //绕某一点,某一轴旋转(相机初始化会朝向人物,所以尽量将
            //人物与相机放在相同x轴上,z值为0)
            transform.RotateAround(Player.position, Player.up, rotateSpeed * Input.GetAxis("Mouse X"));
            //对上下方向的旋转要做一个范围限制
            //保存原来的数据(position,rotation)
            Vector3 originalPos = transform.position;
            Quaternion originalRot = transform.rotation;
            transform.RotateAround(Player.position, transform.right, -rotateSpeed * Input.GetAxis("Mouse Y"));
            //得到x的旋转
            float x = transform.eulerAngles.x;
            if (x < 10 || x > 80)  //超出范围,还原这一步旋转
            {
                transform.position = originalPos;
                transform.rotation = originalRot;
            }
        }
        //更新offsetPosition
        offsetPosition = transform.position - Player.position;
    }
}

然后在我们的PlayView.cs中添加

public class PlayView : MonoBehaviour
{  
    public  void Init(string ID)
    {
        gameObject.AddComponent<Move>();
        Data data=gameObject.AddComponent<Data>();
        gameObject.AddComponent<AniController>();
        data.ID = ID;
        //现在新建的角色的id就是登陆的玩家的id
        if (ID == PlayerData.ID) {
            //给主相机挂载相机跟随人物的脚本
            Camera.main.gameObject.AddComponent<FollowPlayer>()
                .Player = PlayerSpawner.Instance
                .GetPlayer(PlayerData.ID).transform;
        }
    }
}

然后就OK了!

我们导出一个客户端来测试一波

 

 


断线逻辑

相信大家也看出来了,我们现在没有断线逻辑。

如果我们登陆又退出,是会出现问题的。

首先,先核对一下服务端和客户端两边的Keys,确保一致

var keys={ 
    Connection:"connection",
    Disconnection : "disconnect",
    OtherDisconnect : "otherDisconnect",
    Login : "login",
    InitGameComplete : "initgamecomplete",
    Chat : "chat",
    ReceiveChat : "receivechat",
    Spawn : "spawn",
    Move : "move",
    Follow : "follow",
    UpdatePosition : "updateposition",
    Attack : "attack"
}

在服务端的StartGame.js中加了一个对Disconnection的监听,完整文件如下:

var chatRoom=require('./Chatroom')

let players={}

module.exports.InintStartGame=function(socket,playID){
    //获取当前的player对象
    let player={
       id:playID,
       position:{x:0,y:0,z:0},
       rotation:{x:0,y:0,z:0}
    }

    socket.on(keys.InitGameComplete,function(data){
        chatRoom.InitChatRoom(socket)
        //将player对象保存到数组当中
        players[playID]=player
        //告诉老对象当前登录对象的信息
        socket.broadcast.emit(keys.Spawn,player)
        //告诉当前登录对象,已在线对象的信息
        for(let player in players){
            //不再发送自己了,避免重复
            if(player.id!=playID)
                socket.emit(keys.Spawn,players[player])
        }
    })
    socket.on(keys.Move,function(data){
        player.position.x=data.StartPos.x;
        player.position.y=data.StartPos.y;
        player.position.z=data.StartPos.z;
        //给客户端发送
        socket.emit(keys.Move,data);
        //给所有客户端广播
        socket.broadcast.emit(keys.Move,data);
    })
    //这里的键值是socketio定义好的关键字,检测下线
    socket.on(keys.Disconnection,function(data){
        delete players[playID]
        //把下线的id传给其他客户端
        socket.broadcast.emit(keys.OtherDisconnect,{id:playID})
    })
}

下面是connectView.cs,很早之前的文件

public class ConnectView : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        NetWorkMgr.Instance.AddListener(Keys.Connection, Connect);
        NetWorkMgr.Instance.AddListener(Keys.Disconnection, DisConnect);
        NetWorkMgr.Instance.AddListener(Keys.OtherDisconnect, OtherDisConnect);
    }
    private void OnDestroy()
    {
        NetWorkMgr.Instance.RemoveListener(Keys.Connection, Connect);
        NetWorkMgr.Instance.RemoveListener(Keys.Disconnection, DisConnect);
        NetWorkMgr.Instance.RemoveListener(Keys.OtherDisconnect, OtherDisConnect);
    }
    //建立连接的方法
    private void Connect(SocketIOEvent data)
    {
        Debug.Log("connect server Success");
    }
    //断开链接的方法
    private void DisConnect(SocketIOEvent data) {
        PlayerSpawner.Instance.RemovePlayer(PlayerData.ID);
    }
    private void OtherDisConnect(SocketIOEvent data)
    {
        string id = Util.GetID(data);
        PlayerSpawner.Instance.RemovePlayer(id);
    }
}

 

 


避免重复登录

这个在服务端做手脚就好了

我们在StartGame.js中记录了当前的登录用户,所以我们先将这个对象设置成全局对象

var players={}
global.players=players

然后在login.js中,CheckAccount方法里加一句检测条件即可

function CheckAccount(data){

    var result=false;
    testAccount.forEach(function(Item){
        if(Item.user==data.user&&Item.passwd==data.passwd&&!players.hasOwnProperty(data.user)){
            result=true
        }
    })
    return result
}

 


发送消息的小技巧

我们发送消息,是通过socket去发送,通过socket的id来辨识自己该不该接收这个消息。

例如:

A给服务端发送消息,socket的ID是1,服务端返回消息,socket的ID是2,A就会接收。如果socket的ID是1,A就不会接收,因为它以为那是自己发出去的消息。

我们的服务端的聊天室功能,源代码是这样的

module.exports.InitChatRoom=function(socket){
    socket.on(keys.Chat,(data)=>{
        //给自己发送这个消息
        socket.emit(keys.ReceiveChat,data)
        //给其余人发送这个消息
        socket.broadcast.emit(keys.ReceiveChat,data)
    })
}

现在你懂socket.broadcast.emit的原理了吧,就是发送一个socket的id等同于自身的消息,这样,别的客户端就会接收到,我自身的客户端不会接收。那么我们要实现群发,实际就可以直接修改id

但是这个不建议这样操作,只是了解即可。

 


后语

siki的这个课程到这里就追完了,人家还有个第二季,过几天再开始追那个吧。

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

发表评论