Vue+WebSocket+ES6+Canvas 製做【你畫我猜】小遊戲

寫於 2016.06.26html

項目地址:github.com/jrainlau/dr…vue

下載 & 運行

git clone git@github.com:jrainlau/draw-something.git
cd draw-something

node ws-server.js // 開啓websocket服務器

npm run dev  // 運行客戶端程序

而後瀏覽器打開localhost:8080便可
複製代碼

效果預覽:node

總體架構

由於閒得慌,一直和朋友在玩你畫我猜之類的小遊戲,忽然想到能不能本身也作一個呢,反正閒着也是閒着,同時正好能夠學習一下websocket的用法。git

首先分析總體架構部分:github

能夠看到,總體架構很是簡單,僅僅是一臺服務器和兩個客戶端。web

  • WebSocket服務器:提供數據同步,內容分發功能,採用nodejs寫成。
  • 繪圖畫布:進行繪圖的區域,同時可以獲取關鍵詞,其繪製的內容會同步到猜圖畫布中。
  • 猜圖畫布:同步自繪圖畫布,輸入框可以提交關鍵詞,檢測答案是否正確。

下面來看具體的代碼實現。npm

WebSocket服務器

服務器採用node.js進行搭建,使用了ws實現websocket功能。新建一個名爲ws-socket.js的文件,代碼以下:canvas

/*** 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真的很是小清新。

歡迎持續關注個人專欄,會不斷送出乾貨哦,盡請期待!

相關文章
相關標籤/搜索