[Engine] How to load same scripts when working with multi-applications?

As mentioned above, I have a multi-application project developed with an engine (there are multiple PlayCanvas applications running on the same page). When I wanted to load the same script between different applications, only the last script can be loaded normally, but the value cannot be passed into it(returns undefined). I’ve tried the following methods:

  1. Give the application a name, and add the application name to the script every time you register the script

A warning pops up saying that the script with the same name already exists

  1. Write a function to register a script by myself:
    registerScript(script, name) {
        script.__name = name;
        if (!this.app.scripts._scripts[name]){
            this.app.scripts.add(script);
        }
    }

Still not working. For some reason, I can’t post all the code, but can someone give me some suggestions?

Hi @kprimo,

I am curious why the engine will complain about the script already existing, since each pc.Application instance will create a separate scripts registry.

I understand that it’s not possible to share any code, but could you explain a bit how you boot each PlayCanvas instance and how you load the scene/scripts in place?

1 Like

Sure! In fact I am making something like UE4 material system for playcanvas, which is based on rete.js.

I have made some node classes, when I create a new node that calls PlayCanvas, the built-in vue framework will create a new canvas DOM, and then I wrote a function to create a new playcanvas app:

function createApp() {

    // Each application is assigned a canvas 
    var canvas = document.getElementById("graph_" + id);DOM with a unique id

    if (canvas) {

        var options = {
                ......
            }
        };
        var app = new pc.Application(canvas, options);

        // Name multi-apps in order to distinguish them.
        app.name = canvas.id;

        // do something init here (create scene by script, etc.) The logic of loading the script is also here.
        ......

        app.start();

    }

}

I have gone through many tests here, whether it is multi-application coexistence or loading resources is normal, but there is a problem when loading the script…

Side note: We are also making a material/shader editor :sweat_smile: https://youtu.be/64fcCy9lvq8?t=4822

2 Likes

It’s over :joy:, I have been working overtime recently and I missed it! But that’s okay! I just assumed what I was doing was practicing.

2 Likes

Looking at the createScript code, there is an optional parameter for the app instance, otherwise it uses whatever pc.Application.getApplication() returns.

It looks like the way to do it is to wrap the script itself into something that you can call with an app instance.

Eg

function createFlyCameraScript(app) {
    var FlyCamera = pc.createScript('flyCamera', app);

    FlyCamera.attributes.add('speed', {
        type: 'number',
        default: 10
    });

    FlyCamera.attributes.add('fastSpeed', {
        type: 'number',
        default: 20
    });

    FlyCamera.attributes.add('mode', {
        type: 'number',
        default: 0,
        enum: [{
            "Lock": 0
        }, {
            "Drag": 1
        }]
    });

    FlyCamera.prototype.initialize = function () {
        // Camera euler angle rotation around x and y axes
        var eulers = this.entity.getLocalEulerAngles();
        this.ex = eulers.x;
        this.ey = eulers.y;
        this.moved = false;
        this.lmbDown = false;

        // Disabling the context menu stops the browser displaying a menu when
        // you right-click the page
        this.app.mouse.disableContextMenu();
        this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
        this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
        this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
    };

    FlyCamera.prototype.update = function (dt) {
        // Update the camera's orientation
        this.entity.setLocalEulerAngles(this.ex, this.ey, 0);

        var app = this.app;

        var speed = this.speed;
        if (app.keyboard.isPressed(pc.KEY_SHIFT)) {
            speed = this.fastSpeed;
        }

        // Update the camera's position
        if (app.keyboard.isPressed(pc.KEY_UP) || app.keyboard.isPressed(pc.KEY_W)) {
            this.entity.translateLocal(0, 0, -speed * dt);
        } else if (app.keyboard.isPressed(pc.KEY_DOWN) || app.keyboard.isPressed(pc.KEY_S)) {
            this.entity.translateLocal(0, 0, speed * dt);
        }

        if (app.keyboard.isPressed(pc.KEY_LEFT) || app.keyboard.isPressed(pc.KEY_A)) {
            this.entity.translateLocal(-speed * dt, 0, 0);
        } else if (app.keyboard.isPressed(pc.KEY_RIGHT) || app.keyboard.isPressed(pc.KEY_D)) {
            this.entity.translateLocal(speed * dt, 0, 0);
        }
    };

    FlyCamera.prototype.onMouseMove = function (event) {
        if (!this.mode) {
            if (!pc.Mouse.isPointerLocked())
                return;
        } else {
            if (!this.lmbDown)
                return;
        }


        // Update the current Euler angles, clamp the pitch.
        if (!this.moved) {
            // first move event can be very large
            this.moved = true;
            return;
        }
        this.ex -= event.dy / 5;
        this.ex = pc.math.clamp(this.ex, -90, 90);
        this.ey -= event.dx / 5;
    };

    FlyCamera.prototype.onMouseDown = function (event) {
        if (event.button === 0) {
            this.lmbDown = true;

            // When the mouse button is clicked try and capture the pointer
            if (!this.mode && !pc.Mouse.isPointerLocked()) {
                this.app.mouse.enablePointerLock();
            }
        }
    };

    FlyCamera.prototype.onMouseUp = function (event) {
        if (event.button === 0) {
            this.lmbDown = false;
        }
    };
}

Add that to the document head as a <script> and call it twice, once per app instance.

1 Like

I have noticed this, but when the program is actually running, the program cannot correctly distinguish between different apps. Usually the last instantiated app overwrites the previous one. The scripts are often registered in the last running script…

Now I have functionalized the content originally written as a script file, and everything is working normally.

This is how I used the above script in the multi app example:

<!DOCTYPE html>
<html>
<head>
    <title>PlayCanvas Multiple Applications</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link rel="icon" type="image/png" href="../playcanvas-favicon.png" />
    <script src="../../build/playcanvas.js"></script>
    <script src="../../scripts/camera/fly-camera.js"></script>
    <style>
        #canvas-1 {
            position: absolute;
            width: 45%;
            left: 0px;
            margin: 0 0;
        }

        #canvas-2 {
            position: absolute;
            width: 45%;
            right: 0px;
            margin: 0 0;
        }
    </style>
</head>

<body>
    <!-- The canvas element -->
    <canvas id="canvas-1"></canvas>
    <canvas id="canvas-2"></canvas>

    <!-- The script -->
    <script>
        var canvas = document.getElementById("canvas-1");

        // Create the app and start the update loop
        var app1 = new pc.Application(canvas, {
            mouse: new pc.Mouse(canvas),
            touch: new pc.TouchDevice(canvas),
            keyboard: new pc.Keyboard(document.body)
        });
        app1.start();
        app1.setCanvasResolution(pc.RESOLUTION_AUTO);

        // Add a script to the register
        createFlyCameraScript(app1);

        var entity, light, camera;

        // Create an Entity with a box component
        entity = new pc.Entity(app1);
        entity.name = "Box 1";
        entity.addComponent("model", {
            type: "box"
        });
        app1.root.addChild(entity);

        // Create an Entity with a camera component
        var camera = new pc.Entity(app1);
        camera.addComponent("camera", {
            clearColor: new pc.Color(0.4, 0.45, 0.5)
        });
        camera.translate(0, 0, 4);
        app1.root.addChild(camera);

        // Add script to camera
        camera.addComponent("script");
        camera.script.create("flyCamera");

        // Create an Entity with a omni light component
        var light = new pc.Entity(app1);
        light.addComponent("light", {
            type: "omni",
            color: new pc.Color(1, 1, 1),
            range: 100
        });
        light.translate(5, 0, 15);
        app1.root.addChild(light);

        app1.on("update", function (dt) {
            var entity = app1.root.findByName("Box 1");
            if (entity) {
                entity.rotate(0, 10 * dt, 0);
            }
        });

        var canvas = document.getElementById("canvas-2");

        // Create the app and start the update loop
        var app2 = new pc.Application(canvas, {
            mouse: new pc.Mouse(canvas),
            touch: new pc.TouchDevice(canvas),
            keyboard: new pc.Keyboard(document.body)
        });
        app2.start();

        app2.setCanvasResolution(pc.RESOLUTION_AUTO);

        // Add a script to the register
        createFlyCameraScript(app2);

        var entity, light, camera;

        // Create an Entity with a box component
        entity = new pc.Entity(app2);
        entity.name = "Box 2";
        entity.addComponent("model", {
            type: "box"
        });
        app2.root.addChild(entity);

        // Create an Entity with a camera component
        var camera = new pc.Entity(app2);
        camera.addComponent("camera", {
            clearColor: new pc.Color(0.4, 0.45, 0.5)
        });
        camera.translate(0, 0, 4);
        app2.root.addChild(camera);

        // Add script to camera
        camera.addComponent("script");
        camera.script.create("flyCamera");

        // Create an Entity with a omni light component
        var light = new pc.Entity(app2);
        light.addComponent("light", {
            type: "omni",
            color: new pc.Color(1, 1, 1),
            range: 100
        });
        light.translate(5, 0, 15);
        app2.root.addChild(light);

        app2.on("update", function (dt) {
            // var entity = app2.root.findByName("Box 2");
            if (entity) {
                entity.rotate(0, 10 * dt, 0);
            }
        });

        window.addEventListener("resize", function () {
            app1.resizeCanvas(canvas.width, canvas.height);
            app2.resizeCanvas(canvas.width, canvas.height);
        });
    </script>
</body>
</html>
1 Like