Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any tips for how to generate a minimap client-side? #117

Closed
Jarred-Sumner opened this issue Jun 22, 2020 · 4 comments
Closed

Any tips for how to generate a minimap client-side? #117

Jarred-Sumner opened this issue Jun 22, 2020 · 4 comments

Comments

@Jarred-Sumner
Copy link
Contributor

Jarred-Sumner commented Jun 22, 2020

I've been trying to add a minimap to the game for the past few days. My current setup is as follows:

  • Individual blocks within a chunk are represented as single colors.
  • While loading a chunk, it gets the topmost color for a column (x,z pair). These colors are provided at build time.
  • AWorldMap class has two dictionaries, one for the height of a column and one for the color of a column.
  • After loading the chunk, it updates the color in the WorldMap class for the x,z pair
  • A MinimapRender class gets the current chunk and steps back 5 chunks and forward 5 chunks for both x and z

Is this...a reasonable way to do it?

The problems I'm running into are:

  • The player position as displayed on the minimap is wrong. I think my code for finding the current position of the player within the chunk is wrong?
  • The center position on the minimap is wrong
  • I think the chunk images need to be rotated? That's what it looks like, but I don't understand why.

Here's a more visual explanation. You can click on it to make the image bigger
Frame 92

This is what the code for actually drawing the chunk images looks like:

if (!this.position) {
  return;
}
this.ctx.fillStyle = "#ccc";
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);

const noa = this.loader.noa;
const {
  _renderPosition: renderPosition,
  position,
} = noa.entities.getPositionData(noa.playerEntity);

const cs = 32.0;

const globalX = position[0]; //renderPosition[0] + noa.worldOriginOffset[0];
const globalY = position[1]; //renderPosition[1] + noa.worldOriginOffset[1];
const globalZ = position[2]; //renderPosition[2] + noa.worldOriginOffset[2];

const currentChunkZ = globalZ / cs;
const currentChunkX = globalX / cs;

this.minChunkX = Math.floor(currentChunkX - MinimapRender.squareLength);
this.minChunkZ = Math.floor(currentChunkZ - MinimapRender.squareLength);
this.maxChunkX = Math.ceil(currentChunkX + MinimapRender.squareLength);
this.maxChunkZ = Math.ceil(currentChunkZ + MinimapRender.squareLength);

const translateX = ((globalX % cs) + cs) % cs;
const translateZ = ((globalZ % cs) + cs) % cs;

const { minChunkX, maxChunkX, minChunkZ, maxChunkZ } = this;

let chunkXOffset = -1;
let chunkZOffset = -1;

const drawXOffset = 0;
const drawYOffset = 0;

for (let chunkX = minChunkX; chunkX < maxChunkX; chunkX++) {
  chunkXOffset++;
  chunkZOffset = -1;

  for (let chunkZ = minChunkZ; chunkZ < maxChunkZ; chunkZ++) {
    chunkZOffset++;

    const imageData = this.worldMap.imageAt(
      Math.round(chunkX),
      Math.round(chunkZ)
    );

    if (!imageData) {
      continue;
    }

    let x = chunkXOffset * imageData.width - drawXOffset;
    let y = chunkZOffset * imageData.height - drawYOffset;


    this.ctx.putImageData(imageData, x, y);

    if (
      Math.floor(currentChunkZ) === Math.floor(chunkX) &&
      Math.floor(currentChunkZ) === Math.floor(chunkZ)
    ) {
      this.ctx.fillStyle = "pink";
      this.ctx.strokeStyle = "red";
      const r = 4;
      this.ctx.beginPath();
      this.ctx.arc(x, y, r, 0, 359);
      this.ctx.font = "10px Arial";
      this.ctx.strokeText(
        `${Math.floor(globalX)},${Math.floor(globalY)},${Math.floor(
          globalZ
        )}`,
        x,
        y
      );
      this.ctx.fill();
      this.ctx.closePath();
    }

  }

}

this.ctx.canvas.style["transform"] = `translateX(${
  (MINIMAP_SIZE - MINIMAP_WIDTH) / -2
}px) translateX(${
  (MINIMAP_SIZE - MINIMAP_HEIGHT) / -2
}px) translateX(-${translateX}px) translateY(-${translateZ}px)`;

The pink dot / text is just for debugging

@fenomas
Copy link
Owner

fenomas commented Jun 22, 2020

This is a cool idea! The noa-related code (e.g. how you're getting the player position) looks right to me. It's a bit hard to grok the rest, but I'm guessing there's just a math bug somewhere in those offsets and modulos. (and maybe the fact that in Canvas the Y coord runs from top to bottom?)

That said:

      Math.floor(currentChunkZ) === Math.floor(chunkX) &&
      //                     ^ X?
      Math.floor(currentChunkZ) === Math.floor(chunkZ)

@Jarred-Sumner
Copy link
Contributor Author

Thanks for taking a look – it was a few math things.

Plus, the axis was wrong for getting the colors. I was setting all the x to z and all the z to x.

Framerate is a little too low now on Safari though and I optimized a lot of this code already. I hate Safari.

This is what it looks like now. I'm still going to add a few things (location name) and a full-screen map when you press M, but I'm happy with it so far. I'd rather a more Fortnite/World of Warcraft-style map design than a pixel art map design....but that seems much more time consuming to code

minimap

It'd be slightly better if I fix the occasional missing frame at the bottom but that's a thing I can fix later

@fenomas
Copy link
Owner

fenomas commented Jun 24, 2020

Hey, this looks really cool. Are you constructing the whole image each tick? The low-hanging fruit optimization would be for each chunk to render its own 32x32 image, cached and only periodically recreated, and the main loop would just take those N images and composite them to the right place on the canvas. This would also make it relatively easy to make the map rotate with the player if you want (just rotate them before drawing them into the canvas). Anyway cool stuff!

@Jarred-Sumner
Copy link
Contributor Author

Jarred-Sumner commented Jun 24, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants