0
# Physics Engines
1
2
Multiple physics engine options from simple AABB collision to advanced 2D physics simulation, providing realistic movement, collision detection, and physics-based gameplay mechanics.
3
4
## Capabilities
5
6
### Simple Physics Engine
7
8
Basic AABB (Axis-Aligned Bounding Box) collision detection for straightforward movement and wall collision.
9
10
```python { .api }
11
class PhysicsEngineSimple:
12
"""
13
Simple physics engine using AABB collision detection.
14
Prevents sprites from moving through solid walls and barriers.
15
"""
16
def __init__(self, player_sprite: arcade.Sprite, walls: arcade.SpriteList):
17
"""
18
Create a simple physics engine.
19
20
Args:
21
player_sprite: The sprite that will be moved and checked for collisions
22
walls: SpriteList containing solid obstacles that block movement
23
"""
24
25
player_sprite: arcade.Sprite
26
walls: arcade.SpriteList
27
28
def update(self) -> None:
29
"""
30
Update physics simulation. Call this each frame to apply movement and handle collisions.
31
Moves the player sprite based on its change_x and change_y values, then checks
32
for collisions with walls and prevents movement through them.
33
"""
34
35
def add_sprite(self, sprite: arcade.Sprite) -> None:
36
"""
37
Add a wall sprite to the collision list.
38
39
Args:
40
sprite: Sprite to add as a collidable wall
41
"""
42
43
def remove_sprite(self, sprite: arcade.Sprite) -> None:
44
"""
45
Remove a wall sprite from the collision list.
46
47
Args:
48
sprite: Sprite to remove from walls
49
"""
50
```
51
52
### Platformer Physics Engine
53
54
Advanced physics engine designed for platformer games with gravity, jumping, ladders, and moving platforms.
55
56
```python { .api }
57
class PhysicsEnginePlatformer:
58
"""
59
Physics engine designed for 2D platformer games with gravity, jumping mechanics,
60
ladder climbing, and moving platform support.
61
"""
62
def __init__(self, player_sprite: arcade.Sprite, platforms: arcade.SpriteList = None,
63
gravity_constant: float = 0.5, ladders: arcade.SpriteList = None,
64
walls: arcade.SpriteList = None):
65
"""
66
Create a platformer physics engine.
67
68
Args:
69
player_sprite: The player character sprite
70
platforms: SpriteList of platforms the player can stand on
71
gravity_constant: Strength of gravity (pixels per frame squared)
72
ladders: SpriteList of ladder sprites the player can climb
73
walls: SpriteList of wall sprites that block horizontal movement
74
"""
75
76
player_sprite: arcade.Sprite
77
platforms: arcade.SpriteList
78
ladders: arcade.SpriteList
79
walls: arcade.SpriteList
80
gravity_constant: float
81
82
def update(self) -> None:
83
"""
84
Update physics simulation.
85
Applies gravity, handles jumping, processes collisions with platforms/walls/ladders,
86
and manages platformer-specific movement mechanics.
87
"""
88
89
def enable_multi_jump(self, allowed_jumps: int) -> None:
90
"""
91
Enable multi-jumping (double jump, triple jump, etc.).
92
93
Args:
94
allowed_jumps: Total number of jumps allowed (including initial jump)
95
"""
96
97
def disable_multi_jump(self) -> None:
98
"""Disable multi-jumping, allowing only single jumps."""
99
100
def increment_jump_count(self) -> None:
101
"""Increment the current jump count (call when player jumps)."""
102
103
def can_jump(self, y_distance: float = 5) -> bool:
104
"""
105
Check if the player can jump.
106
107
Args:
108
y_distance: Distance to check below player for ground
109
110
Returns:
111
True if player can jump, False otherwise
112
"""
113
114
def is_on_ladder(self) -> bool:
115
"""
116
Check if the player is currently on a ladder.
117
118
Returns:
119
True if player is on a ladder, False otherwise
120
"""
121
122
def add_sprite(self, sprite: arcade.Sprite, sprite_type: str) -> None:
123
"""
124
Add a sprite to the physics engine.
125
126
Args:
127
sprite: Sprite to add
128
sprite_type: Type of sprite ("platform", "wall", or "ladder")
129
"""
130
131
def remove_sprite(self, sprite: arcade.Sprite) -> None:
132
"""
133
Remove a sprite from all physics lists.
134
135
Args:
136
sprite: Sprite to remove
137
"""
138
```
139
140
### Pymunk Physics Engine
141
142
Advanced 2D physics simulation using the Pymunk library for realistic physics behavior.
143
144
```python { .api }
145
class PymunkPhysicsEngine:
146
"""
147
Advanced 2D physics engine using Pymunk for realistic physics simulation
148
including rigid body dynamics, joints, constraints, and collision callbacks.
149
"""
150
def __init__(self, gravity: tuple[float, float] = (0, -981), damping: float = 1.0):
151
"""
152
Create a Pymunk physics engine.
153
154
Args:
155
gravity: Gravity vector (x, y) in pixels per second squared
156
damping: Global damping factor (1.0 = no damping, lower = more damping)
157
"""
158
159
gravity: tuple[float, float]
160
damping: float
161
space: object # pymunk.Space
162
163
def add_sprite(self, sprite: arcade.Sprite, mass: float = 1,
164
moment: float = None, body_type: int = None,
165
collision_type: str = "default",
166
friction: float = 0.2, elasticity: float = 0.0) -> None:
167
"""
168
Add a sprite to the physics simulation.
169
170
Args:
171
sprite: Sprite to add physics body to
172
mass: Mass of the body (affects physics interactions)
173
moment: Moment of inertia (None = auto-calculate from shape)
174
body_type: Pymunk body type (DYNAMIC, KINEMATIC, STATIC)
175
collision_type: String identifier for collision callbacks
176
friction: Surface friction (0.0 = no friction, 1.0+ = high friction)
177
elasticity: Bounciness (0.0 = no bounce, 1.0 = perfect bounce)
178
"""
179
180
def add_sprite_list(self, sprite_list: arcade.SpriteList, mass: float = 1,
181
moment: float = None, body_type: int = None,
182
collision_type: str = "default",
183
friction: float = 0.2, elasticity: float = 0.0) -> None:
184
"""
185
Add all sprites in a sprite list to physics simulation.
186
187
Args:
188
sprite_list: SpriteList to add
189
mass: Mass for each sprite
190
moment: Moment of inertia for each sprite
191
body_type: Pymunk body type for each sprite
192
collision_type: Collision type for each sprite
193
friction: Friction for each sprite
194
elasticity: Elasticity for each sprite
195
"""
196
197
def remove_sprite(self, sprite: arcade.Sprite) -> None:
198
"""
199
Remove a sprite from the physics simulation.
200
201
Args:
202
sprite: Sprite to remove
203
"""
204
205
def add_collision_handler(self, first_type: str, second_type: str,
206
begin_handler: callable = None,
207
pre_solve_handler: callable = None,
208
post_solve_handler: callable = None,
209
separate_handler: callable = None) -> None:
210
"""
211
Add collision event handlers for specific collision types.
212
213
Args:
214
first_type: First collision type identifier
215
second_type: Second collision type identifier
216
begin_handler: Called when collision begins
217
pre_solve_handler: Called before collision resolution
218
post_solve_handler: Called after collision resolution
219
separate_handler: Called when objects separate
220
"""
221
222
def step(self, delta_time: float = 1/60, resync_sprites: bool = True) -> None:
223
"""
224
Step the physics simulation forward in time.
225
226
Args:
227
delta_time: Time step in seconds
228
resync_sprites: Whether to update sprite positions from physics bodies
229
"""
230
231
def resync_sprites(self) -> None:
232
"""Update all sprite positions and rotations from their physics bodies."""
233
234
def apply_force(self, sprite: arcade.Sprite, force: tuple[float, float],
235
point: tuple[float, float] = None) -> None:
236
"""
237
Apply a force to a sprite's physics body.
238
239
Args:
240
sprite: Target sprite
241
force: Force vector (x, y) in Newtons
242
point: Point to apply force at (None = center of mass)
243
"""
244
245
def apply_impulse(self, sprite: arcade.Sprite, impulse: tuple[float, float],
246
point: tuple[float, float] = None) -> None:
247
"""
248
Apply an impulse to a sprite's physics body.
249
250
Args:
251
sprite: Target sprite
252
impulse: Impulse vector (x, y) in Newton-seconds
253
point: Point to apply impulse at (None = center of mass)
254
"""
255
256
def set_velocity(self, sprite: arcade.Sprite, velocity: tuple[float, float]) -> None:
257
"""
258
Set the velocity of a sprite's physics body.
259
260
Args:
261
sprite: Target sprite
262
velocity: Velocity vector (x, y) in pixels per second
263
"""
264
265
def get_velocity(self, sprite: arcade.Sprite) -> tuple[float, float]:
266
"""
267
Get the velocity of a sprite's physics body.
268
269
Args:
270
sprite: Target sprite
271
272
Returns:
273
Velocity vector (x, y) in pixels per second
274
"""
275
276
def set_position(self, sprite: arcade.Sprite, position: tuple[float, float]) -> None:
277
"""
278
Set the position of a sprite's physics body.
279
280
Args:
281
sprite: Target sprite
282
position: Position (x, y) in pixels
283
"""
284
285
def get_sprites_from_space(self) -> list[arcade.Sprite]:
286
"""
287
Get all sprites currently in the physics space.
288
289
Returns:
290
List of sprites with physics bodies
291
"""
292
293
class PymunkPhysicsObject:
294
"""
295
Helper class for creating physics objects with specific properties.
296
"""
297
def __init__(self, shape: object, body: object):
298
"""
299
Create a physics object.
300
301
Args:
302
shape: Pymunk shape object
303
body: Pymunk body object
304
"""
305
306
shape: object # pymunk.Shape
307
body: object # pymunk.Body
308
309
class PymunkException(Exception):
310
"""Exception raised for Pymunk physics errors."""
311
pass
312
```
313
314
### Physics Engine Utilities
315
316
Helper functions and constants for physics engine setup and configuration.
317
318
```python { .api }
319
# Pymunk body type constants
320
DYNAMIC: int = 0 # Objects affected by forces and collisions
321
KINEMATIC: int = 1 # Objects with infinite mass, position controlled manually
322
STATIC: int = 2 # Objects with infinite mass that never move
323
324
# Common physics materials
325
class PhysicsMaterial:
326
"""Predefined physics materials with realistic properties."""
327
328
# Material properties: (friction, elasticity)
329
STEEL: tuple[float, float] = (0.7, 0.2)
330
ICE: tuple[float, float] = (0.03, 0.1)
331
RUBBER: tuple[float, float] = (1.0, 0.9)
332
WOOD: tuple[float, float] = (0.4, 0.3)
333
CONCRETE: tuple[float, float] = (0.8, 0.1)
334
GLASS: tuple[float, float] = (0.2, 0.8)
335
```
336
337
## Usage Examples
338
339
### Simple Physics Engine Example
340
341
```python
342
import arcade
343
344
class SimplePhysicsGame(arcade.Window):
345
def __init__(self):
346
super().__init__(800, 600, "Simple Physics")
347
348
self.player_list = None
349
self.wall_list = None
350
self.physics_engine = None
351
352
def setup(self):
353
# Create sprite lists
354
self.player_list = arcade.SpriteList()
355
self.wall_list = arcade.SpriteList()
356
357
# Create player
358
self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png", 0.5)
359
self.player_sprite.center_x = 64
360
self.player_sprite.center_y = 96
361
self.player_list.append(self.player_sprite)
362
363
# Create walls
364
for x in range(0, 800, 64):
365
wall = arcade.Sprite(":resources:images/tiles/grassMid.png")
366
wall.center_x = x
367
wall.center_y = 32
368
self.wall_list.append(wall)
369
370
# Create physics engine
371
self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
372
373
def on_draw(self):
374
self.clear()
375
self.wall_list.draw()
376
self.player_list.draw()
377
378
def on_update(self, delta_time):
379
self.physics_engine.update()
380
381
def on_key_press(self, key, modifiers):
382
if key == arcade.key.UP:
383
self.player_sprite.change_y = 5
384
elif key == arcade.key.DOWN:
385
self.player_sprite.change_y = -5
386
elif key == arcade.key.LEFT:
387
self.player_sprite.change_x = -5
388
elif key == arcade.key.RIGHT:
389
self.player_sprite.change_x = 5
390
391
def on_key_release(self, key, modifiers):
392
if key in (arcade.key.UP, arcade.key.DOWN):
393
self.player_sprite.change_y = 0
394
elif key in (arcade.key.LEFT, arcade.key.RIGHT):
395
self.player_sprite.change_x = 0
396
397
def main():
398
game = SimplePhysicsGame()
399
game.setup()
400
arcade.run()
401
402
if __name__ == "__main__":
403
main()
404
```
405
406
### Platformer Physics Engine Example
407
408
```python
409
import arcade
410
411
class PlatformerGame(arcade.Window):
412
def __init__(self):
413
super().__init__(1000, 650, "Platformer Physics")
414
415
self.player_list = None
416
self.platform_list = None
417
self.ladder_list = None
418
self.physics_engine = None
419
420
# Player movement constants
421
self.PLAYER_MOVEMENT_SPEED = 10
422
self.GRAVITY = 1
423
self.PLAYER_JUMP_SPEED = 20
424
425
def setup(self):
426
# Create sprite lists
427
self.player_list = arcade.SpriteList()
428
self.platform_list = arcade.SpriteList()
429
self.ladder_list = arcade.SpriteList()
430
431
# Create player
432
self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png", 0.4)
433
self.player_sprite.center_x = 64
434
self.player_sprite.center_y = 128
435
self.player_list.append(self.player_sprite)
436
437
# Create platforms
438
for x in range(0, 1000, 64):
439
platform = arcade.Sprite(":resources:images/tiles/grassMid.png")
440
platform.center_x = x
441
platform.center_y = 32
442
self.platform_list.append(platform)
443
444
# Create floating platforms
445
for x in range(200, 600, 200):
446
platform = arcade.Sprite(":resources:images/tiles/grassMid.png")
447
platform.center_x = x
448
platform.center_y = 200
449
self.platform_list.append(platform)
450
451
# Create ladder
452
for y in range(100, 300, 64):
453
ladder = arcade.Sprite(":resources:images/tiles/ladderMid.png")
454
ladder.center_x = 500
455
ladder.center_y = y
456
self.ladder_list.append(ladder)
457
458
# Create physics engine with gravity
459
self.physics_engine = arcade.PhysicsEnginePlatformer(
460
self.player_sprite,
461
platforms=self.platform_list,
462
gravity_constant=self.GRAVITY,
463
ladders=self.ladder_list
464
)
465
466
# Enable double jumping
467
self.physics_engine.enable_multi_jump(2)
468
469
def on_draw(self):
470
self.clear()
471
self.platform_list.draw()
472
self.ladder_list.draw()
473
self.player_list.draw()
474
475
# Draw debug info
476
on_ladder = self.physics_engine.is_on_ladder()
477
can_jump = self.physics_engine.can_jump()
478
arcade.draw_text(f"On ladder: {on_ladder}", 10, 580, arcade.color.WHITE, 16)
479
arcade.draw_text(f"Can jump: {can_jump}", 10, 560, arcade.color.WHITE, 16)
480
481
def on_update(self, delta_time):
482
self.physics_engine.update()
483
484
# Scroll camera to follow player
485
self.center_camera_to_player()
486
487
def on_key_press(self, key, modifiers):
488
if key == arcade.key.UP:
489
# Jump if on ground, or climb ladder
490
if self.physics_engine.is_on_ladder():
491
self.player_sprite.change_y = self.PLAYER_MOVEMENT_SPEED
492
elif self.physics_engine.can_jump():
493
self.player_sprite.change_y = self.PLAYER_JUMP_SPEED
494
self.physics_engine.increment_jump_count()
495
496
elif key == arcade.key.DOWN:
497
if self.physics_engine.is_on_ladder():
498
self.player_sprite.change_y = -self.PLAYER_MOVEMENT_SPEED
499
500
elif key == arcade.key.LEFT:
501
self.player_sprite.change_x = -self.PLAYER_MOVEMENT_SPEED
502
503
elif key == arcade.key.RIGHT:
504
self.player_sprite.change_x = self.PLAYER_MOVEMENT_SPEED
505
506
def on_key_release(self, key, modifiers):
507
if key in (arcade.key.UP, arcade.key.DOWN):
508
if self.physics_engine.is_on_ladder():
509
self.player_sprite.change_y = 0
510
elif key in (arcade.key.LEFT, arcade.key.RIGHT):
511
self.player_sprite.change_x = 0
512
513
def main():
514
game = PlatformerGame()
515
game.setup()
516
arcade.run()
517
518
if __name__ == "__main__":
519
main()
520
```
521
522
### Pymunk Physics Engine Example
523
524
```python
525
import arcade
526
527
class PymunkExample(arcade.Window):
528
def __init__(self):
529
super().__init__(800, 600, "Pymunk Physics")
530
531
self.sprite_list = None
532
self.physics_engine = None
533
534
def setup(self):
535
self.sprite_list = arcade.SpriteList()
536
537
# Create Pymunk physics engine with gravity
538
self.physics_engine = arcade.PymunkPhysicsEngine(gravity=(0, -900))
539
540
# Create ground
541
for x in range(0, 800, 64):
542
sprite = arcade.SpriteSolidColor(64, 32, arcade.color.BROWN)
543
sprite.center_x = x
544
sprite.center_y = 16
545
self.sprite_list.append(sprite)
546
547
# Add to physics as static (non-moving) body
548
self.physics_engine.add_sprite(sprite, body_type=arcade.STATIC, friction=0.6)
549
550
# Create dynamic boxes that fall
551
for i in range(5):
552
sprite = arcade.SpriteSolidColor(32, 32, arcade.color.RED)
553
sprite.center_x = 100 + i * 100
554
sprite.center_y = 300
555
self.sprite_list.append(sprite)
556
557
# Add as dynamic body with physics properties
558
self.physics_engine.add_sprite(sprite,
559
mass=1.0,
560
friction=0.4,
561
elasticity=0.3,
562
collision_type="box")
563
564
# Create a ball
565
ball = arcade.SpriteCircle(20, arcade.color.BLUE)
566
ball.center_x = 400
567
ball.center_y = 400
568
self.sprite_list.append(ball)
569
570
# Add ball with high bounce
571
self.physics_engine.add_sprite(ball,
572
mass=0.5,
573
friction=0.3,
574
elasticity=0.8,
575
collision_type="ball")
576
577
# Add collision handler
578
def handle_collision(arbiter, space, data):
579
print("Box and ball collided!")
580
return True
581
582
self.physics_engine.add_collision_handler("box", "ball", begin_handler=handle_collision)
583
584
def on_draw(self):
585
self.clear()
586
self.sprite_list.draw()
587
588
# Draw physics debug info
589
arcade.draw_text("Click to add falling boxes", 10, 570, arcade.color.WHITE, 16)
590
591
def on_update(self, delta_time):
592
# Step physics simulation
593
self.physics_engine.step(delta_time)
594
595
def on_mouse_press(self, x, y, button, modifiers):
596
# Add a new falling box at mouse position
597
sprite = arcade.SpriteSolidColor(32, 32, arcade.color.GREEN)
598
sprite.center_x = x
599
sprite.center_y = y
600
self.sprite_list.append(sprite)
601
602
# Add random velocity for fun
603
self.physics_engine.add_sprite(sprite, mass=1.0, friction=0.4, elasticity=0.2)
604
self.physics_engine.set_velocity(sprite, (0, 100))
605
606
def main():
607
game = PymunkExample()
608
game.setup()
609
arcade.run()
610
611
if __name__ == "__main__":
612
main()
613
```