我最近一直在开发一款游戏,游戏中有玩家和其他实体可以碰撞的墙,但我唯一的问题是我不知道如何解决矩形到矩形的碰撞。
代码如下:
class Mouse {
constructor() {
throw new Error(`new Mouse() is not allowed.\nTry using the Mouse.init() method instead.`);
}
static x = 0;
static y = 0;
static movement = {
x: 0,
y: 0
}
static pressed = false;
static setMousePosition(e) {
this.x = e.pageX;
this.y = e.pageY;
this.movement.x = e.movementX;
this.movement.y = e.movementY;
}
static init() {
window.addEventListener("mousedown", (e) => { this.setMousePosition(e); this.pressed = true; });
window.addEventListener("mouseup", (e) => { this.pressed = false; });
window.addEventListener("mousemove", (e) => { this.setMousePosition(e); });
}
}
/**
* @description A set of helper functions to make drawing on a 2d canvas easier.
*/
class Draw {
/**
* @param context The canvas context to use for drawing.
*/
constructor(context) {
this.ctx = context;
}
/**
* @description Clears the specified rectangular area, making it fully transparent.
*/
clear(x, y, width, height) {
this.ctx.clearRect(x, y, width, height);
}
rectangle(x, y, width, height, roundness = 0, fill = true, stroke = false, options = {}) {
this.ctx.save();
Object.assign(this.ctx, options);
this.ctx.beginPath();
this.ctx.roundRect(x, y, width, height, roundness);
if (fill) this.ctx.fill();
if (stroke) this.ctx.stroke();
this.ctx.closePath();
this.ctx.restore();
}
arc(x, y, radius, a1, a2 = Math.PI * 2, fill = true, stroke = false, options = {}, counterClockwise = false) {
this.ctx.save();
Object.assign(this.ctx, options);
this.ctx.beginPath();
this.ctx.arc(x, y, radius, a1, a2, counterClockwise);
if (fill) this.ctx.fill();
if (stroke) this.ctx.stroke();
this.ctx.closePath();
this.ctx.restore();
}
text(text, x, y, fill = true, stroke = false, options = {}, maxWidth = undefined) {
this.ctx.save();
Object.assign(this.ctx, options);
if (fill) this.ctx.fillText(text, x, y, maxWidth);
if (stroke) this.ctx.strokeText(text, x, y, maxWidth);
this.ctx.restore();
}
path(path, fill = false, stroke = true, options = {}) {
this.ctx.save();
Object.assign(this.ctx, options);
this.ctx.beginPath();
if (fill) this.ctx.fill(path);
if (stroke) this.ctx.stroke(path);
this.ctx.closePath();
this.ctx.restore();
}
grid(x, y, width, height, cellSize, options = {}) {
this.ctx.save();
Object.assign(this.ctx, options);
this.ctx.beginPath();
for (var cx = x; cx <= x + width; cx += cellSize) {
this.ctx.moveTo(cx, y);
this.ctx.lineTo(cx, y + height);
}
for (var cy = y; cy <= y + height; cy += cellSize) {
this.ctx.moveTo(x, cy);
this.ctx.lineTo(x + width, cy);
}
this.ctx.stroke();
this.ctx.closePath();
this.ctx.restore();
}
text(text, x, y, fill = true, stroke = false, options = {}) {
this.ctx.save();
Object.assign(this.ctx, options);
if (fill) this.ctx.fillText(text, x, y);
if (fill) this.ctx.strokeText(text, x, y);
this.ctx.restore();
}
}
function random(min, max) {
return Math.random() * (max - min) + min;
}
function degreesToRadians(degrees) {
return degrees * Math.PI / 180;
}
var tankClass = {
basic: function (tank) {
return [
new Gun(0, -tank.height * 0.15, tank.width * 0.9, tank.height * 0.3, tank, 0)
];
},
doubleShot: function (tank) {
return [
new Gun(0, -tank.height * 0.12, tank.width * 0.9, tank.height * 0.24, tank, -4),
new Gun(0, -tank.height * 0.12, tank.width * 0.9, tank.height * 0.24, tank, 4),
new Gun(0, -tank.height * 0.15, tank.width * 0.9, tank.height * 0.3, tank, 0)
];
}
}
class Player {
constructor(x, y, width, height, color, bc, startingWeapons = "basic") {
this.initX = x;
this.initY = y;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
this.bc = bc;
this.velX = 0;
this.velY = 0;
this.acceleration = 0.3;
this.gunAngle = 0;
this.weapons = this.setWeapons(startingWeapons);
this.bullets = [];
this.currentReloadTime = 0;
this.reloadTime = 60;
this.recoilX = 0;
this.safeZone = {
x: x - 100,
y: y - 100,
width: width + 100,
height: height + 100
}
}
setWeapons(weaponString) {
var newWeapons = tankClass[weaponString];
return newWeapons(this);
}
reset() {
this.x = this.initX;
this.y = this.initY;
this.velX = 0;
this.velY = 0;
this.gunAngle = 0;
}
setSafeZone(x, y, width, height) {
this.safeZone.x = x;
this.safeZone.y = y;
this.safeZone.width = width;
this.safeZone.height = height;
}
}
class Enemy {
constructor(x, y, width, height, color, bc, startingWeapons = "basic") {
this.initX = x;
this.initY = y;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
this.bc = bc;
this.velX = 0;
this.velY = 0;
this.gunAngle = 0;
this.weapons = this.setWeapons(startingWeapons);
this.bullets = [];
this.currentReloadTime = 0;
this.reloadTime = 60;
this.recoilX = 0;
}
setWeapons(weaponString) {
var newWeapons = tankClass[weaponString];
return newWeapons(this);
}
reset() {
this.x = this.initX;
this.y = this.initY;
this.velX = 0;
this.velY = 0;
this.gunAngle = 0;
}
}
class Gun {
constructor(x, y, width, height, parent, rotation = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.parent = parent;
this.color = "#606060";
this.bc = "#404040";
this.rotation = degreesToRadians(rotation);
}
}
class Bullet {
constructor(x, y, radius, color, bc, parent, velX, velY) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.bc = bc;
this.parent = parent;
this.velX = velX;
this.velY = velY;
}
}
var levels = {
level0: {
map: [
[0, 0, 1, 1, 0, 0],
[0, 0, 1, 0, 0, 0]
]
}
}
class Info_Level {
constructor(gridSize = 64) {
this.GRID_SIZE = gridSize;
this.map = {
walls: []
};
}
load(map) {
var tileOffsetX = 0;
var tileOffsetY = 0;
for (var i = 0; i < map.length; i++) {
for (var j = 0; j < map[i].length; j++) {
if (map[i][j] === 1) {
this.createWall(tileOffsetX, tileOffsetY, this.GRID_SIZE, this.GRID_SIZE, "#000000");
}
if (map[i][j] === 2) {
this.createWall(tileOffsetX, tileOffsetY, this.GRID_SIZE, this.GRID_SIZE, "#000000");
}
tileOffsetX += this.GRID_SIZE;
}
tileOffsetX = 0;
tileOffsetY += this.GRID_SIZE;
}
tileOffsetY = 0;
tileOffsetX = 0;
}
createWall(x, y, width, height, color, id = "") {
this.map.walls.push({
x: x,
y: y,
width: width,
height: height,
color: color,
id: id
});
}
}
var infoLevel = new Info_Level(64);
infoLevel.load(levels.level0.map);
var friction = 0.85;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var vWidth = window.innerWidth;
var vHeight = window.innerHeight;
var player = new Player(vWidth / 2 - 33, vHeight / 2 - 27.5, 75, 60, "#608060", "#204020", "basic");
var keysDown = [];
var enemies = [];
// enemies.push(new Enemy(0, 0, 75, 60, "#806060", "#402020", "basic"));
var draw = new Draw(ctx);
var fps = 60;
function resizeCanvas(canvasElement, width, height) {
vWidth = width;
vHeight = height;
canvasElement.width = vWidth;
canvasElement.height = vHeight;
}
resizeCanvas(canvas, window.innerWidth, window.innerHeight);
function updateTank(tank, isEnemy = false) {
if (tank.currentReloadTime >= 0) {
tank.currentReloadTime--;
}
if (tank.recoilX < 0) {
tank.recoilX += 0.5;
}
if (tank.x < 0) {
tank.x = 0;
}
if (tank.y < 0) {
tank.y = 0;
}
if (tank.x + tank.width > vWidth) {
tank.x = vWidth - tank.width;
}
if (tank.y + tank.height > vHeight) {
tank.y = vHeight - tank.height;
}
tank.velX *= friction;
tank.velY *= friction;
tank.x += tank.velX;
tank.y += tank.velY;
/* This is where the collision detection of the player and a wall happen. */
for (var i = 0; i < infoLevel.map.walls.length; i++) {
var wall = infoLevel.map.walls[i];
if (rectangleToRectangleCollision(tank, wall)) {
tank.velX = 0;
tank.velY = 0;
}
}
if (isEnemy == true) {
var enemyAV = Math.atan2((player.y + player.height / 2) - (tank.y + tank.height / 2), (player.x + player.width / 2) - (tank.x + tank.width / 2));
tank.gunAngle = enemyAV;
if (rectangleToRectangleCollision(player, tank) == false) {
tank.velX += Math.cos(enemyAV) * 0.3;
tank.velY += Math.sin(enemyAV) * 0.3;
}
if (tank.currentReloadTime <= 0) {
for (var i = 0; i < tank.weapons.length; i++) {
var gun = tank.weapons[i];
shootBullet(gun, tank);
}
tank.currentReloadTime = tank.reloadTime;
}
}
ctx.save();
ctx.translate(tank.x + tank.width / 2, tank.y + tank.height / 2);
ctx.rotate(Math.atan2(tank.velY / 2, tank.velX / 2));
draw.rectangle(-tank.width / 2, -tank.height / 2, tank.width, tank.height, 2, true, true, { fillStyle: tank.color, strokeStyle: tank.bc, lineWidth: tank.width / tank.height * 1.75 });
ctx.restore();
for (var i = 0; i < tank.bullets.length; i++) {
var bullet = tank.bullets[i];
bullet.x += bullet.velX;
bullet.y += bullet.velY;
draw.arc(bullet.x, bullet.y, bullet.radius, 0, 2 * Math.PI, true, true, { fillStyle: bullet.color, strokeStyle: bullet.bc, lineWidth: tank.width / tank.height * 1.75 });
}
for (var i = 0; i < tank.weapons.length; i++) {
var gun = tank.weapons[i];
if (gun.x < 0) {
gun.x += gun.width / 240;
}
ctx.save();
ctx.translate(tank.x + tank.width / 2, tank.y + tank.height / 2);
ctx.rotate(tank.gunAngle + gun.rotation);
draw.rectangle(gun.x, gun.y, gun.width, gun.height, 2, true, true, { fillStyle: gun.color, strokeStyle: gun.bc, lineWidth: tank.width / tank.height * 1.75 });
ctx.restore();
}
ctx.save();
ctx.translate(tank.x + tank.width / 2, tank.y + tank.height / 2);
ctx.rotate(tank.gunAngle);
draw.rectangle(-tank.width / 2 * 0.6 + tank.recoilX, -tank.height / 2 * 0.7, tank.width * 0.6, tank.height * 0.7, 2, true, true, { fillStyle: tank.color, strokeStyle: tank.bc, lineWidth: tank.width / tank.height * 1.75 });
ctx.restore();
}
function shootBullet(gun, tank) {
var shootS = new Audio("./assets/shoot.wav");
shootS.play();
gun.x -= gun.width / 12;
tank.recoilX = -tank.width / 16;
var rawVX = Math.cos(tank.gunAngle + gun.rotation);
var rawVY = Math.sin(tank.gunAngle + gun.rotation);
var velX = (rawVX + random(-0.02, 0.02)) * 5;
var velY = (rawVY + random(-0.02, 0.02)) * 5;
tank.bullets.push(new Bullet(tank.x + tank.width / 2 + (rawVX * (tank.width - (tank.height / 2))), tank.y + tank.height / 2 + (rawVY * (tank.width - (tank.height / 2))), gun.height / 2, "#ff0000", "#800000", tank, velX, velY));
}
function main() {
if (keysDown["w"]) {
player.velY -= player.acceleration;
}
if (keysDown["a"]) {
player.velX -= player.acceleration;
}
if (keysDown["s"]) {
player.velY += player.acceleration;
}
if (keysDown["d"]) {
player.velX += player.acceleration;
}
if (Mouse.pressed) {
if (player.currentReloadTime <= 0) {
for (var i = 0; i < player.weapons.length; i++) {
var gun = player.weapons[i];
shootBullet(gun, player);
}
player.currentReloadTime = player.reloadTime;
}
}
player.setSafeZone(player.x + player.width / 2 - 100, player.y + player.height / 2 - 100, 200, 200);
ctx.save();
draw.clear(0, 0, vWidth, vHeight);
updateTank(player, false);
for (var i = 0; i < enemies.length; i++) {
updateTank(enemies[i], true);
}
for (var i = 0; i < infoLevel.map.walls.length; i++) {
var wall = infoLevel.map.walls[i];
draw.rectangle(wall.x, wall.y, wall.width, wall.height, 0, true, false, { fillStyle: wall.color });
}
draw.text("Add Collision Resolution To These Black Boxes", 0, 10, true, true, { textBaseline: "top", textAlign: "left", fillStyle: "#ffffff", strokeStyle: "#000000", font: "Bold 30px Arial" });
ctx.restore();
}
window.onload = function () {
Mouse.init();
setInterval(main, 1000 / fps);
}
function rectangleToRectangleCollision(obj1, obj2) {
if (obj1.x + obj1.width > obj2.x && obj1.y + obj1.height > obj2.y
&& obj1.x < obj2.x + obj2.width && obj1.y < obj2.y + obj2.height) {
return true;
}
return false;
}
window.onresize = function () {
resizeCanvas(canvas, window.innerWidth, window.innerHeight);
}
document.addEventListener("keydown", (e) => {
keysDown[e.key] = true;
});
document.addEventListener("keyup", (e) => {
keysDown[e.key] = false;
});
document.addEventListener("mousemove", () => {
player.gunAngle = Math.atan2(Mouse.y - (player.y + player.height / 2), Mouse.x - (player.x + player.width / 2));
});
*, *:before, *:after {
font-family: roboto, Arial, Helvetica, sans-serif, system-ui;
padding: 0px 0px;
margin: 0px 0px;
box-sizing: border-box;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CD</title>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
注意:我没有使用任何 JavaScript 库或插件,因为我喜欢从头开始制作东西。