~written by Crash-tan~
A shader one! Transitions you write yourself finally move,
SCREEN counts the same pixels as SCREEN_SIZE,
USE COLOR works, and a shader with a mistake in it stops
and tells you what the mistake is, instead of quietly drawing something
else.
Not only shaders, though: blue that drew red, text that was always
white on Windows, videos with their reds and blues swapped, maps and
layers that didn’t reach the edges with LETTERBOX OFF, and
a JavaScript timer that ticked once after it was stopped are all put
right too. And there’s UUID, a name nothing else will ever
have.
CLS !! effect_shader("name") clears a layer with a
transition you wrote: a shader with a USE NUMBER progress
setting, which I take from 0 to 1. Except I didn’t. The layer sat there
unchanged for the whole transition, and then cleared all at once.
A shader’s settings are named in capitals once I’ve read them, so
yours was PROGRESS, and I was setting
progress. The names never matched, and
progress stayed at its DEFAULT. The same went
for target_r, target_g, target_b
and target_a, the color the layer is clearing to.
Now a setting’s name matches whatever case it’s written in, everywhere I set one, so your transition runs from start to finish:
SHADER "curtain"
USE NUMBER progress DEFAULT 0
WHEN DRAWING
c = IMAGE
closed = STEP(1.0 - progress, ABS(UV.x - 0.5) * 2.0)
SET COLOR = MIX(c, RGB(0.5, 0.0, 0.1), closed)
END WHEN
END SHADER
CLS EFFECT("curtain")
CLS 0 !! effect_shader("curtain")
SCREEN in a shader is where the pixel is on the screen.
It counted the window’s pixels, though, while
SCREEN_SIZE counts the game’s. In a window the
game’s size, they agree. In a window twice as big, SCREEN
went up to twice SCREEN_SIZE, so
SCREEN / SCREEN_SIZE reached 1 halfway across, and
screen-space effects like scanlines came out a different size in every
window. A letterbox’s black bars were counted too, so the top left of
your game wasn’t (0, 0).
Now SCREEN is in the game’s pixels, the same ones
SCREEN_SIZE counts, from (0, 0) at the top
left of the game. On a 320x240 game it runs from 0 to 320 across,
however big the window is, on desktop, phones, TVs and the web:
SHADER "dusk"
WHEN DRAWING
down = SCREEN.y / SCREEN_SIZE.y
SET COLOR = MIX(IMAGE, RGB(0.9, 0.4, 0.1), down * 0.6)
END WHEN
END SHADER
If you divided SCREEN by the window’s scale yourself to
make up for it, take that out.
USE COLOR declares a color setting, and it was broken
three ways. Its DEFAULT was never used, so it always
started as nothing at all. It was put in the wrong place, overlapping
the setting before it. And there was no way to give it a color from your
program.
Now it works. Its DEFAULT is RGB(r, g, b)
or RGBA(r, g, b, a), and without one it’s opaque black.
From your program, give it three numbers from 0 to 1, red, green and
blue, or four with the alpha, as a coordinate or a list:
SHADER "tint"
USE COLOR tint DEFAULT RGB(1.0, 1.0, 1.0)
WHEN DRAWING
c = IMAGE
SET COLOR = RGBA(c.r * tint.r, c.g * tint.g, c.b * tint.b, c.a)
END WHEN
END SHADER
SET SPRITE 1 SHADER "tint", @tint=(1.0, 0.4, 0.4)
SET SPRITE 1 !! shader("tint") shader_params({tint: (1.0, 0.4, 0.4)})
SET LAYER 0 !! shader("tint") shader_params({tint: [0.4, 0.4, 1.0, 0.5]})
With three numbers, the alpha is 1. In Python, it’s a tuple or a
list: shader_params={"tint": (1.0, 0.4, 0.4)}. A color
takes four of the 64 numbers a shader’s settings have room for.
A lot of shader mistakes used to do something, just not what you meant. Now each one stops your program, says what was wrong, and says what would be right.
When I build the shader:
RGBA with three values drew black. MIN or
STEP with one value gave you something made up, and
ABS with two ignored the second. Now I say, for example,
“RGBA takes 4 values, RGBA(r, g, b, a), and got 3”.USE settings, but not set them.SAMPLE on something that isn’t a
USE IMAGE, like SAMPLE(IMAGE, UV)
(that’s OWNTEXTURE(UV)), and a USE IMAGE used
as if it were a color.in, var, type,
target or smooth, used for a variable or a
setting.When you put a shader on something:
shader("nope"), on a sprite, a layer, a shape or a
particle, and CLS !! effect_shader("nope"), stop and list
the shaders you do have. If you got only the case wrong, I tell you the
name it was defined with.shader_params({gren: 0.4}) on a shader with
red, green and blue used to set
nothing. Now it stops, and lists the settings that shader takes. The
same goes for @gren=0.4 in the first BASIC.USE NUMBER, a number for a USE COLOR, a number
for a USE IMAGE.shader_params on its own, on a layer with no
shader. It changes the numbers of the shader that’s on, so
there has to be one. A layer that has just run a CLS
transition has none, because a transition takes the layer’s shader off
when it finishes.Two things a shader can’t have used to get my most puzzled error, “I got confused by the syntax here”. Now they get their own:
DEFAULT.
USE NUMBER drift DEFAULT -0.5 still isn’t allowed, but now
I say that’s the problem, and how to get a negative setting anyway: give
it the value when you put the shader on, or turn it round in the
shader.timer, uvs and
imagex start with TIME, UV and
IMAGE, so I read the word and then don’t know what to do
with the rest. Now I say which word the name starts with, and suggest
another name.A NULLS value or a USE line’s
DEFAULT can’t be negative either.
NULLS seconds = -1 wasn’t read as a NULLS line
at all, so it went to the database as part of the SQL, and the database
said “near”NULLS”: syntax error” the first time you ran it. The
same went for any USE or NULLS line I couldn’t
read.
Now I stop at the block itself, show you the line, and say what’s
wrong with it. For a negative one, I suggest a value of 0 or more, or
letting the SQL choose it:
SELECT COALESCE(seconds, -1) AS seconds.
Some shader mistakes only the graphics chip can find, nearly always
values of different sizes in one sum or function: a color plus a place,
or MIN of a color and a number. When it refused one, I
added “This may be a bug in the shader compiler. Please report this
issue.”, even when the mistake was in the shader.
Now I say what that kind of refusal usually means, in the shader
language’s words: values of different sizes in a function or a sum, a
swizzle of a part that isn’t there (UV.z), or
SET COLOR given fewer than four parts. The chip’s own
message comes with it, and it’s now the whole message, with the line it
points at, rather than just “Entry point fs_main at Fragment is
invalid”.
On a lot of computers, Macs among them, the graphics chip keeps a picture’s colors blue first. Some of what I put into a picture went in red first, so red and blue swapped places:
PSET on a layer. Palette blue drew
red, and red drew blue.SET IMAGE.
Whatever was in the picture already, from a MEMORYBUFFER,
LOADIMG or anything else, swapped its reds and blues the
moment SET IMAGE started drawing into it. What
SET IMAGE drew itself came out right, so this drew a red
ring around a blue one:SCREEN 320, 240
MEMORYBUFFER "rings", 64, 64
CIRCLE (32, 32), 31, 1
END MEMORYBUFFER
SET IMAGE "rings"
CIRCLE (32, 32), 20, 1
SET LAYER 0
PUTIMAGE (0, 0), "rings"
Now both rings are blue. The same went for the colors you give with
SET PALETTE OFF, and they’re right now too.
On Windows, everything you PRINTed came out white,
whatever COLOR said. The background color worked, so
COLOR 7, 4 gave you white words on red instead of grey
ones. I keep my letters white and paint them as I draw them, and on
Windows I forgot the paint.
Now text is the color you asked for, on Windows like everywhere else:
SCREEN 640, 480
PRINT "Hello World"
COLOR 4
PRINT "Hello World"
COLOR 7, 4
PRINT "Hello World"
A video’s frames come from a different place on every kind of computer, and some of them come blue first while others come red first. I took them all to be blue first, so on Android, and in the web player, a video’s reds and blues were swapped: a red car drove by blue.
Now every frame says which way round it is, and I put it up the right way.
UUID gives you a name nothing else will ever have: 36
characters, like 3f2b8c1e-9d4a-4e7b-a6c5-1b2d3e4f5a6b, and
a new one every time you read it. It’s handy for naming something that
mustn’t clash with anything else, like a save slot or a player’s
record:
id$ = UUID
PRINT "Save slot: "; id$
SCREEN 320, 240
slot = uuid
PRINT "Save slot", slot
It comes from the computer’s own randomness, not from
RND, so RANDOMIZE doesn’t make it repeat. In
Python, JavaScript and Lua, it’s crash.uuid.
With LETTERBOX OFF, your game’s screen sits on the same
pixels it always does, and the rest of the window is yours to draw on
too. A map stopped at the edge of the screen, though, even when there
was more of it, and the window beside it stayed black. And three kinds
of layer did reach the edges of the window, but stretched to its shape,
so in a wide one a circle came out an egg:
AFFINE or
PERSPECTIVEWRAPX or WRAPYSCREEN 320, 240 !! letterbox(FALSE)
CREATE LAYER 1, 320, 240
SET LAYER 1
CIRCLE (160, 120), 80, 14
SET LAYER 1 !! plane("affine") zoom(1) wrap_x wrap_y
SET SCROLL 160, 120 !! angle(0)
Now all of them go edge to edge, the right shape: a map carries on
past your screen as far as it goes, a plane goes on to the window’s
edges, and a wrapped layer tiles across the whole window. What’s on your
screen doesn’t move. A map or a wrapped layer with a SHADER
of its own still stops at the edge of the screen for now, the right
shape.
In JavaScript, a timer, an event or a render callback that came while
your program was waiting on an await could stop part-way,
and the rest of your program ran in the middle of it. So
clearInterval could return, and the interval still tick
once more afterwards, about one time in fifteen:
let ticks = 0;
const ticking = setInterval(() => { ticks += 1; }, 5);
while (ticks < 2) await crash.yield();
clearInterval(ticking);
const stopped_at = ticks;
await crash.delay(40);
console.log(ticks === stopped_at); // sometimes false!Now a callback runs to the end before your program carries on, the
way JavaScript always promises, and that’s true every
time.
What’s inside a SHADER or SQL block isn’t
BASIC, so the editor used to color all of it like one long string. Now
it’s colored as what it is, in the IDE, in the book and anywhere else I
color code.
In a shader, USE, DEFAULT,
WHEN DRAWING and SET COLOR are keywords, and
MIX, RGB, UV, IMAGE
and the rest of what a shader gives you are built-ins. In a SQL block,
the USE and NULLS lines are colored like
BASIC, and the statement after them like SQL: SELECT and
WHERE are keywords, 'it''s' is a string,
-- like this is a comment, and @least is your
parameter.
That fixes one thing in Crash BASIC 1 as well. There, a
' in a SQL block started a BASIC comment, so a string like
'it''s' was colored as a comment right to the end of the
line.
The help for SHADER used to say what a shader can do
only in Crash BASIC 1. Now the help in Crash BASIC 2, Python, JavaScript
and Lua says it too: what USE NUMBER,
USE COLOR and USE IMAGE take, that a
DEFAULT can’t be negative, what SCREEN counts,
and which mistakes stop your program. The help for SQL says
the same about a NULLS value, in both Crash BASICs.
Every example in the help, in every language, now shows what it does when you try it. Some never opened a screen, so what they printed went nowhere; some made a sprite and never showed it; and some printed underneath a map, which covered the words up. And they all use the same pictures, sounds and maps as the books now: me running, the courtyard, a real explosion, rather than a couple of colored squares.
A few CIRCLE examples gave TRUE where the
fill color goes. The fill is a color, so in palette mode that filled the
circle blue, whatever its outline was, and with
SET PALETTE OFF it was a fill you couldn’t see. They all
give a real color now.
There’s a new book, Shaders with Crash, all about writing
your own shaders. Its cookbook has a recipe for a glow,
where the bright parts of the picture shine onto the dark around them.
Making Games with Crash has a new chapter,
Databases, about keeping scores, saves and shops in a
database: what CLOSE does, where a database is kept,
changing what’s in one, and NULLS. And its chapter on
layers now shows sprites standing on a floor that goes off into the
distance, and jumping off it.
In the books, an example with a mistake in it on purpose now says so above it, and the very next one is the same example put right, so you can run into the mistake and then into the fix. A page opens on its first example that works, instead of greeting you with an error.
All the books are in one place now too, the Crash Library: Learn Crash BASIC, Making Games with Crash, Shaders with Crash, and the Crash BASIC 2 reference, as a book of its own where every example runs with Try it.
The shader cookbook said PX(x, y) divides by the
screen’s size, so that a shader couldn’t count its own picture’s pixels
without being told how big it is. That was wrong. PX counts
the pixels of the picture the shader is drawing: the sprite’s current
frame, or the layer. So OWNTEXTURE(UV + PX(1.0, 0.0)) is
always the next pixel to the right, however big the sprite is and
however far it’s scaled. Only a USE IMAGE picture’s size
has to be passed in. The cookbook’s scrolling recipe uses
PX now, and doesn’t need its picture’s height any more.