内容简介:第一次在掘金发文章,瑟瑟发抖。这个主要是为了学习使用一下canvas和websocket,你画我猜大家应该都玩过,一个人画,其他人猜。目前实现了最基本的功能,慢慢修改。
第一次在掘金发文章,瑟瑟发抖。
这个主要是为了学习使用一下canvas和websocket, 项目地址 。
你画我猜大家应该都玩过,一个人画,其他人猜。目前实现了最基本的功能,慢慢修改。
项目截图
完成进度
- 登录,登录后username存储到了sessionStorage中。
- 座位,登录后可以选择座位,并通过ws告诉所有人你的座位。
- 发送内容,登录后可以通过ws将输入内容发布给所有人。
- 聊天记录,可以接收所有人的聊天打字内容,可以清屏。
- 开始游戏,一楼可以点击开始游戏按钮,开始游戏后不可再调整座位。
- 按座位顺序轮流进行画图与看图猜词,画图玩家不可以输入内容与发送内容。
- 随机在词表中选择词语。用于练习只写了12个词。
- 发送内容为正确词时特殊显示(游戏开始后有效)。
- 记分
- 多房间游戏
- 移动端
客户端
前端从前面的截图可以看到,基本分为四部分,画图部分,选择座位部分,聊天显示部分,发送聊天部分。四部分当然要使用一个websocket连接,所以使用了vuex,同时在vuex中保存用户名等数据。
建立ws连接,存入vuex中。
this.ws = new WebSocket('ws://localhost:3000/'); this.$store.commit('wsStore/connect',ws); 复制代码
登录部分
这里登录不太重要,就直接把用户名放到sessionStorage里了,同时再存入vuex中供其他部分使用。
saveName(){ if(!window.sessionStorage){ alert("浏览器不支持sessionStorage!"); }else{ let storage = window.sessionStorage; storage.setItem("username",this.inputName); } this.username=this.inputName; this.showLogin=0; this.$store.commit('username/setUsername',this.inputName) } 复制代码
完整代码可以查看 github 里的src/components/HomeHeader.vue文件。
画图部分
这部分是最主要的部分,先定义一个canvas
<canvas id="drawBoard" width="700" height="400"></canvas> 复制代码
然后是一个class draw,这里只是使用画笔的函数,从上面gif可以看出来还可以画直线,矩形,圆,调整颜色等,最后也都是写到这个类里面的。
class canvasDraw{ constructor(id,ws){ this.ws= ws; this.canvas = document.getElementById(id); this.ctx = this.canvas.getContext('2d'); this.stage_info = this.canvas.getBoundingClientRect() this.path = { beginX: 0, beginY: 0, endX: 0, endY: 0 } this.isDraw=false } draw(){ let that = this this.canvas.onmousedown = () => { that.ctx.beginPath() that.path.beginX = event.pageX - that.stage_info.left that.path.beginY = event.pageY - that.stage_info.top that.ctx.moveTo( that.path.beginX, that.path.beginY ) that.isDraw=true } this.canvas.onmousemove = () => { if(that.isDraw){ that.drawing(event) } } this.canvas.onmouseup = () => { that.isDraw=false that.ws.send('stop,') } } drawing(e) { this.path.endX = e.pageX - this.stage_info.left this.path.endY = e.pageY - this.stage_info.top this.ctx.lineTo( this.path.endX, this.path.endY ) this.ctx.stroke() this.ws.send(`draw,${this.path.beginX},${this.path.beginY},${this.path.endX},${this.path.endY}`) } clearCanvas(){ this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height); this.ws.send('clear,') } } 复制代码
可以看到,构造函数主要是保存起始和终点的坐标,虽然画笔可以随便画,但是也是由一小段一小段线段组成的。
然后draw函数,当监听到mousedown事件后,新建一条路径,将起始坐标保存下来,随着mousemove事件,鼠标每移动一点,就画一条线段,直到监听到mouseup事件,完成一次画线。
画直线、矩形、圆的原理差不多,都是保存鼠标的起始和终点坐标,然后算一下矩形的长和宽,圆的圆心和半径即可。以画圆的代码为例。
drawRound(){ this.canvas.onmousedown=()=>{ this.ctx.beginPath() this.path.beginX = event.pageX - this.stage_info.left this.path.beginY = event.pageY - this.stage_info.top } this.canvas.onmouseup=()=>{ this.path.endX = event.pageX - this.stage_info.left this.path.endY = event.pageY - this.stage_info.top let width = this.path.endX-this.path.beginX; let height = this.path.endY-this.path.beginY; this.ctx.arc(this.path.beginX+width/2, this.path.beginY+height/2,Math.sqrt(width*width+height*height)/2,0, Math.PI *2); this.ctx.fill(); this.ctx.stroke(); } } 复制代码
改变线条宽度、颜色等就更简单了,只要将选择的数值传到类中的如下函数里即可。
changeColor(color){ this.ctx.strokeStyle = color; } 复制代码
完整代码可以查看 github 里的src/components/DrawBoard.vue文件。
座位部分
这一部分就没什么了,就点击button,做一下判断就好了。例如
seatDown(index){ if(this.$store.state.wsStore.ws.readyState==3){ this.$message('服务器未连接'); return ; } if(this.username==null||this.username==''){ this.$message('请登陆'); }else if(this.seats[index]=='空位'){ this.$store.state.wsStore.ws.send(`seats,${index},${this.username}`); }else{ this.$message('此位置有人'); } }, 复制代码
如果是一楼的话,可以开始游戏,开始游戏就给后端发一个信号,然后就是后端的一些逻辑了。
begin(){ this.$store.state.wsStore.ws.send(`begin,`); }, 复制代码
还有,如果游戏开始之后,不可以再调整座位或者新用户坐下。
watch:{ beginGame(newval,oldval){ let buttonEle = document.querySelectorAll('button[component="seat"]'); if(this.beginGame){ for(let bt of buttonEle){ bt.setAttribute('disabled','disabled'); } }else{ for(let bt of buttonEle){ bt.removeAttribute('disabled'); } } } }, 复制代码
完整代码可以查看 github 里的src/components/TheSeats.vue文件。
聊天显示部分
聊天显示部分只负责将接受到的服务器的消息显示出来,可以清屏。
<div v-for="(record,index) in chatRecords" :key="index" class="record"> <span class="blue">{{record.username}}</span>:{{record.content}} </div> watch:{ msg(newval,oldval){ let record = { username: this.msg[1], content: this.msg[2] }; this.chatRecords.push(record); let div = document.getElementById('totalBoard') div.scrollTop = div.scrollHeight; console.log(div.scrollTop,div.scrollHeight+100) } }, 复制代码
完整代码可以查看 github 里的src/components/ChatBoard.vue文件。
聊天输入部分
聊天输入部分只负责将数据发送给服务器,然后服务器再发送给所有用户,上面的聊天显示部分接受到之后显示出来。
submitWord(){ if(this.$store.state.wsStore.ws.readyState==3){ this.$message('服务器未连接'); return ; } if(this.username==null||this.username==''){ this.$message('请登陆'); return ; } if(this.guassWord!=''){ this.$store.state.wsStore.ws.send(`chat,${this.username},${this.guassWord}`); this.guassWord=''; } } 复制代码
还有如果轮到自己画的话,不可以发送信息。
watch:{ drawuser(newval,oldval){ let inputEle = document.querySelector('input[component="inputBoard"]'); let buttonEle = document.querySelector('button[component="inputBoard"]'); if(this.drawuser==this.username){ inputEle.setAttribute('disabled','disabled') buttonEle.setAttribute('disabled','disabled'); }else{ inputEle.removeAttribute('disabled'); buttonEle.removeAttribute('disabled'); } } }, 复制代码
完整代码可以查看 github 里的src/components/InputBoard.vue文件。
服务端
服务端使用了node的ws模块,还在学习中,写的比较菜,只是满足了游戏需要。
主要是将用户画图时的起始和终点坐标做一个转发。
有用户坐下或调整座位后发送一下最终的座位信息。
游戏开始后,按座位顺序,轮流给每个人发送画图人和要画的词。
用户输入后判断是否为正确词,如果不对,就发送原词,如果猜对了,就发送“猜对了”
// 导入WebSocket模块: const WebSocket = require('ws'); // 引用Server类: const WebSocketServer = WebSocket.Server; // 实例化: const wss = new WebSocketServer({ port: 3000 }); let seats=['空位','空位','空位','空位','空位','空位'] let dict=['老鼠','牛','老虎','兔子','龙','蛇','马','羊','猴子','鸡','狗','猪'] let gameBegin = false; let guassWord = ''; wss.on('connection', function (ws) { console.log(`[SERVER] connection()`); ws.on('message', function (message) { console.log(`[SERVER] Received: ${message}`); let msg = message.split(','); if(msg[0]=="seats"&&msg.length==3){ let i = seats.indexOf(msg[2]) if(i>-1){ seats[i]='空位'; } seats[msg[1]]=msg[2]; wss.clients.forEach((client) => { client.send(`seats,${seats}`) }) }else if(msg[0]=='begin'){ function settime(username,ms){ let i = Math.floor(Math.random()*dict.length); setTimeout(()=>{ guassWord=dict[i]; wss.clients.forEach((client) => { client.send(`begin,${username},${dict[i]}`) }) },ms) } gameBegin=true; let ms = 0; for(let username of seats){ if(username!='空位'){ settime(username,ms) ms+=10000; } } setTimeout(()=>{ wss.clients.forEach((client) => { client.send(`end,`) }) gameBegin=false; },ms) }else if(gameBegin&&msg[0]=='chat'&&msg[2]==guassWord){ wss.clients.forEach((client) => { client.send(`chat,${msg[1]},猜对啦!`) }) }else{ wss.clients.forEach((client) => { client.send(message) }) } }) }); 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- iOS 小游戏项目——你话我猜升级版
- 我猜你需要这个Python调试工具
- 跟我猜 Spring Boot:bean 的注入
- 跟我猜 Spring Boot:bean 的注入
- 跟我猜 spring-boot: bean 的创建
- 跟我猜 spring-boot: bean 的创建
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
RGB转16进制工具
RGB HEX 互转工具
UNIX 时间戳转换
UNIX 时间戳转换