Skip to content

Instantly share code, notes, and snippets.

@lolriven
Forked from sherjilozair/celeste.lua
Created December 3, 2023 05:04
Show Gist options
  • Save lolriven/6d25fffe31a989cb4e6d3ca8461b59bd to your computer and use it in GitHub Desktop.
Save lolriven/6d25fffe31a989cb4e6d3ca8461b59bd to your computer and use it in GitHub Desktop.
Source code for pico-8 version of Celeste
-- ~celeste~
-- matt thorson + noel berry
-- globals --
-------------
room = { x=0, y=0 }
objects = {}
types = {}
freeze=0
shake=0
will_restart=false
delay_restart=0
got_fruit={}
has_dashed=false
sfx_timer=0
has_key=false
pause_player=false
flash_bg=false
music_timer=0
k_left=0
k_right=1
k_up=2
k_down=3
k_jump=4
k_dash=5
-- entry point --
-----------------
function _init()
title_screen()
end
function title_screen()
got_fruit = {}
for i=0,29 do
add(got_fruit,false) end
frames=0
deaths=0
max_djump=1
start_game=false
start_game_flash=0
music(40,0,7)
load_room(7,3)
end
function begin_game()
frames=0
seconds=0
minutes=0
music_timer=0
start_game=false
music(0,0,7)
load_room(0,0)
end
function level_index()
return room.x%8+room.y*8
end
function is_title()
return level_index()==31
end
-- effects --
-------------
clouds = {}
for i=0,16 do
add(clouds,{
x=rnd(128),
y=rnd(128),
spd=1+rnd(4),
w=32+rnd(32)
})
end
particles = {}
for i=0,24 do
add(particles,{
x=rnd(128),
y=rnd(128),
s=0+flr(rnd(5)/4),
spd=0.25+rnd(5),
off=rnd(1),
c=6+flr(0.5+rnd(1))
})
end
dead_particles = {}
-- player entity --
-------------------
player =
{
init=function(this)
this.p_jump=false
this.p_dash=false
this.grace=0
this.jbuffer=0
this.djump=max_djump
this.dash_time=0
this.dash_effect_time=0
this.dash_target={x=0,y=0}
this.dash_accel={x=0,y=0}
this.hitbox = {x=1,y=3,w=6,h=5}
this.spr_off=0
this.was_on_ground=false
create_hair(this)
end,
update=function(this)
if (pause_player) return
local input = btn(k_right) and 1 or (btn(k_left) and -1 or 0)
-- spikes collide
if spikes_at(this.x+this.hitbox.x,this.y+this.hitbox.y,this.hitbox.w,this.hitbox.h,this.spd.x,this.spd.y) then
kill_player(this) end
-- bottom death
if this.y>128 then
kill_player(this) end
local on_ground=this.is_solid(0,1)
local on_ice=this.is_ice(0,1)
-- smoke particles
if on_ground and not this.was_on_ground then
init_object(smoke,this.x,this.y+4)
end
local jump = btn(k_jump) and not this.p_jump
this.p_jump = btn(k_jump)
if (jump) then
this.jbuffer=4
elseif this.jbuffer>0 then
this.jbuffer-=1
end
local dash = btn(k_dash) and not this.p_dash
this.p_dash = btn(k_dash)
if on_ground then
this.grace=6
if this.djump<max_djump then
psfx(54)
this.djump=max_djump
end
elseif this.grace > 0 then
this.grace-=1
end
this.dash_effect_time -=1
if this.dash_time > 0 then
init_object(smoke,this.x,this.y)
this.dash_time-=1
this.spd.x=appr(this.spd.x,this.dash_target.x,this.dash_accel.x)
this.spd.y=appr(this.spd.y,this.dash_target.y,this.dash_accel.y)
else
-- move
local maxrun=1
local accel=0.6
local deccel=0.15
if not on_ground then
accel=0.4
elseif on_ice then
accel=0.05
if input==(this.flip.x and -1 or 1) then
accel=0.05
end
end
if abs(this.spd.x) > maxrun then
this.spd.x=appr(this.spd.x,sign(this.spd.x)*maxrun,deccel)
else
this.spd.x=appr(this.spd.x,input*maxrun,accel)
end
--facing
if this.spd.x!=0 then
this.flip.x=(this.spd.x<0)
end
-- gravity
local maxfall=2
local gravity=0.21
if abs(this.spd.y) <= 0.15 then
gravity*=0.5
end
-- wall slide
if input!=0 and this.is_solid(input,0) and not this.is_ice(input,0) then
maxfall=0.4
if rnd(10)<2 then
init_object(smoke,this.x+input*6,this.y)
end
end
if not on_ground then
this.spd.y=appr(this.spd.y,maxfall,gravity)
end
-- jump
if this.jbuffer>0 then
if this.grace>0 then
-- normal jump
psfx(1)
this.jbuffer=0
this.grace=0
this.spd.y=-2
init_object(smoke,this.x,this.y+4)
else
-- wall jump
local wall_dir=(this.is_solid(-3,0) and -1 or this.is_solid(3,0) and 1 or 0)
if wall_dir!=0 then
psfx(2)
this.jbuffer=0
this.spd.y=-2
this.spd.x=-wall_dir*(maxrun+1)
if not this.is_ice(wall_dir*3,0) then
init_object(smoke,this.x+wall_dir*6,this.y)
end
end
end
end
-- dash
local d_full=5
local d_half=d_full*0.70710678118
if this.djump>0 and dash then
init_object(smoke,this.x,this.y)
this.djump-=1
this.dash_time=4
has_dashed=true
this.dash_effect_time=10
local v_input=(btn(k_up) and -1 or (btn(k_down) and 1 or 0))
if input!=0 then
if v_input!=0 then
this.spd.x=input*d_half
this.spd.y=v_input*d_half
else
this.spd.x=input*d_full
this.spd.y=0
end
elseif v_input!=0 then
this.spd.x=0
this.spd.y=v_input*d_full
else
this.spd.x=(this.flip.x and -1 or 1)
this.spd.y=0
end
psfx(3)
freeze=2
shake=6
this.dash_target.x=2*sign(this.spd.x)
this.dash_target.y=2*sign(this.spd.y)
this.dash_accel.x=1.5
this.dash_accel.y=1.5
if this.spd.y<0 then
this.dash_target.y*=.75
end
if this.spd.y!=0 then
this.dash_accel.x*=0.70710678118
end
if this.spd.x!=0 then
this.dash_accel.y*=0.70710678118
end
elseif dash and this.djump<=0 then
psfx(9)
init_object(smoke,this.x,this.y)
end
end
-- animation
this.spr_off+=0.25
if not on_ground then
if this.is_solid(input,0) then
this.spr=5
else
this.spr=3
end
elseif btn(k_down) then
this.spr=6
elseif btn(k_up) then
this.spr=7
elseif (this.spd.x==0) or (not btn(k_left) and not btn(k_right)) then
this.spr=1
else
this.spr=1+this.spr_off%4
end
-- next level
if this.y<-4 and level_index()<30 then next_room() end
-- was on the ground
this.was_on_ground=on_ground
end, --<end update loop
draw=function(this)
-- clamp in screen
if this.x<-1 or this.x>121 then
this.x=clamp(this.x,-1,121)
this.spd.x=0
end
set_hair_color(this.djump)
draw_hair(this,this.flip.x and -1 or 1)
spr(this.spr,this.x,this.y,1,1,this.flip.x,this.flip.y)
unset_hair_color()
end
}
psfx=function(num)
if sfx_timer<=0 then
sfx(num)
end
end
create_hair=function(obj)
obj.hair={}
for i=0,4 do
add(obj.hair,{x=obj.x,y=obj.y,size=max(1,min(2,3-i))})
end
end
set_hair_color=function(djump)
pal(8,(djump==1 and 8 or djump==2 and (7+flr((frames/3)%2)*4) or 12))
end
draw_hair=function(obj,facing)
local last={x=obj.x+4-facing*2,y=obj.y+(btn(k_down) and 4 or 3)}
foreach(obj.hair,function(h)
h.x+=(last.x-h.x)/1.5
h.y+=(last.y+0.5-h.y)/1.5
circfill(h.x,h.y,h.size,8)
last=h
end)
end
unset_hair_color=function()
pal(8,8)
end
player_spawn = {
tile=1,
init=function(this)
sfx(4)
this.spr=3
this.target= {x=this.x,y=this.y}
this.y=128
this.spd.y=-4
this.state=0
this.delay=0
this.solids=false
create_hair(this)
end,
update=function(this)
-- jumping up
if this.state==0 then
if this.y < this.target.y+16 then
this.state=1
this.delay=3
end
-- falling
elseif this.state==1 then
this.spd.y+=0.5
if this.spd.y>0 and this.delay>0 then
this.spd.y=0
this.delay-=1
end
if this.spd.y>0 and this.y > this.target.y then
this.y=this.target.y
this.spd = {x=0,y=0}
this.state=2
this.delay=5
shake=5
init_object(smoke,this.x,this.y+4)
sfx(5)
end
-- landing
elseif this.state==2 then
this.delay-=1
this.spr=6
if this.delay<0 then
destroy_object(this)
init_object(player,this.x,this.y)
end
end
end,
draw=function(this)
set_hair_color(max_djump)
draw_hair(this,1)
spr(this.spr,this.x,this.y,1,1,this.flip.x,this.flip.y)
unset_hair_color()
end
}
add(types,player_spawn)
spring = {
tile=18,
init=function(this)
this.hide_in=0
this.hide_for=0
end,
update=function(this)
if this.hide_for>0 then
this.hide_for-=1
if this.hide_for<=0 then
this.spr=18
this.delay=0
end
elseif this.spr==18 then
local hit = this.collide(player,0,0)
if hit ~=nil and hit.spd.y>=0 then
this.spr=19
hit.y=this.y-4
hit.spd.x*=0.2
hit.spd.y=-3
hit.djump=max_djump
this.delay=10
init_object(smoke,this.x,this.y)
-- breakable below us
local below=this.collide(fall_floor,0,1)
if below~=nil then
break_fall_floor(below)
end
psfx(8)
end
elseif this.delay>0 then
this.delay-=1
if this.delay<=0 then
this.spr=18
end
end
-- begin hiding
if this.hide_in>0 then
this.hide_in-=1
if this.hide_in<=0 then
this.hide_for=60
this.spr=0
end
end
end
}
add(types,spring)
function break_spring(obj)
obj.hide_in=15
end
balloon = {
tile=22,
init=function(this)
this.offset=rnd(1)
this.start=this.y
this.timer=0
this.hitbox={x=-1,y=-1,w=10,h=10}
end,
update=function(this)
if this.spr==22 then
this.offset+=0.01
this.y=this.start+sin(this.offset)*2
local hit = this.collide(player,0,0)
if hit~=nil and hit.djump<max_djump then
psfx(6)
init_object(smoke,this.x,this.y)
hit.djump=max_djump
this.spr=0
this.timer=60
end
elseif this.timer>0 then
this.timer-=1
else
psfx(7)
init_object(smoke,this.x,this.y)
this.spr=22
end
end,
draw=function(this)
if this.spr==22 then
spr(13+(this.offset*8)%3,this.x,this.y+6)
spr(this.spr,this.x,this.y)
end
end
}
add(types,balloon)
fall_floor = {
tile=23,
init=function(this)
this.state=0
this.solid=true
end,
update=function(this)
-- idling
if this.state == 0 then
if this.check(player,0,-1) or this.check(player,-1,0) or this.check(player,1,0) then
break_fall_floor(this)
end
-- shaking
elseif this.state==1 then
this.delay-=1
if this.delay<=0 then
this.state=2
this.delay=60--how long it hides for
this.collideable=false
end
-- invisible, waiting to reset
elseif this.state==2 then
this.delay-=1
if this.delay<=0 and not this.check(player,0,0) then
psfx(7)
this.state=0
this.collideable=true
init_object(smoke,this.x,this.y)
end
end
end,
draw=function(this)
if this.state!=2 then
if this.state!=1 then
spr(23,this.x,this.y)
else
spr(23+(15-this.delay)/5,this.x,this.y)
end
end
end
}
add(types,fall_floor)
function break_fall_floor(obj)
if obj.state==0 then
psfx(15)
obj.state=1
obj.delay=15--how long until it falls
init_object(smoke,obj.x,obj.y)
local hit=obj.collide(spring,0,-1)
if hit~=nil then
break_spring(hit)
end
end
end
smoke={
init=function(this)
this.spr=29
this.spd.y=-0.1
this.spd.x=0.3+rnd(0.2)
this.x+=-1+rnd(2)
this.y+=-1+rnd(2)
this.flip.x=maybe()
this.flip.y=maybe()
this.solids=false
end,
update=function(this)
this.spr+=0.2
if this.spr>=32 then
destroy_object(this)
end
end
}
fruit={
tile=26,
if_not_fruit=true,
init=function(this)
this.start=this.y
this.off=0
end,
update=function(this)
local hit=this.collide(player,0,0)
if hit~=nil then
hit.djump=max_djump
sfx_timer=20
sfx(13)
got_fruit[1+level_index()] = true
init_object(lifeup,this.x,this.y)
destroy_object(this)
end
this.off+=1
this.y=this.start+sin(this.off/40)*2.5
end
}
add(types,fruit)
fly_fruit={
tile=28,
if_not_fruit=true,
init=function(this)
this.start=this.y
this.fly=false
this.step=0.5
this.solids=false
this.sfx_delay=8
end,
update=function(this)
--fly away
if this.fly then
if this.sfx_delay>0 then
this.sfx_delay-=1
if this.sfx_delay<=0 then
sfx_timer=20
sfx(14)
end
end
this.spd.y=appr(this.spd.y,-3.5,0.25)
if this.y<-16 then
destroy_object(this)
end
-- wait
else
if has_dashed then
this.fly=true
end
this.step+=0.05
this.spd.y=sin(this.step)*0.5
end
-- collect
local hit=this.collide(player,0,0)
if hit~=nil then
hit.djump=max_djump
sfx_timer=20
sfx(13)
got_fruit[1+level_index()] = true
init_object(lifeup,this.x,this.y)
destroy_object(this)
end
end,
draw=function(this)
local off=0
if not this.fly then
local dir=sin(this.step)
if dir<0 then
off=1+max(0,sign(this.y-this.start))
end
else
off=(off+0.25)%3
end
spr(45+off,this.x-6,this.y-2,1,1,true,false)
spr(this.spr,this.x,this.y)
spr(45+off,this.x+6,this.y-2)
end
}
add(types,fly_fruit)
lifeup = {
init=function(this)
this.spd.y=-0.25
this.duration=30
this.x-=2
this.y-=4
this.flash=0
this.solids=false
end,
update=function(this)
this.duration-=1
if this.duration<= 0 then
destroy_object(this)
end
end,
draw=function(this)
this.flash+=0.5
print("1000",this.x-2,this.y,7+this.flash%2)
end
}
fake_wall = {
tile=64,
if_not_fruit=true,
update=function(this)
this.hitbox={x=-1,y=-1,w=18,h=18}
local hit = this.collide(player,0,0)
if hit~=nil and hit.dash_effect_time>0 then
hit.spd.x=-sign(hit.spd.x)*1.5
hit.spd.y=-1.5
hit.dash_time=-1
sfx_timer=20
sfx(16)
destroy_object(this)
init_object(smoke,this.x,this.y)
init_object(smoke,this.x+8,this.y)
init_object(smoke,this.x,this.y+8)
init_object(smoke,this.x+8,this.y+8)
init_object(fruit,this.x+4,this.y+4)
end
this.hitbox={x=0,y=0,w=16,h=16}
end,
draw=function(this)
spr(64,this.x,this.y)
spr(65,this.x+8,this.y)
spr(80,this.x,this.y+8)
spr(81,this.x+8,this.y+8)
end
}
add(types,fake_wall)
key={
tile=8,
if_not_fruit=true,
update=function(this)
local was=flr(this.spr)
this.spr=9+(sin(frames/30)+0.5)*1
local is=flr(this.spr)
if is==10 and is!=was then
this.flip.x=not this.flip.x
end
if this.check(player,0,0) then
sfx(23)
sfx_timer=10
destroy_object(this)
has_key=true
end
end
}
add(types,key)
chest={
tile=20,
if_not_fruit=true,
init=function(this)
this.x-=4
this.start=this.x
this.timer=20
end,
update=function(this)
if has_key then
this.timer-=1
this.x=this.start-1+rnd(3)
if this.timer<=0 then
sfx_timer=20
sfx(16)
init_object(fruit,this.x,this.y-4)
destroy_object(this)
end
end
end
}
add(types,chest)
platform={
init=function(this)
this.x-=4
this.solids=false
this.hitbox.w=16
this.last=this.x
end,
update=function(this)
this.spd.x=this.dir*0.65
if this.x<-16 then this.x=128
elseif this.x>128 then this.x=-16 end
if not this.check(player,0,0) then
local hit=this.collide(player,0,-1)
if hit~=nil then
hit.move_x(this.x-this.last,1)
end
end
this.last=this.x
end,
draw=function(this)
spr(11,this.x,this.y-1)
spr(12,this.x+8,this.y-1)
end
}
message={
tile=86,
last=0,
draw=function(this)
this.text="-- celeste mountain --#this memorial to those# perished on the climb"
if this.check(player,4,0) then
if this.index<#this.text then
this.index+=0.5
if this.index>=this.last+1 then
this.last+=1
sfx(35)
end
end
this.off={x=8,y=96}
for i=1,this.index do
if sub(this.text,i,i)~="#" then
rectfill(this.off.x-2,this.off.y-2,this.off.x+7,this.off.y+6 ,7)
print(sub(this.text,i,i),this.off.x,this.off.y,0)
this.off.x+=5
else
this.off.x=8
this.off.y+=7
end
end
else
this.index=0
this.last=0
end
end
}
add(types,message)
big_chest={
tile=96,
init=function(this)
this.state=0
this.hitbox.w=16
end,
draw=function(this)
if this.state==0 then
local hit=this.collide(player,0,8)
if hit~=nil and hit.is_solid(0,1) then
music(-1,500,7)
sfx(37)
pause_player=true
hit.spd.x=0
hit.spd.y=0
this.state=1
init_object(smoke,this.x,this.y)
init_object(smoke,this.x+8,this.y)
this.timer=60
this.particles={}
end
spr(96,this.x,this.y)
spr(97,this.x+8,this.y)
elseif this.state==1 then
this.timer-=1
shake=5
flash_bg=true
if this.timer<=45 and count(this.particles)<50 then
add(this.particles,{
x=1+rnd(14),
y=0,
h=32+rnd(32),
spd=8+rnd(8)
})
end
if this.timer<0 then
this.state=2
this.particles={}
flash_bg=false
new_bg=true
init_object(orb,this.x+4,this.y+4)
pause_player=false
end
foreach(this.particles,function(p)
p.y+=p.spd
line(this.x+p.x,this.y+8-p.y,this.x+p.x,min(this.y+8-p.y+p.h,this.y+8),7)
end)
end
spr(112,this.x,this.y+8)
spr(113,this.x+8,this.y+8)
end
}
add(types,big_chest)
orb={
init=function(this)
this.spd.y=-4
this.solids=false
this.particles={}
end,
draw=function(this)
this.spd.y=appr(this.spd.y,0,0.5)
local hit=this.collide(player,0,0)
if this.spd.y==0 and hit~=nil then
music_timer=45
sfx(51)
freeze=10
shake=10
destroy_object(this)
max_djump=2
hit.djump=2
end
spr(102,this.x,this.y)
local off=frames/30
for i=0,7 do
circfill(this.x+4+cos(off+i/8)*8,this.y+4+sin(off+i/8)*8,1,7)
end
end
}
flag = {
tile=118,
init=function(this)
this.x+=5
this.score=0
this.show=false
for i=1,count(got_fruit) do
if got_fruit[i] then
this.score+=1
end
end
end,
draw=function(this)
this.spr=118+(frames/5)%3
spr(this.spr,this.x,this.y)
if this.show then
rectfill(32,2,96,31,0)
spr(26,55,6)
print("x"..this.score,64,9,7)
draw_time(49,16)
print("deaths:"..deaths,48,24,7)
elseif this.check(player,0,0) then
sfx(55)
sfx_timer=30
this.show=true
end
end
}
add(types,flag)
room_title = {
init=function(this)
this.delay=5
end,
draw=function(this)
this.delay-=1
if this.delay<-30 then
destroy_object(this)
elseif this.delay<0 then
rectfill(24,58,104,70,0)
--rect(26,64-10,102,64+10,7)
--print("---",31,64-2,13)
if room.x==3 and room.y==1 then
print("old site",48,62,7)
elseif level_index()==30 then
print("summit",52,62,7)
else
local level=(1+level_index())*100
print(level.." m",52+(level<1000 and 2 or 0),62,7)
end
--print("---",86,64-2,13)
draw_time(4,4)
end
end
}
-- object functions --
-----------------------
function init_object(type,x,y)
if type.if_not_fruit~=nil and got_fruit[1+level_index()] then
return
end
local obj = {}
obj.type = type
obj.collideable=true
obj.solids=true
obj.spr = type.tile
obj.flip = {x=false,y=false}
obj.x = x
obj.y = y
obj.hitbox = { x=0,y=0,w=8,h=8 }
obj.spd = {x=0,y=0}
obj.rem = {x=0,y=0}
obj.is_solid=function(ox,oy)
if oy>0 and not obj.check(platform,ox,0) and obj.check(platform,ox,oy) then
return true
end
return solid_at(obj.x+obj.hitbox.x+ox,obj.y+obj.hitbox.y+oy,obj.hitbox.w,obj.hitbox.h)
or obj.check(fall_floor,ox,oy)
or obj.check(fake_wall,ox,oy)
end
obj.is_ice=function(ox,oy)
return ice_at(obj.x+obj.hitbox.x+ox,obj.y+obj.hitbox.y+oy,obj.hitbox.w,obj.hitbox.h)
end
obj.collide=function(type,ox,oy)
local other
for i=1,count(objects) do
other=objects[i]
if other ~=nil and other.type == type and other != obj and other.collideable and
other.x+other.hitbox.x+other.hitbox.w > obj.x+obj.hitbox.x+ox and
other.y+other.hitbox.y+other.hitbox.h > obj.y+obj.hitbox.y+oy and
other.x+other.hitbox.x < obj.x+obj.hitbox.x+obj.hitbox.w+ox and
other.y+other.hitbox.y < obj.y+obj.hitbox.y+obj.hitbox.h+oy then
return other
end
end
return nil
end
obj.check=function(type,ox,oy)
return obj.collide(type,ox,oy) ~=nil
end
obj.move=function(ox,oy)
local amount
-- [x] get move amount
obj.rem.x += ox
amount = flr(obj.rem.x + 0.5)
obj.rem.x -= amount
obj.move_x(amount,0)
-- [y] get move amount
obj.rem.y += oy
amount = flr(obj.rem.y + 0.5)
obj.rem.y -= amount
obj.move_y(amount)
end
obj.move_x=function(amount,start)
if obj.solids then
local step = sign(amount)
for i=start,abs(amount) do
if not obj.is_solid(step,0) then
obj.x += step
else
obj.spd.x = 0
obj.rem.x = 0
break
end
end
else
obj.x += amount
end
end
obj.move_y=function(amount)
if obj.solids then
local step = sign(amount)
for i=0,abs(amount) do
if not obj.is_solid(0,step) then
obj.y += step
else
obj.spd.y = 0
obj.rem.y = 0
break
end
end
else
obj.y += amount
end
end
add(objects,obj)
if obj.type.init~=nil then
obj.type.init(obj)
end
return obj
end
function destroy_object(obj)
del(objects,obj)
end
function kill_player(obj)
sfx_timer=12
sfx(0)
deaths+=1
shake=10
destroy_object(obj)
dead_particles={}
for dir=0,7 do
local angle=(dir/8)
add(dead_particles,{
x=obj.x+4,
y=obj.y+4,
t=10,
spd={
x=sin(angle)*3,
y=cos(angle)*3
}
})
restart_room()
end
end
-- room functions --
--------------------
function restart_room()
will_restart=true
delay_restart=15
end
function next_room()
if room.x==2 and room.y==1 then
music(30,500,7)
elseif room.x==3 and room.y==1 then
music(20,500,7)
elseif room.x==4 and room.y==2 then
music(30,500,7)
elseif room.x==5 and room.y==3 then
music(30,500,7)
end
if room.x==7 then
load_room(0,room.y+1)
else
load_room(room.x+1,room.y)
end
end
function load_room(x,y)
has_dashed=false
has_key=false
--remove existing objects
foreach(objects,destroy_object)
--current room
room.x = x
room.y = y
-- entities
for tx=0,15 do
for ty=0,15 do
local tile = mget(room.x*16+tx,room.y*16+ty);
if tile==11 then
init_object(platform,tx*8,ty*8).dir=-1
elseif tile==12 then
init_object(platform,tx*8,ty*8).dir=1
else
foreach(types,
function(type)
if type.tile == tile then
init_object(type,tx*8,ty*8)
end
end)
end
end
end
if not is_title() then
init_object(room_title,0,0)
end
end
-- update function --
-----------------------
function _update()
frames=((frames+1)%30)
if frames==0 and level_index()<30 then
seconds=((seconds+1)%60)
if seconds==0 then
minutes+=1
end
end
if music_timer>0 then
music_timer-=1
if music_timer<=0 then
music(10,0,7)
end
end
if sfx_timer>0 then
sfx_timer-=1
end
-- cancel if freeze
if freeze>0 then freeze-=1 return end
-- screenshake
if shake>0 then
shake-=1
camera()
if shake>0 then
camera(-2+rnd(5),-2+rnd(5))
end
end
-- restart (soon)
if will_restart and delay_restart>0 then
delay_restart-=1
if delay_restart<=0 then
will_restart=false
load_room(room.x,room.y)
end
end
-- update each object
foreach(objects,function(obj)
obj.move(obj.spd.x,obj.spd.y)
if obj.type.update~=nil then
obj.type.update(obj)
end
end)
-- start game
if is_title() then
if not start_game and (btn(k_jump) or btn(k_dash)) then
music(-1)
start_game_flash=50
start_game=true
sfx(38)
end
if start_game then
start_game_flash-=1
if start_game_flash<=-30 then
begin_game()
end
end
end
end
-- drawing functions --
-----------------------
function _draw()
if freeze>0 then return end
-- reset all palette values
pal()
-- start game flash
if start_game then
local c=10
if start_game_flash>10 then
if frames%10<5 then
c=7
end
elseif start_game_flash>5 then
c=2
elseif start_game_flash>0 then
c=1
else
c=0
end
if c<10 then
pal(6,c)
pal(12,c)
pal(13,c)
pal(5,c)
pal(1,c)
pal(7,c)
end
end
-- clear screen
local bg_col = 0
if flash_bg then
bg_col = frames/5
elseif new_bg~=nil then
bg_col=2
end
rectfill(0,0,128,128,bg_col)
-- clouds
if not is_title() then
foreach(clouds, function(c)
c.x += c.spd
rectfill(c.x,c.y,c.x+c.w,c.y+4+(1-c.w/64)*12,new_bg~=nil and 14 or 1)
if c.x > 128 then
c.x = -c.w
c.y=rnd(128-8)
end
end)
end
-- draw bg terrain
map(room.x * 16,room.y * 16,0,0,16,16,4)
-- platforms/big chest
foreach(objects, function(o)
if o.type==platform or o.type==big_chest then
draw_object(o)
end
end)
-- draw terrain
local off=is_title() and -4 or 0
map(room.x*16,room.y * 16,off,0,16,16,2)
-- draw objects
foreach(objects, function(o)
if o.type~=platform and o.type~=big_chest then
draw_object(o)
end
end)
-- draw fg terrain
map(room.x * 16,room.y * 16,0,0,16,16,8)
-- particles
foreach(particles, function(p)
p.x += p.spd
p.y += sin(p.off)
p.off+= min(0.05,p.spd/32)
rectfill(p.x,p.y,p.x+p.s,p.y+p.s,p.c)
if p.x>128+4 then
p.x=-4
p.y=rnd(128)
end
end)
-- dead particles
foreach(dead_particles, function(p)
p.x += p.spd.x
p.y += p.spd.y
p.t -=1
if p.t <= 0 then del(dead_particles,p) end
rectfill(p.x-p.t/5,p.y-p.t/5,p.x+p.t/5,p.y+p.t/5,14+p.t%2)
end)
-- draw outside of the screen for screenshake
rectfill(-5,-5,-1,133,0)
rectfill(-5,-5,133,-1,0)
rectfill(-5,128,133,133,0)
rectfill(128,-5,133,133,0)
-- credits
if is_title() then
print("x+c",58,80,5)
print("matt thorson",42,96,5)
print("noel berry",46,102,5)
end
if level_index()==30 then
local p
for i=1,count(objects) do
if objects[i].type==player then
p = objects[i]
break
end
end
if p~=nil then
local diff=min(24,40-abs(p.x+4-64))
rectfill(0,0,diff,128,0)
rectfill(128-diff,0,128,128,0)
end
end
end
function draw_object(obj)
if obj.type.draw ~=nil then
obj.type.draw(obj)
elseif obj.spr > 0 then
spr(obj.spr,obj.x,obj.y,1,1,obj.flip.x,obj.flip.y)
end
end
function draw_time(x,y)
local s=seconds
local m=minutes%60
local h=flr(minutes/60)
rectfill(x,y,x+32,y+6,0)
print((h<10 and "0"..h or h)..":"..(m<10 and "0"..m or m)..":"..(s<10 and "0"..s or s),x+1,y+1,7)
end
-- helper functions --
----------------------
function clamp(val,a,b)
return max(a, min(b, val))
end
function appr(val,target,amount)
return val > target
and max(val - amount, target)
or min(val + amount, target)
end
function sign(v)
return v>0 and 1 or
v<0 and -1 or 0
end
function maybe()
return rnd(1)<0.5
end
function solid_at(x,y,w,h)
return tile_flag_at(x,y,w,h,0)
end
function ice_at(x,y,w,h)
return tile_flag_at(x,y,w,h,4)
end
function tile_flag_at(x,y,w,h,flag)
for i=max(0,flr(x/8)),min(15,(x+w-1)/8) do
for j=max(0,flr(y/8)),min(15,(y+h-1)/8) do
if fget(tile_at(i,j),flag) then
return true
end
end
end
return false
end
function tile_at(x,y)
return mget(room.x * 16 + x, room.y * 16 + y)
end
function spikes_at(x,y,w,h,xspd,yspd)
for i=max(0,flr(x/8)),min(15,(x+w-1)/8) do
for j=max(0,flr(y/8)),min(15,(y+h-1)/8) do
local tile=tile_at(i,j)
if tile==17 and ((y+h-1)%8>=6 or y+h==j*8+8) and yspd>=0 then
return true
elseif tile==27 and y%8<=2 and yspd<=0 then
return true
elseif tile==43 and x%8<=2 and xspd<=0 then
return true
elseif tile==59 and ((x+w-1)%8>=6 or x+w==i*8+8) and xspd>=0 then
return true
end
end
end
return false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment