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:
- quite impractical – as someone also pointed out somewhere, it would require a lot of manual work
- 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)