内容简介:各位看官,好久不见,这篇文章本应该是在2018年的时候与你们见面的,结果因为种种原因拖到了此时此刻(我的锅啊我的锅),再过一周就要春节了,在这里和大家说一声过年好啊!!!今天是个好日子,我们来一起利用socket.io和canvas这两样利器,搞出个简单的《我画你猜》游戏吧快过年了,大家放松一下忙碌了一年的紧张神经,玩玩自己搞出来的小游戏吧!
各位看官,好久不见,这篇文章本应该是在2018年的时候与你们见面的,结果因为种种原因拖到了此时此刻(我的锅啊我的锅),再过一周就要春节了,在这里和大家说一声过年好啊!!!
今天是个好日子,我们来一起利用socket.io和canvas这两样利器,搞出个简单的《我画你猜》游戏吧
快过年了,大家放松一下忙碌了一年的紧张神经,玩玩自己搞出来的小游戏吧!
PS: 如果对接下来说的即时通信的实现没有太搞懂的,可以再看看这里
好吧,天下武功为快不破,赶紧先上个 目录结构
实现消息的即时通信
由于之前专门写过文章介绍了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转成json let 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(''); } } ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 复制代码
客户端上述代码都做了哪些事情?
- 常量区分聊天与绘画
const LINE = 0; const MESSAGE = 1; 复制代码
- 监听服务端发来的消息
- 监听消息
socket.on('message', msg => {}); 复制代码
- 转换消息为json格式
let data = JSON.parse(msg); 复制代码
- 消息类型为聊天
if (data.type === MESSAGE) { // 添加内容 ...省略 // 滚动到最新消息位置 ...省略 } 复制代码
- 发送消息
- 发送消息方法
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和startY gameObj.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和startY gameObj.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(); } ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 复制代码
别看上面代码突然多了好多好多,但实际上也无非下面几点,莫慌,我们来梳理一下
- 区分消息为画线的数据
- 在监听消息的函数里,通过
data.type === LINE
来区分出来消息类型为画线的数据
- 在监听消息的函数里,通过
- 鼠标在canvas上的事件
- mousedown按下事件
- 记录点击时的位置
- 将按下的位置赋给gameObj.startX和gameObj.startY
- 修改gameObj.isDrawing状态为true
- mousemove移动事件
- 记录移动的位置
- 绘制线段
- 发送画线数据到服务端
- 把移动的最新位置更新到gameObj上
- mouseup抬起事件
- 修改gameObj.isDrawing状态为初始值false
- mousedown按下事件
- 实现画线方法
- 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)); }); 复制代码
继续来梳理一下以上代码都做了什么?
- 游戏常量区分消息类型及游戏状态
const GAME = 2; let gameObj = { ...省略 // 游戏状态 WAITTING: 0, START: 1, OVER: 2, RESTART: 3, // 当前轮到谁来绘图 isPlayer: false }; 复制代码
- data.type为GAME表示进行游戏
- 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)); // 遍历客户端,然后找到画画的那个用户告诉他相关data let 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); // 当前状态修改为START currentState = START; } server.listen(8888); 复制代码
服务端,我的朋友,你刚才都做了什么?
- 添加游戏常量以及游戏的状态和逻辑
// 游戏常量 const GAME = 2; // 游戏状态 const WAITTING = 0; ...省略 const RESTART = 3; // 游戏逻辑 let player = 0; ...省略 let len = 0; 复制代码
- 初始化游戏和游戏人数
// 把游戏的消息通知所有人 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); } 复制代码
- 开始游戏 - startGame
- 分配玩家画画
player = (player + 1) % len; 复制代码
- 随机分配个图案
let random = Math.floor(Math.random() * wordsList.length); currentAnswer = wordsList[random]; 复制代码
- 通知所有玩家游戏开始
let data = {}; ...省略 io.emit('message', JSON.stringify(data)); 复制代码
- 遍历客户端,然后找到画画的那个用户告诉他相关data
let count = 0; io.clients((err, client) => { client.forEach(item => { // 匹配为分配的玩家才可以绘制答案 if (count === player) { let game = {}; ...省略 // 这条消息只有绘图的玩家才能看到 socket.send(JSON.stringify(game)); } count++; }); }); 复制代码
- 1分钟游戏结束并改变当前状态
timer = setTimeout(() => { ...省略 }, 60 * 1000); currentState = START; 复制代码
- 监听消息判断是否有人回答正确
// 判断是不是有玩家答对了 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); } 复制代码
好了,到这里就都结束了,小伙伴们可以跑起来试一试了,当然动手敲起来才是王道了
就写到这里吧,感谢大家一直以来的关照,祝大家2019年猪事顺利了,哈哈,886
最后的最最后: 必须把地址发给大家以作 参考 啊
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
复杂:信息时代的连接、机会与布局
罗家德 / 中信出版集团股份有限公司 / 2017-8-1 / 49.00 元
信息科技一方面创造了人们互联的需要,另一方面让人们在互联中抱团以寻找归属感,因此创造了大大小小各类群体的认同和圈子力量的兴起,即互联的同时又产生了聚群,甚至聚群间的相斥。要如何分析这张网?如何预测它的未来变化?如何在网中寻找机会,实现突围?本书提出了4个关键概念──关系、圈子、自组织与复杂系统: • 关系 关系是人与人的连接,又可以被分为强关系和弱关系。强关系就是和你拥有亲密关系的人,......一起来看看 《复杂:信息时代的连接、机会与布局》 这本书的介绍吧!