NEW v4.0! [Guide and Resources] How to MASSIVELY improve performance in GDevelop with Object Culling! Making any Project Run Smoothly!

@MrMen
@erdo
@Points

New version is now up and running, tis way better!!! Even with objects culled you can still use their data and properties, like checking their position and such!!

This new version turned out amazing, i even added the feature to permanently cull objects, so that you can delete staying bullets without any events or logic.

Hope you guys enjoy!

4 Likes

That is great. Is the script useful if off-screen objects have to maintain behaviors, move, collide and so on? I imagine that everything that depends on the animation number, frame number and so on would have to be replaced by variables in that case, right?

1 Like

Sadly no, it dosent work for that. :frowning:

You can acess information on the object, but not collisions and such, i also have no idea about behaviors.

1 Like

EDIT Everything was fixed in the new version! 3.0 :smiley:

I just found an issue that ill have to fix tomorrow.

The script dosent recreate all the properties, i need to add size and scale to it as well, also z order as such.

I dont have any tiles sprites on my recent projects, so i tried it on a old project that did and the result isnt very good, tiles sprites or sprites that were previously changed in size in the editor get recreated with their original size, which is an issue…

Like i said, ill try to fix it tomorrow and add it in when its done :slight_smile:

3 Likes

New version is now out!! 3.0!!

  • The script now works perfectly with Tiled Sprites

  • Objects now get culled based on their Bounding Box and not their Origin Point, this ensures objects that are still in view but the Origin Point is not, dont get culled untill the entire object is out of view.

  • Recreating works the same, if the bounding box is in view, then the object is recreated.

  • More property handling to faithfully recreate objects, things like Size, Angle, Hidden/Showing and more was added.

  • Objects now get recreated with the same size they were culled, same for angles, or if the object was hidden, it will be set as hidden again.

  • All previous information is still being stored, like X and Y Position, to be called on and used even if the object is Culled.

I think at this point it does quite literally everything except for collision, and thats impossible without keeping the object in scene.

Every method i tried for collision while objects are culled does not work, at least for me, the performance gained is minimal, if any, completly deleting the object like my script does is the only way iv found of deastically improving performance.

2 Likes

Got a new shinny version 4.0!

The new version handles object timers, storing them and recreating while “sort of” keeping them running even when the object is culled.

What is actually happening is the timer is stored, then when it gets recreated, the elapsed time between cull and recreation is added to the object, essentially keeping it running.

If instead youd like the object timer to do things like “every x seconds add to a variable”, you can simple exclude the object that has that timer from being culled.

Version 4.0 will be put up tomorrow after i do some more testing and making sure it cant be optimized more! :slight_smile:

This has been fun but its getting close to the definitive version! :smiley:

If any of you has any feedback, it would be much appreciated!

2 Likes

I think you should make it an extension it would be great
Please sorry to say I got lost on the way this was exactly what my chatgpt app told me but I didn’t understand the process of culling still confused
Let me just go straight to the point I want to use this to make my Minecraft game performance better since it deals with so many blocks

2 Likes

You dont need permission :slight_smile: You can use it in your game!

All i ask is if you can, please give me feedback if its working for you, if theres anything wrong or if youd like any mod to it :smiley:

Also, i got a new version coming later that handles restoring object timers! I just need to make sure its optimized first

1 Like

Great :+1::+1:
Cuz this a huge leap in gdevelop now with your code my Minecraft game will be able to work on Low end devices

1 Like

Check back later for the updated script that handles timers! It might be helpful to you!

You can use this one fore now, and then later simply replace the script, its that simple to update :slight_smile:

1 Like

After a day of testing, version 4.0 is now up!

It does everything 3.0 did but it also includes handling Object Timers!!

Hope you guys enjoy! :smiley:

This is very good progress! Good job! Is there a possibility of it being included in Gdevelop? Maybe as an extension?

1 Like

If they want to make it into an extension they more then have my blessing for it i think it would be an awesome addition.

Specially since most of the work is pretty much done.

Reason im not making it into an extension and submitting it is because honestly, i have no clue how lol

Iv looked into it, but i dont know how to make in a flexible way where people can just add it to their projects and it will work just like it is now.

So if the devs want to have a go at it, its appreciated help :slight_smile:

1 Like

For anyone who wants to read on what exactly goes on in the script, heres all the info on how it works!

Version 4.0

Overview

This script is designed to improve the performance of a game made with GDevelop 5 by culling (removing) objects that are outside the camera view and then restoring them when they come back into view. This is achieved by storing the state of objects before they are culled and restoring their state when they are recreated.

Detailed Breakdown

Initialization and Setup

  1. Initialize Deleted Objects Storage: The script starts by checking if a property deletedObjects exists on runtimeScene. If it doesn’t exist, it initializes it as an empty object. This object will be used to store the state of culled objects.

    if (!runtimeScene.deletedObjects) {
        runtimeScene.deletedObjects = {};
    }
    
  2. Get Camera Information: The script fetches the camera position and size once. This is used to determine the visible area and calculate the boundaries for culling.

    const cameraLayer = runtimeScene.getLayer("");
    const cameraX = cameraLayer.getCameraX();
    const cameraY = cameraLayer.getCameraY();
    const cameraWidth = cameraLayer.getCameraWidth();
    const cameraHeight = cameraLayer.getCameraHeight();
    
  3. Get Cull Buffer: The script retrieves the CullBuffer scene variable. This variable is used to add a buffer zone around the camera’s visible area to prevent objects from being culled too aggressively.

    const cullBuffer = runtimeScene.getVariables().get("CullBuffer").getAsNumber();
    
  4. Calculate Culling Boundaries: The script calculates the left, right, top, and bottom boundaries for culling based on the camera position, size, and cull buffer.

    const leftBoundary = cameraX - cameraWidth / 2 - cullBuffer;
    const rightBoundary = cameraX + cameraWidth / 2 + cullBuffer;
    const topBoundary = cameraY - cameraHeight / 2 - cullBuffer;
    const bottomBoundary = cameraY + cameraHeight / 2 + cullBuffer;
    
  5. Get All Object Instances: The script retrieves all object instances in the scene.

    const allObjectsInstances = runtimeScene.getAdhocListOfAllInstances();
    

Processing Objects for Culling

  1. Set Up for Processing in Batches: To avoid spikes in CPU usage, the script processes objects in batches. It determines the start and end indices for the current batch based on a scene variable CullStartIndex.

    const maxObjectsPerFrame = 50; // Adjust this number based on performance needs
    const startIndex = runtimeScene.getVariables().has("CullStartIndex")
        ? runtimeScene.getVariables().get("CullStartIndex").getAsNumber()
        : 0;
    const endIndex = Math.min(startIndex + maxObjectsPerFrame, allObjectsInstances.length);
    
    runtimeScene.getVariables().get("CullStartIndex").setNumber(endIndex === allObjectsInstances.length ? 0 : endIndex);
    
  2. Get Current Time: The script gets the current scene time. This is used to track when objects are culled.

    const currentTime = runtimeScene.getTimeManager().getTimeFromStart() / 1000; // Time in seconds
    
  3. Iterate Over Objects: The script iterates over the objects in the current batch. For each object, it performs the following checks and actions:

    for (let i = startIndex; i < endIndex; i++) {
        const object = allObjectsInstances[i];
        const objectName = object.getName();
        const objectLayer = object.getLayer();
    
    • Skip Non-Base Layer Objects: Only objects on the base layer (default layer) are processed.

      if (objectLayer !== "") continue;
      
    • Skip Objects Excluded from Culling: If an object has a variable named ExcludeCull set to true, it is skipped.

      const variables = object.getVariables();
      if (variables.has("ExcludeCull") && variables.get("ExcludeCull").getAsBoolean()) {
          continue;
      }
      
    • Get Object Bounding Box: The script gets the object’s bounding box to check its position relative to the culling boundaries.

      const aabb = object.getAABB();
      const objectLeft = aabb.min[0];
      const objectRight = aabb.max[0];
      const objectTop = aabb.min[1];
      const objectBottom = aabb.max[1];
      
    • Check Culling Conditions: If the object is completely outside the culling boundaries, it is marked for deletion or storage and deletion.

      if (objectRight < leftBoundary || objectLeft > rightBoundary || objectBottom < topBoundary || objectTop > bottomBoundary) {
          if (variables.has("Cull") && variables.get("Cull").getAsBoolean()) {
              objectsToDelete.push(object);
          } else {
      
    
    - **Store Object Properties**: The script stores the object's properties (position, angle, layer, hidden state, variables, timers, and culling time) for later restoration.
    
        ```javascript
        if (!runtimeScene.deletedObjects[objectName]) {
            runtimeScene.deletedObjects[objectName] = [];
        }
    
        const objectProperties = {
            x: object.getX(),
            y: object.getY(),
            zOrder: object.getZOrder(),
            angle: object.getAngle(),
            layer: objectLayer,
            hidden: object.isHidden(),
            variables: {},
            timers: {},
            cullTime: currentTime
        };
    
        // Store object variables
        const objectVariablesList = variables._variables.items;
        for (const variableName in objectVariablesList) {
            if (objectVariablesList.hasOwnProperty(variableName)) {
                objectProperties.variables[variableName] = objectVariablesList[variableName].getAsString();
            }
        }
    
        // Store object timers
        if (object._timers && object._timers.items) {
            const timerItems = object._timers.items;
            for (const timerName in timerItems) {
                if (timerItems.hasOwnProperty(timerName)) {
                    objectProperties.timers[timerName] = timerItems[timerName].getTime() / 1000.0; // Store the timer value in seconds
                    console.log(`Stored timer ${timerName} for object ${objectName} with time ${objectProperties.timers[timerName]}`);
                }
            }
        } else {
            console.log(`Object ${objectName} does not support timers or has no timers.`);
        }
    
        runtimeScene.deletedObjects[objectName].push(objectProperties);
        objectsToStoreAndDelete.push(object);
    
  4. Delete Objects: The script deletes the objects that are marked for deletion.

    for (let i = 0; i < objectsToDelete.length; i++) {
        objectsToDelete[i].deleteFromScene(runtimeScene);
    }
    
    for (let i = 0; i < objectsToStoreAndDelete.length; i++) {
        objectsToStoreAndDelete[i].deleteFromScene(runtimeScene);
    }
    

Restoring Culled Objects

  1. Get Current Time for Restoration: The script gets the current scene time to calculate the elapsed time since the objects were culled.

    const restoreTime = runtimeScene.getTimeManager().getTimeFromStart() / 1000; // Time in seconds
    
  2. Iterate Over Stored Objects: The script iterates over the stored objects to recreate them if they are within the visible boundaries.

    const deletedObjects = runtimeScene.deletedObjects;
    for (let objectName in deletedObjects) {
        const objectList = deletedObjects[objectName];
        for (let i = objectList.length - 1; i >= 0; i--) {
            const data = objectList[i];
            const objectLeft = data.x;
            const objectRight = data.x + (data.width || 0);
            const objectTop = data.y;
            const objectBottom = data.y + (data.height || 0);
    
            if (objectRight >= leftBoundary && objectLeft <= rightBoundary && objectBottom >= topBoundary && objectTop <= bottomBoundary) {
    
  3. Calculate Elapsed Time: The script calculates the elapsed time since the object was culled.

    const elapsedTime = restoreTime - data.cullTime;
    
  4. Recreate Object and Restore Properties: The script recreates the object and restores its properties including position, angle, layer, hidden state, variables, and timers (adjusting for elapsed time).

    const newObject = runtimeScene.createObject(objectName);
    newObject.setPosition(data.x, data.y);
    newObject.setZOrder(data.zOrder);
    newObject.setAngle(data.angle);
    newObject.setLayer(data.layer);
    
    // Restore optional properties
    if (data.width !== undefined && newObject.setWidth) {
        newObject.setWidth(data.width);
    }
    if (data.height !== undefined && newObject.setHeight) {
        newObject.setHeight(data.height);
    }
    if (data.opacity !== undefined && newObject.setOpacity) {
        newObject.setOpacity(data.opacity);
    }
    
    // Restore hidden state
    if (data.hidden !== undefined) {
        newObject.hide(data.hidden);
    }
    
    // Restore variables
    const targetVariables = newObject.getVariables();
    const sourceVariables = data.variables;
    for (const variableName in sourceVariables) {
        if (sourceVariables.hasOwnProperty(variableName)) {
            targetVariables.get(variableName).setString(sourceVariables[variableName]);
        }
    }
    
    // Restore timers and add elapsed time
    const objectTimers = data.timers;
    for (const timerName in objectTimers) {
        const timerValue = objectTimers[timerName];
        newObject.resetTimer(timerName); // Reset the timer with the same name
        newObject._timers.get(timerName).setTime((timerValue + elapsedTime) * 1000); // Add elapsed time to the timer value
        console.log(`Restored timer ${timerName} for object ${objectName} with time ${timerValue + elapsedTime} seconds`);
    }
    
    // Remove from the list after recreation
    objectList.splice(i, 1);
    
  5. Clean Up Empty Lists: After recreating objects, the script cleans up any empty lists to free memory.

    if (objectList.length === 0) {
        delete deletedObjects[objectName];
    }
    

Summary of the Script

  1. Initialization:

    • Sets up a storage object for deleted objects (runtimeScene.deletedObjects).
    • Retrieves camera position and size.
    • Calculates culling boundaries based on camera size and a buffer.
  2. Processing Objects for Culling:

    • Processes objects in batches to avoid CPU spikes.
    • Skips objects on non-base layers and those marked as excluded from culling.
    • Checks if objects are outside the culling boundaries.
    • Stores properties of objects to be culled, including their position, variables, and timers.
    • Queues objects for deletion.
  3. Deleting Culled Objects:

    • Deletes objects that are outside the culling boundaries.
  4. Restoring Culled Objects:

    • Recreates objects that have moved back into the visible area.
    • Restores their properties, including position, variables, and timers (with elapsed time added).
  5. Clean Up:

    • Cleans up the storage to free memory by removing empty lists.

How Storing and Restoring Timers Works

  • Storing Timers:

    • Before deleting an object, the script captures its timers and stores them in runtimeScene.deletedObjects.
    • It stores the elapsed time of each timer in seconds.
  • Restoring Timers:

    • When recreating an object, the script restores its timers by resetting them and setting their time to the stored value plus the elapsed time since culling.

Conclusion

This script efficiently manages the culling and restoration of objects to improve game performance in GDevelop 5. By carefully storing and restoring object properties and timers, it ensures a smooth and consistent game experience without losing the state of objects that move out of and back into the camera’s view.

5 Likes

Great
I can try to make it into an extension
When I’m less busy
But I don’t know how to setup extension icon so it will just have the gdevelop icon
If am done turning it into an extension I’ll send it to you

1 Like

You might wanna wait, i just made a big mod to it!

It now handles save states too!

2 Likes

Great job!!!
Could you add the culling option following the layer in addition to the camera area?
And is it possible to change the position of deleted objects in memory?
Example: For a P2P game, the units of the different players are moving.

Thank :wink:

Can you explain this better? I dont get it, the camera is what follows the Layer

As for moving objects off camera, thats something i still need to work on but ill check what i can do :slight_smile:

Right now im working on a big updated with the save states thats gonna be awesome!

Ok
Bro this ur code is really helpful
Thnx


Great! You’re motivated :wink:
Be able to make a selection of objects to delete or spare, depending on the layer.
Ex: The 2 Fire objects are on a different layer.

1 Like