Blasting

One of the main things that make Lode Runner different from other platformers is being able to blast a hole and leap into it.


Blast!

I thought this would be easy - something I could do quickly and move on to something else. Boy, was I wrong!

How does Lode Runner do it?

Good question, I'm glad you asked. Lode Runner has one spritesheet for each theme that contains themed objects and one that contains shared animations that are used across all themes like gold/tool sparkle, doors, and masks.
A tile gets blended with a mask and played forward, in reverse, or flipped backwards.

Not so easy

Firstly, I asked myself "How did Presage do this in the early 90's?". Here's what I came up with:

  1. They used some sort of blending (obvious)
  2. It was written by professional programmers who knew what they were doing

Most likely they would have blit the images to the screen using a mask. However, this technique is not available in XNA.
A quick web search shows many people strongly recommending against CPU based aphla blending and instead doing it via a shader. Using a shader is easy. Using a shader with 2 different textures and sampling from 2 different texture coordinates isn't.
The results I got ranged from no blending to blending from completely wrong coordinates.


I have no idea what was blending with what!

I swapped out the proper mask sheet with the below test image and just sampled the mask colour. This way made it easy to see exactly what part of the mask was being used.


The star was the target & the surrounding mess was a guide


A step closer but the squares shouldn't be different at each blast

Solution

After tinkering around with it for a while, I finally got blasting to work - it's all about how you combine the coordinates of the tile and mask.
Getting it to flip backwards (blasting from the left) was the next hurdle. Turns out, flipping the texture that's passed into a shader is incredibly simple. Flipping textures (mask) that aren't automatically passed in is incredibly difficult.

The solution I came up with was to:

  1. Flip the tile horizontally in the shader
  2. Blend with mask
  3. Flip the resulting image horizontally in XNA


Blasting from the right and the left. The purple tile is done in XNA.

If anyone's interested, here's the complete shader and code to flip via XNA:

// Texture sent from LR.
uniform extern texture ScreenTexture;
sampler screen : register(s0) = sampler_state
{
    Texture = 	<ScreenTexture>;
    MinFilter = POINT; // Stops pixels bleeding together.
    MagFilter = POINT;
};

// Alpha map (mask) texture to blend with.
uniform extern texture maskTexture;
sampler mask : register(s1) = sampler_state
{
    Texture = 	<maskTexture>;
    MinFilter = POINT;
    MagFilter = POINT;
};

float2 spritePos; // Screen coordinates of the tile.
float2 maskPos; // Screen coordinates of the mask.
bool flip;

float4 PixelShaderFunction(float2 inCoord: TEXCOORD0) : COLOR
{

    // Convert the mask position to 0-1 texels.
    float2 maskTexel = (maskPos - spritePos) / 512;
    float2 pos = inCoord + maskTexel;

    // Flip the sprite horizontally - this will be corrected during SpriteBatch.Draw.
    float x = inCoord.x;
    if (flip) x = (spritePos.x / 512) + (((spritePos.x + 22) / 512) - inCoord.x);

    // Blend!
    float4 color = tex2D(screen, float2(x, inCoord.y));
    if (maskPos.x < 0) return color; // used for blast anim effect.
    float4 maskColor = tex2D(mask, pos);
    if (maskColor.a > 0) color.rgba = 0;
    return color;

}

technique
{
    pass P0
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
    spriteBatch.Draw(texture, pos, source, Color.White, 0, Vector2.Zero, 1, SpriteEffects.FlipHorizontally, 0)) 

UPDATE 30/11/16:

I've had spare time now that the game is near complete so I revisited this 'hacky' solution.
The mask is now flipped horizontally in the pixel shader, removing the need to flip the tile instead and then flip it back in the SpriteBatch draw.
You'll notice that scaling has also been applied - this is due to the sprite sheets now being different sizes since we've added extra content like Jane & Digmo and all their gold and items.

Updated code for your perusal:

float4 PixelShaderFunction(float2 inCoord: TEXCOORD0) : COLOR
{

	// Convert the mask position to 0-1 texels.
	float2 maskTexel = (maskPos - spritePos) / 512;
	float2 pos = (inCoord + maskTexel) * scale;

	// Flip the mask horizontally.
	if (flip) {
		float maskRight = ((maskPos.x + 22) + spritePos.x) / 512;
		pos = float2((maskRight.x - inCoord.x) * scale, pos.y);
	}

	// Blend!
	float4 color = tex2D(screen, inCoord);
	if (maskPos.x < 0) return color; // used for blast anim effect.
	float4 maskColor = tex2D(mask, pos);
	if (maskColor.a > 0) color.rgba = 0;
	return color;

}