Global Camera System

In the end I created my extension called Global Camera Controller, but I don’t want to submit it to GDevelop right now, because I want people to test it out, so the final extension won’t have any bugs.

Right now the extension has these features:

  • Follow an object on ALL layers simultaneously
  • Follow a specific world position (X, Y) on ALL layers
  • Set a global zoom level across all layers (with per‑layer exclusion)
  • Configure per-layer parallax scale (X/Y) to create depth effects or lock certain layers in place
  • Exclude specific layers from global zoom (useful for UI, HUD, etc.)
3 Likes

I would suggest making itch page and uploading there as well any other extensions you have/will make

Not cause you should but cause even if it will be added to gdevelop repository
Then it will be easier for other users to find all extensions you made
And we do tend to believe that if someone had one good idea then for sure he had more

For example

But check what you will find if you browse his stuff on itch
https://eldritchtentacles.itch.io

1 Like

I will upload it to itch.io when I will be less lazy about it :sleeping:

2 Likes

While I understand the request, I don’t think every game wants the camera to follow something by default… at least, that’s what I believe.

So if you want the camera to automatically follow all layers, you’d end up forcing someone else to set a layer’s camera follow by 0%.
Why should they have to do that?

Just this,…speaking for myself, as long as it can be done…and it WORKS…i’m fine.

For example, I find Unreal’s Blueprints disorienting (even though I’ve made mods for some games), I used GameMaker for a while and didn’t like GML, I tried Unity and made a complete mess with animations (even though I think it’s well designed, I just can’t use it), and so on.

The perfect engine just doesn’t exist… you have to find a middle ground.
Every engine has its own developers and its own way of seeing things.

Your request should stay and can be considered, but I wouldn’t compare GD to Construct or Godot to GD and vice versa… some limitations exist in all of them, depending on the user’s point of view…and if you ask me…personally i prefer Godot, but i’m too lazy to learn GD script. :sweat_smile:

The same applies to what to remove…but in that case is much more difficult.What I said about AI?.

EDIT: and thx about the extension, someone will surely find it useful.

Excellent observation
Now let’s ask very stupid question
More games will want to follow something by default
Or more games would not?

I get so used to what we have i don’t really care
But at the same time i would always assume something being default or not should be dictated by use case ratio

Yeah, who knows?..

I’m in favor of having the tools needed to do it, and having nothing set by default… in this case.

The answer is very simple — it’s easier and faster to exclude each layer, than include each one.
That’s why it should be by default. That’s it.

And even if there a some sorta situation where person for some reason wants every layer to be fixed and only one layer to move with camera, it’s a minority, I even can’t imagine the example of this.

The thing is that usually layers that should be fixed are UI and Pause layers, usually these types of layers less in amount than “game” layers such as background1, background2, background3, entity, light layer, foreground, etc

In fact, I’ve actually upvoted it before commenting because I like the idea.

BUT, I prefer anything that doesn’t force to do things (someone here knows that), and that’s why I was saying that as long as I can do it myself, it’s fine for me.

1 Like

It’s just an opt-in or opt-out approach.

And GDevelop isn’t consistent with it, for example GDevelop forces in each new layer 3D effects and there are no settings to change the default behavior of it.

So if you want to create only 2D game, with only 2D effects and don’t want odd 3D mess in it, that makes visual clarity of layer properties look bad and messy, you will delete each 3D effect on each new layer. And for example if you have some sorta OCD, you will be annoyed by it, deeply in your soul.

Also, about comparing C3 and GDevelop, it should be compared, to make GD better than C3. Some features like extensions are much better in GD, support is much better, but still we have not very consistent UX (in some places it’s very good, in some it’s a nightmare), worse performance, worse and outdated audio system, outdated render engine that doesn’t support multiple blend modes. If people will be silent about it and tell to not compare game engines, it will stagnate.

1 Like

You can use this code: it moves every layer together, except UI.
Keep it always active, with no conditions. It works!

I don’t know JavaScript, so I asked GPT-5.2 (Plus) for it — sorry! :laughing:

// === Camera Sync: ANY non-UI layer can drive -> all non-UI layers follow ===
// ES5-safe (niente ??, niente let/const, niente arrow)

(function installCamSyncAnyOnce() {
  if (!window.GD_CAMSYNC_ANY) window.GD_CAMSYNC_ANY = {};
  if (window.GD_CAMSYNC_ANY.__installed) return;
  window.GD_CAMSYNC_ANY.__installed = true;

  function almostEqual(a, b, eps) {
    return Math.abs(a - b) <= eps;
  }

  function cameraDiffers(camA, camB, eps) {
    if (!almostEqual(camA.x, camB.x, eps)) return true;
    if (!almostEqual(camA.y, camB.y, eps)) return true;
    if (!almostEqual(camA.z, camB.z, eps)) return true;
    if (!almostEqual(camA.r, camB.r, eps)) return true;
    return false;
  }

  window.GD_CAMSYNC_ANY.tick = function (runtimeScene, opts) {
    opts = opts || {};
    var excludedLayerName = (typeof opts.excludedLayer === "string" && opts.excludedLayer) ? opts.excludedLayer : "UI";
    var cameraId = (typeof opts.cameraId === "number") ? opts.cameraId : 0;

    // default: true
    var syncZoom = (opts.syncZoom === false) ? false : true;
    var syncRotation = (opts.syncRotation === false) ? false : true;

    // epsilon per confronti float
    var eps = (typeof opts.eps === "number") ? opts.eps : 0.0001;

    // cache layer names
    var layerNames = runtimeScene.__GD_CAMSYNC_ANY_LAYER_NAMES;
    if (!layerNames) {
      layerNames = [];
      runtimeScene.__GD_CAMSYNC_ANY_LAYER_NAMES = layerNames;
    }
    layerNames.length = 0;
    runtimeScene.getAllLayerNames(layerNames);

    // ricava lista "non UI"
    var nonUi = runtimeScene.__GD_CAMSYNC_ANY_NONUI;
    if (!nonUi) {
      nonUi = [];
      runtimeScene.__GD_CAMSYNC_ANY_NONUI = nonUi;
    }
    nonUi.length = 0;

    for (var i = 0; i < layerNames.length; i++) {
      if (layerNames[i] !== excludedLayerName) nonUi.push(layerNames[i]);
    }

    if (nonUi.length === 0) return;

    // stato globale applicato nell’ultimo tick
    var g = runtimeScene.__GD_CAMSYNC_ANY_GLOBAL;
    if (!g) {
      g = { x: 0, y: 0, z: 1, r: 0 };
      runtimeScene.__GD_CAMSYNC_ANY_GLOBAL = g;

      // init: prendi la camera del primo layer non-UI
      var initLayer = runtimeScene.getLayer(nonUi[0]);
      g.x = initLayer.getCameraX(cameraId);
      g.y = initLayer.getCameraY(cameraId);
      g.z = syncZoom ? initLayer.getCameraZoom(cameraId) : 1;
      g.r = syncRotation ? initLayer.getCameraRotation(cameraId) : 0;
    }

    // 1) trova un "driver": primo layer non-UI che differisce dal globale
    var driverCam = null;

    for (var j = 0; j < nonUi.length; j++) {
      var ln = nonUi[j];
      var L = runtimeScene.getLayer(ln);

      var cam = {
        x: L.getCameraX(cameraId),
        y: L.getCameraY(cameraId),
        z: syncZoom ? L.getCameraZoom(cameraId) : 1,
        r: syncRotation ? L.getCameraRotation(cameraId) : 0
      };

      if (cameraDiffers(cam, g, eps)) {
        driverCam = cam; // questo layer ha “mosso” la camera -> diventa SSOT per questo frame
        break;
      }
    }

    // 2) se c’è driver, aggiorna globale
    if (driverCam) {
      g.x = driverCam.x;
      g.y = driverCam.y;
      g.z = driverCam.z;
      g.r = driverCam.r;
    }

    // 3) applica globale a tutti i layer non-UI
    for (var k = 0; k < nonUi.length; k++) {
      var name = nonUi[k];
      var layer = runtimeScene.getLayer(name);

      layer.setCameraX(g.x, cameraId);
      layer.setCameraY(g.y, cameraId);

      if (syncZoom) layer.setCameraZoom(g.z, cameraId);
      if (syncRotation) layer.setCameraRotation(g.r, cameraId);
    }
  };
})();

// === CHIAMATA OGNI FRAME ===
window.GD_CAMSYNC_ANY.tick(runtimeScene, {
  excludedLayer: "UI",
  cameraId: 0,
  syncZoom: true,
  syncRotation: true,
  eps: 0.0001
});