一直想作一个联机的游戏,以前也用socket.io作了几个demo,不过那个时候不知道帧同步这回事,因此那时我就是经过将全部玩家的数据(位置啊,血量啊),还有子弹的全部数据转发给全部的玩家(除了本身),而后其余的玩家经过判断是否有这个数据,若是没有就生成一个,有的话就将覆盖掉。javascript
不过上面的这种作法超级卡,无比的卡,异常的卡,无可奈何,百度了一下怎么作联机游戏。前端
网络上,有两种作联机游戏的方式,一种是状态同步,一种是帧同步。java
下面就简单的介绍一下两种方法的区别,不过就不过多的说了。(由于我仍是只知其一;不知其二2333333)node
联机游戏最重要的就是全部客户端显示统一。git
这个作法是服务器为主,服务器将全部的计算处理好,而后返回统一的数据给全部的玩家,玩家经过这个数据渲染出游戏的界面。(这个方法和我以前作的有点相似= = )后端
这种作法很安全,由于全部的数据在服务器中,客户端无论怎么改数据,最后执行的依旧是服务器中的数据。安全
这个就是本文重点讲的,在百度上怎么搜都没有看到js
/node
/socket.io
的联机游戏教程,因此只能本身硬着头皮学(瞎写)。服务器
目前我实现的帧同步作法是,客户端发出的操做并不会在本地处理,而是会上传到服务器,让服务器保存全部玩家的操做,而后在固定时间发送给全部的客户端。而后客户端就会在固定的频率上处理这些操做,以到达一个同步的效果。网络
而且帧同步会保存玩家的操做,因此很容易作回放&观战很简单。app
下面我简单说明一下作的两个demo。(代码过于烂,能够学习思路,可是不能抄,会死。)
这个demo主要实现了中途加入游戏的玩家能够看到已经在游戏中的玩家、游戏回放、全部玩家操做统一。
这个主要是实现了一整个的房间系统,建立私密房间、加入房间、房间列表、踢出房间。游戏就写了一个移动(后面不想写了...)
还作了一个简单的聊天室,两个频道,一个世界频道(全部人都看到),一个房间频道(房间内看到)。
我这里的作法是这样的, 经过先后端两个action.js
来分别解析发送给双方的动做:
服务器:
const actions = {
// 玩家加入游戏的操做
'player.add': (player, package) => {
},
// 建立房间
'room.create': (player, package) => {
},
// 加入房间
'room.add': (player, package) => {
},
// 离开房间
'room.leave': (player, package) => {
},
// 踢出房间
'room.shit': (player, package) => {
},
// 房间列表
'room.list': (player, package) => {
},
// 房间中准备游戏
'room.ready': (player, package) => {
},
// 建立一个游戏
'game.create': (player, package) => {
},
// 游戏的操做
'game.action': (player, package) => {
},
// 系统信息分发
'message': (player, package) => {
}
}
// 处理数据包
module.exports = function (data) {
if (!data.action) {
console.warn('没法处理的数据包', data);
return;
}
let action = actions[data.action];
if (!action) {
console.warn('没法处理的动做: ' + data.action);
return;
}
actions[data.action](this, data);
}
// 而后若是有玩家连接上了服务,因此的数据都是经过使用on('all'), emit('all')这个方法来接受和发送的
this.socket.on('all', data => {
action.call(this, data)
});
复制代码
而后前端发送数据包是这样发送的:
on(name, fn) {
if (!this.io) {
console.error('not connect socket server.');
return;
}
this.io.on(name, fn);
},
emit(name, data) {
if (!this.io) {
console.error('not connect socket server.');
return;
}
this.io.emit(name, data);
},
// 在这里发送数据包
action(name, data) {
this.emit('all', {
data,
action: name,
time: new Date().getTime()
});
},
复制代码
客户端也有一个action
来解析后端发送的数据包,这里我就不粘贴代码了(由于都同样)
目前个人作法是,服务器固定一个频率将接受到的全部玩家操做发送给全部的客户端。
class G{
constructor(room){
this.room = room;
// 这个是保存整个游戏全部的操做
this.frames = [];
// 这个保存每帧的客户端操做
this.actions = {};
// 频率,也就是帧,在每一帧发送保存客户端的全部操做
this.packageNum = global.option.gameFrame;
this.interval = null;
this.start();
}
start(){
this.interval = setInterval(() => {
// 将房间中全部玩家的动做统一发送给房间内全部人
global.Core.socket.emit('game.action', this.actions, null, this.room.key);
// 将这一帧的操做保存起来,后面就能够经过这个来制做游戏回放了。
this.frames.push(this.actions);
// 将操做清空
this.room.playerList.forEach(item => {
this.actions[item.id] = [];
});
}, 1000 / this.packageNum);
}
}
复制代码
而后客户端就能够经过解析game.action
:
// action.js
'game.action': package => {
game.complite(package.data);
},
// game.js
complite(action) {
// 将动做拿出来给玩家的实例处理
Object.keys(action).forEach(key => {
action[key].forEach(ac => {
this.playerList[key].action(ac);
})
});
// 接收到后端发过来的动做帧才会让每一个玩家实例移动,这样就能够达到全部客户端显示统一
Object.keys(this.playerList).forEach(key => {
this.playerList[key].move();
})
}
复制代码
客户端发送游戏操做是这样的:
// 这些代码很简单= = ,我就懒得写注释了2333.
let prev = null;
function action(key, flag) {
let action = key + (flag ? '_up' : '');
if (action == prev) return;
app.action('game.action', {
action
});
prev = action;
}
let event = {
'0'(flag) {
action('left', flag);
},
'1'(flag) {
action('top', flag);
},
'2'(flag) {
action('right', flag);
},
'3'(flag) {
action('bottom', flag);
},
'-5'(flag){
action('speed', flag);
}
}
document.body.onkeydown = ev => {
if (!this.playStatus) return;
let code = ev.keyCode - 37;
event[code] && event[code]();
}
document.body.onkeyup = ev => {
if (!this.playStatus) return;
let code = ev.keyCode - 37;
event[code] && event[code](true);
}
复制代码
在以前game.js
的注释我就写了,经过保存每一动做帧(我瞎编的名词,就是服务器每帧保存玩家的动做)玩家的操做,若是某个玩家须要看回放,就能够接受这个集合,而后遍历执行完全部的动做 = =(是否是很简单)
this.io.on('getGameLog', data => {
this.logs = data;
this.play();
})
play() {
// 若是看完了全部动做就退出
if (this.playLog >= this.logs.length) {
this.playStatus = false;
return;
}
this.playStatus = true;
let item = this.logs[this.playLog];
game.complete(item);
// 解析每一帧
this.playLog++;
// 这里是播放的倍率
setTimeout(this.play.bind(this), 1000 / (20 * this.playX));
}
复制代码
由于本人文笔有限,可能会有错别字、思路没讲清的内容,还请多多担待= =(可能会看不懂我写的是什么个鬼东西)
还有,就是这个思路可能不是很行...,由于这是我本身想的,会有不少的问题,这里就当作抛砖引玉了。
撒花,在掘金第一篇文章~