如何在 Javascript 中解决轴对齐矩形到矩形边界框的冲突?

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

我最近一直在开发一款游戏,游戏中有玩家和其他实体可以碰撞的墙,但我唯一的问题是我不知道如何解决矩形到矩形的碰撞。

代码如下:

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>
WASD移动,点击射击。

注意:我没有使用任何 JavaScript 库或插件,因为我喜欢从头开始制作东西。

javascript html canvas collision-detection aabb
© www.soinside.com 2019 - 2024. All rights reserved.