如何在2D瓷砖游戏地图中检测到碰撞

时间:2018-07-02 00:05:32

标签: javascript canvas 2d tile

我制作了这个基本游戏,在其中绘制了地图和一个玩家,该玩家可以移动到任何地方,但是我该如何制作才能使其在地图上的图块[1]上不移动? 另外,当我尝试检查player.x是否大于50时,它可能会左移,但比起一次单击2个键,它会通过     const context = document.querySelector(“ canvas”)。getContext(“ 2d”);

var rgb = 'rgb(' + Math.random()*256 + ',' + Math.random()*256 + ',' + Math.random()*256 + ','+Math.random() + ')';

document.onload = Loop();

var width = 1500;
var height = 800;

function Loop(){

  var width = 1500;
  var height = 800;

  context.canvas.height = height;
  context.canvas.width = width;

  this.interval = setInterval(Update, 1000/100);

}



const Player = function(x, y, w, h, color) {
  this.x = x; this.y = y; this.w = w; this.h = h;

  this.speedY = 0; this.speedX = 0;
  this.Draw = function(){
    context.fillStyle = this.color;
    context.fillRect(this.x, this.y, this.w, this.h);
  };
  this.Move = function(){
    this.x += this.speedX;
    this.y += this.speedY;
  };
};<code>

var player = new Player(100,100,50, 50, rgb);


var Key = {};
function Update(){
  context.clearRect(0, 0, width, height);
  Map();
  player.Draw();
  player.Move();

onkeydown = onkeyup = function(e){
  player.speedX = 0;
  player.speedY = 0;
  e = e || event;
  Key[e.keyCode] = e.type == 'keydown';
    if(Key[37] || Key[65]) {player.speedX -= 2}
    if(Key[38] || Key[87]) {player.speedY -= 2}
    if(Key[39] || Key[68]) {player.speedX += 2}
    if(Key[40] || Key[83]) {player.speedY += 2}
    if(Key[32]) {player.color = 'rgb(' + Math.random()*256 + ',' + Math.random()*256 + ',' + Math.random()*256 + ','+Math.random()*1 + ')';}
  };
}

var map = [
1, 1, 1, 1, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 1, 1, 1, 1
    ];

var row = 5;
var column = 5;


function Map(){
  for(let y = -1; y < column; y++){

    for(let x = -1; x < row; x++){
      switch(map[((y*row) + x)]) {
        case 0: context.fillStyle = player.color;
          break;
        case 1: context.fillStyle = "#ffffff";
          break;
        default: context.fillStyle = "#000000";
      }
    context.fillRect(x*50, y*50, 50, 50);

    }
  }
}

2 个答案:

答案 0 :(得分:0)

首先,查看您的代码,实现基本的碰撞检测需要丢失一些东西,这些东西是:

  1. 玩家正在移动的当前方向。这很重要,因为它允许确定碰撞检测的功能区分正在检查碰撞的哪一侧(上,下,左或右)因为玩家一次只能与一侧碰撞。

  2. 图块的位置和大小。这也非常重要,因为像第一个点一样,玩家只有一块可以与之碰撞的块,并且知道大小和位置就可以根据玩家的大小和位置来确定是否是碰撞。

此外,由于您提到它是基本游戏,因此以下实现是基本碰撞检测。如果要制作更复杂,更大的游戏,则应尝试研究四叉树以提高碰撞检测效率: https://gamedevelopment.tutsplus.com/tutorials/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space--gamedev-374

现在这是用于检测碰撞的功能,出于可读性和简短性的考虑,p代表玩家对象,t代表瓦片对象。该函数根据玩家的运动方向返回玩家是否正在与瓷砖碰撞。

function isColliding(p, t){
  if (p.direction == 'up') {
   return p.y +(p.height/2)-p.speedY< t.y + t.height && p.y > t.y
      && p.x + p.width > t.x && p.x < t.x + t.width;
  }
  if (p.direction == 'down') {
    return p.y + (p.height/2)+p.speedY > t.y && p.y < t.y
      && p.x + p.width > t.x && p.x < t.x + t.width;
  }
  if (p.direction == 'right') {
    return p.x + p.width+p.speedX > t.x && p.x < t.x
      && p.y +(p.height/2)> t.y && p.y + p.height < t.y +t.height+ (p.height / 2);
  }
  if (p.direction == 'left') {
    return p.x -p.speedX< t.x + t.width && p.x > t.x
      && p.y +(p.height/2)> t.y && p.y + p.height < t.y +t.height+ (p.height / 2);
  }
  return false;
}

您可能希望将其放在播放器移动功能中,以便在移动时不断检测瓷砖。为此,您需要修改键盘按下检测,以便通过每次使用不同的键盘按下来更新玩家的方向,这是一个简单的示例:

document.onkeydown = function(event){
    if (event.keyCode == 87)
        player.up = true;
    else if (event.keyCode == 65)
        player.left = true;
    else if (event.keyCode == 83)
        player.down = true;
    else if (event.keyCode == 68)
        player.right = true;
}

以及玩家每次移动(用户按下按键)的另一个简单示例:

const Player= function(/*Param stuff*/){
  /*Property stuff*/
  //tileArray is the array (or object, your choice) of all the current tiles in the map
  this.move=function(tileArray){
    //Go through all tiles to see if player is colliding with any of them
    for(var t in tileArray){
      if(this.up){
        if(isColliding(this, tileArray[t]){
          //functionality for when player collides
        }else{
          //functionality for when player doesn't collide
        }
      }
      //check if player is going down, left, etc
    }
  }
}

这些只是如何执行检测的示例。您应该使用它作为实现代码功能的参考,因为我不是根据您发布的内容来编写它的。

PS。

请确保在用户停止按下键后也将方向转换为false。

答案 1 :(得分:0)

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
			body {
				background-color: black;
			}
			
			canvas {
				display: block;
				margin: auto;
				border: solid 1px white;
				border-radius: 10px;
			}
			
			script {
				display: none;
			}
		</style>
	</head>
	
	<body>
		<canvas id="canvas"></canvas>
		<script type="application/javascript">
		
		void function() {
			
			"use strict";
			
			// Classes
			function Camera(x,y) {
				this.x = x || 0.0;
				this.y = y || 0.0;
			}
			
			Camera.prototype = {
				set: function(x,y) {
					this.x = x || 0.0;
					this.y = y || 0.0;
				},
				
				pan: function(x,y) {
					this.x += x || 0.0;
					this.y += y || 0.0;
				}
			};
			
			var nextID = 0;
			
			function Tile(colour) {
				this.id = nextID++;
				this.colour = colour || "black";
			}
			
			function Map(width,height) {
				this.width = width || 1;
				this.height = height || 1;
				this.map = [];
				this.map.length = this.height;
				
				for (var y = 0; y < this.height; ++y) {
					this.map[y] = [];
					this.map[y].length = width;
					
					for (var x = 0; x < this.width; ++x) {
						this.map[y][x] = Math.random() < 0.2 ?
							this.TILE_WALL:
							this.TILE_GRASS;
					}
					
					this.map[y][0] = this.TILE_WALL;
					this.map[y][this.width - 1] = this.TILE_WALL;
				}
				
				for (var x = 0; x < this.width; ++x) {
					this.map[0][x] = this.TILE_WALL;
					this.map[this.height - 1][x] = this.TILE_WALL;
				}
			}
			
			Map.prototype = {
				TILE_WIDTH: 32.0,
				TILE_HEIGHT: 32.0,
				INV_TILE_WIDTH: 0.0,
				INV_TILE_HEIGHT: 0.0,
			
				TILE_AIR: new Tile("#00000000"),
				TILE_GRASS: new Tile("#00AA00FF"),
				TILE_WALL: new Tile("#555555FF"),
			
				set: function(x,y,tile) {
					this.map[y][x] = tile;
				},
			
				scaleX: function(x) {
					return (x * this.INV_TILE_WIDTH) | 0;
				},
			
				scaleY: function(y) {
					return (y * this.INV_TILE_HEIGHT) | 0;
				},
			
				isColliding: function(x,y) {
					return x > -1 && x < this.width
						&& y > -1 && y < this.height
						&& this.map[y][x].id > 1;
				},
			
				render: function(ctx,camera) {
					for (var y = 0; y < this.height; ++y) {
						for (var x = 0; x < this.width; ++x) {
							var tile = this.map[y][x];
							var _x = x * this.TILE_WIDTH - camera.x;
							var _y = y * this.TILE_HEIGHT - camera.y;
							
							ctx.fillStyle = tile.colour;
							ctx.fillRect(_x,_y,this.TILE_WIDTH - 1,this.TILE_HEIGHT - 1);
						}
					}
				}
			};
			
			Map.prototype.INV_TILE_WIDTH = 1.0 / Map.prototype.TILE_WIDTH;
			Map.prototype.INV_TILE_HEIGHT = 1.0 / Map.prototype.TILE_HEIGHT;
			
			function Player(x,y) {
				this.x = x || 0.0;
				this.y = y || 0.0;
				this.dx = 0.0;
				this.dy = 0.0;
				this.isUp = false;
				this.isDown = false;
				this.isLeft = false;
				this.isRight = false;
			}
			
			Player.prototype = {
				WIDTH: 20.0,
				HEIGHT: 20.0,
				
				ACCELERATION: 1.0,
				DEACCELERATION: 0.5,
				MAX_SPEED: 3.0,
				
				tick: function(map) {
					// Movement
					if (this.isUp) {
						this.dy -= this.ACCELERATION;
						
						if (this.dy < -this.MAX_SPEED) {
							this.dy = -this.MAX_SPEED;
						}
					} else if (this.dy < 0.0) {
						this.dy += this.DEACCELERATION;
						
						if (this.dy > 0.0) {
							this.dy = 0.0;
						}
					}
					
					if (this.isDown) {
						this.dy += this.ACCELERATION;
						
						if (this.dy > this.MAX_SPEED) {
							this.dy = this.MAX_SPEED;
						}
					} else if (this.dy > 0.0) {
						this.dy -= this.DEACCELERATION;
						
						if (this.dy < 0.0) {
							this.dy = 0.0;
						}
					}
					
					if (this.isLeft) {
						this.dx -= this.ACCELERATION;
						
						if (this.dx < -this.MAX_SPEED) {
							this.dx = -this.MAX_SPEED;
						}
					} else if (this.dx < 0.0) {
						this.dx += this.DEACCELERATION;
						
						if (this.dx > 0.0) {
							this.dx = 0.0;
						}
					}
					
					if (this.isRight) {
						this.dx += this.ACCELERATION;
						
						if (this.dx > this.MAX_SPEED) {
							this.dx = this.MAX_SPEED;
						}
					} else if (this.dx > 0.0) {
						this.dx -= this.DEACCELERATION;
						
						if (this.dx < 0.0) {
							this.dx = 0.0;
						}
					}
					
					// Collision
					if (this.dx !== 0.0) {
						var minY = map.scaleY(this.y);
						var maxY = map.scaleY(this.y + this.HEIGHT);
						var minX = 0;
						var maxX = 0;
						
						if (this.dx < 0.0) {
							minX = map.scaleX(this.x + this.dx);
							maxX = map.scaleX(this.x);
						} else {
							minX = map.scaleX(this.x + this.WIDTH);
							maxX = map.scaleX(this.x + this.WIDTH + this.dx);
						}
						
						loop:
						for (var y = minY; y <= maxY; ++y) {
							for (var x = minX; x <= maxX; ++x) {
								if (map.isColliding(x,y)) {
									this.x = this.dx < 0.0 ?
										(x + 1) * map.TILE_WIDTH:
										x * map.TILE_WIDTH - this.WIDTH - 1;
								
									this.dx = 0.0;
									break loop;
								}
							}
						}
					}
					
					if (this.dy !== 0.0) {
						var minX = map.scaleX(this.x);
						var maxX = map.scaleX(this.x + this.WIDTH);
						var minY = 0;
						var maxY = 0;
						
						if (this.dy < 0.0) {
							minY = map.scaleY(this.y + this.dy);
							maxY = map.scaleY(this.y);
						} else {
							minY = map.scaleY(this.y + this.HEIGHT);
							maxY = map.scaleY(this.y + this.HEIGHT + this.dy);
						}
						
						loop:
						for (var y = minY; y <= maxY; ++y) {
							for (var x = minX; x <= maxX; ++x) {
								if (map.isColliding(x,y)) {
									this.y = this.dy < 0.0 ?
										(y + 1) * map.TILE_HEIGHT:
										y * map.TILE_HEIGHT - this.HEIGHT - 1;
								
									this.dy = 0.0;
									break loop;
								}
							}
						}
					}
					
					this.x += this.dx;
					this.y += this.dy;
				},
				
				render: function(ctx,camera) {
					camera.set(this.x,this.y);
				
					ctx.lineWidth = 1;
					ctx.strokeStyle = "black";
					ctx.fillStyle = "darkred";
					ctx.beginPath();
					ctx.rect(this.x - camera.x,this.y - camera.y,this.WIDTH,this.HEIGHT);
					ctx.fill();
					ctx.stroke();
				}
			};
			
			// Variables
			var canvasWidth = 180;
			var canvasHeight = 160;
			var canvas = null;
			var ctx = null;
			var camera = null;
			var map = null;
			var player = null;
			
			// Functions
			function onKeyDown(e) {
				switch(e.key.toUpperCase()) {
					case "W": player.isUp = true; break;
					case "S": player.isDown = true; break;
					case "A": player.isLeft = true; break;
					case "D": player.isRight = true; break;
				}
			}
			
			function onKeyUp(e) {
				switch(e.key.toUpperCase()) {
					case "W": player.isUp = false; break;
					case "S": player.isDown = false; break;
					case "A": player.isLeft = false; break;
					case "D": player.isRight = false; break;
				}
			}
			
			function loop() {
				// Tick
				player.tick(map);
				
				// Render
				ctx.fillStyle = "gray";
				ctx.fillRect(-canvasWidth >> 1,-canvasHeight >> 1,canvasWidth,canvasHeight);
				
				map.render(ctx,camera);
				player.render(ctx,camera);
				
				//
				requestAnimationFrame(loop);
			}
			
			// Entry point (first to execute)
			onload = function() {
				canvas = document.getElementById("canvas");
				canvas.width = canvasWidth;
				canvas.height = canvasHeight;
				
				ctx = canvas.getContext("2d");
				ctx.translate(canvasWidth >> 1,canvasHeight >> 1);
				
				camera = new Camera(0.0,0.0);
				map = new Map(10,10);
				player = new Player(40.0,40.0);
				
				map.set(1,1,map.TILE_GRASS);
				
				addEventListener("keydown",onKeyDown);
				addEventListener("keyup",onKeyUp);
				
				loop();
			}
			
		}();
		
		</script>
	</body>
</html>