从控制台到浏览器,我一生都热衷于游戏。 因此,当我开始学习javascript时,构建自己的游戏在我想尝试的个人项目列表中居于首位。
我绝不是创建HTML5游戏或使用socket.io的专家,但我想分享到目前为止所学到的知识,希望可以帮助其他希望对使用socket.io有所了解的人游戏环境。
本教程假定您了解javascript和Node.js / express的基础知识,以及canvas API的工作原理(ps是Mozilla撰写的关于画布的入门入门指南)。
我还要感谢Alvin Lin,他自己的多人游戏指南对我学习时提供了很好的参考。
什么是socket.io,为什么需要它?
Socket.io是一个库,其中包含Websockets和Node.js的API。 可以将其视为一个可以简化并增加与Websockets交互的便利性的库。
好的,什么是Websockets? Websockets是一种API,允许客户端和服务器之间的持久连接。 双方都可以随时发送和接收数据,直到关闭连接为止(与传统的HTTP连接不同,后者在发送响应后关闭)。
可以用来比较HTTP与Websocket的示例是一个场景,假设您和朋友一起在一个项目上远程工作:
- HTTP就像您或您的朋友每次想要共享信息时,您都必须互相调用然后挂断电话。 如果您只需要偶尔共享一些内容,那就太好了。 但是,如果所需频率高得多,这将变得很乏味。
- 如果您或您的朋友仍然互相打电话,但只是没有挂断电话,直到项目完成,Websockets才挂断电话。
这是我们最终要制作的游戏:
这是一款简单的2D游戏,玩家可以在屏幕上控制一个蓝色框并尝试收集金币。 每个硬币在计分板上的价值为1分。
现在,让我们开始吧!
设定
单击此处以方便地启动样板。 它包括使用Canvas,Socket.io和Express构建游戏所需的一切。 它还附带了webpack设置。
让我们回顾一些代码:
index.html:
这里的一切都应该是不言自明的。 注意socket.io标头中的script标记,该标记允许我们的客户端代码访问io全局。
服务器/app.js:
同样,大多数代码是使用express设置的标准服务器。 唯一的额外添加是在底部附近。 此处的io表示socket.io服务器实例。 然后,它侦听连接事件,在该事件中它将继续控制台记录一条消息以及称为socket.id的消息,socket.id代表分配给每个连接套接字的ID。
下一行代码显示了类似的模式,除了它使用套接字而不是io。 它正在侦听“断开连接”事件,该事件随后将触发回调,该回调发送另一条消息。
io.on('connection',(socket)=> {
console.log('用户已连接:',socket.id);
socket.on('disconnect',function(){
console.log('用户已断开连接');
});
});
客户端/ index.js
这里没有太多代码。 只是canvas和socket.io的一些方便的常量。
继续并尝试在控制台上执行“ npm start”,以启动并运行所有程序。 如果您退出localhost:3000,则关闭浏览器窗口后,应该会在控制台上看到连接消息和断开连接消息。
在继续使用此代码之前,我们首先需要仔细考虑我们游戏的多人游戏性质。 将涉及什么类型的数据? 它是静态的(例如“一次完成”吗?)还是需要某种持久状态(例如,玩家位置,得分计数器)。
对于一个简单的游戏,您想要代码的架构逻辑为:
- 让服务器端存储游戏的“状态”,能够修改状态,还可以接收玩家的更新
- 使客户端能够接收和呈现服务器发送的“游戏”状态,并向服务器发送玩家动作
对于第一部分,我们设定一个目标,即在玩家加入时能够渲染玩家“盒子”,并且能够移动它。
创建游戏状态
首先,我们需要创建一些东西来存储我们的游戏状态。 由于我们现在不需要太多数据来制作游戏,因此我们可以简单地使用如下代码:
const gameState = {
玩家:{}
}
玩家对象将用作存储我们的玩家对象的哈希。 请将其添加到server / app.js文件中(请记住,我们希望我们的服务器对状态负责)。
渲染玩家
接下来,我们需要弄清楚如何以及何时添加新玩家。 这是socket.io派上用场的地方。 花点时间考虑一下我们需要从游戏状态接收什么样的数据,以“吸引”玩家。
接下来,转到您的client / index.js文件并添加以下行:
socket.emit('newPlayer');
发出函数的行为与您期望的相同。 它“发出”服务器可以监听的自定义事件。 对于上面的代码,我们要做的只是在客户代码开始时发出一次名为“ newPlayer”的事件。
现在再次回到我们的服务器代码,让我们构建一些可以响应此事件的东西。 在您的io代码块中,插入:
socket.on('newPlayer',()=> {
gameState.players [socket.id] = {
x:250,
y:250,
宽度:25,
高度:25
}
}
上面的代码现在将侦听’newPlayer’事件,并在接收到该事件后,将利用我们的玩家哈希并根据其ID创建一个唯一的玩家条目。 但是请稍等…我们错过了什么吗?
当然,我们还需要通过在离开时删除玩家来确保gameState的正确顺序。 这将有助于防止即使不再存在的“幽灵”玩家也可能被渲染。 在“断开连接”回调函数中添加以下代码:
删除gameState.players [socket.id]
至于我们在该对象内部发送的数据,它包含画布渲染所需的所有必要信息(坐标+尺寸)。
在io代码块之后,添加一个小函数:
setInterval(()=> {
io.sockets.emit('state',gameState);
},1000/60);
此功能基本上以每秒60次的速率将gameState对象“发射”到所有套接字。
现在,让我们回到客户端代码,并创建一个有助于将播放器绘制到画布上的函数。
const drawPlayer =(玩家)=> {
ctx.beginPath();
ctx.rect(player.x,player.y,player.width,player.height);
ctx.fillStyle ='#0095DD';
ctx.fill();
ctx.closePath();
};
一旦有了这些,我们现在可以创建如下内容:
socket.on('state',(gameState)=> {
为(让gameState.players中的玩家){
rawPlayer(gameState.players [player])
}
}
增加运动
我们如何为游戏增添动感? 好吧,让我们考虑一下。 当前游戏状态存储玩家的x和y坐标,这些坐标确定了它在画布上的显示位置,对吗? 因此,为了“移动”玩家,我们需要能够将更新发送到服务器,告诉服务器调整x和y坐标。
这将与我们在使用socket.on和socket.emit之前所做的类似。 在这里,我们可以利用客户端上的文档事件侦听器来侦听按键(例如,按箭头按钮)。
如果我们可以收听,那么我们可以将数据发送到服务器,告诉服务器何时按下了向上箭头。 然后,服务器可以获取该数据,并可能更改玩家的y坐标。
这是代码的外观。 首先,一个keyDown和keyUp处理程序以及代表“键”状态的数据值。 这将放在我们的client / index.js文件中。
const playerMovement = {
上:错误,
下:错误,
左:假,
正确:错误
}; const keyDownHandler =(e)=> {
如果(e.keyCode == 39){
playerMovement.right = true;
} else if(e.keyCode == 37){
playerMovement.left = true;
} else if(e.keyCode == 38){
playerMovement.up = true;
} else if(e.keyCode == 40){
playerMovement.down = true;
}
}; const keyUpHandler =(e)=> {
如果(e.keyCode == 39){
playerMovement.right = false;
} else if(e.keyCode == 37){
playerMovement.left = false;
} else if(e.keyCode == 38){
playerMovement.up = false;
} else if(e.keyCode == 40){
playerMovement.down = false;
}
};
接下来,我们将添加侦听器并发出函数:
setInterval(()=> {
socket.emit('playerMovement',playerMovement);
},1000/60); document.addEventListener('keydown',keyDownHandler,false);
document.addEventListener('keyup',keyUpHandler,false);
最后,我们将以后端的逻辑结束,以接收移动数据并操纵相应的玩家坐标:
socket.on('playerMovement',(playerMovement)=> {
常量玩家= gameState.players [socket.id]
const canvasWidth = 480
const canvasHeight = 320
如果(playerMovement.left && player.x> 0){
player.x-= 4
}如果(playerMovement.right && player.x <canvasWidth-player.width){
player.x + = 4
}
如果(playerMovement.up && player.y> 0){
player.y-= 4
}如果(playerMovement.down && player.y <canvasHeight-player.height){
player.y + = 4
}
})
让我们现在看看这一切! 再执行一次“ npm start”以编译我们的bundle.js并启动服务器。 现在,我们应该(希望一切正常)能够加载浏览器,并使播放器加入其中,并能够使用箭头键移动。
oo! 请继续关注第二部分,在此我们将深入研究一些基本的游戏机制,例如碰撞和得分。