在 HTML 画布中使用 .scale() 会导致命中检测问题

问题描述 投票:0回答:1

注意

在Dropbox上,我在Hand tropbox上演示问题的视频的链接是:https://www.dropbox.com/scl/fo/p19oite64o22ssh9bl8s1b/alnnuvrjznbkk7qhrglggdy = gpxahqury = gpxahqur1kmffddqrw4bbkny = gpxahqurkey = gpxahqury = gpxahqur1km1ulow4bbkyb。

背景

我正在尝试创建一款 agario 类型的游戏,其中玩家以圆圈形式生成,并且能够吃小而圆形的食物来变大。我在客户端使用 HTML canvas,在服务器端使用 Node.js。

问题

当玩家吃更多的食物时,我一直很难弄清楚如何正确地缩放世界。在这个游戏中,当玩家触摸食物时,他们就会吃掉它。我使用 .scale() 方法慢慢缩小,这样当玩家变大时,他们最终不会完全占据屏幕,这样他们除了自己就看不到任何东西。然而,随着玩家变大,命中检测会变得更差 - 玩家会在食物上面并且不会吃它,或者会在触摸它之前吃掉它。似乎这种糟糕的命中检测对应的方向是:当玩家向上移动时,食物被吃得较晚,因为食物会与玩家重叠而不会被吃掉。当玩家向左移动时也会发生同样的情况,食物将与玩家重叠而不会被吃掉。相反,当玩家向右或向下移动时,食物将在玩家接触食物之前被吃掉。就好像我只需将玩家向右移动一定的距离,但我不知道要更改哪些代码,也不知道为什么它首先会导致问题。

代码

我删除了似乎与该问题无关的代码。

Node.js 服务器目前处理所有事情(生成食物、计算玩家之间的碰撞、食物等),除了提供玩家坐标之外,因为客户端在每一帧上将其坐标发送到服务器。

“Player”类创建玩家对象的结构。在 Player 类中,我有 draw() 方法,如下所示:

draw(viewport) { if (this.isLocal) { //if it's the local player, send coordinates to server socket.emit('currentPosition', { x: this.x / zoom, y: this.y / zoom, radius: this.radius / zoom, speedMultiplier: this.speedMultiplier, vx: this.vx, //update these on server side? vy: this.vy }); } ctx.save(); ctx.translate(-viewport.x, -viewport.y); //scale the player ctx.scale(zoom, zoom); ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = this.color; ctx.fill(); ctx.strokeStyle = this.strokeColor; ctx.lineWidth = 5; ctx.stroke(); ctx.closePath(); ctx.restore(); }

在此draw()方法中,我使用
ctx.scale(zoom, zoom)

来缩放播放器。我的理解是,这本质上是将 x/y 位置和半径乘以缩放系数。 u2028u2028我还有一个 Food 类,它创建食物对象。 Food 类的 draw() 方法如下所示:

draw(viewport) {
        ctx.save();
        ctx.translate(-viewport.x, -viewport.y);
        ctx.scale(zoom, zoom); //scale foods
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.strokeStyle = this.strokeColor;
        ctx.lineWidth = 3.5;
        ctx.stroke();
        ctx.closePath();
        ctx.restore();
}

这两个draw()方法都是为了根据玩家吃了多少食物来缩放玩家和食物。当玩家吃掉单个食物时,缩放率会降低。服务器告诉玩家何时吃过食物:

socket.on('foodEaten', (data) => { const player = gamePlayers.get(data.playerId); if (player.id == localPlayer.id) { zoom *= 0.9999; } …

因此,对于玩家吃的每一种食物,他们都会缩小 0.9999。

玩家的移动由鼠标移动的位置决定。当他们移动鼠标时,此事件侦听器会计算鼠标指向的方向并将玩家设置在该路径上:

canvas.addEventListener('mousemove', (event) => { const rect = canvas.getBoundingClientRect(); mouse.x = (event.clientX - rect.left) / zoom; mouse.y = (event.clientY - rect.top) / zoom; // Update player's target position localPlayer.targetX = (mouse.x + viewport.x / zoom); localPlayer.targetY = (mouse.y + viewport.y / zoom); const dx = (localPlayer.targetX - localPlayer.x) * zoom; const dy = (localPlayer.targetY - localPlayer.y) * zoom; const distance = Math.sqrt(dx * dx + dy * dy); ...does some other stuff in between... const xMovingDirection = dx / distance; const yMovingDirection = dy / distance; localPlayer.movingDirection = { x: xMovingDirection, y: yMovingDirection}; });

我在 Player 类的 draw() 方法中提到,它们在绘制玩家之前将当前位置发送到服务器:

socket.emit('currentPosition', { x: this.x / zoom, y: this.y / zoom, radius: this.radius / zoom, speedMultiplier: this.speedMultiplier, vx: this.vx, vy: this.vy });

我将 x 和 y 坐标以及半径除以缩放级别,以允许服务器忽略各个玩家的缩放级别,以便游戏中的每个其他玩家都会发送非缩放坐标。

服务器收到此信息后,会根据游戏中的每种食物评估玩家的位置,检查它们是否发生碰撞:

socket.on('currentPosition', (data) => { //get player's current x/y coordinates and update them const player = room.players.get(socket.id); if (player) { player.x = data.x; //update player x position player.y = data.y; //update player y position room.foods.forEach((food, foodId) => { //check for foods being eaten if (checkCollision(player, food)) { player.radius += food.radius * normalRadiusIncreaseRate; //increase player radius let newFood; //add food back in newFood = new Food(); //add new food item room.foods.set(newFood.id, newFood); //let player know that they ate food io.to(room.roomId).emit('foodEaten', { food: food, playerId: player.id, radius: player.radius, newFood: newFood }); room.foods.delete(food.id); //delete eaten food } //send this player’s data to other players socket.to(room.roomId).emit('updatePlayerTarget', { id: socket.id, x: player.x, y: player.y // radius: player.radius }); } });

如果玩家碰撞到食物,他们应该吃掉它。 Node.js 向客户端发出“foodEaten”,这允许客户端更新吃掉食物的玩家的半径。它还给出了玩家在方块末端的 x 和 y 坐标。

问题

为什么当使用 .scale() 时,玩家和食物之间的同步会随着时间的推移而变得更差?

javascript node.js sockets canvas collision-detection
1个回答
0
投票

socket.emit('currentPosition', { x: this.x / zoom, y: this.y / zoom, radius: this.radius / zoom });

相反,我应该按原样发送玩家的坐标,而不除以缩放系数。这是因为缩放系数仅影响客户端的视觉表示,而不影响实际的坐标值。

当我使用 .scale(zoom, Zoom) 时,它会改变坐标在屏幕上的显示方式,但底层坐标本身保持不变。因此,为了保持客户端和服务器之间的一致性,发送到服务器的坐标应该是原始世界坐标,不受缩放的影响。

© www.soinside.com 2019 - 2024. All rights reserved.