Skip to content

Instantly share code, notes, and snippets.

@andymasteroffish
Created September 11, 2022 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andymasteroffish/a82b3ccdf93dcd922c070e368a02dbe4 to your computer and use it in GitHub Desktop.
Save andymasteroffish/a82b3ccdf93dcd922c070e368a02dbe4 to your computer and use it in GitHub Desktop.
-- 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