The image is not loaded yet even tho Phaser thinks so

Hello,
I have a problem with the Phaser images which tells me incorrect sizes.

I have a bunch of images to load. I’m loading them via this.load.image(). After loading them I want to continue with the rest of the code where I’m taking - for instance - width/height and positions. I’m checking the Phaser to see if it downloaded already via:
this.game.textures.gameTextureKeys()
If there is a key to the image I believe it is already loaded. My issue is baseSprite.height and baseSprite.displayHeight, which tells me the incorrect size, 32px. If I set a timeout for 3-4s with console.log to the .height, then it gives me the correct size.

I’m using scene.load.on(“complete”), but it doesn’t do much. It doesn’t matter if the image is cached or not, this is completely random. Sometimes I’m lucky and it works properly.

What should I check? I’m using Phaser 3.80.1

Hi, can you show the code?

import {Scene} from "phaser";

// default.preload.scene.ts
export class DefaultScene extends Scene {
  assetsToBeLoaded = [];
  sceneConfiguration = { bg: []}; // bg images

  preload() {
    this.load.image('blue_box', "boxes/blue_box.png");

    this.load.atlas('map_atlas', "atlas/map_atlas.png", "atlas/map_atlas.json");
    this.load.atlas('animals_atlas', "atlas/animals_atlas.png", "atlas/animals_atlas.json");

    for (let bg of this.sceneConfiguration.bg) {
      this.load.image(bg.name, bg.key)
    }

    this.load.on("complete", val => { console.log("completed")});

    for (let img of this.assetsToBeLoaded) {
      this.load.image(img, img);
    }
  }

  create() {
    this.scene.start(MAIN_MENU_SCENE, {});
  }
}

// default.main.scene.ts
export class DefaultMainScene extends Scene {

  preload() {};

  create() {
    // code for camera, background, events etc
    const sceneConfiguration = { bg: []};

    for (let item of sceneConfiguration.bg) {
      const img = this.add.image(item.x, item.y, item.key);
      // rest of the code which changes displaySize, origin etc
    }

    this.createLevelEnv();
  }

  createLevelEnv() {
    // get data from the ngrx store
    this.store.select(selectDefaultLevelEnvironment).subscribe(res => {
      const clone = structuredClone(res);
      this.levelData = new FirstLevel(this);
      levelData.generateLevelElements(clone);
    })
  }
}
// inside generateLevelElements
generateLevelElements(clone) {
  for (let el of clone) {
    const levelObject = new Phaser.GameObjects.Container(this)

    const image = getImage(el); // returns a path to the image, the path is also a key
    
    // bunch of code which doesn't matter - setting local variables
    // [...]

    const loadedAssets = this.game.textures.getTextureKeys();
    if (loadedAssets.indexOf(image) !== -1) {
      this.baseSprite.setTexture(image);
      
      // bunch of stuff which doesnt matter
      // [...]

      generateHighlight(scene, element, levelObject);
      
      this.levelObjects.push(levelObject);
    } else {
      loadImage(this.currentScene, loadedAssets).then(() => {
        setTimeout(() => {
          // without timeout it doesnt work; this is a first problem
          this.baseSprite.setTexture(image);

          // bunch of stuff which doesn't matter
          // [...]

          generateHighlight(scene, element, levelObject);

          this.levelObjects.push(levelObject);
        }, 500)
      })
    }
  }
}

loadImage(scene, imageToLoad) {
  const promise = new Promise((res, rej) => {
    scene.load.image(imagesLoaded);
    
    const loaded = () => {
      res();
    }
    
    const error = () => {
      rej();
    }
    
    scene.load.on("complete", loaded, scene);
    scene.load.on("error", error, scene);
    scene.load.start();
  });
  return promise
}

generateHighlight(element) {
  // code for making it highlighted
  
  const bulb = new Highlight(scene, element, 'BULB');
}

export class Highlight extends Phaser.GameObjects.Container {
  element = null;
  
  constructor(scene, element, type) {
    this.element = element;
    
    this.generateShape();
  }
  
  generateShape() {
    // this is a second problem
    console.log(this.element.baseSprite.height) // if I put setTimeout 2s -> size is correct, without its a matter of luck
  }
}

I had to remove much of the code, modify it, or change its name, but the flow is correct

I can’t test this easily but I would guess the problem is in loadImage().

Remove setTimeout()s, obviously. :slight_smile:

Why is this imagesLoaded instead of imageToLoad?

It’s safer and a little more correct to use FILE_KEY_COMPLETE or ADD_KEY. Then you know exactly what was loaded. COMPLETE means every file loaded or failed, or no files were queued after start().

If you do use COMPLETE I think it needs to be like

function loadImage(scene, images) {
  const promise = new Promise((res, rej) => {
    const loaded = () => {
      scene.load.off('complete', loaded, scene);
      scene.load.off('error', error, scene);

      res();
    };

    const error = () => {
      scene.load.off('complete', loaded, scene);
      scene.load.off('error', error, scene);

      rej();
    };

    scene.load.on('complete', loaded, scene);
    scene.load.on('error', error, scene);

    scene.load.image(images);
    scene.load.start();
  });
  return promise;
}

Use this.textures.exists('key') to verify that a texture exists.

1 Like

The promise is an asynchronous function which means that the code will keep running even if the data has not been loaded yet. I think the beheavior is normal. I’m not sure what you are trying to achieve, but the solutions would be eihter to load the image during the preload or to start your game only after the data has been loaded in the callback of the promise.

1 Like

I decided to create an array with promises, where the promise listens to ‘filecomplete’ event. I’m using Promise.allSettled to ensure everything is done inside an array.

Thank you very much

Just to explain — to prevent rendering errors, Phaser gives the “missing” texture (32 × 32) if you try to retrieve a texture that doesn’t exist, either with textures.get(), setTexture(), or creating a game object.