An error is reported when asset is passed in, but the value can be read normally when string is passed in

I’m trying to work with node.js and play canvas engine. Got very confused today because when I was trying to pass in an asset into an ES6 class, it reported bugs (actually the value passed in is null). However, when I define the type of variable into a string, it can be passed in normally. WTF??

image

Here is my code.

import * as pc from 'playcanvas';

export default class MfGrid extends pc.ScriptType{

    initialize() {

        let material = this.entity.model.meshInstances[0].material;

        let shader = [
            "#ifdef MAPFLOAT\n" + 
            "uniform float material_opacity;\n" + 
            "#endif\n" + 
            "\n" + 
            "#ifdef MAPTEXTURE\n" + 
            "uniform float time;\n" + 
            "uniform float scale;\n" + 
            "uniform sampler2D texture_opacityMap;\n" + 
            "uniform sampler2D mask1;\n" + 
            "uniform sampler2D mask2;\n" + 
            "uniform sampler2D mask3;\n" + 
            "uniform sampler2D mask4;\n" + 
            "uniform float map_scale;\n" + 
            "uniform float panner_x;\n" + 
            "uniform float panner_y;\n" + 
            "uniform float intensity;\n" + 
            "#endif\n" + 
            "\n" + 
            "void getOpacity() {\n" + 
            "    dAlpha = 1.0;\n" + 
            "\n" + 
            "    #ifdef MAPFLOAT\n" + 
            "        dAlpha *= material_opacity;\n" + 
            "    #endif\n" + 
            "\n" + 
            "    #ifdef MAPTEXTURE\n" + 
            "        vec2 scale_offset = vec2(0.5,0.5);\n" + 
            "        vec2 time_offset = vec2(time, 0);\n" + 
            "        vec2 scaleUV = $UV*scale + scale_offset - scale_offset * scale;\n" + 
            "        vec2 mapUV = $UV*map_scale + scale_offset - scale_offset * map_scale;\n" + 
            "        vec2 panner = vec2(panner_x, panner_y);\n" + 
            "        vec4 main_panner = texture2D(texture_opacityMap, mapUV + time_offset);\n" + 
            "        vec4 mask1_fixed = texture2D(mask1, scaleUV);\n" + 
            "        vec4 mask2_fixed = texture2D(mask2, scaleUV);\n" + 
            "        vec4 mask2_teleport = texture2D(mask2, scaleUV + panner);\n" + 
            "        vec4 mask3_fixed = texture2D(mask3, mapUV);\n" + 
            "        vec4 mask4_fixed = texture2D(mask4, mapUV);\n" + 
            "\n" + 
            "        vec4 grid_fixed = intensity * main_panner * mask1_fixed * 10.0 * mask2_fixed * mask2_fixed;\n" + 
            "        vec4 grid_teleport = (intensity * (1.0 - main_panner) * mask2_teleport * 0.1);// * mask2_teleport;\n" + 
            "        \n" + 
            "        vec4 opacity =  (grid_fixed + grid_teleport) * mask3_fixed * mask4_fixed;\n" + 
            "        dAlpha *= opacity.$CH;\n" + 
            "        //dAlpha *= texture2D(texture_opacityMap, $UV).$CH;\n" + 
            "    #endif\n" + 
            "\n" + 
            "    #ifdef MAPVERTEX\n" + 
            "        dAlpha *= clamp(vVertexColor.$VC, 0.0, 1.0);\n" + 
            "    #endif\n" + 
            "}"
        ].join("\n");
    
        material.chunks[this.chunkName + 'PS'] = shader;
        material.setParameter('mask1', this.mask1.resource);
        material.setParameter('mask2', this.mask2.resource);
        material.setParameter('mask3', this.mask3.resource);
        material.setParameter('mask4', this.mask4.resource);
        material.update();
        
        this.timer = 0;
        this.second = -1;
        this.second2 = -1;
        this.isFadeOut = false;
        this.intensity = 0;

    }

    update(dt) {

        this.timer += dt;
        let material = this.entity.model.meshInstances[0].material;
        material.setParameter('scale', this.scale);
        material.setParameter('map_scale', this.map_scale);
        material.setParameter('time', this.timer * this.speed);
                
        if (parseInt(this.timer*this.teleport_freq) !== this.second & this.isFadeOut === false){
            let panner = new pc.Vec2(Math.floor(Math.random() * 16)*0.0625,Math.floor(Math.random() * 16)*0.0625);
            material.setParameter('panner_x',panner.x);
            material.setParameter('panner_y',panner.y);
            this.isFadeOut = false;
            ++this.second;
        }

        else if (parseInt(this.timer*this.fadeout_freq) !== this.second2){
            let panner = new pc.Vec2(Math.floor(Math.random() * 16)*0.0625,Math.floor(Math.random() * 16)*0.0625);
            material.setParameter('panner_x',panner.x);
            material.setParameter('panner_y',panner.y);
            this.isFadeOut = !this.isFadeOut;
            ++this.second2;
        }
        
        if (this.isFadeOut === true){
            this.intensity = this.fadeout_curve.value(this.timer*this.fadeout_freq-Math.floor(this.timer*this.fadeout_freq));
            material.setParameter('intensity', this.intensity);        
        }
        else{
            this.intensity = this.teleport_curve.value(this.timer*this.teleport_freq-Math.floor(this.timer*this.teleport_freq));
            material.setParameter('intensity', this.intensity);
        }
    
        material.update();
        
    }

}

And I tried to pass in assets after I defined the variables, like this:

MfGrid.attributes.add('chunkName' , { type: 'string' });
MfGrid.attributes.add('mask1', { type: 'string'});
MfGrid.attributes.add('mask2', { type: 'string'});
MfGrid.attributes.add('mask3', { type: 'string'});
MfGrid.attributes.add('mask4', { type: 'string'});
MfGrid.attributes.add('speed' , { type: 'number' , default : 0.1});
MfGrid.attributes.add('scale' , { type: 'number' , default : 2});
MfGrid.attributes.add('map_scale' , { type: 'number' , default : 1});
MfGrid.attributes.add('teleport_freq' , {type: 'number' , default: 2});
MfGrid.attributes.add('fadeout_freq' , {type: 'number' , default: 0.5});
MfGrid.attributes.add('teleport_curve', { type: 'curve'});
MfGrid.attributes.add('fadeout_curve', { type: 'curve'});

app.assets.loadFromUrl('../public/assets/textures/mask001.jpg','texture',function(err,asset){
    let mask01 = asset;
    app.assets.loadFromUrl('../public/assets/textures/mask002.jpg','texture',function(err,asset){
        let mask02 = asset;
        app.assets.loadFromUrl('../public/assets/textures/gridmask.jpg','texture',function(err,asset){
            let mask03 = asset;
            grid.addComponent('script');
            grid.script.create('mfGrid',{
                attributes: {
                    chunkName : 'opacity',
                    mask1: mask01,
                    mask2: mask02,
                    mask3: mask03,
                    mask4: mask02,
                    speed: 0.1,
                    scale: 3,
                    map_scale: 1,
                    teleport_freq: 2,
                    fadeout_freq: 2,
                    teleport_curve: {
                        type:pc.CURVE_LINEAR,
                        keys:[0,1]
                    },
                    fadeout_curve: {
                        type:pc.CURVE_LINEAR,
                        keys:[0,1]
                    },
                }
            });
            console.log(grid.script.mfGrid);
        });
    });
});

I can make sure that the same code (when defined as an asset) can run normally in the editor version, so I am sure that the problem is when using ES6 syntax. Any suggestions?

Hi @kprimo,

That’s strange, it should work as an asset attribute. Are you sure the assets load correctly and are available when assigned to the new script instance?

Is it possible to create a small reproducible project to debug?

Yes, I’m sure. It’s OK. Here is the link to the total project:

Google Drive

I packaged it into an npm package with the parcel tool, and the test page can be seen in the test folder.

Ok good, I’ve run npm i and then opened a local web server to run the test/index.html but I am getting the following error:

I use the live server plug-in of vs code to run without problems, maybe it can works.

Yes, that’s what I’ve used and navigated to the test folder but it throws that error.

oh, you should navigate to the p3d folder, it’s my fault, sorry.

You mean the base folder? Since there isn’t any p3d subfolder in there.

But in the base folder there is no .html file for the browser to run from what I see.

Yes, open the base folder with vs code, then locate index.html in vs code and run the live server.

So, did this:

And still got the same error :frowning:

I tested it on another computer and there was no error :flushed:. I think is it because of the other environment configuration on your computer that affects the operation?

No idea, I am doing a lot of web dev / node.js with npm etc, not sure what may be broken on my side. If you find something related let me know to test.

Understand, I will. Thank you for your kindness :blush:.

1 Like

So, when you are adding an attribute to the MfGrid script, you define it of type string. Later, after an asset is loaded, you are assigning that attribute an object of type Asset.

MfGrid.attributes.add('mask1', { type: 'string'});
...
app.assets.loadFromUrl(...,function(err,asset){
let mask01 = asset;
...
attributes: {
    ...
    mask1: mask01,

I am not sure what happens when you assign a value of wrong type to a script attribute, but this is probably not what you wanted. Did you perhaps want to set the type to asset?

If you get a null as a result of loadFromUrl method, it means the asset was not found and null value was returned. You can add a guard on err to prevent it, e.g.:

app.assets.loadFromUrl(...,function(err,asset){
   if (err) {
       console.warn('Failed to load asset', url, err);
       return;
   } 
   let mask01 = asset;
2 Likes

From what I understand that’s the problem of the OP, when the script attribute type is set to asset, he can’t update the properties with the loaded assets when the script is added.

2 Likes

Hmm… I see, @Leonidas.

Well, I am not familiar with p3d. @kprimo is that what powers your local server? If so, make sure it enables serving all files from public folder, versus only including referenced files. You can check it by entering full path to your texture file in the browser and see that the file can be accessed, e.g. http://localhost:your-port/public/assets/textures/mask001.jpg. The issue is in the asset loading failure. You should find the reason the file cannot be accessed by the asset registry. Most probably because of server config.

1 Like

Here is a sample engine-only project:

index.html

<html>
<body>
    <canvas id="application-canvas"></canvas>
    <script src="./index.js"></script>
</body>
</html>

my-class.js

import * as pc from 'playcanvas';

class MyClass extends pc.ScriptType {
    initialize() { }
}
pc.registerScript(MyClass, 'myClass');

MyClass.attributes.add('myTexture', { type: 'asset', assetType: 'texture' });

app.js

import * as pc from 'playcanvas';

const canvas = document.getElementById("application-canvas");
    
const app = new pc.Application(canvas, {
    mouse: new pc.Mouse(document.body),
    touch: new pc.TouchDevice(document.body)
});

export default app;

index.js

import * as pc from 'playcanvas';
import app from './app';
import MyClass from './my-class.js';

function load() {
    
    // instead of callbacks inside callbacks, you can use an array of assets
    // I use one as an example
    const assetManifest = [
        {
            type: "texture",
            url: "./assets/LeXXik.png"
        }
    ];
    
    // once all loaded, we can run the main script
    let assetsToLoad = assetManifest.length;
    assetManifest.forEach(function (entry) {
        app.assets.loadFromUrl(entry.url, entry.type, function (err, asset) {
            if (!err && asset) {
                assetsToLoad--;
                entry.asset = asset;
                if (assetsToLoad === 0) {
                    run(assetManifest);
                }
            }
        });
    });
    
}
load();

function run(manifest) {
    const entity = new pc.Entity();
    entity.addComponent('script');
    entity.script.create('myClass');

    const myScript = entity.script.scripts[0];
    console.log(myScript.myTexture);        // null

    myScript.myTexture = manifest[0].asset; // Asset { ... }
}
3 Likes

Yes, I want to set the type to asset, but the texture asset which has been loaded will turn null when being passed in. I originally thought that the asset had not been loaded when it was passed in, so I wanted to pass in the relative path to the MF_Grid script before loading it, but when I had not passed in the URL, I just changed the variable type from asset to string, the incoming value that turned out to be null becomes a usable resource, that is, an asset that has been loaded. This is why I am so confused.

My friend said that the asset may become a base64 string after being loaded, but I think it may not be the case.

Thank you for the example, which is very enlightening :heart:.

1 Like