Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏
Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏
来源:jrainlau
链接:https://segmentfault.com/a/1190000005804860
项目地址:https://github.com/jrainlau/draw-something
下载 & 运行
git clone git@github.com: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真的非常小清新。
Vue+WebSocket+ES6+Canvas 制作「你画我猜」小游戏的更多相关文章
- Loj #2324. 「清华集训 2017」小 Y 和二叉树
Loj #2324. 「清华集训 2017」小 Y 和二叉树 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙上, ...
- loj #2325. 「清华集训 2017」小Y和恐怖的奴隶主
#2325. 「清华集训 2017」小Y和恐怖的奴隶主 内存限制:256 MiB时间限制:2000 ms标准输入输出 题目类型:传统评测方式:文本比较 题目描述 "A fight? Co ...
- [LOJ#2324]「清华集训 2017」小Y和二叉树
[LOJ#2324]「清华集训 2017」小Y和二叉树 试题描述 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙 ...
- [LOJ#2323]「清华集训 2017」小Y和地铁
[LOJ#2323]「清华集训 2017」小Y和地铁 试题描述 小Y是一个爱好旅行的OIer.一天,她来到了一个新的城市.由于不熟悉那里的交通系统,她选择了坐地铁. 她发现每条地铁线路可以看成平面上的 ...
- LibreOJ #6191. 「美团 CodeM 复赛」配对游戏
二次联通门 : LibreOJ #6191. 「美团 CodeM 复赛」配对游戏 /* LibreOJ #6191. 「美团 CodeM 复赛」配对游戏 概率dp */ #include <cs ...
- 「区间DP」「洛谷P1043」数字游戏
「洛谷P1043」数字游戏 日后再写 代码 /*#!/bin/sh dir=$GEDIT_CURRENT_DOCUMENT_DIR name=$GEDIT_CURRENT_DOCUMENT_NAME ...
- 了解python,利用python来制作日常猜拳,猜价小游戏
初次接触python,便被它简洁优美的语言所吸引,正所谓人生苦短,python当歌.python之所以在最近几年越发的炽手可热,离不开它的一些特点: 1.易于学习:Python有相对较少的关键字,结构 ...
- JS制作蔡徐坤打篮球小游戏(鸡你太美?)
一.前提: 和我之前写的 QT小球游戏 差不多(指的是实现方法). 感谢大佬的 Github:https://github.com/kasuganosoras/cxk-ball 外加游戏网页:http ...
- LibreOJ #2325. 「清华集训 2017」小Y和恐怖的奴隶主(矩阵快速幂优化DP)
哇这题剧毒,卡了好久常数才过T_T 设$f(i,s)$为到第$i$轮攻击,怪物状态为$s$时对boss的期望伤害,$sum$为状态$s$所表示的怪物个数,得到朴素的DP方程$f(i,s)=\sum \ ...
随机推荐
- 2019.01.21 洛谷P3919 【模板】可持久化数组(主席树)
传送门 题意简述:支持在某个历史版本上修改某一个位置上的值,访问某个历史版本上的某一位置的值. 思路: 用主席树直接维护历史版本即可. 代码: #include<bits/stdc++.h> ...
- 2018.12.15 bzoj3676: [Apio2014]回文串(后缀自动机)
传送门 对原串建立一个后缀自动机,然后用反串在上面匹配. 如果当前匹配的区间[l,r][l,r][l,r]包裹了当前状态的endposendposendpos中的最大值,那么[l,maxpos][l, ...
- warning: this decimal constant is unsigned only in ISO C90问题的处理及理解
参考:https://blog.csdn.net/duguduchong/article/details/7709482 https://bbs.csdn.net/topics/391892978?p ...
- c# devexpress学习绘图
用字典方式存储数据并绘图:http://www.xuebuyuan.com/465384.html 数据库存储数据,并对图形作各种设置:http://www.cnblogs.com/xuhaibiao ...
- c# 快速排序法并记录数组索引
在遗传算法中,只需要对适应性函数评分进行排序,没必要对所有的个体也参与排序,因为在适应性函数评分排序是可以纪律下最初的索引,排序后的索引随着元素排序而变动,这样就知道那个评分对应那个个体了: usin ...
- 机器学习P7
优化问题: https://www.cnblogs.com/liaohuiqiang/p/7805954.html KKT条件就是把高数里面求不等式约束条件问题的分类方法写成两个条件.
- DOS的几个常用命令
1.rem:注释 DOS中的注释,其后面的内容会被自动忽略.双冒号(::)也有相同的效果 相当于R语言和Python中的# 2.set:设置变量 set var = 1 将1赋值给变量var 打印出来 ...
- Effective C++ 随笔(2)
条款5 了解c++默默编写并调用哪些函数 编译器自动生成的copy 构造函数,copy赋值操作符,析构函数,构造函数,这些都是public和inline的,此处inline的意思是他们的定义都是在头文 ...
- MIT Molecular Biology 笔记4 DNA相关实验
视频 https://www.bilibili.com/video/av7973580?from=search&seid=16993146754254492690 教材 Molecular ...
- Page页面生命周期——微信小程序
onLoad:function (options) { //页面初始化 console.log('index Load') }, onShow:function () { // ...