Pre-render images with masks to texture

I’m building a puzzle game to play with coworkers while we’re all working remote, and I’m struggling finding the right way to pre-render puzzle pieces with a mask applied from a Graphics game object.

I tried using a RenderTexture but rendering it this way doesn’t seem to use the mask applied to image game objects.

I recently found that Phaser.Cameras.Scene2D.Camera has a renderToTexture prop but I can’t find a good example of how to correctly use that.

I’m also not sure if this is the best way to do it anyway since I’m new to HTML5 game frameworks and WebGL.

What is the best way to render a series of images with graphics masks to a single texture?

Can you make a spritesheet and apply the mask in something like photoshop?

In this case I’m creating the puzzle pieces from source images that the users will upload.

Here is a simplified version what I’m doing. This would normally loop through rendering many puzzle pieces to the RenderTexture.

const rt = scene.make.renderTexture({
    x: 0,
    y: 0,
    width: 200,
    height: 200,
}, false)
    .setOrigin(0, 0);

const pieceShape = scene.make.graphics({}, false)
    .fillCircle(15, 15, 10);

const puzzlePiece = scene.add.image(0, 0, 'puzzle')
    .setOrigin(0, 0)
    .setVisible(false);

puzzlePiece.setMask(pieceShape.createGeometryMask());

rt.draw(
    puzzlePiece,
    0,
    0,
    1,
);

rt.saveTexture('simplePiece');
rt.destroy(true);

scene.add.image(
    0,
    0,
    'simplePiece',
)
    .setOrigin(0, 0);

This renders a square 200x200 (the size of the RenderedTexture) image and doesn’t use the circular mask.

(Update: Added some clarification)

Sorry, I’m not sure how to solve this. Hopefully somebody else does.

No problem! Thanks for your help.

It seems like Render Textures don’t respect masks when rendering Game Objects. Is there any way you can avoid pre-rendering the tiles? Crops might be useful.

If this is not feasible (which wouldn’t surprise me, because each mask would likely cause a batch flush, which can become prohibitively slow), I’d look into Canvas Textures. I’m not sure what their memory footprint will be compared to a Render Texture, but they’ll let you use the full canvas API, including clipping paths.

2 Likes

Thanks Telinc1! That worked.

I’m using the Canvas API directly and it’s working great. To bridge the gap between Phaser and the ContextAPI, I’m creating my paths in Phaser but then iterating through the points to draw it on the canvas.

const cols = 5;
const rows = 5;
const circleRadius = 20;
const padding = 10;
const frameSize = circleRadius * 2 + padding * 2;

const canvas = scene.textures.createCanvas(
    'puzzlePieces',
    cols * frameSize,
    rows * frameSize,
);

const ctx = canvas.context;

for (let x = 0; x < cols; x++) {
    for (let y = 0; y < rows; y++) {
        const xOffset = x * frameSize;
        const yOffset = y * frameSize;

        // Fill the entire frame with a color so we see it's area.
        ctx.fillStyle = '#' + [0, 0, 0, 0, 0, 0]
            .map(() => (2 + Math.round(Math.random() * 12)).toString(16))
            .join('');
        ctx.fillRect(xOffset, yOffset, frameSize, frameSize);

        const points = new Phaser.Curves.Path(
            xOffset + padding + circleRadius * 2,
            yOffset + padding + circleRadius,
        )
            .circleTo(circleRadius)
            .getPoints();

        ctx.save();

        ctx.moveTo(points[0].x, points[0].y);
        ctx.beginPath();

        for (let i = 1; i < points.length; i++) {
            ctx.lineTo(points[i].x, points[i].y);
        }

        ctx.closePath();
        ctx.clip();

        // Draw the rectangle again, but this time it will be clipped to the circle.
        ctx.fillStyle = '#' + [0, 0, 0, 0, 0, 0]
            .map(() => (2 + Math.round(Math.random() * 12)).toString(16))
            .join('');
        ctx.fillRect(xOffset, yOffset, frameSize, frameSize);

        // If drawing an image, you can use this:
        // const puzzleImg = scene.textures.get('puzzle').getSourceImage() as HTMLImageElement;
        // ctx.drawImage(puzzleImg, 0, 0);

        ctx.restore();

        // Add the texture frame.
        canvas.add(
            `${x}x${y}`,
            0,
            x * frameSize,
            y * frameSize,
            frameSize,
            frameSize,
        );
    }
}

canvas.refresh();

for (let x = 0; x < cols; x++) {
    for (let y = 0; y < rows; y++) {
        scene.add.image(x * frameSize, y * frameSize, 'puzzlePieces', `${x}x${y}`)
            .setOrigin(0, 0);
    }
}

Here is the image that was displayed:

download

2 Likes

I was able to make a jigsaw puzzle game with irregular shape based on your code above. However, i’m facing some issue with a specific part of the pieces.

A piece can have 3 different kinds of side : flat (border of the board), hole and neck. The logic for the game is working fine.

The flat and hole sides are working fine.

However, there is some issues regarding the neck sides. It seems like the code below crop my pieces to a maximum rectangle frame but when I try to increase the width and the height of it, the pieces have strange shape.

canvas.add(
${x}x${y},
0,
x * frameSize,
y * frameSize,
frameSize,
frameSize,
);