基于springboot + websocket + html5 canvas打造网络版坦克大战

前言

该项目为本人业余时间原创,禁止任何一切商业行为,转载须经过本人同意,本人微信号: Jiang_Vin

gitbub: https://github.com/jiangvin/webtank

项目部署地址: http://116.63.170.134:8201/

地图编辑页面:   http://116.63.170.134:8201/map

之前工作重心一直偏向后台微服务集群研究,业务项目页主要是单工通信为主,最近一直想扩展自己的技能,想用websocket技术做点东西。

1.websocket运用的极致就是即时战略游戏,因为我只有1个人,之前也没做过游戏,思来想去决定做 网络版坦克大战(即时性强,游戏逻辑简单,后续可以基于它继续入坑AI学习)。

2.既然要做就想要做好,之前因为工作关系前端也写得少,所以这次想利用html5 canvas写个 电脑-手机-平板 的全平台支援游戏,电脑用键盘控制,手机平板用触控控制。

3.既然是网络游戏,效率考验也是重要环节,所以这里想设计成多游戏房间模式,服务器可以创建不限数量的游戏房间,每个游戏房间能容纳不限数量的玩家进行游戏,来看看最终的运算效率和服务器承受计限到底如何。

4.关于游戏性。因为这是大学毕后做的第一个网络类型游戏,初期的想法很简单:游戏房间分为PVP PVE EVE三种模式,其中E为电脑AI(EVE模式也是为之后AI训练架设温床),玩家可以在任何时间加入或者离开房间,如果某个房间的玩家全部离开,则房间自动关闭,及时释放服务器资源。

坑挖得有点大,看之后能不能一步步填满,之前也租了服务器,之后定期会把master的代码部署到服务器上供测试:

微信浏览器测试
微信浏览器

账号系统

既然是网络游戏,肯定需要账号系统,我这里设计得比较简单,所有用户都只需要一个不重复的用户名则可以登录,用户名可以支援各种语言。

用户加入: 一旦用户输用户名则websocket会通过url的形式带入后端,后端通过DefaultHandshakeHandler截获名字,通过HttpSessionHandshakeInterceptor进行websocket握手前检测名字是否重复,若不重复则通过ChannelInterceptor通知用户服务中心。

用户离开: 一样通过ChannelInterceptor通知用户服务中心。

游戏逻辑相关: 一开始再想如何得知用户已经订阅完所有path并且确定加入成功?后面决定当前端完成所有加入的前置操作时会给后端发一个READY消息,当后端接收到了READY后则可以通知所有人: XXX加入了游戏并且给他初始化坦克。

逻辑流程图:

1.建立连接

2.订阅地址

3.暂停并发送CLIENT_READY

4.接收游戏数据

5.接收SERVER_READY并解除暂停

关于超时重连问题: 在测试中发现有一定记录连接服务器会超时,这里前端加了一个行为判断,当连接超过5秒的时候结束锁定,并返回一个超时信息。因为后续的连接可能涉及到切换场景,所以这里的超时比较麻烦,先考虑设定成先暂停,再切换场景,最后再切换回来的操作,代码如下:

        Room.getOrCreateRoom();
        Common.runNextStage();
        Status.setStatus(Status.getStatusPause(), "加入房间中...");
        Common.sendStompMessage({
            "roomId": roomId,
            "joinTeamType": selectGroup
        }, "JOIN_ROOM");
        Common.addConnectTimeoutEvent(function () {
            Common.runLastStage();
        });

关于操控和碰撞检测: 试这块逻辑非常坎坷,写了三版,最终效果大致才满意,先测试一下,之后再整理逻辑。

前端竖屏适配

因为手机一般是竖屏,所以这里的策略是判断窗体的高度大于宽度,则旋转90度,并且规定了一个标准尺寸:800 * 500,如果手机屏幕小于这个数则整个画面自动缩放,代码如下: `Common.windowChange = function () { const width = document.documentElement.clientWidth; const height = document.documentElement.clientHeight; const scale = Resource.calculateScale(width, height);

const newWidth = width / scale;
const newHeight = height / scale;
let style = "";
//变形的中心点为左上角
style += "-webkit-transform-origin: 0 0;";
style += "transform-origin: 0 0;";
if (width >= height) {
    // 横屏
    style += "width:" + newWidth + "px;";
    style += "height:" + newHeight + "px;";
    style += "-webkit-transform: rotate(0) scale(" + scale + ");";
    style += "transform: rotate(0) scale(" + scale + ");";
    _canvas.width = newWidth;
    _canvas.height = newHeight;
} else {
    // 竖屏
    style += "width:" + newHeight + "px;";
    style += "height:" + newWidth + "px;";
    style += "-webkit-transform: rotate(90deg) scale(" + scale + ") translate(0px," + -newWidth + "px);";
    style += "transform: rotate(90deg) scale(" + scale + ") translate(0px," + -newWidth + "px);";
    _canvas.width = newHeight;
    _canvas.height = newWidth;
}
let wrapper = document.getElementById("wrapper");
wrapper.style.cssText = style;
Control.generateTouchModeInfo(height > width);

 

触控适配

刚旋转的时候发现触控跑偏了,才醒悟因为画面被旋转了,所以触控的点也要跟着旋转,这里专门写了一个函数处理触控点,如果发现画面被旋转缩放了,触控也需要相应的旋转缩放到屏幕中间去: `Common.getTouchPoint = function (eventPoint) { let x = eventPoint.clientX; let y = eventPoint.clientY;

//缩放处理
const scale = Resource.getScale();

const touchPoint = {};
if (Control.getControlMode().portrait) {
    //竖屏
    touchPoint.x = y;
    touchPoint.y = Common.height() * scale - x;
} else {
    //横屏
    touchPoint.x = x;
    touchPoint.y = y;
}

touchPoint.x /= scale;
touchPoint.y /= scale;

return touchPoint;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值