各位看官,很久不見,這篇文章本應該是在2018年的時候與大家見面的,結果由於種種緣由拖到了此時此刻(個人鍋啊個人鍋),再過一週就要春節了,在這裏和你們說一聲過年好啊!!!html
今天是個好日子,咱們來一塊兒利用socket.io和canvas這兩樣利器,搞出個簡單的《我畫你猜》遊戲吧git
快過年了,你們放鬆一下忙碌了一年的緊張神經,玩玩本身搞出來的小遊戲吧!github
PS: 若是對接下來講的即時通訊的實現沒有太搞懂的,能夠再看看這裏express
好吧,天下武功爲快不破,趕忙先上個目錄結構 json
因爲以前專門寫過文章介紹了socket.io實現即時通訊的內容,那麼我這邊就儘可能快速寫起了canvas
首先經過express來啓動一個服務而且來建立socket.io的鏈接bash
// app.js文件
const express = require('express');
const app = express();
// 設置靜態文件夾
// 這樣設置會自動識別當前文件夾下的index.html文件
app.use(express.static(__dirname));
const server = require('http').createServer(app);
const io = require('socket.io')(server);
server.listen(8888);
複製代碼
啓動服務,而後訪問localhost:8888,就能夠訪問到index.html文件上的內容了併發
小貼士: app
io is not defined
的錯誤,就是須要先啓動服務後纔會自動生成一個
socket.io/socket.io.js
文件,如上圖所示再去引用就OK了
// index.js文件
// 用來處理遊戲對象數據
let gameObj = {};
// socket實例
let socket = io();
// 監聽connect事件
socket.on('connect', () => {
console.log('客戶端鏈接成功');
});
複製代碼
到這一步刷新頁面在控制檯裏就能夠看到客戶端鏈接成功
這7個字了dom
寫到這裏,只是邁出了小小的一步罷了,接下來,咱們馬不停蹄繼續寫下處理消息的邏輯吧
// app.js
...省略
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 區分是聊天仍是在繪圖
const LINE = 0;
const MESSAGE = 1;
const userList = ['皮卡丘', '巴大蝴', '比比鳥', '妙蛙種子', '小火龍', '傑尼龜'];
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
io.on('connection', socket => {
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 隨機分配用戶名併發送給全部人
const user = userList[Math.floor(Math.random() * userList.length)];
const message = `歡迎${user}加入遊戲!!!`;
// 將數據封裝成json對象
let data = {};
// 經過type來區分
data.type = MESSAGE;
data.sender = '系統';
data.message = message;
// 將消息分發出去
// 消息數據必須是字符串類型,so須要轉換一下
io.emit('message', JSON.stringify(data));
socket.on('message', msg => {
// 傳過來的消息也是json字符串格式的,須要JSON.parse轉成json
let data = JSON.parse(msg);
// 若是是聊天類型,就給sender賦值爲當前用戶名
if (data.type === MESSAGE) {
data.sender = user;
}
io.emit('message', JSON.stringify(data));
});
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
});
server.listen(8888);
複製代碼
上面代碼裏把消息數據都打包成json的格式是爲了方便處理,畢竟消息數據只能接收字符串的格式
而後在發送的時候再經過JSON.stringify
給轉成json字符串,這樣就不會致使報錯了
固然解析對應的消息數據時再經過JSON.parse
來轉換成真正的json便可了
服務端發送和接收消息都搞完了,接下來就該客戶端出場了,客戶端除了上述兩個功能以外還會展現消息(聽起來屌屌的)
那麼,不囉嗦了,快開搞吧!!!
// index.js文件
++++++++++++++++++++++++++++++++++++++++++++++++++++++
const LINE = 0;
const MESSAGE = 1;
++++++++++++++++++++++++++++++++++++++++++++++++++++++
let gameObj = {};
let socket = io();
++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 監聽服務端發來的消息
socket.on('message', msg => {
// 須要先用JSON.parse轉一下
let data = JSON.parse(msg);
console.log(data); // {type: 1, sender: "系統", message: "歡迎皮卡丘進入遊戲"}
// 若是類型爲聊天
if (data.type === MESSAGE) {
let li = `<li><span>${data.sender}: </span>${data.message}</li>`;
$('#history').append(li);
// 聊天區域滾動到最新聊天內容位置
$('#history-wrapper').scrollTop($('#history-wrapper')[0].scrollHeight);
}
});
// 點擊發送按鈕發消息
$('#btn').click(sendMsg);
// 按回車鍵發送消息
$('#input').keyup(e => {
let keyCode = e.keyCode;
if (keyCode === 13) {
sendMsg();
}
});
// 發送消息函數
function sendMsg() {
let value = $.trim($('#input').val());
if (value !== '') {
let data = {};
data.type = MESSAGE;
data.message = value;
gameObj.socket.send(JSON.stringify(data));
$('#input').val('');
}
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++
複製代碼
客戶端上述代碼都作了哪些事情?
const LINE = 0;
const MESSAGE = 1;
複製代碼
socket.on('message', msg => {});
複製代碼
let data = JSON.parse(msg);
複製代碼
if (data.type === MESSAGE) {
// 添加內容
...省略
// 滾動到最新消息位置
...省略
}
複製代碼
function sendMsg() { ...省略 }
複製代碼
以上就是關於消息通訊的基本實現了,下面咱們要進入下一環節,canvas登場了,繼續看下去
canvas這個元素已經等候多時了,終於輪到它大展身手了,用過canvas的都知道,咱們常見的都是在2d上進行繪圖操做,因此在此以前先來獲取一下
// index.js文件
const LINE = 0;
const MESSAGE = 1;
++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 用原生來獲取,jq對象中並無咱們須要的2d
let cvs = document.getElementById('canvas');
let ctx = cvs.getContext('2d');
let gameObj = {
// 當前用戶是否在繪圖
isDrawing: false,
// 下一條線的起始點
startX: 0,
startY: 0
};
++++++++++++++++++++++++++++++++++++++++++++++++++++++
...省略
socket.on('message', msg => {
let data = JSON.parse(msg);
if (data.type === MESSAGE) {
...省略
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++
else if (data.type === LINE) {
// 這是畫線函數,專門繪製所用
drawLine(ctx, data.startX, data.startY, data.endX, data.endY, 1);
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++
});
// 發送消息函數
...省略
++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 開始在畫板上畫畫了
// 鼠標按下時的操做
$('#canvas').on('mousedown', function(e) {
let cvsPos = $(this).offset(),
mouseX = e.pageX - cvsPos.left || 0,
mouseY = e.pageY - cvsPos.top || 0;
// 更新一下startX和startY
gameObj.startX = mouseX;
gameObj.startY = mouseY;
// 更新爲繪圖狀態
gameObj.isDrawing = true;
});
// 鼠標移動時的操做
$('#canvas').on('mousemove', function(e) {
// 當繪圖狀態爲true的時候才能夠繪製
if (gameObj.isDrawing) {
let cvsPos = $(this).offset(),
mouseX = e.pageX - cvsPos.left || 0,
mouseY = e.pageY - cvsPos.top || 0;
if (gameObj.startX !== mouseX && gameObj.startY !== mouseY) {
// 開始繪製線段,drawLine爲畫線函數
drawLine(ctx, gameObj.startX, gameObj.startY, mouseX, mouseY, 1, $('#color').val());
// 既然畫線了,那就把畫的線段數據也打包成json傳給服務端
let data = {};
data.startX = gameObj.startX;
data.startY = gameObj.startY;
data.endX = mouseX;
data.endY = mouseY;
data.type = LINE;
// 別猶豫,直接經過socket發給服務端
socket.send(JSON.stringify(data));
// 這裏還要更新一下startX和startY
gameObj.startX = mouseX;
gameObj.startY = mouseY;
}
}
});
// 鼠標擡起時的操做
$('#canvas').on('mouseup', function() {
gameObj.isDrawing = false;
});
// 畫線函數
function drawLine(ctx, x1, y1, x2, y2, thick) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = thick;
ctx.strokeStyle = '#00a1f4';
ctx.stroke();
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++
複製代碼
別看上面代碼忽然多了好多好多,但實際上也無非下面幾點,莫慌,咱們來梳理一下
data.type === LINE
來區分出來消息類型爲畫線的數據說難不難,說簡單不簡單。不過視熟練程度不一樣,會有不一樣的感覺罷了,多寫多練習,知識天然會收錄到本身的手中
寫到這裏基本能夠說是socket.io和canvas的要領都掌握了,那麼最後的最後,咱們不能忘記文章的標題主旨,是多人遊玩,不只僅是簡單的自娛自樂,也要衆人同樂
那麼讓咱們再加把勁,實現多人遊戲邏輯
玩遊戲嘛,天然有個遊戲的邏輯和規則
遊戲邏輯在客戶端方面的實現相對來講仍是比較簡單的,更多的操做仍是要靠服務端那裏了
那麼就先由簡到難的實現一下吧
// index.js文件
const LINE = 0;
const MESSAGE = 1;
// 添加個遊戲常量
const GAME = 2;
let gameObj = {
...省略
// 遊戲狀態
WAITTING: 0,
START: 1,
OVER: 2,
RESTART: 3,
// 當前輪到誰來繪圖
isPlayer: false
};
...省略
socket.on('message', msg => {
let data = JSON.parse(msg);
if (data.type === MESSAGE) {
...省略
} else if (data.type === LINE) {
...省略
} else if (data.type === GAME) { // 若是進行遊戲,傳過來的type值必須是GAME
// 經過data.state來判斷遊戲當前的進度
// 遊戲開始的邏輯
if (data.state === gameObj.START) {
// 遊戲要是開始了就須要清空畫布
ctx.clearRect(0, 0, cvs.width, cvs.height);
// 清空聊天記錄和隱藏從新開始
$('#restart').hide();
$('#history').html('');
// 區分一下是當前畫圖的玩家仍是猜圖的玩家
if (data.isPlayer) {
gameObj.isPlayer = true;
$('#history').append(`<li>輪到你了,請你畫出<span class="answer">${data.answer}</span></li>`);
} else {
$('#history').append(`<li>遊戲即將開始,請準備,大家有一分鐘的時間去猜答案哦</li>`);
}
}
// 遊戲結束的邏輯
if (data.state === gameObj.OVER) {
gameObj.isPlayer = false;
$('#restart').show();
$('#history').append(`<li>本輪遊戲的獲勝者是<span class="winner">${data.winner}</span>,正確答案是: ${data.answer}</li>`);
}
if (data.state === gameObj.RESTART) {
$('#restart').hide();
ctx.clearRect(0, 0, cvs.width, cvs.height);
}
}
});
...省略
// 畫線函數
...省略
// 重玩
$('#restart').on('click', function() {
let data = {};
data.type = GAME;
data.state = gameObj.RESTART;
socket.send(JSON.stringify(data));
});
複製代碼
繼續來梳理一下以上代碼都作了什麼?
const GAME = 2;
let gameObj = {
...省略
// 遊戲狀態
WAITTING: 0,
START: 1,
OVER: 2,
RESTART: 3,
// 當前輪到誰來繪圖
isPlayer: false
};
複製代碼
上述三點就是客戶端實現多人遊戲的代碼了,不要停歇,立刻就要到頭了,繼續寫服務端的邏輯吧
// app.js文件
...省略
const LINE = 0;
const MESSAGE = 1;
// 添加遊戲常量
const GAME = 2;
// 遊戲狀態和遊戲邏輯
const WAITTING = 0;
const START = 1;
const OVER = 2;
const RESTART = 3;
let player = 0;
let wordsList = ['蘋果', '運動鞋', '火箭', '足球', '小黃人', '汽車', '小鳥'];
let currentAnswer;
let currentState = WAITTING;
let timer;
// 鏈接的客戶端數量
let len = 0;
io.on('connection', socket => {
...省略
// 將數據封裝成json對象
...省略
// 把遊戲的消息通知全部人
let game = {};
game.type = GAME;
game.state = WAITTING;
io.emit('message', JSON.stringify(game));
// 遍歷客戶端的鏈接
io.clients((err, client) => {
if (err) throw err;
len = client.length;
});
// 當前狀態爲等待而且鏈接數超過兩個的時候纔開始遊戲
if (currentState === WAITTING && len > 2) {
startGame(socket);
}
socket.on('message', msg => {
...省略
// 判斷是否是有玩家答對了
if (currentState === START && data.message === currentAnswer) {
let game = {};
game.type = GAME;
game.answer = currentAnswer;
game.winner = user;
game.state = OVER;
io.emit('message', JSON.stringify(game));
currentState = WAITTING;
clearTimeout(timer);
}
// 從新開始
if (data.state === RESTART && data.type === GAME) {
startGame(socket);
}
});
});
// 開始遊戲方法
function startGame(socket) {
// 分配一個玩家來畫畫
player = (player + 1) % len;
// 隨機分配個圖案
let random = Math.floor(Math.random() * wordsList.length);
currentAnswer = wordsList[random];
// 通知全部玩家遊戲開始
let data = {};
data.type = GAME;
data.isPlayer = false;
data.state = START;
io.emit('message', JSON.stringify(data));
// 遍歷客戶端,而後找到畫畫的那個用戶告訴他相關data
let count = 0;
io.clients((err, client) => {
client.forEach(item => {
if (count === player) {
let game = {};
game.type = GAME;
game.state = START;
game.isPlayer = true;
game.answer = currentAnswer;
// 這條消息只有繪圖的玩家才能看到
socket.send(JSON.stringify(game));
}
count++;
});
});
// 1分鐘後遊戲結束
timer = setTimeout(() => {
let obj = {};
obj.type = GAME;
obj.state = OVER;
obj.winner = '沒有人啊!';
obj.answer = currentAnswer;
io.emit('message', JSON.stringify(obj));
}, 60 * 1000);
// 當前狀態修改成START
currentState = START;
}
server.listen(8888);
複製代碼
服務端,個人朋友,你剛纔都作了什麼?
// 遊戲常量
const GAME = 2;
// 遊戲狀態
const WAITTING = 0;
...省略
const RESTART = 3;
// 遊戲邏輯
let player = 0;
...省略
let len = 0;
複製代碼
// 把遊戲的消息通知全部人
let game = {};
...省略
io.emit('message', JSON.stringify(game));
// 遍歷客戶端的鏈接
io.clients((err, client) => {
if (err) throw err;
len = client.length;
});
// 當前狀態爲等待而且鏈接數超過兩個的時候纔開始遊戲
if (currentState === WAITTING && len > 2) {
startGame(socket);
}
複製代碼
player = (player + 1) % len;
複製代碼
let random = Math.floor(Math.random() * wordsList.length);
currentAnswer = wordsList[random];
複製代碼
let data = {};
...省略
io.emit('message', JSON.stringify(data));
複製代碼
let count = 0;
io.clients((err, client) => {
client.forEach(item => {
// 匹配爲分配的玩家才能夠繪製答案
if (count === player) {
let game = {};
...省略
// 這條消息只有繪圖的玩家才能看到
socket.send(JSON.stringify(game));
}
count++;
});
});
複製代碼
timer = setTimeout(() => {
...省略
}, 60 * 1000);
currentState = START;
複製代碼
// 判斷是否是有玩家答對了
if (currentState === START && data.message === currentAnswer) {
let game = {};
game.type = GAME;
game.answer = currentAnswer;
game.winner = user;
game.state = OVER; // 狀態修改成OVER
// 把該消息數據傳遞給全部玩家
io.emit('message', JSON.stringify(game));
// 恢復當前狀態初始值
currentState = WAITTING;
// 清空1分鐘計時器
clearTimeout(timer);
}
複製代碼
好了,到這裏就都結束了,小夥伴們能夠跑起來試一試了,固然動手敲起來纔是王道了
最後的最最後: 必須把地址發給你們以做參考啊