Skip to content

Commit 853eca7

Browse files
committed
Added bumper cars minigame
1 parent 6f420af commit 853eca7

24 files changed

+458
-8
lines changed

assets/car1.png

7.6 KB
Loading

assets/car10.png

9.12 KB
Loading

assets/car11.png

9.12 KB
Loading

assets/car12.png

9.13 KB
Loading

assets/car13.png

8.01 KB
Loading

assets/car2.png

9.04 KB
Loading

assets/car3.png

8.9 KB
Loading

assets/car4.png

8.95 KB
Loading

assets/car5.png

8.23 KB
Loading

assets/car6.png

8.36 KB
Loading

assets/car7.png

8.48 KB
Loading

assets/car8.png

9.13 KB
Loading

assets/car9.png

8.95 KB
Loading

assets/earth.png

18.4 KB
Loading

assets/plate.png

146 KB
Loading

assets/space.png

994 KB
Loading

src/BombScene.adept

+1-2
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ struct BombScene (
171171
}
172172

173173
each Particle in this.particles {
174-
if it.animation.atEnd(), this.particles.remove(idx--); continue
175-
it.animation.update()
174+
if it.update(), this.particles.remove(idx--); continue
176175
}
177176

178177
each Bomb in this.bombs {

src/BumperScene.adept

+357
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
2+
import math
3+
import Optional
4+
import "Scene.adept"
5+
import "drawing.adept"
6+
7+
struct BumperScene (
8+
struct Scene,
9+
started bool,
10+
message String,
11+
message_x float,
12+
world *b2World,
13+
friction_body *b2Body,
14+
cars 4 <BumperCar> Optional,
15+
winner int,
16+
won_when double,
17+
particles <Particle> List
18+
) {
19+
func enter {
20+
this.world = new b2World
21+
gravity POD b2Vec2 = POD b2Vec2(0.0f, 0.0f)
22+
b2World(this.world, &gravity)
23+
24+
// Create ground body
25+
if true {
26+
bodydef b2BodyDef = b2BodyDef()
27+
bodydef.type = b2_staticBody
28+
29+
b2_w float = 4.0f * 0.5f * captViewWidth() / PPM
30+
b2_h float = 4.0f * 0.5f * captViewHeight() / PPM
31+
toB2Coords(captViewWidth() / 2.0f, captViewHeight() / 2.0f, undef b2_x float, undef b2_y float)
32+
bodydef.position.Set(b2_x, b2_y)
33+
body *b2Body = this.world.CreateBody(&bodydef)
34+
35+
box b2PolygonShape = b2PolygonShape()
36+
box.SetAsBox(b2_w, b2_h)
37+
body.CreateFixture(&box as *b2Shape, 0.0f)
38+
this.friction_body = body
39+
}
40+
41+
this.winner = -1
42+
this.won_when = NEVER
43+
}
44+
45+
func exit {
46+
if this.world {
47+
this.world.__defer__()
48+
delete this.world
49+
}
50+
}
51+
52+
func step {
53+
unless this.world, return
54+
55+
define time_step = 1.0f / 60.0f
56+
define velocity_iterations = 6
57+
define position_iterations = 2
58+
59+
this.world.Step(time_step, velocity_iterations, position_iterations)
60+
61+
unless this.started, each Gamepad in static gamedata.gamepads {
62+
if it.start() {
63+
this.started = true
64+
this.showMessage("Use A to instantly boost!")
65+
this.createPlayers()
66+
}
67+
}
68+
69+
unless this.started, return
70+
71+
each Gamepad in static gamedata.gamepads {
72+
player_idx int = it.id - GLFW_JOYSTICK_1
73+
74+
unless this.cars[player_idx].has && this.cars[player_idx].getPointer().died_when == NEVER, continue
75+
76+
car *BumperCar = this.cars[player_idx].getPointer()
77+
force POD b2Vec2
78+
79+
if it.rightHeld(), force.x += 1.0f
80+
if it.leftHeld(), force.x -= 1.0f
81+
if it.upHeld(), force.y += 1.0f
82+
if it.downHeld(), force.y -= 1.0f
83+
84+
force.Normalize()
85+
force.MultiplyAssign(10.0f)
86+
car.body.ApplyForceToCenter(&force, true)
87+
88+
if it.a() {
89+
force.MultiplyAssign(0.5f)
90+
car.body.ApplyLinearImpulseToCenter(&force, true)
91+
92+
this.particles.add(particle(car.body.GetPosition().x, car.body.GetPosition().y, 32.0f, 32.0f, gamedata.smoke_animation.newChildInstance()))
93+
}
94+
95+
if force.x != 0.0f || force.y != 0.0f {
96+
angle float = car.body.GetAngle()
97+
target_angle float = atan2f(force.y, force.x) + PI / 2.0f
98+
99+
torque float = 5.0f * normalizeFloat(radiansDifference(angle, target_angle))
100+
car.body.ApplyTorque(torque, false)
101+
}
102+
}
103+
104+
each Particle in this.particles {
105+
if it.update(), this.particles.remove(idx--); continue
106+
}
107+
108+
remaining usize = 0
109+
last_alive int = -1
110+
each <BumperCar> Optional in static this.cars {
111+
unless it.has, continue
112+
113+
car *BumperCar = it.getPointer()
114+
b2_position *b2Vec2 = car.body.GetPosition()
115+
116+
toScreenCoords(b2_position.x, b2_position.y, undef screen_x float, undef screen_y float)
117+
118+
if car.died_when == NEVER && distance(screen_x, screen_y, captViewWidth() / 2.0f, captViewHeight() / 2.0) > 200.0f {
119+
car.died_when = glfwGetTime()
120+
}
121+
122+
if car.died_when != NEVER && glfwGetTime() - car.died_when > 2.0 {
123+
// Destroy car
124+
this.world.DestroyBody(car.body)
125+
126+
// Die completely if no lives left
127+
if car.lives == 0 {
128+
cars_array POD <<BumperCar> Optional> Array = array(this.cars at 0, 4)
129+
place usize = cars_array.count() + 1
130+
if place > 1, gamedata.scores[car.player_idx] += (5 - place) * 3
131+
it.rid()
132+
continue
133+
}
134+
135+
// Otherwise decrease life count
136+
if this.won_when != NEVER, car.lives--
137+
138+
// And respawn
139+
car.body = this.newPlayerBody()
140+
car.died_when = NEVER
141+
}
142+
143+
last_alive = idx as int
144+
remaining++
145+
}
146+
147+
if this.won_when == NEVER && remaining <= 1 {
148+
this.winner = last_alive
149+
this.won_when = glfwGetTime()
150+
151+
if this.winner != -1 {
152+
car *<BumperCar> Optional = &this.cars[this.winner]
153+
gamedata.scores[this.winner] += 100 + (car.has ? 13 * car.getPointer().lives : 0)
154+
}
155+
}
156+
157+
this.message_x -= 32.0f / 60.0f
158+
159+
if this.message_x < -1.0f * captTextCharacterWidthForScale(12.0f) * this.message.length as float - 512.0f {
160+
this.showMessage(this.randomMessage())
161+
}
162+
163+
if this.won_when != NEVER && glfwGetTime() - this.won_when > 5.0 {
164+
gamedata.setScene(new ScoreScene)
165+
return
166+
}
167+
}
168+
169+
func draw {
170+
// Draw space and stars background
171+
const space_tile_h float = 1536.0f / 2.0f
172+
define space_scroll_speed = 64.0f
173+
captDrawTextureTiled(textures.space, 0.0f, 0.0f - (glfwGetTime() * space_scroll_speed) % space_tile_h, 2048.0f / 2.0f, space_tile_h, 2048.0f, space_tile_h * 3.0f)
174+
175+
// Draw background plate
176+
captDrawTexture(textures.earth, captViewWidth() / 2.0f - 200.0f, captViewHeight() / 2.0f - 200.0f, 400.0f, 400.0f)
177+
178+
// Draw how-to-play instructions
179+
unless this.started {
180+
captDrawTexture(textures.black, 64.0f, captViewHeight() / 2.0f - 80.0f, captViewWidth() - 128.0f, 160.0f)
181+
drawTextCentered("Knock other players off the floating platform,", _captain_font, 16.0f, captViewWidth() / 2.0f, captViewHeight() / 2.0f - 24.0f)
182+
drawTextCentered("The last remaining player wins!", _captain_font, 16.0f, captViewWidth() / 2.0f, captViewHeight() / 2.0f + 24.0f)
183+
return
184+
}
185+
186+
each Particle in static this.particles {
187+
drawTextureForBox2D(it.animation.now(), it.b2_x, it.b2_y, it.screen_w / PPM, it.screen_h / PPM, 0.0f)
188+
}
189+
190+
each <BumperCar> Optional in static this.cars {
191+
unless it.has, continue
192+
193+
car *BumperCar = it.getPointer()
194+
character Character = gamedata.characters[car.player_idx]
195+
position *b2Vec2 = car.body.GetPosition()
196+
angle float = car.body.GetAngle()
197+
scale float = 1.0f
198+
199+
if car.died_when != NEVER {
200+
scale = clamp(1.0f - 2.0f * (glfwGetTime() - car.died_when) / 2.0f, 0.0f, 1.0f)
201+
}
202+
203+
drawTextureForBox2D(getCarTexture(character), position.x, position.y, scale * 25.0f / PPM, scale * 44.0f / PPM, angle)
204+
drawTextureForBox2D(getIdleTexture(character), position.x, position.y, scale * 16.0f / PPM, scale * 16.0f / PPM, 0.0f)
205+
}
206+
207+
if true {
208+
drawTextWithFont(this.message, this.message_x, 18.0f, _captain_font, 10.0f)
209+
}
210+
211+
alive int = 0
212+
213+
repeat 4 using reverse_idx {
214+
const idx usize = 3 - reverse_idx
215+
const character Character = gamedata.characters[idx]
216+
217+
if character == Character::NONE || !this.cars[idx].has, continue
218+
219+
const lives int = this.cars[idx].getPointer().lives
220+
221+
define font_size = 12.0f
222+
drawTextWithFont("P% % - % lives" % (idx + 1) % pad(getName(character), 6) % lives, captViewWidth() - captTextCharacterWidthForScale(font_size) * 20.0f, captViewHeight() - 4.0f - (font_size * 24.0f / 16.0f) * cast float (alive + 1), _captain_font, font_size)
223+
alive++
224+
}
225+
226+
if this.won_when != NEVER {
227+
captDrawTexture(textures.black, 64.0f, captViewHeight() / 2.0f - 40.0f, captViewWidth() - 128.0f, 80.0f)
228+
229+
if this.winner != -1 {
230+
drawTextCentered("P% % wins!" % (this.winner + 1) % getName(gamedata.characters[this.winner]), _captain_font, 16.0f, captViewWidth() / 2.0f, captViewHeight() / 2.0f)
231+
} else {
232+
drawTextCentered("Nobody wins!", _captain_font, 16.0f, captViewWidth() / 2.0f, captViewHeight() / 2.0f)
233+
}
234+
}
235+
}
236+
237+
func createPlayers {
238+
angle float = PI / 4.0f
239+
distance float = 128.0f
240+
241+
each Gamepad in static gamedata.gamepads {
242+
player_idx int = it.id - GLFW_JOYSTICK_1
243+
244+
this.maybeSpawnPlayer(player_idx)
245+
}
246+
}
247+
248+
func maybeSpawnPlayer(player_idx usize) successful {
249+
if gamedata.characters[player_idx] == Character::NONE, return false
250+
251+
this.cars[player_idx] = some(bumperCar(player_idx, this.newPlayerBody()))
252+
return true
253+
}
254+
255+
func newPlayerBody() *b2Body {
256+
character_shape b2PolygonShape = b2PolygonShape()
257+
character_shape.SetAsBox(0.5f * 25.0f / PPM, 0.5f * 44.0f / PPM)
258+
259+
toB2Coords(captViewWidth() / 2.0f, captViewHeight() / 2.0f, undef b2_x float, undef b2_y float)
260+
261+
bodydef b2BodyDef = b2BodyDef()
262+
bodydef.type = b2_dynamicBody
263+
bodydef.position.Set(b2_x, b2_y)
264+
265+
body *b2Body = this.world.CreateBody(&bodydef)
266+
267+
fixturedef b2FixtureDef = b2FixtureDef()
268+
fixturedef.shape = &character_shape as *b2Shape
269+
fixturedef.density = 1.0f
270+
fixturedef.friction = 0.1f
271+
fixturedef.restitution = 0.9f
272+
273+
passive_friction b2FrictionJointDef = b2FrictionJointDef()
274+
passive_friction.bodyA = body
275+
passive_friction.bodyB = this.friction_body
276+
passive_friction.maxForce = 1.0f
277+
passive_friction.maxTorque = 3.0f
278+
279+
this.world.CreateJoint(cast *b2JointDef &passive_friction)
280+
body.CreateFixture(&fixturedef)
281+
return body
282+
}
283+
284+
func showMessage(message String) {
285+
message.make()
286+
287+
this.message = message.commit()
288+
this.message_x = captViewWidth() + 128.0f
289+
}
290+
291+
func randomMessage() String {
292+
messages <String> List = (embed "static_assets/joke_headlines.txt").splitIntoViews("\n")
293+
return messages.get(random(messages.length)).clone()
294+
}
295+
}
296+
297+
struct BumperCar (
298+
player_idx int,
299+
body *b2Body,
300+
died_when double,
301+
lives int
302+
)
303+
304+
func bumperCar(player_idx int, body *b2Body) BumperCar {
305+
bc POD BumperCar
306+
bc.player_idx = player_idx
307+
bc.body = body
308+
bc.died_when = NEVER
309+
bc.lives = 5
310+
return bc
311+
}
312+
313+
func normalizeRadians(angle float) float {
314+
define TWO_PI = 2.0f * PI
315+
316+
while angle < 0.0f, angle += TWO_PI
317+
while angle >= TWO_PI, angle -= TWO_PI
318+
319+
return angle
320+
}
321+
322+
func radiansDifference(raw_alpha float, raw_beta float) float {
323+
// NOTE: Assumes 'alpha' and 'beta' are normalized
324+
const alpha float = normalizeRadians(raw_alpha)
325+
const beta float = normalizeRadians(raw_beta)
326+
327+
const clockwise float = radiansDistanceClockwise(alpha, beta)
328+
const counter_clockwise float = radiansDistanceCounterClockwise(alpha, beta)
329+
330+
return clockwise < counter_clockwise ? -1.0f * clockwise : counter_clockwise
331+
}
332+
333+
func radiansDistanceCounterClockwise(normalized_alpha, normalized_beta float) float {
334+
// NOTE: Assumes 'alpha' and 'beta' are normalized
335+
define alpha = normalized_alpha
336+
define beta = normalized_beta
337+
338+
return beta > alpha ? beta - alpha : beta + 2.0f * PI - alpha
339+
}
340+
341+
func radiansDistanceClockwise(normalized_alpha, normalized_beta float) float {
342+
// NOTE: Assumes 'alpha' and 'beta' are normalized
343+
define alpha = normalized_alpha
344+
define beta = normalized_beta
345+
346+
return beta < alpha ? alpha - beta : alpha + 2.0f * PI - beta
347+
}
348+
349+
func normalizeFloat(value float) float {
350+
return value < 0.0f ? -1.0f : value > 0.0f ? 1.0f : 0.0f
351+
}
352+
353+
func count(this *<<$T> Optional> Array) usize {
354+
count usize = 0
355+
repeat this.length, if this.items[idx].has, count++
356+
return count
357+
}

0 commit comments

Comments
 (0)