Created
September 11, 2022 22:47
-
-
Save andymasteroffish/a82b3ccdf93dcd922c070e368a02dbe4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Cold Sun Surf | |
-- https://andymakes.itch.io/cold-sun-surf | |
-- Code by Andy Wallace | |
-- andymakes.com | |
-- Made for PICO-1K jam https://itch.io/jam/pico-1k-2022 | |
-- This is a breakdown of the compressed code. The code is identical. All I have added is white space and comments. | |
-- You can copy/paste this code to Pico-8 and mess with it if you'd like. | |
-- This could definitely be more compressed. There were no other major things I wanted in the game so I got it under the limit and stopped. | |
-- The dedication to saving a few bytes varies. Some of it I wrote earlier when I thought I would have to pinch every penny. | |
-- Later on (like with the sounds) I got loose because I realized I had breathing room. | |
-- In several places "?" is used to write text or even play sounds | |
-- This is documented here: https://www.lexaloffle.com/dl/docs/pico-8_manual.html#Appendix_A | |
-- A lot of this game uses a list of objects that have some basic physics applied to them. Here are the values of each object | |
-- x : x position | |
-- y : y position | |
-- h : horizontal velocity | |
-- v : vertical velocity | |
-- t : type (0=graphic/fx, 1=player, 2=fuel or money) | |
-- c : color | |
------------------------------------- | |
-- ** Prepping the sprite sheet ** -- | |
--when the game first starts, I draw a circle + the title to the sprite sheet | |
--this will be used for visual effects | |
cls() --clear the screen | |
circfill(64,64,11) --draw the circle (for the sun) | |
?"\^w\^tcold sun surf",14,99 --write the title text using the wide (^w) and tall (^t) flags | |
?"by @andy_makes" --my name at the smaller size | |
memcpy(0,24576,16384) --copy the whole screen to the sprite sheet | |
------------------------ | |
-- ** Initializing ** -- | |
--redefining a few functions that get used a lot | |
c=cos | |
s=sin | |
r=rnd | |
--setting the pallette | |
pal({9,2,5,130,132,129}) | |
--some values that don't get reset between rounds | |
t=0 --time as frame count | |
hs=0 --high score | |
----------------- | |
-- ** Reset ** -- | |
::r:: --goto label | |
--l is my object list (L for "list") | |
--I start it with the player object (type=1) | |
l={{x=0,y=-50,h=0,v=0,t=1,c=1}} | |
pa=0 --player angle | |
f=9 --fuel (9 is max) | |
g=1 --game state. 1=playing, 0=game over | |
--using 1 and 0 is shorter than "true" or "false" but also makes it easy to do math and avoid if statements | |
sc=0 --score | |
--if the game just started, meaning that time is still 0, put the player in the sun to trigger the game over screen | |
--the game over screen is the title | |
if(t<1)l[1].y=0 | |
--------------------- | |
-- ** Game Loop ** -- | |
--the goto label | |
::_:: | |
cls() --clear the screen | |
--all of the math gets easier (and shorter) if the sun is centered on 0,0 so using the camera function to move the view | |
camera(-64,-64) | |
--------------------- | |
-- ** Sun + Title ** -- | |
--draw the current sprite. | |
--if g is 0, all 16 rows of the sheet get drawn (16-0*5) | |
--if g is 1 (when the game is running), only the top 11 rows get drawn (16-1*5) | |
--the title is drawn on the bottom, so this allows the sun but not the title to be drawn during gameplay | |
spr(0,-64,-64,16,16-g*5) | |
--every frame, grab a bunch of random pixels on the sprite sheet and change the color if they are not black | |
--this gives the sun and title the plasma effect | |
for i=0,999do | |
--grab a random spot | |
x=r(128) | |
y=r(128) | |
--define a random color at the start of my palette | |
--c is in use, so I often use "v" for "value" | |
v=1+r(2) | |
--the title is in the lower part. if y is greater than 90, the color value can use brighter colors | |
if(y<90)v=3+r(4) | |
--if the random pixel is not black, set it to the new color | |
if(sget(x,y)>0)sset(x,y,v) | |
end | |
--store the player position each frame | |
px=l[1].x | |
py=l[1].y | |
--------------------- | |
-- ** Player Input ** | |
--get the current button state as a bitfield (each button is a single 1 or 0) | |
b=btn() | |
--X resets. X is button 5 so 2^4 = 32 | |
if(b==32)goto r | |
--neat little trick to get left/right out of btn | |
--(b*2-3) will be -1 or 1 if only left or only right is pressed | |
--taken from here: https://gist.github.com/kometbomb/7ab11b8383d3ac94cbfe1be5fb859785#example-how-to-move-left-and-right-in-minimal-chars | |
-- dividing by 40 because pico-8 angles go from 0 to 1. So now it takes 40 frames to make a full rotation | |
if(b>0 and b<3)pa-=(b*2-3)/40 | |
--calculating thrust (h for "tHrust") | |
--if any other buttons besides left and right are held, b will be greater than 3 | |
--so b-3 will be possitive if other buttons are held and negative (or 0) if not | |
--this is clamped to be a range from 0 to 1 using mid | |
--the results it divided by 9 because a value of roughly 0.1 felt good for thrust per frame | |
h=mid(0,b-3,1)/9 | |
--but if there is no fuel there is no thrust | |
if(f<0)h=0 | |
--if we are thrusting, reduce fuel by that amount | |
f-=h | |
--if we are thrusting, make a sound (low, noisy grumble) | |
--h is thrust and g is gamestate. they both need to be above 0 to trigger the sound | |
if(h*g>0)?"\av1i6x2a" | |
--we also create some exaust particles. | |
--v is the angle they'll come out | |
--it is nearly the same as the player, but with a bit of randomness | |
v=pa+r(.1) | |
--if we're thrusting, add an fx object to the list | |
--it is created at the player position | |
--the color is slightly variable between 3 and 4, but favoring 3 | |
if(h>0)add(l,{x=px,y=py,h=-c(v),v=-s(v),t=0,c=3.4+r()}) | |
----------------------------------- | |
-- ** Generating Game Objects ** -- | |
--every 32 frames we generate fuel or money | |
--the angle (v) is generated every frame even though we don't use it most frames | |
--this allows the if statement to stay at a sinlge line which can use the abbreviated form | |
v=r() | |
--using modulo to check time/frame count has had 32 frames elapse | |
--if so, add a fuel or money object | |
--the type is 2 in either case, but money or fuel is decided by the color which is randomly set to 1 or 2 | |
if(t%32<1)add(l,{x=s(v)*90,y=c(v)*90,h=0,v=0,t=2,c=flr(1+r(2))}) | |
--add some space debris every frame to show the terrifying gravity of the sun | |
d=31+r(20) --how far away to spawn it | |
--create an fx object | |
--putting a little bit of rotaiton on it so it seems to orbit | |
add(l,{x=s(v)*d,y=c(v)*d,h=s(v+.25)/2,v=c(v+.25)/2,t=0,c=4+r(3)}) | |
--increase our game time if game is running (g==1 is game running, g==0 is game over) | |
t+=g | |
--------------------- | |
-- ** Object Loop ** -- | |
--this is the big loop that manages all of our objects | |
--this handles graphic effects as well as the player and the collectables | |
for o in all(l)do | |
--get the tangent angle (ta) from this object to the sun | |
--this would be the angle -0.5, but I want just a little radial force, so I use 0.48 | |
ta=atan2(o.x,o.y)-.48 | |
--set the friction. velocity will be multipled by this | |
--lower value = more friction | |
fr=.98 | |
--the collectibles get way more friction | |
if(o.t>1)fr=.8 | |
--update the (h)orizontal and (v)ertical velocity | |
o.h = o.h*fr + c(ta)/9 + c(pa)*h | |
o.v = o.v*fr + s(ta)/9 + s(pa)*h | |
--this can be thought of as having a few steps. I'll use h as an example | |
-- apply friction to the current value | |
-- h *= fr | |
-- add some force from the sun along the tangent angle (~0.11 force) | |
-- h += cos(ta) / 9 | |
-- add force from thrust along the player angle | |
-- h += cos(pa) * h | |
-- note: in theory all objects are affected by thrust, but the player is the very first object in the list and thrust will be set to 0 so later objects are unaffected | |
--move the object acording to the velocity | |
o.x+=o.h | |
o.y+=o.v | |
--reset thrust so only the player is affected | |
h=0 | |
--store the position of this object in variables with short names because these values get used a lot | |
x=o.x | |
y=o.y | |
--draw a circle for the object | |
--the size is determined by the type | |
circfill(x,y,1+o.t,o.c) | |
--type 1 is the player. we want little Sputnik legs | |
if o.t==1 then | |
for i=-1,1,2 do | |
line( x, y, x+c(pa-.45*i)*7, y+s(pa-.45*i)*7 ) | |
end | |
end | |
--type 2 is the collectible | |
--la is an array that stores the symbol and the sound for this collectible | |
--first two entries are the letter that gets drawn, last two are the sound | |
la={'f','$',"\ax1ce","\aeb"} | |
--if this is a collectible (type 2), draw the symbol on top of the circle | |
if(o.t>1) ?la[o.c],x-1,y-2,1+o.c%2 | |
--anything that is too close to the sun is destroyed | |
--using abs to do this, which winds up making a diamond shape collision box | |
--circular hit detection is slow and takes a lot of chars. diamond is fine. | |
if(abs(x)+abs(y)<17) del(l,o) | |
--collectibles (type 2) also check their position against the player | |
--using a slightly smaller hitbox than the sun, but ultimately a lot larger than the actual collectible | |
if o.t>1 and abs(px-x)+abs(py-y)<13 then | |
--color determines if this is fuel(1) or money(2) | |
--if it is fuel, add 2 but don't let fuel go above 9 | |
if(o.c<2) f=min(f+2,9) | |
--during gameplay, if a money object was touched, increase score | |
--no "if" here because if color is 1 for fuel, (o.c-1) = 0 | |
--if color is 2 for money (o.c-1) = 1 | |
sc+=g*(o.c-1) | |
--play a sound from the array depending on the color | |
?la[o.c+2] | |
--remove this object | |
del(l,o) | |
end | |
--at the end each loop, we check if the first element is no longer the player | |
--if the first element is not type 1, then the player must have just died (and been deleted) | |
--doing this in the loop so I can use the x and y values I saved earlier even though it stops being relevant after the player was checked | |
if l[1].t!=1 and g>0 then | |
--set the gamestate to 0, marking that the game is done | |
g=0 | |
--check if this is a new high score | |
hs=max(hs,sc) | |
--create a bunch of fx particles in the player's color to make it look like they exploded | |
--having the loop go from 0 to 1 because that is a full circle in pico-8 | |
--this creates 50 evenly spaced particles | |
--most of them go right into the sun and are instantly destroyed | |
for a=0,1,.02 do | |
--slightly randomized velocity | |
v=2+rnd(1) | |
--create and add the particle | |
add(l,{x=x,y=y,h=c(a)*v,v=s(a)*v,t=0,c=1}) | |
end | |
--play an explosion sound | |
--only doing this when we're past frame 2 so it doesn't play on game start | |
if(t>2)?"\ai6s1ceabc" | |
end | |
--end of the object update loop | |
end | |
-------------------- | |
-- ** Fuel Bar ** -- | |
--drawing bars along the side of the screen to show fuel | |
--the bar is 80 pixels tall, but if game is over (g=0), the height gets set to 0 to not draw | |
for i=1,80*g,2do | |
--the color is set with 2-sgn(f*9-i). It will be 1 or 3 | |
--this checks how the current y compares to f*9 | |
--max fuuel is 9, so 9x9=81, almost the same height as our bars! | |
line(55,40-i,60,40-i,2-sgn(f*9-i)) | |
end | |
---------------- | |
-- ** Text ** -- | |
--if we're not on the menu (meaning that our frame counter has increased), show the score | |
if(t>1)?"$"..sc,-8,-56,1 | |
--if g==0 (game over) and high score is more than 1, show the high score | |
if(hs*(1-g)>0) ?"best:\n$"..hs,-63,-63 | |
--if we're on the menu, show the instructions | |
--"\*8 " is a neat trick that prints 8 blank spaces | |
if(t<2)?"collect fuel ◆ collect money\n ⬅️+➡️ turn ◆ 🅾️ to thrust\n\*8 ❎ to reset",-56,-60,2 | |
--------------------- | |
-- ** Clean Up ** -- | |
--flip sends everything to the screen | |
--goto _ bounces to the top of the game loop ::_:: | |
flip()goto _ | |
--That's it! Hopefully this was helpful! | |
--If you made it this far, maybe you'll like my patreon: https://www.patreon.com/andymakes |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment