code / tomo-lost-signal

Lines522 Tomo498 Markdown11 make10
1 others 3
INI3
(262 lines)
1 # This file defines a World struct that keeps track of everything
3 use random
4 use ./raylib.tm
5 use ./player.tm
6 use ./camera.tm
7 use ./box.tm
8 use ./letter.tm
9 use ./satellite.tm
11 struct Goal(pos:Vector2)
12 SIZE := Vector2(32,32)
13 func draw(g:Goal)
14 texture := Texture.load((./assets/Hurricane.png))
15 texture.draw(g.pos, Goal.SIZE)
17 # Return a displacement relative to `a` that will push it out of `b`
18 func solve_overlap(a_pos:Vector2, a_size:Vector2, b_pos:Vector2, b_size:Vector2 -> Vector2)
19 a_left := a_pos.x - a_size.x/2
20 a_right := a_pos.x + a_size.x/2
21 a_top := a_pos.y - a_size.x/2
22 a_bottom := a_pos.y + a_size.y/2
24 b_left := b_pos.x - b_size.x/2
25 b_right := b_pos.x + b_size.x/2
26 b_top := b_pos.y - b_size.y/2
27 b_bottom := b_pos.y + b_size.y/2
29 # Calculate the overlap in each dimension
30 overlap_x := (a_right _min_ b_right) - (a_left _max_ b_left)
31 overlap_y := (a_bottom _min_ b_bottom) - (a_top _max_ b_top)
33 # If either axis is not overlapping, then there is no collision:
34 if overlap_x <= 0 or overlap_y <= 0
35 return Vector2(0, 0)
37 if overlap_x < overlap_y
38 if a_right > b_left and a_right < b_right
39 return Vector2(-(overlap_x), 0)
40 else if a_left < b_right and a_left > b_left
41 return Vector2(overlap_x, 0)
42 else
43 if a_top < b_bottom and a_top > b_top
44 return Vector2(0, overlap_y)
45 else if a_bottom > b_top and a_bottom < b_bottom
46 return Vector2(0, -overlap_y)
48 return Vector2(0, 0)
50 func overlaps(a_pos:Vector2, a_size:Vector2, b_pos:Vector2, b_size:Vector2 -> Bool)
51 return solve_overlap(a_pos, a_size, b_pos, b_size) != Vector2(0, 0)
53 struct Particle(pos,vel:Vector2,radius:Num32,color:Color)
54 func update(p:&Particle, dt:Num32)
55 p.pos += dt*p.vel
56 p.vel *= Num32(0.95)
57 p.radius -= dt*Num32(20.0)
59 func draw(p:Particle)
60 if p.radius > 0
61 DrawCircleV(p.pos, p.radius, p.color)
63 func draw_centered_text(x,y:Int32, text:Text, color:Color, font_size=Int32(48), shadow=yes)
64 if shadow
65 draw_centered_text(x+2,y,text, Color(0,0,0), font_size, shadow=no)
66 draw_centered_text(x-2,y,text, Color(0,0,0), font_size, shadow=no)
67 draw_centered_text(x,y+2,text, Color(0,0,0), font_size, shadow=no)
68 draw_centered_text(x,y-2,text, Color(0,0,0), font_size, shadow=no)
70 string := CString(text)
71 width := MeasureText(string, 48)
72 DrawText(string, x - width/2, y - font_size/2, font_size, color)
74 struct World(
75 player=@Player(Vector2(0,0), Vector2(0,0)),
76 camera=@Camera(Vector2(0,0)),
77 goal:Goal?=none,
78 satellites:@[@Satellite]=@[],
79 particles:@[@Particle]=@[],
80 boxes:@[@Box]=@[],
81 letters:@[Letter]=@[],
82 dt_accum=Num32(0.0),
83 won_time:Num?=none,
85 DT := Num32(1.)/Num32(60.)
86 STIFFNESS := Num32(0.3)
88 func from_map(path:Path -> @World)
89 world := @World()
90 world.load_map(path.read() or exit("Could not find the game map: $path"))
91 world.camera.zoom = C_code : Num32 (
92 (float)GetScreenWidth()/1000.
94 return world
96 func update(w:@World, dt:Num32)
97 w.dt_accum += dt
98 while w.dt_accum > 0
99 w.update_once()
100 w.dt_accum -= World.DT
101 w.camera.follow(w.player.pos)
102 w.camera.update(dt)
104 func update_once(w:@World)
105 if w.goal and w.player.pos.dist(w.goal!.pos) < 30
106 w.player.target_vel = Vector2(0,0)
107 w.player.pos = w.player.pos.mix(w.goal!.pos, .03)
108 else if w.player.has_signal and not w.player.dead
109 target_x := C_code:Num32 (
110 (Num32_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0))
112 target_y := C_code:Num32 (
113 (Num32_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0))
115 w.player.target_vel = Vector2(target_x, target_y).norm() * Player.WALK_SPEED
117 w.player.update()
118 if w.won_time
119 w.player.facing = w.player.facing.norm().rotated(Num32.TAU/60)
121 w.player.has_signal = no
122 for s in w.satellites
123 s.update(w.boxes, w.player)
125 for p in w.particles
126 p.update(World.DT)
128 w.particles[] = [p for p in w.particles if p.radius > 0]
130 GR := Num32(.5) + Num32.sqrt(5)!/Num32(2)
131 if not w.won_time and w.goal and overlaps(w.player.pos, Player.SIZE, w.goal!.pos, Goal.SIZE)
132 w.won_time = GetTime()
134 colors := [
135 Color(0xFF, 0x00, 0x66),
136 Color(0x00, 0xCC, 0xFF),
137 Color(0xFF, 0xFF, 0x33),
138 Color(0x66, 0xFF, 0x66),
139 Color(0xFF, 0x66, 0x00),
140 Color(0x99, 0x33, 0xFF),
141 Color(0xFF, 0x33, 0x99),
142 Color(0x00, 0xFF, 0xCC),
144 for i in 50
145 angle := Num32.TAU * ((Num32(i) * GR) mod Num32(1))
146 w.particles.insert(
147 @Particle(
148 pos=w.goal!.pos,
149 vel=Vector2(random.num32(100,500),0).rotated(angle),
150 radius=random.num32(7,10),
151 color=colors[i mod1 colors.length],
156 # Resolve player overlapping with any boxes:
157 for i in 3
158 for b in w.boxes
159 correction := solve_overlap(w.player.pos, Player.SIZE, b.pos, b.size)
160 if b.fatal and correction != Vector2(0,0) and not w.player.dead
161 # Player hit a killer wall
162 w.player.dead = yes
163 w.camera.add_shake(100)
164 colors := [
165 Color(0xFF, 0xA5, 0x00, 0xc0),
166 Color(0xD2, 0x69, 0x1E, 0xc0),
167 Color(0x8B, 0x45, 0x13, 0xc0),
168 Color(0x70, 0x42, 0x22, 0xc0),
169 Color(0x55, 0x33, 0x22, 0xc0),
170 Color(0x88, 0x88, 0x88, 0xc0),
171 Color(0x44, 0x22, 0x11, 0xc0),
172 Color(0xCC, 0x44, 0x00, 0xc0),
174 for i in 50
175 angle := Num32.TAU * ((Num32(i) * GR) mod Num32(1))
176 w.particles.insert(
177 @Particle(
178 pos=w.player.pos,
179 vel=Vector2(random.num32(100,600),0).rotated(angle),
180 radius=random.num32(10,30),
181 color=colors[i mod1 colors.length],
185 w.player.pos += World.STIFFNESS * correction
187 w.camera.update(World.DT)
189 func draw(w:@World)
190 ClearBackground(Color(0,0,0))
191 bg := Texture.load((./assets/background.png), yes)
192 bg.draw(
193 Vector2(0,0),
194 Vector2(Num32(GetScreenWidth()), Num32(GetScreenHeight())),
195 texture_offset=w.camera.pos*Num32(0.5),
196 tint=Color(0xff,0xff,0xc0,0xFF),
198 bg.draw(
199 Vector2(0,0),
200 Vector2(Num32(GetScreenWidth()), Num32(GetScreenHeight())),
201 texture_offset=w.camera.pos*Num32(0.25) + Vector2(500,300),
202 tint=Color(0xff,0xff,0xc0,0x80),
206 w.camera.begin_drawing()
207 defer w.camera.end_drawing()
209 for l in w.letters
210 l.draw()
212 for s in w.satellites
213 s.draw_beam()
215 for b in w.boxes
216 b.draw()
218 for s in w.satellites
219 s.draw()
221 if goal := w.goal
222 goal.draw()
224 w.player.draw()
226 for p in w.particles
227 p.draw()
230 if w.won_time
231 draw_centered_text(GetScreenWidth()/2, GetScreenHeight()/2 + 70, "You Win!", color=Color(0x80,0xff,0x80))
232 else if w.player.dead or (not w.player.has_signal and w.player.pos.dist(w.player.prev_pos) < Num32(1.0))
233 draw_centered_text(GetScreenWidth()/2, GetScreenHeight()/2 + 70, "Press 'R' to Restart", color=Color(0xff,0x80,0x80))
235 func load_map(w:@World, map:Text)
236 w.boxes[] = []
237 box_size := Vector2(25., 50.)
238 star_textures := [Texture.load(t) for t in (./assets/WhiteStar*).glob()]
239 for y,line in map.lines()
240 for x,cell in line.split()
241 pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y)
242 if cell == "[" or cell == "]"
243 box := @Box(pos, size=box_size)
244 w.boxes.insert(box)
245 else if cell == "#"
246 box := @Box(pos, size=box_size, color=Color(0xe0,0x10,0x10), fatal=yes)
247 w.boxes.insert(box)
248 else if cell == "-"
249 box := @Box(pos, size=box_size, color=Color(0xc0,0xc0,0xff,0x40))
250 w.boxes.insert(box)
251 else if cell == "]"
252 pass # Ignored
253 else if cell == "@"
254 pos += box_size/2 - Player.SIZE/2
255 w.player = @Player(pos,pos)
256 else if cell == "?"
257 w.goal = Goal(pos)
258 else if cell == "+"
259 w.satellites.insert(@Satellite(pos))
260 else if cell != " "
261 w.letters.insert(Letter(CString(cell), pos))