How can i delete an external layout?

In Gdevelop there is a option to create external layout but there no option to delete it.

It might be the case only for me because of which I added it to a feature request. if there is a option to delete the external layout please show me.

I am currently making a endless runner game which uses about 25 external layout and is generating the level infinitely . While I have a timer so that the level doesn’t generate infinitely and cause the game to crash . but my problem is because the level generates forever and player is always going forward, the scene has way too many object copies left behind which causes the game to lag. I myself have found a way to solve this but if there was an option to delete a whole layout at once it would be way more handy.

i don’t think i explained it very well cause i am in a hurry but i hope you guys got the general idea.

Hi, there is the ‘destroy when outside of screen’- behavior that may be useful.

i know but that will cause other issues

Hello, Ayman_brr

I think you can use a condition to check the position of your instances and delete them when they go above a certain value. I made an example assuming that the player is running horizontally to the right and that the instances are accumulating to the left, but you change it according to how your game works:

If there are many different objects, it might be more interesting for you to add them in a group and use it in that condition.

To be specific to the original question. External layouts are for building prefab layouts of objects and then importing them into the scene via event logic.

Once imported, they are no longer part of that external layout nor are they addressable as such. To put it another way, you are crearing objects from an external layout into your scene. You are not adding an external layout itself into your scene.

If you are wanting to track objects created from an external layout, there are a few options:

As suggested above, you can create an object group in your external layout and add all of the objects to it. The object group will need to be global for this to show up in normal scenes, though. I’ve only ever needed to target objects specifically created from an external layout once, but this was the method I used.

Another option is that you add an object variable to each object (not instance), named something like “sourceLayout” and a text string of “mylayoutnamehere”, although this method will require you to target each type of object in the destination scene to check if it has the variable you care about.

Hey all,

I happened to want exactly this functionality, and turns out there is a lot of question on the forum asking for it.

Unfortunately, none of the two suggestions to handle this in a generic way seem to work for me as I think they are:

  1. quite impractical – as someone also pointed out somewhere, it would require a lot of manual work
  2. actually solving my use-case, where I want to use external layouts to represent stages in my game, that I can load and unload* – this should work regardless of the object type, that proposed solutions would not handle.

Turns out it is quite easy to implement one if we save the list of objects being created in memory and later just delete them via function call.

Here are 2 functions I implemented (pasting the code as each one has just one JavaScript event):

CreateObjectsFromLayoutTracked

// (1) update createObjectsFrom but this time return the array of created objects.
// This is taken directly from
// https://github.com/4ian/GDevelop/blob/ca9fe51/GDJS/Runtime/RuntimeInstanceContainer.ts#L243
function _createObjectsFromFixed(
    data /*: InstanceData[]*/,
    xPos /*: float*/,
    yPos /*: float*/,
    trackByPersistentUuid /*: boolean*/
) {
    let objects = [];
    for (let i = 0, len = data.length; i < len; ++i) {
        const instanceData = data[i];
        const objectName = instanceData.name;
        const newObject = runtimeScene.createObject(objectName);
        if (newObject !== null) {
            if (trackByPersistentUuid) {
                // Give the object the same persistentUuid as the instance, so that
                // it can be hot-reloaded.
                newObject.persistentUuid = instanceData.persistentUuid || null;
            }
            newObject.setPosition(instanceData.x + xPos, instanceData.y + yPos);
            newObject.setAngle(instanceData.angle);
            if (
                gdjs.RuntimeObject3D &&
                newObject instanceof gdjs.RuntimeObject3D
            ) {
                if (instanceData.z !== undefined) newObject.setZ(instanceData.z);
                if (instanceData.rotationX !== undefined)
                    newObject.setRotationX(instanceData.rotationX);
                if (instanceData.rotationY !== undefined)
                    newObject.setRotationY(instanceData.rotationY);
            }

            newObject.setZOrder(instanceData.zOrder);
            newObject.setLayer(instanceData.layer);
            newObject
                .getVariables()
                .initFrom(instanceData.initialVariables, true);
            newObject.extraInitializationFromInitialInstance(instanceData);
            objects.push(newObject);
        }
    }
    return objects;
}

// (2) Load the layout and create objects
// This is just a re-implementation of createObjectsFromExternalLayout that
// simply uses our function.
const externalLayout = eventsFunctionContext.getArgument('LayoutName');
const externalLayoutData = runtimeScene
    .getGame()
    .getExternalLayoutData(externalLayout);
if (externalLayoutData === null) {
    return;
}

// trackByPersistentUuid is set to false as we don't want external layouts
// instantiated at runtime to be hot-reloaded.
const xPos = eventsFunctionContext.getArgument('XPos');
const yPos = eventsFunctionContext.getArgument('YPos');
let newObjects = _createObjectsFromFixed(
    externalLayoutData.instances,
    xPos,
    yPos,
    /*trackByPersistentUuid=*/
    false
);

// (3) This is the actual logic to save the resulting objects into a memory.
if (gdjs._TrackedLayoutObjects === undefined) {
    gdjs._TrackedLayoutObjects = new Map();
}

if (!gdjs._TrackedLayoutObjects.has(externalLayout)) {
    gdjs._TrackedLayoutObjects.set(externalLayout, []);
}

gdjs._TrackedLayoutObjects.get(externalLayout).push(...newObjects);

DeleteObjectsFromLayoutTracked

if (gdjs._TrackedLayoutObjects === undefined) {
    return;
}

const externalLayout = eventsFunctionContext.getArgument('LayoutName');
if (!gdjs._TrackedLayoutObjects.has(externalLayout)) {
    return;
}
let objects = gdjs._TrackedLayoutObjects.get(externalLayout);
objects.forEach(function (el) {
    // Turns out it works just fine even if the object was already deleted earlier.
    el.deleteFromScene(runtimeScene);
});

That is all – one can put this into functions in a custom extension and use this to load layouts instead of the built-in one.

I may put this in an extension for everyone to use if I have some time later. We could also update the engine to return list of objects from existing API instead of void to make this even shorter.

Let me know if you have any questions.

Hope that helps,
Dawid

* Using scenes for levels is quite impractical in my opinion, as it would require copying events (also from external sheets, given they can be associated with only one scene)

3 Likes

Thanks for the post, and the potential solution for folks.

As a heads up, that’s not how external events work. They’re linked to a “default” scene to allow auto-population of scene object names or variable names. They can be used in a link event in any scene, otherwise FSMs would never work in most games (since most best practices around FSMs in GDevelop have you use external events for each state).

If your external events are object specific and in multiple scenes, the objects should be global (The point of global objects), but external events do not require any copying between scenes.

1 Like

Ah, yes of course, obviously one can use external events in many scenes as long as they only refer to the global objects.

But IIUC as soon as you reference an object that is local to the scene given “external events” are linked to, you cannot use it anywhere else. (I guess you can, I would assume events working on scene-local objects will simply be ignored).

Correct. Non global object based events would just not fire.

So I am not sure if you solved this, but a workaround that I have in my game which uses external layouts to build levels, is I run literally everything in the level from an external layout, and I reset the actual level whenever it changes to a uh, new level; then it reloads what it needs (randomly in my case) based on variables.
Mine randomly selects the ELs to add in, but you could easily add in variables for a set level system and still reload in when the level changes from say level 1 completion to level two beginning.
Hope thats helpful, if not, I tried. :smile: