What is the best way to organize objects in a 2D game?

I am currently working on a unique perspective-based 2D/3D game using javascript.

In the image below, you can see the X and Y-axis that I have implemented for this project.


My question: On the map, I have several objects labeled as "1" and "2" with properties such as:

  • positionX / positionY
  • sizeX / sizeY

Object "1" is located at coordinates x:3, y:2, while Object "2" is at x:5, y:4. Both objects have a size of w:1, h:1.

My goal is to sort these objects in ascending order based on their positions and sizes, so that in the 3D view, I know which objects come in front of others. This will help me draw all objects in the correct order on the canvas, creating layers in the foreground and background.


https://i.sstatic.net/c6Zw6.png

Note: The camera has a fixed position, meaning the X and Y values of the camera are identical. Therefore, the camera position must not be factored in while calculating CameraX = CameraY.

This is what I have tried so far:

let objects = [
  {
    name: "objectA",
    x: 8,
    y: 12,
    w: 2,
    h: 2
  }, 
  {
    name: "objectB",
    x: 3,
    y: 5,
    w: 2,
    h: 2
  },
  {
    name: "objectC",
    x: 6,
    y: 2,
    w: 1,
    h: 3
  }
] 

let sortObjects = (objects) => {
  return objects.sort((a, b)=> {
    let distanceA = Math.sqrt(a.x**2 + a.y**2);
    let distanceB = Math.sqrt(b.x**2 + b.y**2);
    return distanceA - distanceB;
  });
}

let sortedObjects = sortObjects(objects);
console.log(sortedObjects);

// NOTE in 3d: objects are drawn in the order they appear in the sorted array...

Edit to the snippet above:

I attempted to sort the objects based on their x/y coordinates, but it seems that the width and height parameters also need to be considered to avoid errors.

How should I incorporate width/height into the calculations? I am unsure about this aspect, so any guidance would be greatly appreciated.

Answer №1

I'm uncertain about your intention behind:

Keep in mind: The Camera is in a fixed position - let's assume the camera has the same X and Y values so that the camera position must not be factored in while calculating CameraX = CameraY.

Therefore, here is a solution for a general case.

The key is to arrange the objects based on their closest distance to the camera. This factor depends on the object's dimensions and its relative position.

The JavaScript algorithm for this can be written as below:

// If, for instance, the horizontal distance is greater than the width / 2, deduct width / 2; same applies for vertical
let distClamp = (dim, diff) => {
    let dist = Math.abs(diff);
    return (dist > 0.5 * dim) ? (dist - 0.5 * dim) : dist;
}

// Determining the closest distance to the camera
let closestDistance = (obj, cam) => {
    let dx = distClamp(obj.width, obj.x - cam.x);
    let dy = distClamp(obj.height, obj.y - cam.y);
    return Math.sqrt(dx * dx + dy * dy);
}

// Sorting based on this metric
let sortObject = (objects, camera) => {
    return objects.sort((a, b) => {
        return closestDistance(a, camera) - closestDistance(b, camera);
    });
}

UPDATE this solution may not be entirely accurate due to oversimplified assumptions, will revise or remove shortly.

Answer №2

Alright, let's tackle this challenge! Instead of ordering objects by distance, we'll arrange them based on obstruction. This means that for objects A and B, A can obstruct B, B can obstruct A, or neither object obstructs the other. To determine the order for drawing, we must establish whether A obstructs B or vice-versa.

Here's the approach I've developed. Although I've had limited testing ability, the logic behind the process seems solid.

Step 1. Associate each object with its boundaries and store the original object for later reference:

let step1 = objects.map(o => ({
  original: o,
  xmin: o.x,
  xmax: o.x + o.w,
  ymin: o.y,
  ymax: o.y + o.h
}));

Step 2. Determine the two corners of each object that create the largest obstruction to the camera's field of view when connected by a line:

let step2 = step1.map(o => {
  const [closestX, farthestX] = [o.xmin, o.xmax].sort((a, b) => Math.abs(camera.x - a) - Math.abs(camera.x - b));
  const [closestY, farthestY] = [o.ymin, o.ymax].sort((a, b) => Math.abs(camera.y - a) - Math.abs(camera.y - b));

  return {
    original: o.original,
    x1: closestX,
    y1: o.xmin <= camera.x && camera.x <= o.xmax ? closestY : farthestY,
    x2: o.ymin <= camera.y && camera.y <= o.ymax ? closestX : farthestX,
    y2: closestY
  };
});

Step 3. Arrange the objects by checking for intersection between line segments drawn from the camera to each endpoint of one object and the other object. The object with intersecting line segments is closer and should be drawn after.

let step3 = step2.sort((a, b) => {
  const camSegmentA1 = {
    x1: camera.x,
    y1: camera.y,
    x2: a.x1,
    y2: a.y1
  };
  const camSegmentA2 = {
    x1: camera.x,
    y1: camera.y,
    x2: a.x2,
    y2: a.y2
  };
  const camSegmentB1 = {
    x1: camera.x,
    y1: camera.y,
    x2: b.x1,
    y2: b.y1
  };
  const camSegmentB2 = {
    x1: camera.x,
    y1: camera.y,
    x2: b.x2,
    y2: b.y2
  };

  function intersects(seg1, seg2) {
    // Intersection logic here
  }

  function squaredDistance(pointA, pointB) {
    // Distance calculation logic here
  }

  // Intersection checks and comparison logic here
});

Step 4. Retrieve the original objects sorted from farthest to closest:

let results = step3.map(o => o.original);

Combining all the steps:

results = objects.map(o => {
  // Mapping and sorting logic here
}).sort((a, b) => {
  // More sorting logic here
}).map(o => o.original);

Feel free to test it out and let me know if it meets your requirements!

Answer №3

The problem lies in the use of the euclidean distance to calculate the distance of an object from the point (0, 0) instead of measuring the distance from the line y = -x. To achieve this, utilizing Manhattan distance is the appropriate approach.

const orderObjects = (objects) => {
  return objects.sort((a, b) => {
    let distanceA = a.x + a.y;
    let distanceB = b.x + b.y;
    return distanceA - distanceB;
  });
}

Implementing this function will organize the objects vertically within your rotated coordinate system.

Answer №4

Take into account the sum of x and y in every cell of the illustration.

https://i.sstatic.net/1jnsk.png

If you want to arrange the cells from top to bottom, just sort them based on the value of x+y.

Answer №5

Perhaps you'll uncover some valuable information here (best with Firefox & explore the DEMO)

For me, the concept of depth boils down to pos.x + pos.y [in assetHelper.js -> get depth() {...] just like the initial response details. The sorting process is then a straightforward comparison [in canvasRenderer -> depthSortAssets() {...]

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

Guide to emphasizing text with hover effects and excluding whitespace (javascript)

Currently, I am utilizing jQuery for hover highlighting purposes. However, I am facing an issue where the entire div gets highlighted instead of just the text. I attempted to use an "a tag" but don't want a reference link. I know this should be a simp ...

What is the best way to create a seamless Asynchronous loop?

Adhering to the traditional REST standards, I have divided my resources into separate endpoints and calls. The primary focus here revolves around two main objects: List and Item (with a list containing items as well as additional associated data). For ins ...

Tips for reading user input in a terminal using node.js

Before deploying my code to the server, I'm looking to conduct some local node testing. How can I take terminal input and use it as an input for my JavaScript script? Is there a specific method like readline that I should be using? ...

Guide to: Implementing Radio Buttons to Adjust Image Opacity

How can I dynamically change the opacity of an image using JavaScript when a radio button is clicked? I attempted to achieve this using a forEach function, but the current code causes all images to instantly switch to opacity 1; Additionally, after refre ...

Issue: AngularJS Injector Module Error

Recently started learning angularjs and trying to set up an app. Here's a simple and direct way to do it: Angular controller: (function () { 'use strict'; angular .module('myQuotesApp') .controller(' ...

Finding the most recent instance of a deeply buried value based on a specific property

I have an example object: const obj = { group: { data: { data: [ { id: null, value: 'someValue', data: 'someData' } ...

Utilizing ng-switch for handling null values or empty strings

Can anyone assist me with this issue? I have a variable called $rootScope.user = {name:"abc",password:"asd"}. When a service response occurs, I am dynamically creating a rootscope variable by assigning it. How can I use ng-switch to check if the variable h ...

Regular Expressions in JavaScript can be used to find and extract text that comes after a specific word until the end of the line

Looking to develop a regular expression to parse a document within a Node.js application. The regex I have generated successfully identifies everything on a line following a certain word. However, I am struggling to figure out how to avoid including the sp ...

Run a function once two or more ajax requests have been successfully completed

Is there a way to perform two separate ajax requests independently and have a function executed only after both of them succeed? Instead of sending one request after the other, is there a way to execute them separately and still trigger the function only w ...

The dropdown menu vanishes from sight as soon as the cursor moves away from a link

Recently, I encountered an issue while trying to create a dropdown menu using Jquery. The problem arose when attempting to select the second link, as the entire menu would disappear. Additionally, is there a way to ensure that only one dropdown menu is vis ...

Error: Unable to access the 'center' property of an undefined value in the three.module.js file

I started delving into coding a few months back, with my focus on mastering three.js/react. Currently, I am engrossed in a tutorial located at [https://redstapler.co/three-js-realistic-rain-tutorial/], where I aim to create a lifelike rain background. The ...

The Bootstrap slide function encounters issues within the AJAX success callback

Here's the code snippet with a 'slide' event inside an AJAX success call. success: function(data) { $('#my_container').html(data); // Successfully loading #mycarousel into #my_container $('#mycarousel').bind( ...

Error displaying component with react-router v5 not found

Currently, I am immersed in developing interactive dashboard web applications using ReactJS. I am utilizing React Routing to seamlessly navigate through the website's content. Here is a snapshot of my web application: https://i.sstatic.net/Ye8bH.png ...

Creating a Node.js application using Express requires careful consideration of file structure and communication methods

Struggling to pass login form credentials to existing JavaScript files for logic and info retrieval. Login page contains a form with POST method. main.js handles the login: main.js module.exports = { returnSessionToken: function(success, error) { ...

Struggling with the compilation of this Typescript code

Encountering a compile error: error TS2339: Property 'waitForElementVisible' does not exist on type 'signinPage' SigninPage code snippet: export class signinPage{ constructor(){ emailInput: { selector: 'input[type ...

Is there a way to dynamically insert my own <divs> into a container?

I've been doing a lot of research on this issue, but I haven't come across any solutions that have been helpful so far. The problem I'm facing is that I have a div with an id that is supposed to act as a container (#cont_seguim). On the rig ...

Obtain the count of unique key-value pairs represented in an object

I received this response from the server: https://i.stack.imgur.com/TvpTP.png My goal is to obtain the unique key along with its occurrence count in the following format: 0:{"name":"physics 1","count":2} 1:{"name":"chem 1","count":6} I have already rev ...

When iPhone images are uploaded, they may appear sideways due to the embedded exif data

When trying to upload images from a mobile device like an iPhone, it's common for the images to appear sideways unless viewed directly in Chrome. It seems that this issue is related to the image exif orientation data, which Chrome can ignore but othe ...

What is the best way to save the content of an RFC822 message body as a String?

let inbox = require("inbox"); let client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ user: "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="99f4e0fcf4f8f0f5d9fef4f8f0f5b7fa ...

The issue with React Testing using Jest: The domain option is necessary

I've recently developed a React app that implements Auth0 for Authentication. Right now, I'm working on setting up a test suite using Jest to test a specific function within a component. The function I want to test is createProject() in a React ...