Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏

Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏

转载

来源:jrainlau

链接:https://segmentfault.com/a/1190000005804860

项目地址:https://github.com/jrainlau/draw-something

下载 & 运行

git clone [email protected]:jrainlau/draw-something.git

cd draw-something && npm install

node ws-server.js // 开启websocket服务器

npm run dev // 运行客户端程序

然后浏览器打开localhost:8080即可

效果预览:

整体架构

因为闲得慌,一直和朋友在玩你画我猜之类的小游戏,突然想到能不能自己也做一个呢,反正闲着也是闲着,同时正好可以学习一下websocket的用法。

首先分析整体架构部分:

可以看到,整体架构非常简单,仅仅是一台服务器和两个客户端。

  • WebSocket服务器:提供数据同步,内容分发功能,采用nodejs写成。
  • 绘图画布:进行绘图的区域,同时能够获取关键词,其绘制的内容会同步到猜图画布中。
  • 猜图画布:同步自绘图画布,输入框能够提交关键词,检测答案是否正确。

下面来看具体的代码实现。

WebSocket服务器

服务器采用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真的非常小清新。

时间: 2024-12-09 17:44:01

Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏的相关文章

LibreOJ #6191. 「美团 CodeM 复赛」配对游戏

二次联通门 : LibreOJ #6191. 「美团 CodeM 复赛」配对游戏 /* LibreOJ #6191. 「美团 CodeM 复赛」配对游戏 概率dp 不是很懂为什么这样做... */ #include <cstdio> #include <iostream> const int BUF = 12312312; char Buf[BUF], *buf = Buf; inline void read (int &now) { for (now = 0; !isdi

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主 试题描述 "A fight? Count me in!" 要打架了,算我一个. "Everyone, get in here!" 所有人,都过来! 小Y是一个喜欢玩游戏的OIer.一天,她正在玩一款游戏,要打一个Boss. 虽然这个Boss有 \(10^{100}\) 点生命值,但它只带了一个随从--一个只有 \(m\) 点生命值的"恐怖的奴隶主". 这个"恐怖的奴隶主&qu

了解python,利用python来制作日常猜拳,猜价小游戏

初次接触python,便被它简洁优美的语言所吸引,正所谓人生苦短,python当歌.python之所以在最近几年越发的炽手可热,离不开它的一些特点: 1.易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单.2.易于阅读:Python代码定义的更清晰.3.易于维护:Python的成功在于它的源代码是相当容易维护的.4.一个广泛的标准库:Python的最大的优势之一是丰富的库,跨平台的,在UNIX,Windows和Macintosh兼容很好.5.互动模式:互动

canvas制作一个在线画图版

HTML代码:<!DOCTYPE html> <html> <head> <title> 在线画图</title> <meta charset="utf-8" /> <style> #d{margin-left:30%;}</style> </head> <body> <div id="d"> <canvas id="c

LibreOJ #2325. 「清华集训 2017」小Y和恐怖的奴隶主(矩阵快速幂优化DP)

哇这题剧毒,卡了好久常数才过T_T 设$f(i,s)$为到第$i$轮攻击,怪物状态为$s$时对boss的期望伤害,$sum$为状态$s$所表示的怪物个数,得到朴素的DP方程$f(i,s)=\sum \frac{1}{sum+1}*(f(i+1,s')+[s==s'])$ 状态数只有$C_{8+3}^3=165$个,所以就可以矩乘优化啦.再加上一个用于转移的$1$,矩阵大小是$166*166$的,因为多组询问,所以可以先把$2$的所有次幂的矩阵都预处理出来. 然后会发现复杂度是$O(T*166^3

「 CODE[VS] P2853 」 方格游戏

题目大意 给定一张 $n\times n$ 的网格.每个格子上都有一个系数 $a$,先下 $A$ 和 $B$ 两人选择两条 $(1,1)\rightarrow (n,n)$ 路径.要求着两条路径不能相同.并且要计算出两条路径每一个相对应的格子上的系数的差的绝对值之和. 要求选择路径是满足下列条件: 只能选择坐标增加的方向. 解题思路 棋盘 DP. 既然是在一个棋盘中.并且已经规定了行走的方向.所以在移动的格子的数量相同时.两个路径停留的点到 $(1,1)$ 这个点的曼哈顿距离(横坐标$+$纵坐标

「Newcoder练习赛40D」小A与最大子段和

题目 挺好的一道题 我们考虑把\(i\)作为选取的最大子段的结束位置,我们如何往前计算贡献呢 考虑一下这个乘上其在队列中的位置可以表示为这个数被算了多少次,而我们往前扩展一位当前已经被扩展的就会被计算一次 设\(s_i\)表示序列的前缀和 扩展一次 \[s_i-s_{i-1}\] 再扩展一次 \[s_i-s_{i-1}+s_i-s_{i-2}\] 发现如果我们往前算到第\(j\)项的话贡献就是 \[(i-j+1)\times s_i-\sum_{k=j-1}^{i-1}s_k\] 如果对前缀和序

【微信小游戏实战】零基础制作《欢乐停车场》一、游戏设计

1.游戏立项 微信小游戏中有一款<欢乐停车场>的小游戏,大家可以搜索玩下.这是一款益智类的小游戏,游戏中有红.黄.绿.蓝.紫5辆豪车6个停车位,玩家通过可行走路线移动小车,最终让各颜色的小车停到对应的颜色车位,则完成本关挑战.接下来的日子,我将同大家一步一步的来实现这款小游戏,从零基础入门微信小游戏的开发和发布. 2.本篇内容 CocosCreator零基础制作<欢乐停车场Plus>微信小游戏  一.游戏设计 3.文章检索 1).[微信小游戏实战]零基础制作<欢乐停车场>

用Canvas制作可以根据手势摆动的树

用Canvas制作可以根据手势摆动的树 根据工作的需要,制作一个摆动的树做为页面的背景.为了增加页面的交互性,我又为背景中的树增加了鼠标(触控)事件,使他能够根据鼠标(触控)做出相应的动作,当手指做上下或者左右滑动的时候树会跟着摆动.先看看最终效果. Step1.完成HTML页面,新建一个Tree类 完成HTML页面后新建一个Tree类用来记录树的各个属性.其中x,y为树根部的坐标值,branchLen,branchWidth分别是树枝的长度与宽度,depth为树枝的层数,canvas用来接页面