Recent Posts

HaxeFlixel Tutorial: Mask

Announcement: I’m entering crunch-time for a game project I’m working on. Hopefully it’ll finish by early August. I may not be able to update on the regular weekends, but I will try.

I’m a fan of image manipulation, but I can’t comprehend the math behind it all. In fact, before writing this tutorial, I spent about 4 hours researching and troubleshooting masking in HaxeFlixel.

We’re going to cover image masking today. I apologise in advance if the information below are inaccurate or inefficient. I’d appreciate any corrections or suggestions — just comment below this post and I’ll get back to you.

Introduction

There are two scenarios I could think of, when masking is necessary:

Taking an image and masking it with another image

Cutting a hole with a mask image (reverse masking)

As such, we shall proceed with the tutorial with the above two methods as our goal.

Setup

Let’s setup our HaxeFlixel project, as usual:

1

flixel tpl-n"MyMaskTest"

As for placeholder assets, we shall be using the images included in the Flixel Power Tool Test Suite Github page (Note: cloning didn’t work for me, so I had to download the ZIP file instead).

Masking with two images

PhotonStorm’s Power Flixel Tools allows image masking, as demonstrated in the demo page (specifically, the FlxDisplay page). Thankfully, it was ported to HaxeFlixel under the
flixel.util.FlxSpriteUtil library.

Let’s start off with loading the two images in MenuState.hx:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

package;

import flixel.FlxState;

import flixel.FlxG;

import flixel.FlxSprite;

classMenuStateextendsFlxState

{

override publicfunctioncreate():Void

{

super.create();

// Set the background color for visibility sake

FlxG.camera.bgColor=0xFF00FF00;

// Create two separate images with PNG transparency

varbase=newFlxSprite(0,0);

varmask=newFlxSprite(0,0);

base.loadGraphic("assets/images/shocktroopers_leon.png");

mask.loadGraphic("assets/images/mask-test.png");

// Add them to stage

add(base);

add(mask);

}

override publicfunctionupdate():Void

{

super.update();

}

override publicfunctiondestroy():Void

{

super.destroy();

}

}

And if you build with
lime test neko , you’ll get this:

The output is straight-forward — there’s no masking yet. To mask the two images, you need to merge them into a result FlxSprite, and add that to the stage, like this:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

package;

import flixel.FlxState;

import flixel.FlxG;

import flixel.FlxSprite;

import flixel.util.FlxSpriteUtil;

classMenuStateextendsFlxState

{

override publicfunctioncreate():Void

{

super.create();

// Set the background color for visibility sake

...

// Create two separate images with PNG transparency

...

// Add them to stage

// add(base);

// add(mask);

// Apply the mask to the base image, and add the result to stage

varresult=newFlxSprite(200,100);

FlxSpriteUtil.alphaMaskFlxSprite(base,mask,result);

add(result);

}

...

}

The above code produces this result:

Note two issues with the output:

The x/y position of the
base and
mask FlxSprites doesn’t seem to affect the
result FlxSprite. It merges at the origin top-left point (0,0) by default.

The
base image’s transparency isn’t preserved after merging (note the pink background for the character)

As of writing, I couldn’t figure out how to easily fix the above issues. If you need a solution for the issues mentioned, my only suggestion for now is to use your favorite image editor (e.g. Photoshop) and create your desired masked/transparent PNG instead.

Reverse masking

This example seems to be most common. Let’s rewrite the existing code in MenuState.hx:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

package;

import flixel.FlxState;

import flixel.FlxG;

import flixel.FlxSprite;

import flixel.util.FlxSpriteUtil;

classMenuStateextendsFlxState

{

var_player:FlxSprite;

var_dummy:FlxSprite;

var_curtain:FlxSprite;

var_mask:FlxSprite;

override publicfunctioncreate():Void

{

super.create();

// Create the sprites

varbg=newFlxSprite(0,0);

_dummy=newFlxSprite(200,100);

_player=newFlxSprite(100,100);

_curtain=newFlxSprite();

// Create the sprites' graphics

varscreenW=FlxG.stage.stageWidth;

varscreenH=FlxG.stage.stageHeight;

bg.makeGraphic(screenW,screenH,0xFFcccc00);

_dummy.makeGraphic(20,50,0xFFFF0000);

_player.makeGraphic(20,50,0xFF0000FF);

_curtain.makeGraphic(screenW,screenH,0x66000000);

// Add them to stage

add(bg);

add(_dummy);

add(_player);

add(_curtain);

}

override publicfunctionupdate():Void

{

super.update();

// Allow player movement

varmSpeed=3;

if(FlxG.keys.pressed.LEFT)_player.x-=mSpeed;

if(FlxG.keys.pressed.RIGHT)_player.x+=mSpeed;

if(FlxG.keys.pressed.UP)_player.y-=mSpeed;

if(FlxG.keys.pressed.DOWN)_player.y+=mSpeed;

}

override publicfunctiondestroy():Void

{

super.destroy();

}

}

And the result is as follows:

Note the
curtain is a semi-transparent black rectangle that covers the whole screen. Let’s say we want to cut a hole where the player and dummy is, perhaps we’d do it like this:

But the result is undesired (or perhaps desired, depending on your intention):

What exactly is happening? After I did some tinkering, here’s the explanation I could come up with:

All FlxSprite images are cached, whether it’s created with
makeGraphic or loaded with
loadGraphic .

When an image is cached, doing a
makeGraphic (using the same shape and color) or
loadGraphic (using the same image path) will result in the cached image being used, instead of a new image.

In the above code, we tried to create a
newMask FlxSprite, then load the
_curtain ‘s image data into it. We assumed the
_curtain ‘s image is uncut, which is true. However, the
_mask ‘s bitmap data has been cached, so the cached image is used, resulting in the
_mask being continuously re-cut and updated instead.

The first solution I would think of, was to do a clone of the bitmapData instead, which means we don’t use the cached image:

From what I can tell, the
newMask FlxSprite ends up caching the unique
_curtain ‘s bitmapData. This means, with every
update() cycle, a new image is created and cached — eventually, memory will run out and the game will freeze.

To fix this issue, we have to dive deeper into the bitmapData — this is where newbies like me start getting uncomfortable for using image-manipulating API:

I’m sorry to hear you’re having issues with getting your program to run. Indeed, Haxeflixel has changed a lot since my articles were written. I haven’t worked with Haxe for over a year so I can’t give you any insight off the top of my head. I’ve considered updating/rewriting my Haxeflixel tutorials, but I have other priorities in my life now… so I guess that is very unlikely at the moment.

I’m sorry, and I hope you manage to figure it out eventually! Your best bet for now would be to ask around in the forums: http://forum.haxeflixel.com/