一直想作一個聯機的遊戲,以前也用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));
}
複製代碼
由於本人文筆有限,可能會有錯別字、思路沒講清的內容,還請多多擔待= =(可能會看不懂我寫的是什麼個鬼東西)
還有,就是這個思路可能不是很行...,由於這是我本身想的,會有不少的問題,這裏就當作拋磚引玉了。
撒花,在掘金第一篇文章~