floppy is a micro game engine for beginners written in javascript.
- it has basic primitives for drawing (rectangles, circles, lines, text)
- it includes interaction (click, tap)
- it includes sound (generated)
- it comes with a (simple) web editor online and ...
- ... with a tutorial to write your first game (simple, but complete)
- Step 0
- Step 1 - Coloring Background
- Step 2 - Drawing Pad
- Step 3 - Drawing Ball
- Step 4 - Moving Ball
- Step 5 - Bounching Right
- Step 6 - Bounching Left
- Step 7 - Bounching Top
- Step 8 - Moving Pad
- Step 9 - Bounching Pad
- Step 10 - Restarting Ball
- Step 11 - Adding Score
- Step 12 - Adding Lifes
- Step 13 - Adding Challenge
- Step 14 - Adding Sound
- Step 15 - Publishing Online
In this tutorial we will create a squash like game.
When opening the web editor default code include two functions (update and render) with empty body.
function update() {
}
function render() {
}
What it is inside those functions it is executed continuosly (about 60 times each second). First the update function, then the render function.
In order to color the whole game window with a light blue, we add a Rect command to the render function.
function update() {
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue"); // NEW
}
WIDTH is a constant = 320 = the whole width of game window
HEIGHT is a constant = 480 = the whole height of game window
To draw the pad, first we define four new variables (padX, padY, padW, padH) and then add a Rect command (using those variables).
padX=100; // NEW
padY=430; // NEW
padW=120; // NEW
padH=15; // NEW
function update() {
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green"); // NEW
}
To draw the ball, first we define its variables (ballX, ballY, ballR) and then add a Circle command (using those variables).
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160; // NEW
ballY=70; // NEW
ballR=16; // NEW
function update() {
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red"); // NEW
}
To move the ball we update its position (ballX, ballY).
Three new variables are defined in order to manage the update (dirX, dirY, speed).
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1; // NEW
dirY=1; // NEW
speed=2; // NEW
function update() {
ballX=ballX+dirX*speed; // NEW
ballY=ballY+dirY*speed; // NEW
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red"); // NEW
}
When the ball reaches the right border of game window we set dirX to -1 in order to have the ball bounching.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
function update() {
if (ballX + ballR > WIDTH) { // NEW
dirX=-1; // NEW
} // NEW
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
}
When the ball reaches the left border of game window we set dirX to 1 in order to have the ball bounching.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) { // NEW
dirX=1; // NEW
} // NEW
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
}
When the ball reaches the top border of game window we set dirY to 1 in order to have the ball bounching.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) { // NEW
dirY=1; // NEW
} // NEW
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
}
To move the pad, when TAPPED is true, we set the position of the pad taking the x position of our tapping (TAPX).
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) {
dirY=1;
}
if (TAPPED) { // NEW
padX=TAPX-padW/2; // NEW
} // NEW
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
}
When the ball reaches the pad we set dirY to -1 in order to have the ball bounching on the pad.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) {
dirY=1;
}
if (TAPPED) {
padX=TAPX-padW/2;
}
if ( (ballX >padX) && (ballX <padX+padW) && (ballY+ballR>padY) ) { // NEW
dirY=-1; // NEW
} // NEW
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
}
If the ball crosses the pad games is restarted from initial position.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) {
dirY=1;
}
if (TAPPED) {
padX=TAPX-padW/2;
}
if ( (ballX >padX) && (ballX <padX+padW) && (ballY+ballR>padY) ) {
dirY=-1;
}
if ( ballY+ballR > padY+padH ) { // NEW
ballX=160; // NEW
ballY=70; // NEW
}
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
}
In order to manage and show the score, we add a new variable (score) and we use the Text command in the render function.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
score=0; // NEW
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) {
dirY=1;
}
if (TAPPED) {
padX=TAPX-padW/2;
}
if ( (ballX >padX) && (ballX <padX+padW) && (ballY+ballR>padY) ) {
dirY=-1;
score=score+10; // NEW
}
if ( ballY+ballR > padY+padH ) {
ballX=160;
ballY=70;
}
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
Text(score,50,50,30,"green"); // NEW
}
To manage multiple lifes, we add a new variable (lifes). We set it initially to 3 and we subtract 1 every time we lose a ball.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
score=0;
lifes=3; // NEW
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) {
dirY=1;
}
if (TAPPED) {
padX=TAPX-padW/2;
}
if ( (ballX >padX) && (ballX <padX+padW) && (ballY+ballR>padY) ) {
dirY=-1;
score=score+10;
}
if ( ballY+ballR > padY+padH ) {
ballX=160;
ballY=70;
lifes=lifes-1; // NEW
}
if (lifes==0) { // NEW
speed=0; // NEW
} // NEW
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
Text(score,50,50,30,"green");
Text(lifes,250,50,30,"red"); // NEW
}
To add a challenge in the game, we increase the ball speed by 0.2 at each bounch on the pad.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
score=0;
lifes=3;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
}
if (ballX < ballR) {
dirX=1;
}
if (ballY < ballR) {
dirY=1;
}
if (TAPPED) {
padX=TAPX-padW/2;
}
if ( (ballX >padX) && (ballX <padX+padW) && (ballY+ballR>padY) ) {
dirY=-1;
score=score+10;
speed=speed+0.2; // NEW
}
if ( ballY+ballR > padY+padH ) {
ballX=160;
ballY=70;
lifes=lifes-1;
}
if (lifes==0) {
speed=0;
}
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
Text(score,50,50,30,"green");
Text(lifes,250,50,30,"red");
}
Last enrichment. We add a sound (generic) when bounching using the SoundEffect command.
padX=100;
padY=430;
padW=120;
padH=15;
ballX=160;
ballY=70;
ballR=16;
dirX=1;
dirY=1;
speed=2;
score=0;
lifes=3;
function update() {
if (ballX + ballR > WIDTH) {
dirX=-1;
SoundEffect (); //
}
if (ballX < ballR) {
dirX=1;
SoundEffect (); //
}
if (ballY < ballR) {
dirY=1;
SoundEffect (); //
}
if (TAPPED) {
padX=TAPX-padW/2;
}
if ( (ballX >padX) && (ballX <padX+padW) && (ballY+ballR>padY) ) {
dirY=-1;
score=score+10;
speed=speed+0.2;
SoundEffect (); //
}
if ( ballY+ballR > padY+padH ) {
ballX=160;
ballY=70;
lifes=lifes-1;
}
if (lifes==0) {
speed=0;
}
ballX=ballX+dirX*speed;
ballY=ballY+dirY*speed;
}
function render() {
Rect(0,0,WIDTH,HEIGHT,"lightblue");
Rect(padX,padY,padW,padH,"green");
Circle(ballX,ballY,ballR,"red");
Text(score,50,50,30,"green");
Text(lifes,250,50,30,"red");
}
We can finally publish the game using HTML Pasta.
Here the game published online. Game.
function update() {};
function render() {};
Rect(x, y, w, h, "color");
Circle(x, y, r, "color");
Text("text", x, y, size, "color");
Line(x_init, y_init, x_end, y_end, width, "color");
WIDTH
HEIGHT
TAPPED
TAPX
TAPY
SoundEffect(frequencyValue, attack, decay, type, volumeValue, panValue, wait, pitchBendAmount, reverse, randomValue, dissonance, echo, reverb, timeout);
more information @: https://github.com/kittykatattack/sound.js#generating-sound-effects-and-music
Random(min, max); // Random integer from min to max
PitchToFrequency(octave, semitone); // Frequency from octave (integer), semitone (integer)
with:
- live preview
- syntax warnings
- saving game
- exporting game
- using offline
For game engine: How To Design A Mobile Game With HTML5
For approach: pico-8
For sound: sound.js