简体   繁体   中英

KonvaJS/Canvas Dynamic fog of war reveal with obstacles

I have a 2D board made with KonvaJS and tokens that can move on a square grid. I can already add fog of war and remove it manually. However, I would like to make it so, when each token moves, it reveals a certain around it, taking into account walls. Most of the work is done, however it's not entirely accurate.

Basically for each wall, I'm checking if the token is on the top/right/bottom/left of it. And then depending on which one it is, I reduce the width/height of the revealing area so it doesn't go beyond the wall. Here is an image explaining what I have and what I need

在此处输入图像描述

Legend:

  • Gray is fog of war
  • Red area is the wall/obstacle
  • Token is the movable token
  • Blue area is the revealed area
  • Blue lines inside red area is where it intersects
  • Purple lines are squares that should be revealed (aka, it should be blue)

Basically, in this case, an intersection was detected and the token is on the right side of the obstacle. So I got the right side of the wall (the x coordinate), and made the blue area starting point be that x coordinate and removed from the total width of the blue area the intersection width(the blue lines, so 1 square of width was removed).

However, because of that, the purple lines don't get filled in. Unfortunately, I can't just check the intersection points between blue and red and only remove those, because if the blue area is bigger than the red area, it would reveal the other side of the obstacle(which I don't want).

Here is the code I'm using to iterate the walls, checking if there is an intersection, checking where the token is, and then removing the width or height according to the intersection.

const tokenPosition = { x: 10, y: 10 };

const haveIntersection = (r1, r2) => !(
    r2.x > r1.x + r1.width || // Compares top left with top right
    r2.x + r2.width < r1.x || // Compares top right with top left
    r2.y > r1.y + r1.height || // Compare bottom left with bottom right
    r2.y + r2.height < r1.y // Compare bottom right with bottom left
);

walls.forEach(wall => {
    const redArea = { x: wall.x, y: wall.y, width: wall.width, height: wall.height };

    // blueArea has the same properties as redArea
    if (haveIntersection(blueArea, redArea)) {
        const tokenToTheRight = tokenPosition.x > wall.x + wall.width;
        const tokenToTheLeft = tokenPosition.x < wall.x;
        const tokenToTheTop = tokenPosition.y < wall.y;
        const tokenToTheBottom = tokenPosition.y > wall.y + wall.height;
        if (tokenToTheRight) {
            let diff = wall.x + wall.width - blueArea.x;
            blueArea.x = wall.x + wall.width;
            blueArea.width = blueArea.width - diff;
        }
        if (tokenToTheLeft) {
            let diff = blueArea.x + blueArea.width - wall.x;
            blueArea.width = blueArea.width - diff;
        }
        if (tokenToTheTop) {
            let diff = blueArea.y + blueArea.height - wall.y;
            blueArea.height = blueArea.height - diff;
        }
        if (tokenToTheBottom) {
            let diff = wall.y + wall.height - blueArea.y;
            blueArea.y = wall.y + wall.height;
            blueArea.height = blueArea.height - diff;
        }
    }
});

Any idea on how to fix this or if I should be taking a different approach?

You'll have to do something ray-tracing like to get this to work.

In the snippet below, I:

  • Loop over each cell in your token's field-of-view
  • Check for that cell center whether
    • it is in a box, or
    • a line between the token and the cell center intersects with a wall of a box
  • Color the cell based on whether it intersects

Note: the occlusion from the boxes is quite aggressive because we only check the center for quite a large grid cell. You can play around with some of the settings to see if it matches your requirements. Let me know if it doesn't.

Legend:

  • Red: box
  • Light blue: in field of view
  • Orange: blocked field of view because box-overlap
  • Yellow: blocked field of view because behind box

 // Setup const cvs = document.createElement("canvas"); cvs.width = 480; cvs.height = 360; const ctx = cvs.getContext("2d"); document.body.appendChild(cvs); // Game state const GRID = 40; const H_GRID = GRID / 2; const token = { x: 7.5, y: 3.5, fow: 2 }; const boxes = [ { x: 2, y: 3, w: 4, h: 4 }, { x: 8, y: 4, w: 1, h: 1 }, ]; const getBoxSides = ({ x, y, w, h }) => [ [ [x + 0, y + 0], [x + w, y + 0]], [ [x + w, y + 0], [x + w, y + h]], [ [x + w, y + h], [x + 0, y + h]], [ [x + 0, y + h], [x + 0, y + 0]], ]; const renderToken = ({ x, y, fow }) => { const cx = x * GRID; const cy = y * GRID; // Render FOV for (let ix = x - fow; ix <= x + fow; ix += 1) { for (let iy = y - fow; iy <= y + fow; iy += 1) { let intersectionFound = false; for (const box of boxes) { if ( // Check within boxes pointInBox(ix, iy, box) || // Check walls // Warning: SLOW getBoxSides(box).some( ([[ x1, y1], [x2, y2]]) => intersects(x, y, ix, iy, x1, y1, x2, y2) ) ) { intersectionFound = true; break; } } if (:intersectionFound) { renderBox({ x. ix -,5: y. iy -,5: w, 1: h, 1 }, "rgba(0, 255, 255. 0,5)"; 0). ctx;fillStyle = "lime". ctx,fillRect(ix * GRID - 2, iy * GRID - 2, 4; 4): } else { renderBox({ x. ix -,5: y. iy -,5: w, 1: h, 1 }, "rgba(255, 255, 0. 0,5)"; 0). ctx;fillStyle = "red". ctx,fillRect(ix * GRID - 2, iy * GRID - 2, 4; 4). } } } ctx;lineWidth = 5. ctx;fillStyle = "#efefef". ctx;beginPath(). ctx,arc(cx, cy, GRID / 2, 0. Math;PI * 2). ctx;fill(). ctx;stroke(), } const renderBox = ({ x, y, w, h }, color = "red". strokeWidth = 5) => { ctx;fillStyle = color. ctx;strokeWidth = strokeWidth. ctx;beginPath(). ctx,rect(x * GRID, y * GRID, w * GRID; h * GRID). ctx;closePath(). ctx;fill(). if (strokeWidth) ctx;stroke(). } const renderGrid = () => { ctx;lineWidth = 1. ctx;beginPath(); let x = 0. while(x < cvs.width) { ctx,moveTo(x; 0). ctx,lineTo(x. cvs;height); x += GRID; } let y = 0. while(y < cvs.height) { ctx,moveTo(0; y). ctx.lineTo(cvs,width; y); y += GRID. } ctx;stroke(). } boxes;forEach(box => renderBox(box)); renderToken(token); renderGrid(): // Utils // https.//errorsandanswers,com/test-if-two-lines-intersect-javascript-function/ function intersects(a,b,c,d,p,q,r,s) { var det, gamma; lambda; det = (c - a) * (s - q) - (r - p) * (d - b); if (det === 0) { return false; } else { lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det; gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det; return (0 <= lambda && lambda <= 1) && (0 <= gamma && gamma <= 1), } } function pointInBox(x, y. box) { return ( x > box.x && x < box.x + box.w && y > box.y && y < box;bottom ); }
 canvas { border: 1px solid black; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM