As part of the Gameduino Kickstarter campaign, four rewards were "I'll hack up your game idea." Watterott Electronic took one of the rewards, and simply asked for a Gameduino version of their logo, animated.
The background layer is a simple black-to-gray color ramp, using the same program as the bgcolor demo.
There's a little more code in the loop to make the ramp fade in and out.
It seemed a nice idea to make the Watterott spark animate. Starting with a static version of the spark, five different versions with the points moved slightly:
painting each 25x12 character version on sucessive frames - and adding a 1 pixel random scroll - gives a constantly jittery spark:
#include <SPI.h>
#include <GD.h>
// ----------------------------------------------------------------------
// qrand: quick random numbers
// ----------------------------------------------------------------------
static uint16_t lfsr = 1;
static void qrandSeed(int seed)
{
if (seed) {
lfsr = seed;
} else {
lfsr = 0x947;
}
}
static byte qrand1() // a random bit
{
lfsr = (lfsr >> 1) ^ (-(lfsr & 1) & 0xb400);
return lfsr & 1;
}
static byte qrand(byte n) // n random bits
{
byte r = 0;
while (n--)
r = (r << 1) | qrand1();
return r;
}
// ----------------------------------------------------------------------
#include "watterott_logo.h"
#include "bgstripes.h"
int focusx, focusy;
// JCB-PARTICLE-A
#define NSPR 200
struct spr {
int x, y;
signed char vx, vy;
} sprites[NSPR];
static void born(byte i)
{
sprites[i].x = focusx + qrand(4);
sprites[i].y = focusy -16 + qrand(5);
sprites[i].vx = -8 + qrand(4);
sprites[i].vy = -qrand(4) - 1;
}
static void kill(byte i)
{
sprites[i].y = 309;
sprites[i].vx = 0;
sprites[i].vy = 0;
}
// JCB-PARTICLE-B
int atxy(int x, int y)
{
return (y << 6) + x;
}
static byte sprcnt = 0;
void setup()
{
GD.begin();
// Black-to-gray transition starting at line 150
GD.microcode(bgstripes_code, sizeof(bgstripes_code));
GD.wr16(COMM+0, 112);
GD.uncompress(RAM_CHR, logobg_chr_compressed);
GD.copy(PALETTE4A, spark_palette, sizeof(spark_palette));
GD.uncompress(RAM_SPRIMG, wlogo_compressed);
}
void bgfade(byte t)
{
for (byte i = 0; i < 64; i++) {
byte l = ((t == 31) ? 8 : 0) + ((i * t) >> 4);
GD.wr16(0x3e80 + 2 * i, RGB(l, l, l));
}
}
static void fade(uint16_t addr)
{
uint16_t col = GD.rd16(addr);
if (col & (31 << 10)) col -= (1 << 10);
if (col & (31 << 5)) col -= (1 << 5);
if (col & 31) col -= 1;
GD.wr16(addr, col);
}
// Fade 16 palette entries starting at addr
static void fade16(uint16_t addr)
{
uint16_t cols[16];
GD.__start(addr);
for (byte i = 0; i < 16; i++) {
byte lo = SPI.transfer(0);
byte hi = SPI.transfer(0);
uint16_t col = (hi << 8) | lo;
if (col & (31 << 10)) col -= (1 << 10);
if (col & (31 << 5)) col -= (1 << 5);
if (col & 31) col -= 1;
cols[i] = col;
}
GD.__end();
GD.__wstart(addr);
for (byte i = 0; i < 16; i++) {
uint16_t col = cols[i];
SPI.transfer(lowByte(col));
SPI.transfer(highByte(col));
}
GD.__end();
}
#define FINALX 305
void loop()
{
focusy = 138;
sprcnt = 0;
for (byte i = 0; i < NSPR; i++) {
kill(i);
}
GD.__wstartspr(0);
for (int i = 0; i < 512; i++)
GD.xhide();
GD.__end();
GD.copy(PALETTE16A, wlogo_palette, sizeof(wlogo_palette));
GD.uncompress(RAM_PAL, logobg_pal_compressed);
byte jitter = 0;
for (int t = 0; t < 8 * 72; t++) {
byte frame = (t & 1);
if (t < 32)
bgfade(t);
if (((8 * 72) - 32) < t) {
bgfade(8 * 72 - t);
for (uint16_t a = RAM_PAL; a < RAM_PAL + (256 * 4 * 2); a += 32)
fade16(a);
fade16(PALETTE16A);
}
byte sparking = (72 <= t) && (t <= (72 + FINALX));
if (sparking) {
focusx = t - 72;
if (16 <= focusx && ((focusx & 15) == 0)) {
GD.__wstartspr(sprcnt);
draw_wlogo(focusx, 109, (focusx - 16) >> 4, 0);
GD.__end();
GD.__wstartspr(256 | sprcnt);
draw_wlogo(focusx, 109, (focusx - 16) >> 4, 0);
GD.__end();
sprcnt += GD.spr;
}
}
#if 1
GD.__wstartspr((frame ? 256 : 0) + 56);
byte i;
// JCB-SPARK-A
struct spr *ps;
for (i = 0, ps = &sprites[i]; i < NSPR; i++, ps++) {
draw_spark(ps->x, ps->y, 0, (i & 7));
ps->x += ps->vx;
ps->y += ps->vy;
ps->vy++;
if ((ps->x < 0) || (ps->x > 400) || (ps->y > 310)) {
if (sparking)
born(i);
else
kill(i);
}
}
// JCB-SPARK-B
GD.__end();
#endif
GD.waitvblank();
GD.wr(SPR_PAGE, frame);
// JCB-JITTER-A
GD.wr16(SCROLL_X, random(-1, 2));
GD.wr16(SCROLL_Y, random(-1, 2));
prog_uchar *src = logobg_pic + (12 * 25 * jitter);
for (byte y = 0; y < 12; y++) {
GD.copy(atxy(24, 12 + y), src, 25);
src += 25;
}
// JCB-JITTER-B
jitter = (jitter == 4) ? 0 : (jitter + 1);
}
delay(700);
}
The logo itself is a sprite layer, sourced from this image:
and encoded as 16-color sprites. Each animation frame is 16x48 pixels, so revealing the logo from left-to-right is done by drawing animation frame 0, then frame 1 is drawn 16 pixels to the right, etc.
Taking a single 16x16 version of the spark:
and running a simple system of 200 particles completes the effect. Each particle is 'born' at the same X-coordinate as the logo reveal, falls across the screen with a random velocity, and 'dies' when it passes the bottom of the screen. The sprite drawing is double-buffered using the SPR_PAGE register so there is no tearing.
Keeping track of the spark position and velocity:
#define NSPR 200
struct spr {
int x, y;
signed char vx, vy;
} sprites[NSPR];
static void born(byte i)
{
sprites[i].x = focusx + qrand(4);
sprites[i].y = focusy -16 + qrand(5);
sprites[i].vx = -8 + qrand(4);
sprites[i].vy = -qrand(4) - 1;
}
static void kill(byte i)
{
sprites[i].y = 309;
sprites[i].vx = 0;
sprites[i].vy = 0;
}
And the main loop to move and draw the sparks:
struct spr *ps;
for (i = 0, ps = &sprites[i]; i < NSPR; i++, ps++) {
draw_spark(ps->x, ps->y, 0, (i & 7));
ps->x += ps->vx;
ps->y += ps->vy;
ps->vy++;
if ((ps->x < 0) || (ps->x > 400) || (ps->y > 310)) {
if (sparking)
born(i);
else
kill(i);
}
}