git clone git@github.com:jrainlau/draw-something.git cd draw-something node ws-server.js // 開啓websocket服務器 npm run dev // 運行客戶端程序 而後瀏覽器打開localhost:8080便可
效果預覽:vue
由於閒得慌,一直和朋友在玩你畫我猜之類的小遊戲,忽然想到能不能本身也作一個呢,反正閒着也是閒着,同時正好能夠學習一下websocket的用法。node
首先分析總體架構部分:git
能夠看到,總體架構很是簡單,僅僅是一臺服務器和兩個客戶端。github
WebSocket服務器:提供數據同步,內容分發功能,採用nodejs寫成。web
繪圖畫布:進行繪圖的區域,同時可以獲取關鍵詞,其繪製的內容會同步到猜圖畫布中。npm
猜圖畫布:同步自繪圖畫布,輸入框可以提交關鍵詞,檢測答案是否正確。canvas
下面來看具體的代碼實現。數組
服務器採用node.js
進行搭建,使用了ws
庫實現websocket功能。新建一個名爲ws-socket.js
的文件,代碼以下:瀏覽器
/*** ws-socket.js ***/ 'use strict' // 實例化WebSocketServer對象,監聽8090端口 const WebSocketServer = require('ws').Server , wss = new WebSocketServer({port: 8090}) // 定義關鍵詞數組 let wordArr = ['Monkey', 'Dog', 'Bear', 'Flower', 'Girl'] wss.on('connection', (ws) => { console.log('connected.') // 隨機獲取一個關鍵詞 let keyWord = ((arr) => { let num = Math.floor(Math.random()*arr.length) return arr[num] })(wordArr) // 當服務器接收到客戶端傳來的消息時 // 判斷消息內容與關鍵詞是否相等 // 同時向全部客戶端派發消息 ws.on('message', (message) => { console.log('received: %s', message) if (message == keyWord) { console.log('correct') wss.clients.forEach((client) => { client.send('答對了!!') }) } else { console.log('wrong') wss.clients.forEach((client) => { client.send(message) }) } }) // 服務器初始化時即向客戶端提供一個關鍵詞 wss.clients.forEach((client) => { client.send('keyword:' + keyWord) }) })
使用方法基本按照ws
庫的文檔便可。其中ws.on('message', (message) => { .. })
方法會在接收到從客戶端傳來消息時執行,利用這個方法,咱們能夠從繪圖畫布不斷地向服務器發送繪圖位點的座標,再經過.send()
方法把座標分發出去,在猜圖畫布中獲取座標,實現繪圖數據的同步。
做爲客戶端,我選擇了vue
進行開發,緣由是由於vue
使用簡單快速。事先說明,本項目僅僅做爲平常學習練手的項目而非vue的使用,因此有蠻多地方我是圖方便暴力使用諸如document.getElementById()
之類的寫法的,之後有機會再改爲符合vue
審美的代碼吧~
客戶端結構以下:
| |-- script | |-- components | | |-- drawing-board.vue | | |-- showing-board.vue | | | |-- App.vue | | | |-- index.js | |-- index.html
詳細代碼請直接瀏覽項目,這裏僅對關鍵部分代碼進行剖析。
位於./script/components/
的drawing-board.vue
文件即爲繪圖畫布組件。首先咱們定義一個Draw
類,裏面是全部繪圖相關的功能。
/*** drawing-board.vue ***/ 'use strict' class Draw { constructor(el) { this.el = el this.canvas = document.getElementById(this.el) this.cxt = this.canvas.getContext('2d') this.stage_info = canvas.getBoundingClientRect() // 記錄繪圖位點的座標 this.path = { beginX: 0, beginY: 0, endX: 0, endY: 0 } } // 初始化 init(ws, btn) { this.canvas.onmousedown = () => { this.drawBegin(event, ws) } this.canvas.onmouseup = () => { this.drawEnd() ws.send('stop') } this.clearCanvas(ws, btn) } drawBegin(e, ws) { window.getSelection() ? window.getSelection().removeAllRanges() : document.selection.empty() this.cxt.strokeStyle = "#000" // 開始新的路徑(這一句很關鍵,你能夠註釋掉看看有什麼不一樣) this.cxt.beginPath() this.cxt.moveTo( e.clientX - this.stage_info.left, e.clientY - this.stage_info.top ) // 記錄起點 this.path.beginX = e.clientX - this.stage_info.left this.path.beginY = e.clientY - this.stage_info.top document.onmousemove = () => { this.drawing(event, ws) } } drawing(e, ws) { this.cxt.lineTo( e.clientX - this.stage_info.left, e.clientY - this.stage_info.top ) // 記錄終點 this.path.endX = e.clientX - this.stage_info.left this.path.endY = e.clientY - this.stage_info.top // 把位圖座標發送到服務器 ws.send(this.path.beginX + '.' + this.path.beginY + '.' + this.path.endX + '.' + this.path.endY) this.cxt.stroke() } drawEnd() { document.onmousemove = document.onmouseup = null } clearCanvas(ws, btn) { // 點擊按鈕清空畫布 btn.onclick = () => { this.cxt.clearRect(0, 0, 500, 500) ws.send('clear') } } }
嗯,相信看代碼很容易就看懂了當中邏輯,關鍵就是在drawing()
的時候要不斷地把座標發送到服務器。
定義好Draw
類之後,在ready
階段使用便可:
ready: () => { const ws = new WebSocket('ws://localhost:8090') let draw = new Draw('canvas') // 清空畫布按鈕 let btn = document.getElementById('btn') // 與服務器創建鏈接後執行 ws.onopen = () => { draw.init(ws, btn) } // 判斷來自服務器的消息並操做 ws.onmessage = (msg) => { msg.data.split(':')[0] == 'keyword' ? document.getElementById('keyword').innerHTML = msg.data.split(':')[1] : false } }
猜圖畫布很簡單,只須要定義一個canvas畫布,而後接收服務器發送來的座標並繪製便可。看代碼:
ready: () => { 'use strict' const ws = new WebSocket('ws://localhost:8090'); const canvas = document.getElementById('showing') const cxt = canvas.getContext('2d') // 是否從新設定路徑起點 // 爲了不把路徑起點重複定義在同一個地方 let moveToSwitch = 1 ws.onmessage = (msg) => { let pathObj = msg.data.split('.') cxt.strokeStyle = "#000" if (moveToSwitch && msg.data != 'stop' && msg.data != 'clear') { cxt.beginPath() cxt.moveTo(pathObj[0], pathObj[1]) moveToSwitch = 0 } else if (!moveToSwitch && msg.data == 'stop') { cxt.beginPath() cxt.moveTo(pathObj[0], pathObj[1]) moveToSwitch = 1 } else if (moveToSwitch && msg.data == 'clear') { cxt.clearRect(0, 0, 500, 500) } else if (msg.data == '答對了!!') { alert('恭喜你答對了!!') } cxt.lineTo(pathObj[2], pathObj[3]) cxt.stroke() } ws.onopen = () => { let submitBtn = document.getElementById('submit') // 發送答案到服務器 submitBtn.onclick = () => { let keyword = document.getElementById('answer').value ws.send(keyword) } } }
到這裏,遊戲已經能夠玩啦!不過還有不少細節是有待增強和修改的,好比能夠給畫筆選擇顏色啊,多個用戶搶答計分啊等等。
大半天時間鼓搗出來的玩意兒,雖然粗糙,可是學到的東西還真很多,尤爲是websocket和canvas這兩個我所不熟悉的領域,果真實踐才能出真知。
選擇ES6真的可以極大地提高工做效率,Class
語法的出現簡直不能更贊,做爲才學習jQuery
源碼沒多久的我來講,ES6真的很是小清新。
歡迎持續關注個人專欄,會不斷送出乾貨哦,盡請期待!