Shaders and transitions
Everything so far has been about what I draw. This lesson is about how it looks once it’s drawn: a whole layer tinted for nighttime, a sprite that flashes white when it’s hit, a screen that melts away between one part of a game and the next.
A shader
A shader is a little program that runs once for every pixel of a layer or a
sprite, on the graphics chip, and decides what color that pixel ends up. You
write one in a SHADER block, and give it a name.
SCREEN 320, 240
SHADER "night"
USE NUMBER dark DEFAULT 0.5
WHEN DRAWING
c = IMAGE
SET COLOR = RGBA(c.r * (1.0 - dark), c.g * (1.0 - dark), c.b, c.a)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("night") shader_params({dark: 0.6})
PAUSE
Inside WHEN DRAWING, IMAGE is the pixel’s color as it was drawn, and
whatever you SET COLOR to is what shows. Colors in here are RGBA with
each part from 0 to 1, so c.r * 0.4 is red at forty percent. This one
turns the red and the green down and leaves the blue, and the city goes
blue and dark.
USE NUMBER is a setting the shader takes, with the value it has when
nobody says. SET LAYER with !! shader(...) puts the shader on the
layer, and shader_params gives it its settings by name.
Crash-tan’s Tip: A shader’s body is a language of its own, the one the graphics chip speaks, and I hand it over as you wrote it. It has
MIX,SIN,CLAMP,STEPand friends, but none of your program’s variables: anything it needs from your game comes in through aUSEsetting.
Changing a setting as you go
The settings can change while the shader runs. Here the sun goes down and
comes back up, by changing nothing but dark. The settings go with the
shader they’re for, so every tick says both:
SCREEN 320, 240
SHADER "night"
USE NUMBER dark DEFAULT 0.5
WHEN DRAWING
c = IMAGE
SET COLOR = RGBA(c.r * (1.0 - dark), c.g * (1.0 - dark), c.b, c.a)
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
hour = 0
DO
IF COOLDOWN(1)
hour = hour + 0.01
SET LAYER 0 !! shader("night") shader_params({dark: 0.35 + sin(hour) * 0.35})
END IF
LOOP
SET LAYER 0 !! clear_shader takes it off again.
A shader can also keep time by itself. TIME is how many seconds the game
has been going, and OWNTEXTURE reads the pixel from somewhere else in the
same picture. Read it from a little to the side, by a different amount on
every row, and the city ripples like it’s under water:
SCREEN 320, 240
SHADER "water"
USE NUMBER size DEFAULT 0.01
WHEN DRAWING
c = OWNTEXTURE(VEC2(UV.x + SIN(UV.y * 30.0 + TIME * 3.0) * size, UV.y))
SET COLOR = c
END WHEN
END SHADER
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
SET LAYER 0 !! shader("water")
PAUSE
UV is where the pixel is, from (0, 0) at the top left to (1, 1) at
the bottom right. Nothing in the program moves: the ripple is all the
shader’s.
A shader on a sprite
A sprite takes a shader the same way, with SET SPRITE. Here are three
heroes: one as it is, and two sharing a shader, at half strength and all
the way.
SCREEN 320, 240
SHADER "flash"
USE NUMBER amount DEFAULT 0.0
WHEN DRAWING
c = IMAGE
white = RGBA(1.0, 1.0, 1.0, c.a)
SET COLOR = MIX(c, white, amount)
END WHEN
END SHADER
LOADIMG "hero.png", "crashtan"
ENABLE SPRITE 0
SET SPRITE 0 !! image("crashtan") x(40) y(80) scale((2, 2))
SHOW SPRITE 0
ENABLE SPRITE 1
SET SPRITE 1 !! image("crashtan") x(120) y(80) scale((2, 2))
!! shader("flash") shader_params({amount: 0.5})
SHOW SPRITE 1
ENABLE SPRITE 2
SET SPRITE 2 !! image("crashtan") x(200) y(80) scale((2, 2))
!! shader("flash") shader_params({amount: 1})
SHOW SPRITE 2
PAUSE
MIX(c, white, amount) is the way from one color to another: none of the
way at 0, all of it at 1. Keeping c.a, the pixel’s alpha, keeps the
see-through parts of the sprite see-through, so it’s the hero that turns
white and not the whole square.
Flashing from a behavior
A shader can change over time on its own, too. USE SHADER, in a behavior,
runs a shader while the behavior does, and takes one of its settings from
one value to another over some ticks. With !! loop it goes back and forth
for as long as the sprite has the behavior: a glow for a shield, or a power
that’s about to run out.
SCREEN 320, 240
SHADER "flash"
USE NUMBER amount DEFAULT 0.0
WHEN DRAWING
c = IMAGE
white = RGBA(1.0, 1.0, 1.0, c.a)
SET COLOR = MIX(c, white, amount)
END WHEN
END SHADER
BEHAVIOR "shielded"
USE SHADER "flash" !! param("amount") start(0) to(0.7) duration(30) loop
END BEHAVIOR
LOADIMG "hero.png", "crashtan"
ENABLE SPRITE 0
SET SPRITE 0 !! image("crashtan") x(120) y(80) scale((2, 2))
!! behavior("shielded")
SHOW SPRITE 0
PAUSE
The hero glows white and back, every half second, with the shield on.
Without !! loop, it would go from start to to once, and stay there.
Clearing with style
CLS clears a layer at once. With !! effect, it takes its time about it:
"fade", "melt", "block", "dissolve", "wipex" or "wipey". My
renderer runs the effect by itself, and while it does, transition says
which one it is. When it’s over, transition is "".
SCREEN 320, 240
LOADIMG "city.png", "city"
PUTIMAGE (32, 0), "city"
CLS 0 !! effect("melt")
PRINT "now running:", transition
now running: MELT
Here’s every one of them in turn. The city goes up, stays for a second, and goes away with the next effect:
SCREEN 320, 240
LOADIMG "city.png", "city"
effects = ["fade", "melt", "block", "dissolve", "wipex", "wipey"]
n = 0
showing = FALSE
DO
IF COOLDOWN(60) AND transition = ""
IF showing
CLS 0 !! effect(effects(n))
n = (n + 1) MOD LEN(effects)
ELSE
FRAME
CLS
PUTIMAGE (32, 0), "city"
LOCATE 1, 1
PRINT "next:", effects(n)
END FRAME
END IF
showing = NOT showing
END IF
LOOP
!! speed says how fast an effect goes, and !! size how big the blocks
are for "block". Each layer runs a transition of its own, and
transition(1) asks about layer 1, whichever layer you’re drawing on.
Crash-tan’s Tip: A transition doesn’t stop your program: it runs on my renderer while your code goes on. When the next thing should wait for it, wait in your loop until
transition = "", the way this one does before it draws the city again.
Clearing to a picture
!! to_image clears a layer to a picture instead of a color. With an
effect, that’s a transition from what’s there into the picture: a dissolve
from black into the title screen, say.
SCREEN 320, 240
LOADIMG "city.png", "city"
CLS 0 !! effect("dissolve") to_image("city")
PAUSE
Shaders and transitions are how a game looks between its parts. The parts themselves, the title screen, the level, the game over, are scenes, and they’re next.