C#服务端的微信小游戏——多人在线角色扮演(十)

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

C#服务端的微信小游戏——多人在线角色扮演(十)

服务端与客户端的交流,需要一套指令集,客户端发送的指令要与服务端一致,这样服务端才能理解客户端的意思。
——茂叔

上一篇,我们已经在客户端与服务端之间成功的建立了连接。
那么我们应该为客户端与服务端之间的通信设计一套规则,也就是双方都能听得懂的语言。

指令集

这里,我们采用指令+数据的形式来定义一个类,在type.cs里面定义:

public class CommunicationObject
    {
        public GameCommand command;
        public string data;

        public CommunicationObject(GameCommand command, string data)
        {
            this.command = command;
            this.data = data;
        }

        public override string ToString()
        {
            return JObject.FromObject(this).ToString();
        }
    }

其中的GameCommand是我们的指令集枚举,其定义如下:

public enum GameCommand : byte
    {
        ERROR = 0,
        CONNECTED = 1,
        LOGIN,
        MOVE,
        ATTACK,
        SEARCH,
        TAKE,
        QUIT
    }

目前暂时就这些指令,将来会不断扩充。
客户端也应该有一套完全一样的指令集,以便双方能听懂对方的意思。
我们可以直接在客户端去定义一个类似的“枚举”,尽管javascript其实没有“枚举”这个东西。

但是这样一来,每次我们扩充或者修改了指令集,就必须在两端同时修改定义,这样不仅麻烦,而且容易出错。

不如我们每次都让客户端从服务端下载指令集?

也就是说,每次客户端连接成功之后,服务端就把枚举的定义Json化,然后下发给客户端。
这样我们只需要维护服务端的指令集就可以了。

关于C#Json操作,我选择使用Newtonsoft.json
修改GameConnection的构造方法如下:

public GameConnection(string IP, WebSocket mSocket, Action<string> LOGFUN = null)
        {
            this.IP = IP;
            this.mSocket = mSocket;
            LOG = LOGFUN == null ? x => { } : LOGFUN;
            LOG("来自 " + IP + " 连接建立成功");
            JObject ISJson = JObject.FromObject(Enum.GetValues(typeof(GameCommand))
               .Cast<GameCommand>()
               .ToDictionary(x => x.ToString(), x => (int)x));
            CommunicationObject FirstMsg = new CommunicationObject( GameCommand.CONNECTED, ISJson.ToString());
            ArraySegment<byte> segment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(FirstMsg.ToString()));
            mSocket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None);
            ReceiveDataAsync();
        }

然后再到微信web开发者工具里面去把game.js内容修改如下:

wx.connectSocket({
  url: 'ws://localhost:666/',
  method: "GET",
  success(res){
    console.log("Socket successed:")
    console.log(res)
    wx.onSocketOpen(
      (res) => {
        console.log("Socket opened:")
        console.log(res)
      }
    )
    wx.onSocketClose(
      (res) => {
        console.log("Socket closed:")
        console.log(res)
      }
    )
    wx.onSocketError((res) => {
      console.log("Socket error:")
      console.log(res)
    })
    wx.onSocketMessage((res)=>{
      var msg = JSON.parse(JSON.parse(res.data).data);
      console.log("got message:")
      console.log(msg)
      
    })
  },
  fail(res){
    console.log("Socket failed:")
    console.log(res)
  }
})

调试一下,先启动服务端,再启动客户端,客户端结果如下:
调试效果
看,我们已经成功同步了指令集,以后只要ERRORCONNECTED的指定值不变,添加删除修改其他值就不必考虑客户端的同步定义问题了。

授权与登录

客户端

指令集弄好了,那么,客户端连上以后我们就可以让用户授权客户端使用用户的公开资料,比如昵称、头像什么的……还有就是需要获取用户的登录状态,并获得微信分配我们该用户在这个小游戏的唯一标识openid,我们用这个openid可以直接创建用户,所以就不需要做用户注册功能了。

微信登录的机制是客户端先调用微信API,获得一个叫code的东西,然后我们在服务器端用这个code去取得用户的登录状态和openid

所以,客户端启动后应该先判断用户是否已经授权读取基本资料,如果没有授权,就创建授权按钮让用户授权。如果已经授权,就直接获取用户的登录code然后发送给服务端进行登录。

具体流程如下:

Created with Raphaël 2.2.0 客户端启动 获取用户授权状态 判断用户是否已经授权 获取登录code发送给服务端 结束 显示授权按钮 判断用户授权成功 隐藏授权按钮 yes no yes no

在微具(微信web开发者工具)里面删除之前在game.js里面的全部测试代码。在根目录下新建global.jsnet.js两个文件。
首先在global.js里面导出一个initialize(),用于生成全局变量信息。微信小游戏自带一个GameGlobal的全局变量,我们自定义的全局变量可以直接创建为GameGlobal成员,引用时不需要写GameGlobal

exports.initialize = () => {
  GameGlobal._g = {
    COMMAND: undefined,
    WEBSOCKET_URL : 'ws://localhost:666/',
    DEVICEWIDTH: undefined,
    DEVICEHEIGHT:undefined
  }
}

net.js里面导出initialize(),定义相关的函数,代码如下:

exports.initialize = () => {
  wx.connectSocket({
    url: _g.WEBSOCKET_URL,
    method: "GET",
    success(res) {
      console.log("Socket successed:")
      console.log(res)
      wx.onSocketOpen(
        (res) => {
          console.log("Socket opened:")
          console.log(res)
        }
      )
      wx.onSocketClose(
        (res) => {
          console.log("Socket closed:")
          console.log(res)
        }
      )
      wx.onSocketError((res) => {
        console.log("Socket error:")
        console.log(res)
      })
      wx.onSocketMessage((res) => {
        console.log(res)
        parseMessage(JSON.parse(res.data))
      })
    },
    fail(res) {
      console.log("Socket failed:")
      console.log(res)
    }
  })
}

function parseMessage(msg) {
  if (msg.command == 1) //这是初次连接成功传回的结果
  {
    _g.COMMAND = JSON.parse(msg.data);
    console.log(_g.COMMAND)
    login()
  } else {
    switch (msg.command) //其他命令的处理结果
    {

    }
  }
}

function login() {
  wx.login({
    success(res) {
      var data = {
        command: _g.COMMAND.LOGIN,
        data: res.code
      }
      wx.sendSocketMessage({
        data: JSON.stringify(data)
      })
    }
  })
}

最后修改我们的game.js如下:

var globaldata = require('global.js')
var net = require('net.js')
globaldata.initialize()

wx.getSystemInfo({
  success(res) {
    _g.DEVICEWIDTH = res.windowWidth;
    _g.DEVICEHEIGHT = res.windowHeight;

    wx.getSetting({
      success(res) {
        if (!res.authSetting['scope.userInfo']) {
          let button = wx.createUserInfoButton({
            type: 'text',
            text: '请允许我们使用您的基本资料',
            style: {
              left: _g.DEVICEWIDTH/2-150,
              top: 200,
              width: 300,
              height: 40,
              lineHeight: 40,
              backgroundColor: '#ff0000',
              color: '#ffffff',
              textAlign: 'center',
              fontSize: 16,
              borderRadius: 4
            }
          })
          button.onTap((res) => {
            if (res.errMsg == "getUserInfo:ok")
            {
              button.hide()
              net.initialize()
            }
          })
        }
        else{
          net.initialize()
        }
      }
    })
  }
})

测试一下,可以看到授权按钮,点击后会出现授权对话框,如果选择允许,服务端可以收到登录code。
客户端:
在这里插入图片描述

服务端:调试效果

服务端

服务端收到登录code后,需要调用微信的开放接口auth.code2Session来获取用户的openid。该接口的调用方式为:

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

把我们的appIdappSecretcode带入即可。

首先,我们需要为我们的GameConnection类定义一个事件,用于在接收到客户端信息后的处理工作。
type.js文件里面定义事件代理:

public delegate void ClientCommandHandler(object sender,GameCommand cmd, string data);

GameConnection.cs里面去为GameConnection添加事件:

public event ClientCommandHandler onClientCommand;

然后再接收到客户端信息后,触发这个事件。

……
ArraySegment<byte> receivebuff = new ArraySegment<byte>(new byte[1024]);
                    result = await mSocket.ReceiveAsync(receivebuff, CancellationToken.None);
                    string receiveStr = Encoding.UTF8.GetString(receivebuff.ToArray(), 0, result.Count);
                    LOG("收到来自 " + IP + " 的信息:" + receiveStr);
                    JObject jobject = JObject.Parse(receiveStr);
                    onClientCommand(this,(GameCommand)byte.Parse(jobject["command"].ToString()), jobject["data"].ToString());
……

然后我们到GameServer类里面去写这个事件的方法,并绑定给每个GameConnection

……
GameConnection connection = new GameConnection(request.UserHostAddress, ret.Result.WebSocket, LOG);
connection.onClientCommand +=new ClientCommandHandler(ParseClientCommand);
……
private void ParseClientCommand(object connection,GameCommand cmd, string data)
        {
            switch (cmd)
            {
                case GameCommand.ERROR:
                    break;
                case GameCommand.CONNECTED:
                    break;
                case GameCommand.LOGIN:
                    {
                        string requeststr = "https://api.weixin.qq.com/sns/jscode2session?appid=" + G.AppID + "&secret=" + G.Secret + "&js_code=" + data + "&grant_type=authorization_code";
                        HttpWebRequest wxRequest = (HttpWebRequest)WebRequest.Create(requeststr);
                        wxRequest.Method = "GET";
                        wxRequest.Timeout = 6000;
                        HttpWebResponse wxResponse = (HttpWebResponse)wxRequest.GetResponse();
                        Stream stream = wxResponse.GetResponseStream();
                        StreamReader reader = new StreamReader(stream);
                        string res = reader.ReadToEnd();
                        stream.Close();
                        reader.Close();
                        LOG(res);
                    }
                    break;
                case GameCommand.MOVE:
                    break;
                case GameCommand.ATTACK:
                    break;
                case GameCommand.SEARCH:
                    break;
                case GameCommand.TAKE:
                    break;
                case GameCommand.QUIT:
                    break;
                default:
                    break;
            }
        }
……

我把小游戏的appIdappSecret都定义在全局变量文件里面了,就是GameLib里面G.cs文件里面:

public static string AppID= "wx6748....";
public static string Secret = ".......";

记得换成你自己的哦~!

调试一下,我们会在服务端的日志里面看到效果。
调试效果
嗯,这样我们就成功获得了用户的openid了,在这个过程中,我们初步搭建起了客户端与服务器的通信架构,接下来的开发就方便多了。

下一步,我们要为用户创建账号信息了,关于创建账号的策略,我们下一篇再来讨论。

上一篇:C#服务端的微信小游戏——多人在线角色扮演(九)

请用微信扫描查看游戏效果演示

演示入口

猜你喜欢

转载自blog.csdn.net/foomow/article/details/91961728