The TRANSPARENCY Extension! šŸ‘ļøā€šŸ—Øļø

rando-muser/arcade-transparency: A MakeCode Arcade extension to render semi-transparent sprites.

Have you ever wanted to make a ā€˜semi-transparent’ or translucent sprite, where you can see the tilemap through them? :eyes:

Thanks to some math (Inspired by the lanterns extension and with many user’s help in this thread) now you can! :partying_face:

Just use the ā€˜Make Transparent’ block to make your sprite see-through, or expand it to specify an ā€˜opacity’-- That’s how opaque the spright looks, from 0 (can’t see the sprite at all, only the background) to 100 (the sprite is fully visible, you can’t see the background at all).

You can also use ā€˜remove transparency’ to undo the effect, ā€˜toggle transparency’ to switch whether a sprite is transparent, and the ā€˜is transparent’ boolean to check if your sprite is currently transparent or not.

Here’s a demo of what the extension can do (A to toggle transparency, hold B to fade in and out, WASD to move, menu to test the isTransparent block):

It would be a good extension to use for:

  • Stealthy, ghost-like characters blending into the background
  • Translucent objects like glass and water
  • Clouds, fog, and colorful smoke to add ambiance to a scene

I would love to hear of any bug reports or more features you want added to the extension, and especially anything you make with it! :slight_smile:

rando-muser/arcade-transparency: A MakeCode Arcade extension to render semi-transparent sprites.

25 Likes

Noice

4 Likes

Ms president you are goated

4 Likes

wow, this effect looks AMAZING! just perused your code a bit and you did an awesome job!

i’ve got some tips/suggestions which you can choose to take or leave

Use the //% whenUsed annotation

whenever you declare a variable inside the top level of a namespace (that is to say, inside a namespace but not inside a function), you should add a //% whenUsed annotation above it. this annotation tells the compiler to only include that variable in the code if it’s actually reference in the user’s program.

for example, you could add it to each of the variables here

Cache colors to speed things up

the work you’re doing here is pretty expensive since it’s being done once per pixel for every transparent sprite on every frame. if you want to speed things up, i’d recommend caching the ideal color for each color combo so that you don’t have to constantly recalculate the lowest distance.

here’s an idea for how to write a function that caches the color:

let colorCache = control.createBuffer(256);

function lookupColor(spriteColorIndex: number, backgroundColorIndex: number) {
    const cacheIndex = (spriteColorIndex * 16) + backgroundColorIndex;

    if (colorCache[cacheIndex] === 0) {
        colorCache[cacheIndex] = calculateLowestDistanceColor(spriteColorIndex, backgroundColorIndex)
    }

    return colorCache[cacheIndex]
}
A handy bitshifting trick

looks like you’re doing a lot of work here to extract the red, green, and blue values of each color in the palette. there’s actually a much easier way to extract those numbers using bitshifting!

note: bitshifting can be a little tough to wrap your head around if you aren’t used to thinking about bytes/numbers in terms of 1’s and 0’s. if you don’t understand the comments in the code below, don’t worry about it! i think this is a concept that is much easier to understand with some diagrams/images that explain things

// integers are 32 bits, meaning they contain 4 bytes of data. the number
// returned by p.color() uses the lower 3 bytes of that data to store the
// red, green, and blue color values for the palette color.
// 
// it looks like this:
// byte 1 (bits 0-7)   is the BLUE channel
// byte 2 (bits 8-15)  is the GREEN channel
// byte 3 (bits 16-23) is the RED channel
// byte 4 (bits 24-31) is unused (all 0s)
// 
// or, if we were to write out the individual bits, it would look like
// this:
// 0000 0000 RRRR RRRR GGGG GGGG BBBB BBBB
// 
// note that we're writing this from highest bit to lowest bit, so all the
// blue bits are on the right of our number!
function unpackColor(color: number) {
    // first, let's extract our blue value, to do that we want to grab the 
    // lowest byte. luckily, there's an easy way to do this using the bitwise & operator!
    // 
    // the & operator takes all the bits in the left and right integers and returns
    // a number where only the bits that are 1 in both inputs are set.
    // 
    // for example: 01010101 & 00001111 = 00000101
    // there are only two bits that are 1 in both numbers, so those are the only
    // ones set in the result!
    // 
    // another way of thinking about the & operator is that you can use the right
    // number to "filter" out bits from the left number. if we want to get the lower
    // 8 bits of our number, we just need to & it with something that has only the lower
    // 8 bits set to 1!
    //
    // in other words:
    // 0000 0000 RRRR RRRR GGGG GGGG BBBB BBBB &
    // 0000 0000 0000 0000 0000 0000 1111 1111 =
    // 0000 0000 0000 0000 0000 0000 BBBB BBBB
    //
    // we can write out that number with only the lowest 8 bits set as 0xff
    const blue = color & 0xff
    
    // the right shift operator ">>" shifts all the bits in a number
    // to the right by the number you specify. all of the bits that
    // get shifted past the lowest bit are chopped off and replaced with
    // 0s on the left side.
    //
    // for example: 0110 1001 >> 4 = 0000 0110
    // we shifted the upper 4 bits (0110) to the right and the lower 4 bits
    // were chopped off!
    //
    // to get our green value, we can move all of our bytes "down" by shifting
    // them to the right by a multiple of 8. in this case, we want to move down 1
    // byte, so we shift to the right by 8.
    //
    // just like with our blue value, we also use & 0xff to chop off everything but
    // the lowest 8 bits
    const green = (color >> 8) & 0xff

    // finally, we can get the red byte by shifting "down" two bytes (aka 16 bits).
    // for this one, we don't need to bother using the & operator since we know that
    // all the bits above the red byte are already 0s
    const red = color >> 16;

    return [red, green, blue]
}
Getting the color underneath a pixel

i like the trick you did here for getting the color underneath a pixel! however, as i’m sure you’ve realized, this won’t work for sprites on top of other sprites. if that’s a scenario you’re interested in supporting, then i’ve got an idea you can use to do it!

my arcade-drawing-utils extension has an API that can be used to add code that gets called right before a sprite is drawn to the screen. using that, you can read the background colors directly off of the screen image!

drawing.renderOnSprite(mySprite, drawing.RenderOrder.Below, () => {
    // don't actually draw anything here, just update the transparent image.
    // this code will run as the screen is being rendered, so instead of your
    // getColor(x, y) function, you can just use screen.getPixel(x, y)
})
7 Likes

Ah! Finally we can add ghosts to our games! And glass… and fancy fog effects maybe?? I’m definitely going to be looking at the code to see if some other effects could be made with a few edits, such as tinting things a certain color (blue for waterfalls?!?)

6 Likes

so like opacity

3 Likes

now you can basicly make differ colors with that! or shades I guess but nice!

1 Like

does this work with part of a sprite? (like a fly)

2 Likes

This is the extension we needed thank you. For this gift of awesomenesstisism

1 Like

awesomeness tism bro what

1 Like

So cool! Now I want to make a cloaking system.

1 Like

Random use u still didn’t add this to your bio

1 Like

Amazing extension! You can also use them as heat waves if you put transparency to zero.

3 Likes

I’m going to use it as a steam effect for my Detective Beetle game!
Really nice, randomuser

3 Likes

I’d love to use this as shadows, but I’m afraid it’ll gut the performance

2 Likes

I may use this in cube labs if its compatible with all of the other extensions im using and if the performance stays good.

Woah, thank you so much, I’m really glad you like it! :blush:

#1: Added, I’m glad that exists to help clear up some storage!
#2: Ooo, I hadn’t thought of that, that is great for optimization! The one snag with it is that the opacity of the sprite changes, which has to be factored into calculating the lowest distance color, meaning each opacity would have to have its own buffer.
However, since 50% opacity should have the best contrast and is the default opacity, I’ve added a cache for that opacity & as a block for users to force the code to create a buffer for a certain opacity. LMK if you think a buffer for each opacity would be worth the storage :thinking:
#3: Woahh that is an elegant solution (And explanation of it!), I added that, it will save so much processing power.
#4: That sounds like exactly what I need here, but I don’t think I know how to implement it. I have this code running (just in the namespace, not within any function or loop) and it gives me the error ā€˜assertion failed’. I think it might have to do with the fact that I need to update which sprites are using the drawing.renderOnSprite code each frame, but that code is designed to be set once. (if I run the drawing.renderOnSprite code every frame, the screen goes black and freezes)


Let me know if you know how to implement that code while being able to update which sprites it should run on!