现有一个微信小程序叫中国象棋项目,棋盘类的单机游戏看着有缺少了什么,现在给补上了,加个联机对战的功能,增加了可玩性,对新手来说,实现联机游戏还是有难度的,那要怎么实现的呢,接下来给大家讲一下。
考虑到搭建联机游戏的服务器成本不小,第一个想法是用小程序的蓝牙功能实现游戏联机的,但是其API接口提供的蓝牙硬件支持兼容问题不少,暂时不去折腾了,现在采用UDP通信就很容易实现,可以在WIFI局域网内让两个以上小程序实现通信。
UDP通信
先来了解一下 UDP通信 的工作原理,这是一个面向无连接的传输协议,是UDP通信,与之对应的是 面向可连接的传输协议,是 TCP通信
从上图看出来,UDP通信的方式很简单,可以想象它们能充当其中一个角色,
- client客户端: 只负责发送报文
- server服务端:只负责接收报文
小程序实现UDP通信,要创建client客户端和服务端server,各占一个
socket
端口,
对初学者来说,第一次接触不好理解,端口,可比喻成线路一端的接口。
TCP通信的面向连接是比UDP通信最可靠的,那为什么不优先采用TCP通信呢
小程序的TCP通信实现过程中,需要绑定到wifi,这一点获取wifi信息的处理有遇到问题,对新手来说是比较麻烦的,暂且避之,能正常获取到wifi信息再来考虑
client客户端
直接在一个模块文件中实现,这个在项目中的文件是lan.js
,可以理解它为局域网工具模块,
负责发送
要向server服务端发送报文(消息),就写一个方法sendMessage(e)
来调用,传入服务端的remoteInfo
,实现代码如下
import Util from './util';
function sendMessage(e){
//需要传入的参数
const { message, port, remoteInfo, fail, success, autoClose } = e;
let udp = wx.createUDPSocket();
udp.onError(err=>{
//...这里处理初始化udp的错误
});
udp.onMessage(res=>{
const { remoteInfo, localInfo } = res;
if(autoClose) udp.close();//默认自动关闭udp
//消息res.message是ArrayBuffer对象,要转换为json object对象才好处理
let message = Util.arrayBufferToString({data:res.message});
message = Util.parseJSON(message);
//返回服务端响应的数据
success(message,remoteInfo,localInfo);
});
//绑定端口
udp.bind(port);
//发送消息message 到服务端 `address(IP)`和`port(端口)`
udp.send({
address:remoteInfo.address,
port:remoteInfo.port,
message:toStringMesssage(message)
});
return udp;
}
客户端向服务端发出消息,没必要加请求超时的处理,后面有个逻辑是处理连接的,用它代替连接超时处理的判断
连接服务端
客户端连接到服务端方法是connectServer(remoteInfo,e)
,实现代码如下,加了定时连接请求,如果请求超时了,就会提示用户连接超时(连接断开)
const Timeout = 6000;//超时6s
function connectServer(remoteInfo,e){
const { config, onReceive, onError, onConnect, onDisconnect } = e;
let connectInfo;
let timer,timer2;
//关闭定时器
const closeTimer = function(){
if(timer) {
clearTimeout(timer);
timer=undefined;
}
if(timer2) {
clearTimeout(timer2);
timer2=undefined;
}
};
const clientUdp = wx.createUDPSocket();
clientUdp.onClose(function(){
closeTimer();
});
clientUdp.onError(function(err){
closeTimer();
//...这里处理udp抛出的错误,回调onError
onError(err);
});
//默认不传port,就绑定一个随机的port(端口号)
clientUdp.bind();
let time;
let sendSign = function(){
//定时发送
timer = setTimeout(function(){
let message = {
intent:'keep_connect',
ntime:Date.now(),
};
if(!connectInfo){
message.intent='create_connect';
message.data=config;
}
//定时向服务端发送连接信息
clientUdp.send({
address:remoteInfo.address,
port:remoteInfo.port,
message:JSON.stringify(message)
});
//加个定时器,用于超时判断
timer2 = setTimeout(function(){
connectInfo = null;
onDisconnect({ errMsg:'the request timeout' });
},Timeout);
},config.time || 3000);
};
clientUdp.onMessage(function(res){
//防抖处理
closeTimer();
const { localInfo,remoteInfo } = res;
let message = Util.arrayBufferToString({data:res.message});
message = Util.parseJSON(message);
if(message.intent=='create_connect'){
connectInfo = remoteInfo;
//回调连接事件
onConnect({message:message.data,localInfo,remoteInfo});
}else{
if(time && message.otime==undefined) message.otime = Date.now() - time;
//回调接收事件
onReceive({message,localInfo,remoteInfo});
time = Date.now();
}
sendSign();
});
//开始发送
sendSign();
return clientUdp;
}
方法
sendSign()
就是发送信号的意思,可以这样认为,在网络上冒个泡,可以让对方知道你在线,主动找你沟通,如果超过时间还不吐泡泡,就认为你潜水了(隐身)
server服务端
负责接收
接下来,写一个叫服务端server的创建方法createServer()
,用于监听客户端发来的连接请求,还要处理其它的请求,稍微复杂一点,实现代码如下
import Util from './util';
function createServer(e){
const { config, onReceive, onError, onConnect, onDisconnect } = e;
let udp = wx.createUDPSocket();
udp.onError(function(err){
//...这里处理udp抛出错误,回调onError
onError(err);
});
udp.onClose(function(){
closeTimer();
});
udp.onMessage(function(res){
const { localInfo, remoteInfo } = res;
let message = Util.arrayBufferToString({data:res.message});
message = Util.parseJSON(message);
let response;
switch(message?.intent){
case 'create_connect':
//...处理创建连接请求
break;
case 'keep_connect':
//...处理保持连接请求
break;
default:
//如果没处理,就交给回调onReceive处理
response = onReceive({ localInfo, remoteInfo, message });
}
//如果还没处理,就不需要响应了(不理睬)
if(!response) return;
//服务端响应数据发给客户端
udp.send({
address:remoteInfo.address,
port:remoteInfo.port,
message:toStringMesssage(response)
});
});
//绑定一个服务器端口
let port = udp.bind(config.port);
return {
getPort(){
return port;
},
close(){
udp.close();
}
};
}
有没有觉得,服务端的处理逻辑很像web的服务器处理请求,处理响应来自客户端(浏览器)的请求
管理客户端连接
再具体一点,处理创建和保持连接请求的方法,将上面的代码改一下,添加后的代码如下
const Timeout = 6000;//超时6s
//...
let connectInfo;
let timer;
//保持连接(定时连接检查)
const keepConnectInfo = function(){
timer = setTimeout(function(){
if(connectInfo){
let otime = Date.now()-connectInfo.utime;
//未超时
if(otime<Timeout){
keepConnectInfo();
onReceive({
//...回调接收事件,返回连接状态
});
return;
}
}
connectInfo=null;
//若连接超时了,就回调断开连接的方法
onDisconnect({ errMsg:'wait update is timeout'});
},Timeout);
};
//关闭定时器
const closeTimer = function(){
//...
};
switch(message?.intent){
case 'create_connect'://创建连接请求
{
let data = message.data;
connectInfo = {
//...记录连接信息
};
//回调连接事件
onConnect({
//...传连接数据
});
response = {
intent:message.intent,
//...返回连接后获取的初始化数据
};
//防抖处理
closeTimer();
keepConnectInfo();
break;
}
case 'keep_connect'://保持连接请求
{
if(connectInfo) {
connectInfo.utime = Date.now();
response = {
intent:message.intent,
//...
};
}else{
response = {
intent:'create_connect',
//...返回配置数据
data:config,
};
}
//记录时间差
if(message.ntime) response.utime = response.time - message.ntime;
break;
}
default:
//如果没处理,就交给回调onReceive处理
response = onReceive({ localInfo, remoteInfo, message });
}
//...
可以看出来,这里连接的逻辑是定时检查客户端连接更新的状态,如果超过时间不更新,就判断为连接超时
局域网广播
两个小程序之间是怎么知道对方的IP和端口呢,这就要借助广播IP地址了,
发送广播IP地址,就可以在局域网发现在线的设备,然后请求连接,广播过程是这样的
来打个比喻:
客户端A(你)发送广播消息(点餐订单),交给路由器(平台)处理,路由器会转发消息,附带了你的IP(家)地址和端口(门号),到其余的客户端(抢单),
如果有客户端(抢到单的外卖服务员)对方想回应你,就会给你发消息:你的外卖到了…(票据上有写了对方的IP(店铺)地址和端口(门牌号))
发送广播
客户端怎样发送广播呢,这个方法是sendBroadcast()
,很容易实现,代码如下
function sendBroadcast(e){
const { port, fail, success, showLoading } = e;
let udp = wx.createUDPSocket();
let timer;
let list = [];//记录接收的列表
function complete(callback){
//...
udp.close();//处理完要关闭
callback();
}
if(fail instanceof Function){
udp.onError(function(err){
complete(function(){
fail(err);
});
});
}
udp.onMessage(function(res){
//将接收的消息转换成json对象
res.message = toDataJSON(res.message);
list.push(res);
});
udp.bind();
//发送广播消息,其中port是指定小程序的服务端接收端口
udp.send({
address:'255.255.255.255',
port: port,
message: JSON.stringify({ intent: 'scan' })
});
//加上定时
timer = setTimeout(function(){
//到时结束
complete(function(){
if(success instanceof Function) success({ list });
})
}, e.timeout || 3000);
//...
}
广播IP是
255.255.255.255
,就是发给局域网内的路由器,需要注意的是,不是所有的路由器都是支持广播IP的,要确保支持它,需要登录路由器的控制页面,找看有没有其中的隔离AP
项,取消勾选即可
发送的广播消息是
{ intent: 'scan' }
,需要从另一个小程序的服务端负责接收那方法onReceive()
中处理这个广播消息,返回响应数据
游戏联机
实现游戏的联机方式分两种,上面开始讲过,用其中的一个角色:服务端或者客户端
主动加入
一种是主动加入游戏,就用客户端发送问候消息方法sendMessage(e)
去请求服务端,服务端会处理响应请求
加入前,客户端需要先知道对方的IP地址和端口,从上面讲得发送广播方法
sendBroadcast(e)
来扫描一下,找到对方后,然后发送加入请求
在对方的服务端返回同意消息时,附带了游戏入口消息,告诉客户端加入游戏的途径
主动等待
另一种,是主动创建好游戏地盘,创建好服务端,用一个接收数据的绑定的端口,去负责监听客户端发来的请求,然后处理响应数据
这里注意不要搞错,当另一个小程序客户端发来加入游戏的请求时,要留点心,别把客户端的端口号当作服务端的端口号(接收数据)
关于项目
好了,UDP通信的方法就讲到这里,这样有了大致的方向,
如需要看项目源码的请在 下载列表点这里 找联机游戏相关的项目,那些联机游戏项目里都有应用,有对应的介绍,请放心下载,多谢支持,愿学有所获!
打开项目源码,如果遇到微信开发工具提示
Error: 登录用户不是该小程序的开发者
,需要替换项目的测试号替换为自己的,点击开发工具右边的详情,里面有AppID,可修改替换,
这里有一个中国象棋-单机游戏开发流程详解文章,需要的同学可以先看看,
这里是在原单机游戏项目的基础上增加了联机功能,联机游戏运行的动图效果如下
联机测试
项目还没有自己的测试号,就前往申请一个测试号,申请成功后,登录如下图,其中AppID的就是
- 申请测试号 🔗传送门
开发工具上扫码预览出来的小程序是开发版,只能在自己的微信上体验,想测试联机游戏就这样做,选择里面的真机调试项,这样就可以模拟两个用户来体验了,一个在开发工具上模拟器上,另一个就是自己的真机微信上
如果有两个手机微信就这样试试,用两个手机微信分别登录开发工具弄一个开发版小程序来,这样两个手机微信上就能测试游戏联机,
上面操作有点麻烦,就用自己申请好的一个小程序来测试,发布一个体验版小程序就可以让很多人参与测试了。文章来源:https://www.toymoban.com/news/detail-440896.html
文章来源地址https://www.toymoban.com/news/detail-440896.html
到了这里,关于【联机对战】微信小程序联机游戏开发流程详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!