今天是个好日子,我们来一起利用socket.io和canvas这两样利器,搞出个简单的《我画你猜》游戏吧

好吧,天下武功为快不破,赶紧先上个目录结构

实现消息的即时通信

由于之前专门写过文章介绍了socket.io实现即时通信的内容,那么我这边就尽量快速写起了

首先通过express来启动一个服务并且来创建socket.io的连接

启动服务并建立连接

服务端

// app.js文件
const express = require('express');
const app = express();
// 设置静态文件夹
// 这样设置会自动识别当前文件夹下的index.html文件
app.use(express.static(__dirname));const server = require('http').createServer(app);
const io = require('socket.io')(server);server.listen(8888);
复制代码

启动服务,然后访问localhost:8888,就可以访问到index.html文件上的内容了

小贴士

在客户端访问io的时候有时候会报io is not defined的错误,就是需要先启动服务后才会自动生成一个 socket.io/socket.io.js文件,如上图所示再去引用就OK了

客户端

// index.js文件// 用来处理游戏对象数据
let gameObj = {};
// socket实例
let socket = io();
// 监听connect事件
socket.on('connect', () => {console.log('客户端连接成功');
});
复制代码

到这一步刷新页面在控制台里就可以看到客户端连接成功这7个字了

发送/接收/展示消息

写到这里,只是迈出了小小的一步罢了,接下来,我们快马加鞭继续写下处理消息的逻辑吧

服务端

// app.js...省略+++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 区分是聊天还是在绘图
const LINE = 0;
const MESSAGE = 1;
const userList = ['皮卡丘', '巴大蝴', '比比鸟',  '妙蛙种子', '小火龙', '杰尼龟'];
+++++++++++++++++++++++++++++++++++++++++++++++++++++++io.on('connection', socket => {+++++++++++++++++++++++++++++++++++++++++++++++++++++++// 随机分配用户名并发送给所有人const user = userList[Math.floor(Math.random() * userList.length)];const message = `欢迎${user}加入游戏!!!`;// 将数据封装成json对象let data = {};// 通过type来区分data.type = MESSAGE;data.sender = '系统';data.message = message;// 将消息分发出去// 消息数据必须是字符串类型,so需要转换一下io.emit('message', JSON.stringify(data));socket.on('message', msg => {// 传过来的消息也是json字符串格式的,需要JSON.parse转成jsonlet data = JSON.parse(msg);// 如果是聊天类型,就给sender赋值为当前用户名if (data.type === MESSAGE) {data.sender = user;}io.emit('message', JSON.stringify(data));});+++++++++++++++++++++++++++++++++++++++++++++++++++++++
});server.listen(8888);
复制代码

上面代码里把消息数据都打包成json的格式是为了方便处理,毕竟消息数据只能接收字符串的格式

然后在发送的时候再通过JSON.stringify给转成json字符串,这样就不会导致报错了

当然解析对应的消息数据时再通过JSON.parse来转换成真正的json即可了

服务端发送和接收消息都搞完了,接下来就该客户端出场了,客户端除了上述两个功能之外还会展示消息(听起来屌屌的)

那么,不啰嗦了,快开搞吧!!!

客户端

// index.js文件++++++++++++++++++++++++++++++++++++++++++++++++++++++
const LINE = 0;
const MESSAGE = 1;
++++++++++++++++++++++++++++++++++++++++++++++++++++++let gameObj = {};
let socket = io();++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 监听服务端发来的消息
socket.on('message', msg => {// 需要先用JSON.parse转一下let data = JSON.parse(msg);console.log(data);  // {type: 1, sender: "系统", message: "欢迎皮卡丘进入游戏"}// 如果类型为聊天if (data.type === MESSAGE) {let li = `<li><span>${data.sender}: </span>${data.message}</li>`;$('#history').append(li);// 聊天区域滚动到最新聊天内容位置$('#history-wrapper').scrollTop($('#history-wrapper')[0].scrollHeight);}
});// 点击发送按钮发消息
$('#btn').click(sendMsg);
// 按回车键发送消息
$('#input').keyup(e => {let keyCode = e.keyCode;if (keyCode === 13) {sendMsg();}
});// 发送消息函数
function sendMsg() {let value = $.trim($('#input').val());if (value !== '') {let data = {};data.type = MESSAGE;data.message = value;gameObj.socket.send(JSON.stringify(data));$('#input').val('');}
}++++++++++++++++++++++++++++++++++++++++++++++++++++++
复制代码

客户端上述代码都做了哪些事情?

  1. 常量区分聊天与绘画

    const LINE = 0;
    const MESSAGE = 1;
    复制代码
    
  2. 监听服务端发来的消息

    socket.on('message', msg => {});
    复制代码
    
    let data = JSON.parse(msg);
    复制代码
    
    if (data.type === MESSAGE) {// 添加内容...省略// 滚动到最新消息位置...省略
    }
    复制代码
    
  • 消息类型为聊天

  • 转换消息为json格式

  • 监听消息

  • 发送消息

    function sendMsg() { ...省略 }
    复制代码
    
    • 点击or回车发送

    • 发送消息方法

    以上就是关于消息通信的基本实现了,下面我们要进入下一环节,canvas登场了,继续看下去

    Canvas来绘制画板

    canvas这个元素已经等候多时了,终于轮到它大展身手了,用过canvas的都知道,我们常见的都是在2d上进行绘图操作,所以在此之前先来获取一下

    // index.js文件const LINE = 0;
    const MESSAGE = 1;++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 用原生来获取,jq对象中并没有我们需要的2d
    let cvs = document.getElementById('canvas');
    let ctx = cvs.getContext('2d');let gameObj = {// 当前用户是否在绘图isDrawing: false,// 下一条线的起始点startX: 0,startY: 0
    };
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++...省略socket.on('message', msg => {let data = JSON.parse(msg);if (data.type === MESSAGE) {...省略}++++++++++++++++++++++++++++++++++++++++++++++++++++++else if (data.type === LINE) {// 这是画线函数,专门绘制所用drawLine(ctx, data.startX, data.startY, data.endX, data.endY, 1);}++++++++++++++++++++++++++++++++++++++++++++++++++++++
    });// 发送消息函数
    ...省略++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 开始在画板上画画了
    // 鼠标按下时的操作
    $('#canvas').on('mousedown', function(e) {let cvsPos = $(this).offset(),mouseX = e.pageX - cvsPos.left || 0,mouseY = e.pageY - cvsPos.top || 0;// 更新一下startX和startYgameObj.startX = mouseX;gameObj.startY = mouseY;// 更新为绘图状态gameObj.isDrawing = true;
    });
    // 鼠标移动时的操作
    $('#canvas').on('mousemove', function(e) {// 当绘图状态为true的时候才可以绘制if (gameObj.isDrawing) {let cvsPos = $(this).offset(),mouseX = e.pageX - cvsPos.left || 0,mouseY = e.pageY - cvsPos.top || 0;if (gameObj.startX !== mouseX && gameObj.startY !== mouseY) {// 开始绘制线段,drawLine为画线函数drawLine(ctx, gameObj.startX, gameObj.startY, mouseX, mouseY, 1, $('#color').val());// 既然画线了,那就把画的线段数据也打包成json传给服务端let data = {};data.startX = gameObj.startX;data.startY = gameObj.startY;data.endX = mouseX;data.endY = mouseY;data.type = LINE;// 别犹豫,直接通过socket发给服务端socket.send(JSON.stringify(data));// 这里还要更新一下startX和startYgameObj.startX = mouseX;gameObj.startY = mouseY;}}
    });// 鼠标抬起时的操作
    $('#canvas').on('mouseup', function() {gameObj.isDrawing = false;
    });// 画线函数
    function drawLine(ctx, x1, y1, x2, y2, thick) {ctx.beginPath();ctx.moveTo(x1, y1);ctx.lineTo(x2, y2);ctx.lineWidth = thick;ctx.strokeStyle = '#00a1f4';ctx.stroke();
    }++++++++++++++++++++++++++++++++++++++++++++++++++++++
    复制代码
    

    别看上面代码突然多了好多好多,但实际上也无非下面几点,莫慌,我们来梳理一下

    1. 区分消息为画线的数据

    • 在监听消息的函数里,通过`data.type === LINE`来区分出来消息类型为画线的数据

  • 鼠标在canvas上的事件

    • 修改gameObj.isDrawing状态为初始值false

    • 记录移动的位置

    • 绘制线段

    • 发送画线数据到服务端

    • 把移动的最新位置更新到gameObj上

    • 记录点击时的位置

    • 将按下的位置赋给gameObj.startX和gameObj.startY

    • 修改gameObj.isDrawing状态为true

    • mousedown按下事件

    • mousemove移动事件

    • mouseup抬起事件

  • 实现画线方法

    • ctx.beginPath() - 绘制初始路径

    • ctx.moveTo(x1, y1) - 线段下笔点

    • ctx.lineTo(x2, y2) - 线段移动路径

    • ctx.lineWidth - 线段宽度

    • ctx.storkeStyle - 线段颜色

    • ctx.stroke() - 绘制线段

    说难不难,说简单不简单。不过视熟练程度不同,会有不同的感受罢了,多写多练习,知识自然会收录到自己的手中

    写到这里基本可以说是socket.io和canvas的要领都掌握了,那么最后的最后,我们不能忘记文章的标题主旨,是多人游玩,不仅仅是简单的自娱自乐,也要众人同乐

    那么让我们再加把劲,实现多人游戏逻辑

    构建多人游戏

    游戏逻辑

    玩游戏嘛,自然有个游戏的逻辑和规则

    游戏逻辑在客户端方面的实现相对来说还是比较简单的,更多的操作还是要靠服务端那里了

    那么就先由简到难的实现一下吧

    客户端

    // index.js文件const LINE = 0;
    const MESSAGE = 1;// 添加个游戏常量
    const GAME = 2;let gameObj = {...省略// 游戏状态WAITTING: 0,START: 1,OVER: 2,RESTART: 3,// 当前轮到谁来绘图isPlayer: false
    };...省略socket.on('message', msg => {let data = JSON.parse(msg);if (data.type === MESSAGE) {...省略} else if (data.type === LINE) {...省略} else if (data.type === GAME) { // 如果进行游戏,传过来的type值必须是GAME// 通过data.state来判断游戏当前的进度// 游戏开始的逻辑if (data.state === gameObj.START) {// 游戏要是开始了就需要清空画布ctx.clearRect(0, 0, cvs.width, cvs.height);// 清空聊天记录和隐藏重新开始$('#restart').hide();$('#history').html('');// 区分一下是当前画图的玩家还是猜图的玩家if (data.isPlayer) {gameObj.isPlayer = true;$('#history').append(`<li>轮到你了,请你画出<span class="answer">${data.answer}</span></li>`);} else {$('#history').append(`<li>游戏即将开始,请准备,你们有一分钟的时间去猜答案哦</li>`);}}// 游戏结束的逻辑if (data.state === gameObj.OVER) {gameObj.isPlayer = false;$('#restart').show();$('#history').append(`<li>本轮游戏的获胜者是<span class="winner">${data.winner}</span>,正确答案是: ${data.answer}</li>`);}if (data.state === gameObj.RESTART) {$('#restart').hide();ctx.clearRect(0, 0, cvs.width, cvs.height);}}
    });...省略// 画线函数
    ...省略// 重玩
    $('#restart').on('click', function() {let data = {};data.type = GAME;data.state = gameObj.RESTART;socket.send(JSON.stringify(data));
    });
    复制代码
    

    继续来梳理一下以上代码都做了什么?

    1. 游戏常量区分消息类型及游戏状态

      const GAME = 2;let gameObj = {...省略// 游戏状态
      WAITTING: 0,
      START: 1,
      OVER: 2,
      RESTART: 3,
      // 当前轮到谁来绘图
      isPlayer: false
      };
      复制代码
      
    2. data.type为GAME表示进行游戏

    3. data.state来判断游戏当前的进度

    • 清空画布、隐藏按钮

    • 显示重新开始按钮

    • 显示获胜者和答案

    • 清空画布

    • 清空聊天记录和隐藏重新开始按钮

    • 通过data.isPlayer来区分是绘图者还是猜图者以展示不同文案

    • 游戏开始

    • 游戏结束

    • 重新开始

    上述三点就是客户端实现多人游戏的代码了,不要停歇,马上就要到头了,继续写服务端的逻辑吧

    服务端

    // app.js文件...省略const LINE = 0;
    const MESSAGE = 1;// 添加游戏常量
    const GAME = 2;// 游戏状态和游戏逻辑
    const WAITTING = 0;
    const START = 1;
    const OVER = 2;
    const RESTART = 3;let player = 0;
    let wordsList = ['苹果', '运动鞋', '火箭', '足球', '小黄人', '汽车', '小鸟'];
    let currentAnswer;
    let currentState = WAITTING;
    let timer;
    // 连接的客户端数量
    let len = 0;io.on('connection', socket => {...省略// 将数据封装成json对象...省略// 把游戏的消息通知所有人let game = {};game.type = GAME;game.state = WAITTING;io.emit('message', JSON.stringify(game));// 遍历客户端的连接io.clients((err, client) => {if (err) throw err;len = client.length;});// 当前状态为等待并且连接数超过两个的时候才开始游戏if (currentState === WAITTING && len > 2) {startGame(socket);}socket.on('message', msg => {...省略// 判断是不是有玩家答对了if (currentState === START && data.message === currentAnswer) {let game = {};game.type = GAME;game.answer = currentAnswer;game.winner = user;game.state = OVER;io.emit('message', JSON.stringify(game));currentState = WAITTING;clearTimeout(timer);}// 重新开始if (data.state === RESTART && data.type === GAME) {startGame(socket);}});
    });// 开始游戏方法
    function startGame(socket) {// 分配一个玩家来画画player = (player + 1) % len;// 随机分配个图案let random = Math.floor(Math.random() * wordsList.length);currentAnswer = wordsList[random];// 通知所有玩家游戏开始let data = {};data.type = GAME;data.isPlayer = false;data.state = START;io.emit('message', JSON.stringify(data));// 遍历客户端,然后找到画画的那个用户告诉他相关datalet count = 0;io.clients((err, client) => {client.forEach(item => {if (count === player) {let game = {};game.type = GAME;game.state = START;game.isPlayer = true;game.answer = currentAnswer;// 这条消息只有绘图的玩家才能看到socket.send(JSON.stringify(game));}count++;});});// 1分钟后游戏结束timer = setTimeout(() => {let obj = {};obj.type = GAME;obj.state = OVER;obj.winner = '没有人啊!';obj.answer = currentAnswer;io.emit('message', JSON.stringify(obj));}, 60 * 1000);// 当前状态修改为STARTcurrentState = START;
    }server.listen(8888);
    复制代码
    

    服务端,我的朋友,你刚才都做了什么?

    1. 添加游戏常量以及游戏的状态和逻辑

      // 游戏常量
      const GAME = 2;
      // 游戏状态
      const WAITTING = 0;
      ...省略
      const RESTART = 3;
      // 游戏逻辑
      let player = 0;
      ...省略
      let len = 0;
      复制代码
      
    2. 初始化游戏和游戏人数

      // 把游戏的消息通知所有人
      let game = {};
      ...省略
      io.emit('message', JSON.stringify(game));// 遍历客户端的连接
      io.clients((err, client) => {if (err) throw err;len = client.length;
      });// 当前状态为等待并且连接数超过两个的时候才开始游戏
      if (currentState === WAITTING && len > 2) {startGame(socket);
      }
      复制代码
      
    3. 开始游戏 - startGame

      player = (player + 1) % len;
      复制代码
      
      let random = Math.floor(Math.random() * wordsList.length);
      currentAnswer = wordsList[random];
      复制代码
      
      let data = {};
      ...省略
      io.emit('message', JSON.stringify(data));
      复制代码
      
      let count = 0;
      io.clients((err, client) => {client.forEach(item => {// 匹配为分配的玩家才可以绘制答案if (count === player) {let game = {};...省略// 这条消息只有绘图的玩家才能看到socket.send(JSON.stringify(game));}count++;});
      });
      复制代码
      
      timer = setTimeout(() => {...省略
      }, 60 * 1000);currentState = START;
      复制代码
      
    • 1分钟游戏结束并改变当前状态

    • 遍历客户端,然后找到画画的那个用户告诉他相关data

    • 通知所有玩家游戏开始

    • 随机分配个图案

    • 分配玩家画画

  • 监听消息判断是否有人回答正确

    // 判断是不是有玩家答对了
    if (currentState === START && data.message === currentAnswer) {let game = {};game.type = GAME;game.answer = currentAnswer;game.winner = user;game.state = OVER;  // 状态修改为OVER// 把该消息数据传递给所有玩家io.emit('message', JSON.stringify(game));// 恢复当前状态初始值currentState = WAITTING;// 清空1分钟计时器clearTimeout(timer);
    }
    复制代码
    
  • 好了,到这里就都结束了,小伙伴们可以跑起来试一试了,当然动手敲起来才是王道了

    作者:chenhongdong
    链接:https://juejin.im/post/5bea88f1f265da616a473473

    - END-


    文末彩蛋

    扫一扫二维码回复,"2020",获得2020年最新前端、后端、大数据、人工智能、PHP等视频教程的百度云盘链接,获得之后,记得保存在自己的云盘里哦。

    
    
    编程·前端·社区

Canvas+Socket搞出一个多人游玩的“我画你猜”相关推荐

  1. Android Socket通信(五) -- 实现一个多人聊天室

    系列文章: Android Socket 系列更新计划 Android Socket通信(一) – 初识与相遇 Android Socket通信(二) --UDP,单播,广播和多播(组播) Andro ...

  2. phaser.min.js_如何使用Phaser 3,Express和Socket.IO构建多人纸牌游戏

    phaser.min.js I'm a tabletop game developer, and am continually looking for ways to digitize game ex ...

  3. 在线白板,基于socket.io的多人在线协作工具

    为什么80%的码农都做不了架构师?>>>    首发:个人博客,更新&纠错&回复 是昨天这篇博文留的尾巴,socket.io库的使用练习,成品地址在这里. 代码已经上 ...

  4. python实现简易聊天需要登录博客园zip下载_Python基于Socket实现简易多人聊天室的示例代码...

    前言 套接字(Sockets)是双向通信信道的端点. 套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的机器. 套接字可以通过多种不同的 ...

  5. Java Socket通信实现多人多端网络画板聊天室

    老规矩,先上实现的效果展示! Java Socket通信实现多人多端网络画板聊天室 本文介绍了一个基于Socket实现网络画板聊天室的完整过程,聊天室具备多人文本对话.同步绘图等功能. 初尝试 Soc ...

  6. 用CSS画小猪佩奇,你就是下一个社会人! js将“I am a coder”反转成 “coder a am I”,不许用split,join,subString,reverse;求解方法三...

    用CSS画小猪佩奇,你就是下一个社会人! 欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 作者:江志耿 | 腾讯TEG网络工程师 我是佩奇,哼,这是我的弟弟乔治,呱呱,这是我的妈妈,嚯,这 ...

  7. node php聊天室,利用socket.io实现多人聊天室(基于Nodejs)

    利用socket.io实现多人聊天室(基于Nodejs) socket.io简介 在Html5中存在着这样的一个新特性,引入了websocket,关于websocket的内部实现原理可以看这篇文章,这 ...

  8. 教你用WebRTC撸一个多人视频聊天

    之前公司准备用 webRTC 来实现视频聊天,研究了几天,撸了个 demo 出来,(虽然最后并没有采用这项技术,囧),但是还是写一个出来吧! WebRTC简单介绍 WebRTC (Web Real-T ...

  9. 使用java socket实现一个简单的一对多聊天室

    socket就是指两个应用程序之间通信的抽象对象,我们可以使用socket实现网络应用程序.例如一个多人聊天室. 目录 先从服务端开始 创建一个窗口类 创建一些方法,用于管理服务端链接,或者进行消息的 ...

最新文章

  1. 机器学习(13)岭回归(线性回归的改进)
  2. linux中运行.sql文件
  3. 用python内置函数算复杂度吗_番外篇: Python 面试感受
  4. C#语法:委托与方法
  5. mysql安装注意步骤,mysql安装步骤
  6. php.ini var dump,php安装xdebug后var_dump()不能输变量内容解决办法
  7. 问题:js中怎么继承属性
  8. Kafka从上手到实践 - 庖丁解牛:Partition | 凌云时刻
  9. C/C++二路归并排序
  10. 视频主观质量评价方法总结
  11. MIMIC数据库简介
  12. simditor 图片上传成功后修改图片地址
  13. 使用poi导出excel,及合并单元格边框显示问题
  14. ps中扩展画布的时候,不能选择扩展画布部分的颜色解决方法
  15. 计算机插上u盘就无法点亮,u盘启动电脑无反应,小编教你电脑插上U盘后无法启动解决方法...
  16. Android 常用 Manager的总结
  17. 市场调研-邻苯二甲酰亚胺钾市场现状及未来发展趋势
  18. 大数据最重要的算法是什么,最常用的算法有哪些?
  19. ST2Vec: Spatio-Temporal Trajectory Similarity Learning in Road Networks
  20. Halo博客的谷歌收录自动提交

热门文章

  1. CentOS 7.6的64位安装JAVA JDK
  2. KubeSphere 内置的 Prometheus 通过 remote write 至 Thanos 存更长期数据
  3. CentOS 7 安装好后,无法使用小键盘的解决办法
  4. 指针篇之一 宝藏与藏宝图
  5. Unity中让游戏对象消失或隐藏的几种方法
  6. 人工智能之集束搜索Beam Search Algorithm
  7. python爬取视频自动播放_求助该网站如何让它能自动播放下一个视频。。。醉了,要挂80个课时...
  8. java 内存溢出 jstack,Java——命令jps、jstat、jmap、jstack、jhat、jinfo
  9. 3年半工作经验女程序员,聊聊程序员的薪水、工作内容和发展前景
  10. 深入理解Docker原理