javascript 鼠标控制的玩家与障碍方块之间的 Flink 碰撞解决方案

ee7vknir  于 2023-01-01  发布在  Java
关注(0)|答案(1)|浏览(99)

我正在尝试制作一款具有碰撞检测和解决的游戏。由于某种原因,当我将玩家移动到"敌人方块"的右边时,玩家移动到了"敌人"的左边。我该如何解决这个问题呢?我已经为此工作了几个小时,但没有找到任何解决方案。我不确定这是一个小问题还是我必须改变整个敌人对象。

//declare variables
var body = document.getElementById("body");
var canvas = document.getElementById("canvas");
var iwidth = window.innerWidth;
var iheight = window.innerHeight;
//variable for drawing
var draw = canvas.getContext("2d");
//variables for character paramaters
var playerwidth = 20;
var playerheight = 20;
var playerx = iwidth / 2 - playerwidth / 2;
var playery = iheight / 2 - playerheight / 2;
var playerspeed = 20;
//mouse co-ordinates
var mousex;
var mousey;
//enemy's parameters
var enemyxpositions = [43, 94, 200];
var enemyypositions = [41, 120, 83];
var enemywidths = [12, 43, 45];
var enemyheights = [43, 11, 87];
var i = 0;
var collision = false;
///////////////////////////////////////////////////////////////////////////////////
///////         separating variables and rest of the code                   ///////              
///////////////////////////////////////////////////////////////////////////////////

//puts canvas in top right corner
body.style.margin = "0";


//changes the canvas's style namely color, margin, width and height
canvas.style.backgroundColor = "black";
canvas.style.margin = "0";
canvas.width = iwidth;
canvas.height = iheight;

//the function that the player is drawn in
function drawplayer() {	

	//allows animation
	requestAnimationFrame(drawplayer);

	//clears the canvas every time the function runs so that the image doesn't leave a mark
	draw.clearRect(0, 0, iwidth, iheight);


	//drawing the player
	draw.fillStyle = "#ffff00";
	draw.fillRect(playerx, playery, playerwidth, playerheight);
	draw.fill();


	//checking where the mouse is and letting the player follow it
	if (mousex > playerx + playerwidth / 2) {
		playerx += (mousex - playerx + playerwidth) / playerspeed;
	}
	if (mousex < playerx + playerwidth / 2) {
		playerx -= (playerx - mousex + playerwidth) / playerspeed;
	}
	if (mousey > playery + playerheight / 2) {
		playery += (mousey - playery + playerheight) / playerspeed;
	}
	if (mousey < playery + playerheight / 2) {
		playery -= (playery - mousey + playerheight) / playerspeed;
	}


	//the obstacles' object
	function Enemy(enemyx, enemyy, enemywidth, enemyheight) {
		this.enemyx = enemyx;
		this.enemyy = enemyy;
		this.enemywidth = enemywidth;
		this.enemyheight = enemyheight;
		this.enemies = function() {
			draw.fillStyle = "#0000ff";
			draw.fillRect(enemyx, enemyy, enemywidth, enemyheight);
			draw.fill();
		}
	

		//collision detection
		if (mousex + playerwidth / 2 > this.enemyx && 
			mousex - playerwidth / 2 < this.enemyx + this.enemywidth &&
			mousey + playerheight / 2 > this.enemyy &&
			mousey - playerheight / 2 < this.enemyy + this.enemyheight) {
				collision = true;
		}
		else {
			collision = false;
		}

		//collision implementation
		//left collision
		if (collision == true && mousex + playerwidth / 2 > this.enemyx) {
			playerx = this.enemyx - playerwidth;
		}
		//right collision
		else if (collision == true && mousex - playerwidth / 2 < this.enemyx + this.enemywidth) {
			playerx = this.enemyx + this.enemywidth + 50;
		}
		



	}

		//draws all the obstacles
		for (i = 0; i < enemyxpositions.length; i++) {
			new Enemy(	enemyxpositions[i],
						enemyypositions[i],
						enemywidths[i],
						enemyheights[i]).enemies();
		}


}
drawplayer();


//gets the mouse co-ordinates
window.onmousemove = function mousepos(event) {
	mousex = event.clientX;
	mousey = event.clientY;
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>DUNGE</title>
	<style>
		::-webkit-scrollbar {
			display: none;
		}
		canvas {
			display: block;
		}
		#obstacles {
			opacity: 1;
			margin-top: -100vh;
		}
	</style>
</head>
<body id="body">
	
	<canvas id="canvas"></canvas>
	<script src="script.js"></script>
</body>
</html>
gzszwxb4

gzszwxb41#

冲突解决是一个非常棘手的领域,有很多方法可以采用。对于您的例子中使用鼠标控制的方块,一个简单的方法可能如下:
如果检测到玩家与固定障碍物(敌人、墙壁等)发生碰撞,我们可以通过逐渐"撤销"玩家的动作直到不再与障碍物发生碰撞来解决碰撞。
例如,如果在当前帧上,玩家以y速度5和x速度2移动,并且我们检测到碰撞,那么我们可以通过撤消移动来避免碰撞。然而,这将在障碍物和玩家之间创建不现实的空气间隙,从而导致弹跳效果。我们可以缓慢地移动障碍物的xy位置,移动量很小,例如-0.5,直到检测不到碰撞为止。但是,如果只有一个轴发生碰撞,则撤消两个轴上的移动可能是不正确的。
下面是将x轴和y轴分为不同步长的初步尝试:

const canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 180;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const mouse = {x: 0, y: 0};
const enemy = {x: 130, y: 70, width: 40, height: 40};
const player = {
  x: 0, y: 0, width: 20, height: 20, vx: 0, vy: 0, 
  velocityDamp: 0.06, collisionDamp: 0.3
};

const collides = (a, b) => 
  a.x + a.width >= b.x && a.x <= b.x + b.width &&
  a.y + a.height >= b.y && a.y <= b.y + b.height
;

(function render() {
  player.vx = (mouse.x - player.x) * player.velocityDamp;
  player.vy = (mouse.y - player.y) * player.velocityDamp;
  player.x += player.vx;
  player.y += player.vy;

  while (collides(player, enemy)) {
    player.y -= Math.sign(player.vy) * player.collisionDamp;
  }

  while (collides(player, enemy)) {
    player.x -= Math.sign(player.vx) * player.collisionDamp;
  }

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "blue";
  ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
  ctx.fillStyle = "yellow";
  ctx.fillRect(player.x, player.y, player.width, player.height);  
  requestAnimationFrame(render);
})();

onmousemove = e => {
  mouse.x = e.clientX;
  mouse.y = e.clientY;
};
body {margin: 0;}
canvas {background: #000;}

当碰撞发生在y轴上时,这个方法可以正常工作,但是x轴上的碰撞会导致玩家"弹出"障碍物。调整顺序以便首先处理最不受影响的速度调整应该可以解决这个问题。我们通过"撤消"一个轴上的最后一次移动,检查这个单轴移动是否解决了碰撞,并进行相应的调整来做到这一点。
将所有这些放在一起,下面是一个概念验证:
一个二个一个一个
无论如何,这都不是完美的碰撞解决方案,但它引入了一些基本概念,对于简单的游戏应该足够了。
注意我只对付一个敌人它留给读者去创建一个敌人的数组,并在他们身上循环以检测和解决冲突。解决一个碰撞可能会把玩家推向另一个碰撞。如果障碍物也在移动,情况会更糟。如果你正在制作一个平台游戏,collision grid可能值得考虑,以避免这些问题。
如果处理冲突变得越来越复杂和难以应付,那么使用matter.js这样的库也没什么不好。
使用while解决这些冲突时要小心,因为很容易出现无限循环。考虑在这些循环中添加一个tries计数器,如果循环超过20或30次迭代,则退出(这有点令人不满意,并表明此解决方案不是工业级的;这防止了无限循环,但是可能导致不正确的行为)。
限制播放器的最大速度是另一个重要的预防措施:它可以避免速度太高以至于玩家直接穿过障碍物的情况。探索其他出现问题时的临时解决方案。
除了碰撞检测之外,我还有一些其他建议:

  • 使用对象来封装与游戏实体相关的所有属性。这使得代码比松散的变量(如playerwidthplayerheightplayerspeed等)更容易管理。
  • 避免重复代码明确执行的任务的无意义和嘈杂的注解。
  • 与其添加注解来划分函数的逻辑部分,不如创建具有适当名称的辅助函数。我上面的POC在这方面并不出色--随着游戏的扩展,对象、函数和整体设计变得越来越重要;当您想要添加特性或遇到bug时,在更新循环中内联所有内容会使您的编码体验非常痛苦。
  • Enemy的构造函数放在游戏循环之外。在初始化函数和适当的作用域构造函数中创建一次敌人。
  • 使用camelCased变量代替everythinginlowercase

相关问题