Developing a custom camera system for a top-down RPG game using Javascript Canvas

What specific question do I have to ask now? My goal is to implement a "viewport" camera effect that will track the player without moving the background

I am integrating websocket support and planning to render additional characters on the map - movement needs to be based on the player's actions rather than the map itself so that I can update other players' positions accurately.

Prior post:

I've read through numerous posts related to this topic. It appears that everyone faces unique challenges in their individual implementation of the "Canvas topdown game". This makes it quite difficult to find a solution tailored to your issue without restructuring your entire codebase.

Hopefully, you guys can provide some assistance this time around.

The game so far: developed using Vue3 by the way but the majority of the code is vanilla JavaScript.

This involves a basic topdown map with X and Y axes

In my approach, I'm taking a PNG file created with the Tiled application

Enlarged by 400% - then drawing it using ctx.drawImage

Next, setting up boundaries

and eventually drawing the player character

Currently, I am including a "force" value for the x and y position based on key presses (WASD) This allows the character to move on the map and ensures collision detection functions properly My aim is for the map to draw a certain number of pixels determined by the player's position WITHOUT moving the map as I'm already incorporating SocketIO for multiplayer functionality

I'm quite lost here, unsure about the steps needed to create a viewport within the map....

Hope some of this resonates with you

Here's a snippet of the code

 // Code block goes here...

Answer №1

As there is no runnable snippet provided for reference, here is a basic example demonstrating the usage of transformation methods in CanvasRenderingContext2D to alter the scene's viewport.

Within the draw function:

  • Reset the transform to set the center of the <canvas> as (0, 0) using the setTransform method
  • To draw from the player's perspective:
    • Adjust the context by translating it by -x and -y multiplied by the zoom factor
    • Scale the canvas based on the zoom factor

// Initialization
const cvs = document.createElement("canvas");
cvs.width = 200;
cvs.height = 200;
const ctx = cvs.getContext("2d");

// State setup
const players = [
  [-20, -30, "green"],
  [50, 70, "blue"]
];

let viewport = {
  x: 0,
  y: 0,
  zoom: 1
};

// Drawing functions
const clear = () => {
  ctx.fillStyle = "white";
  ctx.fillRect(-cvs.width / 2, -cvs.height / 2, cvs.width, cvs.height);
};

const drawMap = () => {
  const w = cvs.width - 25;
  const h = cvs.height - 25;
  
  ctx.fillStyle = "#efefef";
  ctx.fillRect(-w / 2, -h / 2, w, h);
}

const drawPlayers = () => {
  for (const [x, y, color] of players) {
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(x, y, 10, Math.PI * 2, 0);
    ctx.closePath();
    ctx.fill();
  }
}

const drawCenter = () => {
  ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
  ctx.fillRect(-1, -15, 2, 30);
  ctx.fillRect(-15, -1, 30, 2);
}

const draw = () => {
  // Zoom out and set (0, 0) at canvas center
  ctx.setTransform(1, 0, 0, 1, cvs.width / 2, cvs.height / 2);
  clear();
  
  const { zoom, x, y } = viewport;
  ctx.translate(-x * zoom, -y * zoom);
  ctx.scale(zoom, zoom);
  
  drawMap();
  drawPlayers();
  
  // For debugging: mark the center of the canvas
  ctx.setTransform(1, 0, 0, 1, cvs.width / 2, cvs.height / 2);
  drawCenter();
}

draw();

document.body.appendChild(cvs);

// UI functionality
const updateViewport = () => {
  const centerValue = document.querySelector("input[name=center]:checked").value;
  
  const [x, y] = players[centerValue] || [0, 0];
  const zoom = document.querySelector("input[type=range]").valueAsNumber;
  
  viewport = { x: x, y: y, zoom };
  draw();
};

document.querySelectorAll("input").forEach(el => {
  el.addEventListener("input", updateViewport);
});
body { display: flex; }
canvas { border: 1px solid red; position: relative; }
fieldset { margin-bottom: 1rem; }
<div> 
  <fieldset>
    <legend>View center:</legend>
    <label>
      <input type="radio" value="-1" name="center" checked> Map
    </label>

    <label>
      <input type="radio" value="0" name="center"> Green player
    </label>

    <label>
      <input type="radio" value="1" name="center"> Blue player
    </label>
  </fieldset>
  
  <fieldset>
    <legend>Zoom</legend> 
    <input type="range" min="0.1" max ="3" step="0.1" value="1">
  </fieldset>
  
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to locate module in node_modules directory when attempting to import an image into TS

In my .tsx file, I am encountering an issue with the following code snippet: import L from 'leaflet'; import icon from 'leaflet/dist/images/marker-icon.png'; import iconShadow from 'leaflet/dist/images/marker-shadow.png'; The ...

No issues raised by Typescript/tslint regarding this in arrow function

After making some creative adjustments, this is what I came up with: const TopBar = () => ( <Button onPress={this.onPress} // No errors shown /> ) Although all my other rules in tslint.json are functioning properly. Is there a way to ma ...

A function that retrieves an array containing each individual element from a multi-dimensional array

In my system, I have two essential objects: Order and ProductOrder. Order Object: { id:number; productOrders: ProductOrder[]; } ProductOrder object: { id: number; productName: string; } Currently, I store an array of Order objects in a variable called o ...

PHP function with JSON response encountered an error during the AJAX call

I am currently working on creating a News Ticker that utilizes PHP, Javascript, and AJAX. The first step involved creating a PHP function called getFeed(), which gathers data from various news websites into an Array. This data is then returned in JSON form ...

Determine the worth of various object attributes and delete them from the list

Currently, my dataset is structured like this: { roof: 'black', door: 'white', windows: 8 }, { roof: 'red', door: 'green', windows: 2 }, { roof: 'black', door: 'green', windows: ...

Error message "ag-grid: Unable to perform the key.forEach function in the console when resizing columns"

Within the application I am working on, I have implemented the ag-grid view. To address the issue related to the last empty pseudo column, I decided to resize the last displayed column using the 'autoSizeColumns' method of ag-grid. While this sol ...

How to set the element in the render method in Backbone?

Currently, I am in the process of developing a web page utilizing BackboneJS. The structure of the HTML page consists of two divs acting as columns where each item is supposed to be displayed in either the first or second column. However, I am facing an is ...

performing a request to Axios API with the inclusion of ${item} within the URL

I am having trouble with calling axios in Vuetify due to backticks and ${} within the URL. I believe I need to convert it into JSON format, but my attempts have been unsuccessful. Could you please provide an explanation? Here is the code snippet. Whenever ...

Create an array of routes specifically for private access using Private Route

In my application, I have defined different routes for admins, employees, and clients. Admins can access routes /x, /y, and /z, employees can access routes /a and /b, and everyone including clients can access 4 other routes. I am trying to implement this l ...

What is the process for incorporating an external script into my Vue methods?

After a user registers, I need to send them a confirmation email using vue.js. I am looking to implement the script provided by . How can I incorporate the "Email.send" method into my vue.js application? <script src="https://smtpjs.com/v3/smtp.js"> ...

How do I access and read a map within an array from Firebase using ReactJS?

I have an array that contains a map with two values: title and content. https://i.stack.imgur.com/WLWVG.png I am trying to read all the values in the array as if it were a map. However, I have attempted this code without success. Can anyone assist me? {d ...

There is a lack of 'Access-Control-Allow-Origin' header, resulting in no access to the API

Having some trouble with the UK Parliament API, I keep encountering this error: XMLHttpRequest cannot load . No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.com' is therefore not a ...

What is the best way to revert a screen's state back to its original state while utilizing react navigation?

How can I reset the state in a functional component back to its initial state when navigating using navigation.navigate()? For example, if a user navigates to screen A, sets some state, then clicks a button and navigates to screen B, and then goes back to ...

Puppeteer App Error: An error has been detected on the client side

I am facing an issue using Puppeteer with NEXT.JS while attempting to capture a screenshot. Everything runs smoothly on localhost, but in production, the captured image comes back with the following error message: Application error - a client-side exceptio ...

Getting a URL path in Next.js without relying on the Link component when the basePath is configured

Base Path from the next.js documentation states: For instance, by setting basePath to /docs, /about will automatically transform into /docs/about. export default function HomePage() { return ( <> <Link href="/about"> ...

JavaScript promises do not guarantee the execution of the loop

There was a block of Javascript code that I needed to modify. Initially, the code looked like this: if(!this.top3){ const promises = this.images.map((el, index) => { return this.getData(el.title, index); }); return Promise.all(promise ...

Tips for obtaining a specific sorting order based on a wildcard property name

Here's the structure of my JSON object, and I need to sort it based on properties starting with sort_ { "sort_11832": "1", "productsId": [ "11832", "160", "180" ], "sort_160": "0", "sort_180": " ...

Passing arguments with $emit - Vue

Here is a simple method to handle alerts using $emit. But when passing arguments, it seems like the event is not being triggered at all. The goal is to update the value of alert with the result. Listening for the event on mount: this.$eventHub.$on(' ...

Exploring the Integration of Node Modules with React

I am still learning about Node and I'm struggling to integrate an NPM package with my React project running on Node. Currently, I have a React component that successfully uploads a zip file through Node server scripts. Now, I am working on the next s ...

Share a status on Facebook with an earlier date

How to Modify Facebook Post Date using Graph API facebook facebook-graph-api I am able to publish on my wall using the Graph API. By utilizing FB nod and graph, I now need to post on Facebook with a date from the past Afterwards, I can adjust the date man ...