Search code examples
javascripttypescriptnext.jsphaserjs

How to set background color to transparent with Phaser 3


Trouble

I added two Text objects to a scene. But for the latter one, I couldn't set background color to transparent.

Can anyone help me?

Here is the screenshot.

Screenshot


What I tried

I want the Text objects to have transparent background.

I have tried to set backgroundColor like:

this.scene.add.text(0, 0, "hoge", {
  backgroundColor: "rgb(255 255 255 / 0.5)",
});

but it doesn't work.

Screenshot


Source Code

This Text object looks good. It has transparent background.

    this.text = (() => {
      const text = this.scene.add.text(0, 0, "x: -, y: -", {
        padding: {
          y: 50 / this.scene.cameras.main.zoom,
        },
        align: "center",
        fixedWidth: containerWidth,
        fixedHeight: containerHeight,
        fontSize: `${100 / this.scene.cameras.main.zoom}px`,
        lineSpacing: 0,
      });

      return text;
    })();

This Text looks bad. It has black background.

    this.text = (() => {
      const text = this.scene.add.text(0, 0, [
        "a10a",
        "VIT: 0",
      ], {
        fixedWidth: containerWidth,
        fixedHeight: containerHeight,
        fontSize: `${100 / this.scene.cameras.main.zoom}px`,
      });

      return text;
    })();

Minimal Reproducable Example

I created minimal reproducable example.

I found that type: CANVAS option in new PhaserGame and this.load.image does something. I could make text background transparent when I commented out those lines. But I do not want to do this because I need to load images and it comes other errors if I change type: CANVAS option.

+ app/
  + _components/
  |  + game/
  |     + scenes/
  |     |   + main-scene/
  |     |       + index.ts
  |     |
  |     + index.tsx
  |
  + globals.css
  + layout.tsx
  + page.tsx
  • _components/game/index.tsx
"use client";

import { CANVAS, Game as PhaserGame, Scale } from "phaser";
import { useEffect, useRef } from "react";
import { MainScene } from "./scenes/main-scene";

export function Game() {
  const game = useRef<Phaser.Game | null>(null);

  useEffect(() => {
    if (game.current === null) {
      game.current = new PhaserGame({
        parent: "game-container",
        // If AUTO, it doesn't work on smartphone browser
        type: CANVAS,
        width: 100,
        height: 100,
        scale: {
          mode: Scale.ScaleModes.FIT,
        },
        backgroundColor: "#028af8",
        scene: [
          MainScene,
        ],
      });
    }

    return () => {
      if (game.current) {
        game.current.destroy(true);
        
        if (game.current !== null) {
          game.current = null;
        }
      }
    }
  }, []);

  return (
    <div id="game-container"></div>
  );
}

export default Game;
  • _components/game/scenes/main-scene/index.ts
import { Scene } from "phaser";

export class MainScene extends Scene {
  constructor () {
    super("MainScene");
  }

  preload() {
    this.load.image("tiles", "assets/tiles.1.png");
  }

  create () {
    this.add.text(0, 0, "Text 1", {
      fontSize: `10px`,
    });

    this.add.text(0, 20, "Text 2", {
      fontSize: `10px`,
    });
  }
}

GitHub

(clone stackoverflow/questions/78791454 branch and run npm run dev to reproduce)

Screenshot


It depends on environment

I found that it happens only in Chrome browser.

  • Reproducable
    • Chrome (126.0.6478.183) in MacOS (14.5)
  • Not Reproducable
    • Safari (17.5) in MacOS (14.5)
    • Chrome (127.0.6533.56) in iOS (17.5.1)
    • Safari in iOS (17.5.1)

Solution

  • Update(2024-07-26) Two possible "Solutions":

    I did some more digging, and found the solutions to the problem:

    1. Since the problem has to do with some canvas cleanup issue, you could simply set the phaser "CanvasPool" to an empty list, in the destructor, before destroying the game object. This would force hte creation of new html-canvas objects

      // ...
      return () => {
        if (game.current) {
          Phaser.Display.Canvas.CanvasPool.pool = [];
          game.current.destroy(true);      
      
          if (game.current !== null) {
            game.current = null;
          }
        }
      } //...
      
    2. It seems that the double call of the useEffect has to do with running the app in development mode (StrictMode). If you start the application in production mode (npm run build and npm run start) phaser will not be start so fast in succession, so this error won't occur also. No other Changes needed (this info is base on this stackoverflow answer and short skimming the documentation)

    I tested both options, with your MRE and both work fine.

    This is a Phaser bases "Workaround" (Initial Answer)

    you could use Phaser BitmapFonts. Since Bitmap Text GameObject is rendered/generated in a different way (not on an hidden internal canvas like the normal Text GameObject), so issue won't happen.

    I tested your demo application with the bitmap font, and it works (on win11 chrome 126+).

    Bitmap Text is not so flexible as the normal Text, but has better performance, they say. There are a few font conversion tools out there, that might help, if you need some special font, but since I never used one I can't suggest any. Also there are many Bitmap Fonts out there, ready to use (like here, or in the phaser bitmap-font examples, ...).