How to make a "game" in 3.5 hours

Previous topic - Next topic

Kitty Hello

Hi,

Here's the story about how I created this game:

http://www.glbasic.com/forum/index.php?topic=6556.0
in about 3.5 hours, totally. Don't be fooled that this is regular time when you start programming. I'm a quite skilled coder for over 20 years now and coding is my day time job.

The whole thing started when I was asked by my wife to train the kid in math (basic times table 1x1 to 10x10). It was late, kid was in bed, so I decided to make an App for her to play when she awakes.

So, starting at Friday, 10pm, my first thought was, that it would have to be a game that's somehow nice to play, with slightly cute visuals and a 2 player mode, because I think it's really important that we share time with our kids, rather than with programming for the kids.

I always wanted to make some sort of "RISK" game with multiplications instead of the dice. But that's rather hard to do in a short time and I don't have the right theme for that game to tie a girl to it, yet. In fact, writing games for girls is a very complex part. They usually don't like competitions, but variety, decorations and style. In the final game, e.g., I put an option to change the colour of the main game's frame for each player. When my girl can choose pink or purple, the game attracts her and thus she's also willing to have a play.

So, step 1 was a quick browsing the internet about multiplication games. There was some penguin game I found rather interesting http://www.multiplication.com/flashgames/PenguinJump.htm which I think could easily be done in a few hours.
The game is about getting a question asked as "5x4", and you have to tap the right answer to proceed.

But a direct copy? No - too boring. Also, penguin animations look boring. I needed a player that can be easier animated but look kinda cute. If the first impression rejects a kid, you have a hard time convincing them of the better. All that led me to the frog/pond theme.

Kicking CorelDraw (I really love Corel - get some time to learn it, it's worth millions. Or use Inkscape if you want something free) I started a frog:
[see attachment 1]

1. 3 ellipses for mouth and eyes
2. 2 more ellipses for an eye. Duplicated the eye (Ctrl+D) and moved it to the right -> 2 eyes
3. Sketched the legs -> just 3 ellipses. For the foot 2 ellipses.
4. selected both foot parts and used the "rear without front" clipping tool -> a foot. Duplicated and mirrored the leg -> a frog.
5. Selected the belly and used "filling - gradient". Made a radial (sphere like) light to dark gradient. Then selected belly, right click+drag to head, selecting "copy filling here" -> filled head. Selected all other limbs and played with solid colour until I was satisfied with the frog.
6. Selected the full leg. Clicked it again (toggles from "move" to "rotate" mode in Corel). Moved the pivot point to the upper limb, then rotated the whole leg. Did the same procedure for the shinbone and foot.
7. I wanted the frog as an animation for LOADANIM instead of multiple images. So, I drew a rectangle around my frog, turning the GRID OPTION on. Then duplicated the rectangle plus frog and placed it (with the grid) right aligned to that box. Pressing "Ctrl+D" (Duplicate) again, it will offset the duplicate to the right of the 2nd box and so on.
8. I now have a set of framed frogs. I quickly animated them. Later, I select the frames and disable the border and filling (making them transparent, but do export them so I have a proper tile set for LOADANIM :)

When making graphics for a quick game, make something that looks nice or you will lose interest. But don't go too much into detail or you will never see light at the end of the tunnel.

The lily was even easier. A circle, overlaid with a triangle. Deformed the triangle to look more "alive", then subtract it from the circle. Draw a light green ray (using duplicate, rotated the clone and duplicate to get more rotated parts). Now all light green striped had a black outline. So I duplicate the circle and intersect it with the rays. Grouping them with the circle and removing the border for that group gave me a lily with no border at all. Then I used the duplicated circle as an overlay with transparent filling and black border.

I'm not much of a graphics guy, so I did not bother to animate the lily. I'll talk about animations, later.

Afterwards I copied all together on a blue/lightblue scene to get an icon and a title screen.

Next I needed the title font. I chose Indiana Jones one, because it was late and it should be some sort of "Adventure" game. Googled for a font that suits my needs and also that gradient colours. Easy.

11:30pm - oh my, it's late. Now hurry with the code.

I made a TYPE with the lines to print and calculate:
Code (glbasic) Select

TYPE Tline
fac1% // factor 1
fac2% // factor 2

icorrect% // what result is the correct one? (Note to self: only one solution must be right!)
results%[4] // results to print

string$ // question (equation) string
ENDTYPE


And a type for the frog data
Code (glbasic) Select

TYPE Tfrog
iline% = -1 // what line am I on?
ipad%  = 2 // what pad (x-direction) am I on?
jumpto%= 2  // pad to jump to. -1 -> wait for input

correct%=TRUE // was the last answer correct? If not -> jump back

jump#=0.1 // 0..1 -> jumping animation. >1 -> landed on new pad. Set jumpto% to -1

col_on_map% = 0x0000ff // colour on the map to see who's heading

is_computer%  =FALSE // is this player computer controlled? (Wait for a second, then jump)

cpu_counter% = 4000 // gettimerall until next move of computer player
ENDTYPE


OK, then I had to fill the math equations for the lines. That ended out pretty hard to do properly, because I didn't want duplicates nor numbers from 1xN or 10xN. My method was just to check against these cases and if so, just GOTO and retry with new RND values. For such ideas it's important to see you're not running into a dead-end loop!
Next was to create fake-results. That was hard, because they should be sort of "neighbor" results.
So, I took this:

Code (glbasic) Select

FOR i=0 TO nof_tasks%-1
LOCAL li AS Tline
@again:
li.fac1 = RND(7) // 2..9 (1x and 10x is stupid)
li.fac2 = RND(7)

// don't repeat the same equation
FOREACH ol IN gLines[]
IF  (ol.fac1 = li.fac1+1 AND ol.fac2=li.fac2+1) OR _
(ol.fac2 = li.fac1+1 AND ol.fac1=li.fac2+1) THEN GOTO again
NEXT

LOCAL fake1% = MOD(MAX(li.fac1%, li.fac2) + 8 - (1+RND(1) ), 8)
LOCAL fake2% = MOD(MIN(li.fac1%, li.fac2) + 1 + RND(1)     , 8)

INC li.fac1, 2
INC li.fac2, 2
INC fake1
INC fake2
   DIMPUSH gLines[], li
   ...
   NEXT


I chose a RND from 0 to 7, and will add 2 later. So I have 2...9 for fac1 and fac2. Fine.
The "fake" results are just a RND of 1..2 offset. Once offset to the positive:
MOD(MIN(li.fac1%, li.fac2) + 1 + RND(1)     , 8)
which is basically:
fake1 = min(fac1, fac2) + 1 + RND(1)
if fake1 > 7 then fake1 = fake1-7 // limit to 0..7

The lower part is a bit trickier, because a negative number returns a nagative MOD, thus I added the mod value before to get a positive value:
MOD(  0 - RND(10) + 10, 10) <- RND gets negative. Adding 10 will make it positive again, mode 10 will cut overflow.

Next I made a function to display the whole "pond" on a given screen size, because I wanted it to be slightly scaleable. I made a sprite (ID=100) and used that sprite with CREATESCREEN to draw the whole "game" onto it.
That way I was able to draw and rotate the whole playfield for each player.
I could use polyvector to do that, or ROTOSPRITE, but that would have caused headache for the touch positions of the 2nd player, later.

And it was already 0:15am.
So I thought really hard and found, that you can use SETROTATION within a SHOWSCREEN loop to render both frog parts.
Yippieh!
My render loop was like:
Code (glbasic) Select

WHILE TRUE
FOR ifrog% = 0 TO LEN(gFrogs[])-1
ALIAS frog AS gFrogs[ifrog]

// silly trick to handle rotated screens
// current frog is at bottom of screen
SELECT ifrog
CASE 0
SETORIENTATION 0
CASE 1
SETORIENTATION 2
ENDSELECT

IF frog.jumpto >= 0
IF frog.correct // jump further if already jumping
INC frog.jump, 0.02
ELSE // jump further, but slower
INC frog.jump, 0.01
ENDIF
IF frog.jump>=1.0 // reached end of jump
frog.jump=0.0 // reset jump
IF frog.correct% // was fine?
INC frog.iline // move to that line
frog.ipad = frog.jumpto // set to that pad

// check for game over...
ENDIF
frog.jumpto = -1 // able to touch new pad
ENDIF

ELSE // can jump
FOR im% = 0 TO GETMOUSECOUNT()-1 // multitouch
SETACTIVEMOUSE im
LOCAL mx%, my%, b1%, b2%
MOUSESTATE mx, my, b1, b2 // rotated mouse coords - YAY!

// computer player - play stupid random
IF frog.is_computer
b1=FALSE
IF frog.cpu_counter% < GETTIMERALL() AND frog.iline < LEN(gLines[])-1 // time to jump
mx = gLines[frog.iline+1].icorrect% * w/5 + w/10 + 4 // tap position for correct result
my=h  + 200 // below center of screen
b1=TRUE
SELECT gDifficulty% // pick time for next jump action
CASE 0
frog.cpu_counter = GETTIMERALL() + 4000
CASE 1
frog.cpu_counter = GETTIMERALL() + 2110
CASE 2
frog.cpu_counter = GETTIMERALL() + 1000
ENDSELECT
ENDIF
ENDIF

IF b1 // tapped
IF my > h // bottom of screen (where this frog is)

LOCAL isel% = (mx-w/10)*5 / w   // selected pad

// valid point
IF isel>=0 AND isel<=3
ALIAS line AS gLines[frog.iline+1]
IF isel = line.icorrect%
frog.correct=TRUE
ELSE
frog.correct=FALSE
ENDIF
frog.jumpto = isel // pad to jump to
ENDIF
ENDIF
ENDIF
NEXT
SETACTIVEMOUSE 0
ENDIF

// draw offscreen game playfield to sprite 100
MakePlayfield(ifrog)

// paste game for player
DRAWSPRITE 100+ifrog, 0, h

// shade out computer player
IF frog.is_computer
ALPHAMODE -0.4
DRAWRECT 0,h,w,h,0
ALPHAMODE 0
ENDIF
NEXT


The drawing code is as simple as:
Code (glbasic) Select

FUNCTION MakePlayfield%: ifrog%
LOCAL w%, h%
GETSPRITESIZE 100, w%, h% // get viewport size - render to sprite

LOCAL yblock# = h/2 // distance of each line to draw
ALIAS frog AS gFrogs[ifrog]

USESCREEN 0
LOCAL ianim = 0
LOCAL jump = 0
LOCAL xfrog = X_of_pad(frog.ipad, w) // get x pixel position of pad(i)

IF frog.jumpto <> -1 // frog is jumping
jump = frog.jump
IF frog.correct% // direct jump to next pad
jump = SIN(jump*90)
ianim=1
LOCAL xf2 = X_of_pad(frog.jumpto, w)
xfrog = xf2 * jump + xfrog*(1.0-jump)
ELSE // jump to next pad - then jump back
IF jump < 0.5 // to pad
ianim=1
jump = SIN(jump*180)*0.8 // jump to water
ELSE // and back
ianim=2
jump = (1. - jump) * 2* 0.8 // move back to old pad
ENDIF
LOCAL xf2 = X_of_pad(frog.jumpto, w)
xfrog = xf2 * jump + xfrog*(1.0-jump)
ENDIF
ENDIF

LOCAL dx = (64-w/4)/2 - 64/2// offset of water lily to draw position


LOCAL ty% = (frog.iline + jump) * (yblock*2) + SIN(GETTIMERALL()/10) * 8 // make the water move slightly
LOCAL tx% = COS(GETTIMERALL()/25) * 16

STARTPOLY 1, 2 // fill background with water seamless texture
POLYVECTOR 0,0, tx, ty,   0xffffff
POLYVECTOR 0,h, tx, ty+h, 0xffffff
POLYVECTOR w,0, w+tx, ty,   0xffffff
POLYVECTOR w,h, w+tx, ty+h, 0xffffff
ENDPOLY

FOR y% = 0 TO LEN(gLines[])-1 // draw the lines to jump to
ALIAS line AS gLines[y%]

LOCAL yd% = (y - frog.iline - jump)*yblock + 48 // where to draw that line
IF yd+64 < 0 THEN CONTINUE // upper bounds of screen
IF yd > h THEN BREAK // lower bounds of screen - ignore all below that

FOR i%=0 TO 3 // 4 pads
LOCAL xd% = X_of_pad(i, w)
DRAWSPRITE 2, xd, yd // draw lily
IF yd > 48 AND y>0 THEN Center( line.results[i], xd+32, yd+28, TRUE) // print result option
NEXT
IF yd > 48 AND y>0 THEN Center(line.string$, w/2, yd-24, TRUE) // question
NEXT

LOCAL yfrog = 48 - SIN(jump*180)*60
IF gGameover% AND frog.iline <> LEN(gLines[]) -1 THEN ianim = 2 // sad face
DRAWANIM 10, ianim, xfrog, yfrog

USESCREEN -1
ENDFUNCTION


As you can see, this is already the final drawing routine. I started with drawing blue water background, the lilys and the frog. Then added the frog-jump-animations and movements. I'm quite a fan of sin/cos, so I used that for the jumping curve. There's a tutorial about how to use that on the forums as well: http://www.glbasic.com/forum/index.php?topic=1389.0
When the game was working I started to beatify the output with e.g. the sin/cos movement of the background water.
As said, I'm not much of a graphics guy. But when you start a game usually looks very static. Making some objects shake in little ellipses often helps to get a game vital visual very easily. Just add dx, dy to your x,y, positions: dx=sin(gettimerall()/10)*max_amplitude; dy=cos(gettimerall()/10)*max_amplitude. If you make the "/10" for sin and cos different, it shakes in a S-shape. Just play with it.

OK, it was 0:50 when the program run fine. I tried to install on my iPhone, but failed because of the code sign process. 1:30, tired to the grave, I picked the Palm Pre2. Copy, run - but... the frog2 acted as if it was frog1. GAH!

Too late to try further - I got some sleep.

Next day, painting the carport the idea came - the bitmap of the offscreen surface must be wrong. After investigating I found out, that using USESCREEN X, then -1 to dar and then X again, it would not work - it's on my TODO list. To fix that, I used a unique texture for each frog's display. Phew.
1:30pm on Saturday I got me some paper for quick bug notes and feature ideas and we had the first match. I lost. Obviously. How was I to think I could beat a 3rd grade kid in maths :/

All in all, I was quite reassured that GLBasic was a good idea to start, because I don't think I would have made it in this time with any other tool. I also see, that when GLBasic has a bug (offscreen can't be used twice in one loop e.g.) it really sucks for the developers. I'm really sorry for any bugs you find and report and will do my best to get them fixed ASAP also for future releases.
















[attachment deleted by admin]

MrTAToad

Very good - would be good to link Twitter & Facebook to this - people could find it useful!

Ian Price

A really nice walk over. Cheers :)
I came. I saw. I played.

Hark0

Very good!

I post a new + link to this topic in my blog...
:good:
http://litiopixel.blogspot.com
litiopixel.blogspot.com - Desarrollo videojuegos Indie · Pixel-Art · Retroinformática · Electrónica Development Indie Videogames · Pixel-Art · Retrocomputing · Electronic

bigsofty

Yep, I have to agree, very nice dev log of your mini game.  :good:
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

Schranz0r

And another 3.5 h to write this tutorial :)
I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

Moru

Documentation is important :-)

matchy

It is cool to make a quick game, like in an evening, day or even weekend. The competitions are good motivations for this also and I wish there's another one coming soon. ;)

Usually, I find myself creating prototypes rather than a fully commercial product and sometimes it could just be colored squares to be filled in later with sprites later as they are not required initally. Of course, it also really depends on the planning, for example; in this case with the frog, it could be all be drawn and saved on to a bitmap within glb (polys). The advantage, for example, would be that user frog skin colours can be customized.

Slydog

matchy: "The competitions are good motivations for this also and I wish there's another one coming soon."

I second that!
I need a diversion from my game!

Ha, how about a "Program a game in 3.5 hours" competition?!  :D
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

XanthorXIII

Very nice Gernot. This is something I want to do for my nephew when I get the time to create something like this.
I like inspirational stuff. Keeps me going and using GLBasic.
Owlcat has wise

codegit

------------------------------------------
1 X Acer TravelMate 4270, laptop, XP PRO
1 X Dell Studio 17 laptop, Windows 7
1 X MacBook Pro 2,2 GHz Core 2 Duo, 2 GB RAM, 160 GB HDD, 9400M
2 X iTouch
1 X HTC Desire (Android 2.1)
iPad soon to be added