0
# Bodies and Shapes
1
2
Bodies represent rigid objects with mass, position, velocity, and rotation, while Shapes define the collision geometry and material properties attached to bodies. Together they form the fundamental components of physics simulation.
3
4
## Body Class
5
6
The `Body` class represents a rigid body with physical properties like mass, moment of inertia, position, velocity, and rotation.
7
8
### Class Definition and Body Types
9
10
```python { .api }
11
class Body:
12
"""
13
A rigid body with mass, position, velocity and rotation.
14
15
Bodies can be copied and pickled. Sleeping bodies wake up in the copy.
16
When copied, spaces, shapes or constraints attached to the body are not copied.
17
"""
18
19
# Body type constants
20
DYNAMIC: int # Normal simulated bodies affected by forces
21
KINEMATIC: int # User-controlled bodies with infinite mass
22
STATIC: int # Non-moving bodies (usually terrain)
23
24
def __init__(
25
self,
26
mass: float = 0,
27
moment: float = 0,
28
body_type: int = DYNAMIC
29
) -> None:
30
"""
31
Create a new body.
32
33
Args:
34
mass: Body mass (must be > 0 for dynamic bodies in space)
35
moment: Moment of inertia (must be > 0 for dynamic bodies in space)
36
body_type: DYNAMIC, KINEMATIC, or STATIC
37
"""
38
```
39
40
### Body Types Explained
41
42
```python { .api }
43
# Dynamic bodies - normal physics objects
44
body = pymunk.Body(mass=10, moment=pymunk.moment_for_circle(10, 0, 25))
45
body.body_type == pymunk.Body.DYNAMIC # True
46
# - Affected by gravity, forces, and collisions
47
# - Have finite mass and can be moved by physics
48
# - Most game objects (balls, boxes, characters)
49
50
# Kinematic bodies - user-controlled objects
51
kinematic_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
52
# - Controlled by setting velocity, not affected by forces
53
# - Have infinite mass, don't respond to collisions
54
# - Good for moving platforms, elevators, doors
55
# - Objects touching kinematic bodies cannot fall asleep
56
57
# Static bodies - immovable terrain
58
static_body = pymunk.Body(body_type=pymunk.Body.STATIC)
59
# Or use space.static_body
60
# - Never move (except manual repositioning)
61
# - Infinite mass, provide collision boundaries
62
# - Optimized for performance (no collision checks with other static bodies)
63
# - Ground, walls, level geometry
64
```
65
66
### Core Properties
67
68
```python { .api }
69
# Identity
70
body.id: int
71
"""Unique identifier for the body (changes on copy/pickle)"""
72
73
# Physical properties
74
body.mass: float
75
"""Mass of the body (must be > 0 for dynamic bodies in space)"""
76
77
body.moment: float
78
"""
79
Moment of inertia - rotational mass.
80
Can be set to float('inf') to prevent rotation.
81
"""
82
83
body.body_type: int
84
"""Body type: DYNAMIC, KINEMATIC, or STATIC"""
85
86
# Position and orientation
87
body.position: Vec2d
88
"""Position in world coordinates. Call space.reindex_shapes_for_body() after manual changes."""
89
90
body.center_of_gravity: Vec2d = Vec2d(0, 0)
91
"""Center of gravity offset in body local coordinates"""
92
93
body.angle: float
94
"""Rotation angle in radians. Body rotates around center of gravity."""
95
96
body.rotation_vector: Vec2d
97
"""Unit vector representing current rotation (readonly)"""
98
99
# Motion properties
100
body.velocity: Vec2d
101
"""Linear velocity of center of gravity"""
102
103
body.angular_velocity: float
104
"""Angular velocity in radians per second"""
105
106
# Force accumulation (reset each timestep)
107
body.force: Vec2d
108
"""Force applied to center of gravity (manual forces only)"""
109
110
body.torque: float
111
"""Torque applied to the body (manual torques only)"""
112
```
113
114
### Derived Properties
115
116
```python { .api }
117
body.kinetic_energy: float
118
"""Current kinetic energy of the body (readonly)"""
119
120
body.is_sleeping: bool
121
"""Whether the body is currently sleeping for optimization (readonly)"""
122
123
body.space: Optional[Space]
124
"""Space the body belongs to, or None (readonly)"""
125
126
body.shapes: KeysView[Shape]
127
"""View of all shapes attached to this body (readonly)"""
128
129
body.constraints: KeysView[Constraint]
130
"""View of all constraints attached to this body (readonly)"""
131
```
132
133
### Force and Impulse Application
134
135
```python { .api }
136
def apply_force_at_world_point(
137
self,
138
force: tuple[float, float],
139
point: tuple[float, float]
140
) -> None:
141
"""
142
Apply force as if applied from world point.
143
144
Forces are accumulated and applied during physics step.
145
Use for continuous forces like thrusters or wind.
146
147
Args:
148
force: Force vector in Newtons
149
point: World position where force is applied
150
151
Example:
152
# Apply upward thrust at right side of body
153
body.apply_force_at_world_point((0, 1000), body.position + (50, 0))
154
"""
155
156
def apply_force_at_local_point(
157
self,
158
force: tuple[float, float],
159
point: tuple[float, float] = (0, 0)
160
) -> None:
161
"""
162
Apply force as if applied from body local point.
163
164
Args:
165
force: Force vector in body coordinates
166
point: Local position where force is applied (default center)
167
"""
168
169
def apply_impulse_at_world_point(
170
self,
171
impulse: tuple[float, float],
172
point: tuple[float, float]
173
) -> None:
174
"""
175
Apply impulse as if applied from world point.
176
177
Impulses cause immediate velocity changes.
178
Use for instantaneous events like explosions or collisions.
179
180
Args:
181
impulse: Impulse vector (force * time)
182
point: World position where impulse is applied
183
184
Example:
185
# Explosion impulse
186
direction = target_pos - explosion_center
187
impulse = direction.normalized() * 500
188
body.apply_impulse_at_world_point(impulse, explosion_center)
189
"""
190
191
def apply_impulse_at_local_point(
192
self,
193
impulse: tuple[float, float],
194
point: tuple[float, float] = (0, 0)
195
) -> None:
196
"""Apply impulse as if applied from body local point."""
197
```
198
199
### Coordinate Transformations
200
201
```python { .api }
202
def local_to_world(self, v: tuple[float, float]) -> Vec2d:
203
"""
204
Convert body local coordinates to world space.
205
206
Local coordinates have (0,0) at center of gravity and rotate with body.
207
208
Args:
209
v: Vector in body local coordinates
210
211
Returns:
212
Vector in world coordinates
213
214
Example:
215
# Get world position of point on body
216
local_point = (25, 0) # 25 units right of center
217
world_point = body.local_to_world(local_point)
218
"""
219
220
def world_to_local(self, v: tuple[float, float]) -> Vec2d:
221
"""
222
Convert world space coordinates to body local coordinates.
223
224
Args:
225
v: Vector in world coordinates
226
227
Returns:
228
Vector in body local coordinates
229
"""
230
231
def velocity_at_world_point(self, point: tuple[float, float]) -> Vec2d:
232
"""
233
Get velocity at a world point on the body.
234
235
Accounts for both linear and angular velocity.
236
Useful for surface velocity calculations.
237
238
Args:
239
point: World position
240
241
Returns:
242
Velocity at that point
243
244
Example:
245
# Get velocity at edge of rotating wheel
246
edge_point = body.position + (wheel_radius, 0)
247
edge_velocity = body.velocity_at_world_point(edge_point)
248
"""
249
250
def velocity_at_local_point(self, point: tuple[float, float]) -> Vec2d:
251
"""Get velocity at a body local point."""
252
```
253
254
### Sleep Management
255
256
```python { .api }
257
def activate(self) -> None:
258
"""Wake up a sleeping body."""
259
260
def sleep(self) -> None:
261
"""Force body to sleep (optimization for inactive bodies)."""
262
263
def sleep_with_group(self, body: 'Body') -> None:
264
"""
265
Sleep this body together with another body.
266
267
Bodies in the same sleep group wake up together when one is disturbed.
268
Useful for connected objects like a stack of boxes.
269
"""
270
```
271
272
### Custom Integration Functions
273
274
```python { .api }
275
body.velocity_func: Callable[[Body, Vec2d, float, float], None]
276
"""
277
Custom velocity integration function called each timestep.
278
279
Function signature: velocity_func(body, gravity, damping, dt)
280
Override to implement custom gravity, damping, or other effects per body.
281
282
Example:
283
def custom_gravity(body, gravity, damping, dt):
284
# Custom gravity based on body position
285
if body.position.y > 500:
286
gravity = Vec2d(0, -500) # Weaker gravity at height
287
pymunk.Body.update_velocity(body, gravity, damping, dt)
288
289
body.velocity_func = custom_gravity
290
"""
291
292
body.position_func: Callable[[Body, float], None]
293
"""
294
Custom position integration function called each timestep.
295
296
Function signature: position_func(body, dt)
297
Override to implement custom movement behavior.
298
"""
299
```
300
301
## Shape Base Class
302
303
Shapes define collision geometry and material properties. All shapes inherit from the base `Shape` class.
304
305
### Base Shape Properties
306
307
```python { .api }
308
class Shape:
309
"""
310
Base class for all collision shapes.
311
312
Shapes can be copied and pickled. The attached body is also copied.
313
"""
314
315
# Physical properties
316
shape.mass: float = 0
317
"""
318
Mass of the shape for automatic body mass calculation.
319
Alternative to setting body mass directly.
320
"""
321
322
shape.density: float = 0
323
"""
324
Density (mass per unit area) for automatic mass calculation.
325
More intuitive than setting mass directly.
326
"""
327
328
shape.moment: float
329
"""Moment of inertia contribution from this shape (readonly)"""
330
331
shape.area: float
332
"""Area of the shape (readonly)"""
333
334
shape.center_of_gravity: Vec2d
335
"""Center of gravity of the shape (readonly)"""
336
337
# Material properties
338
shape.friction: float = 0.7
339
"""
340
Friction coefficient (0 = frictionless, higher = more friction).
341
Combined with other shape's friction during collision.
342
"""
343
344
shape.elasticity: float = 0.0
345
"""
346
Bounce/restitution coefficient (0 = no bounce, 1 = perfect bounce).
347
Combined with other shape's elasticity during collision.
348
"""
349
350
shape.surface_velocity: Vec2d = Vec2d(0, 0)
351
"""
352
Surface velocity for conveyor belt effects.
353
Only affects friction calculation, not collision resolution.
354
"""
355
356
# Collision properties
357
shape.collision_type: int = 0
358
"""Collision type identifier for callback filtering"""
359
360
shape.filter: ShapeFilter = ShapeFilter()
361
"""Collision filter controlling which shapes can collide"""
362
363
shape.sensor: bool = False
364
"""
365
If True, shape detects collisions but doesn't respond physically.
366
Useful for trigger areas, pickups, detection zones.
367
"""
368
369
# Relationships
370
shape.body: Optional[Body]
371
"""Body this shape is attached to (can be None)"""
372
373
shape.space: Optional[Space]
374
"""Space this shape belongs to (readonly)"""
375
376
shape.bb: BB
377
"""Cached bounding box - valid after cache_bb() or space.step()"""
378
```
379
380
### Shape Query Methods
381
382
```python { .api }
383
def point_query(self, p: tuple[float, float]) -> PointQueryInfo:
384
"""
385
Test if point lies within shape.
386
387
Args:
388
p: Point to test
389
390
Returns:
391
PointQueryInfo with distance (negative if inside), closest point, etc.
392
"""
393
394
def segment_query(
395
self,
396
start: tuple[float, float],
397
end: tuple[float, float],
398
radius: float = 0
399
) -> Optional[SegmentQueryInfo]:
400
"""
401
Test line segment intersection with shape.
402
403
Returns:
404
SegmentQueryInfo if intersected, None otherwise
405
"""
406
407
def shapes_collide(self, other: 'Shape') -> ContactPointSet:
408
"""
409
Get collision information between two shapes.
410
411
Returns:
412
ContactPointSet with contact points and normal
413
"""
414
415
def cache_bb(self) -> BB:
416
"""Update and return shape's bounding box"""
417
418
def update(self, transform: Transform) -> BB:
419
"""Update shape with explicit transform (for unattached shapes)"""
420
```
421
422
## Circle Shape
423
424
```python { .api }
425
class Circle(Shape):
426
"""
427
Circular collision shape - fastest and simplest collision shape.
428
429
Perfect for balls, wheels, coins, circular objects.
430
"""
431
432
def __init__(
433
self,
434
body: Optional[Body],
435
radius: float,
436
offset: tuple[float, float] = (0, 0)
437
) -> None:
438
"""
439
Create a circle shape.
440
441
Args:
442
body: Body to attach to (can be None)
443
radius: Circle radius
444
offset: Offset from body center of gravity
445
446
Example:
447
# Circle at body center
448
circle = pymunk.Circle(body, 25)
449
450
# Circle offset from body center
451
circle = pymunk.Circle(body, 15, offset=(10, 5))
452
"""
453
454
# Properties
455
radius: float
456
"""Radius of the circle"""
457
458
offset: Vec2d
459
"""Offset from body center of gravity"""
460
461
# Unsafe modification (use carefully during simulation)
462
def unsafe_set_radius(self, radius: float) -> None:
463
"""Change radius during simulation (may cause instability)"""
464
465
def unsafe_set_offset(self, offset: tuple[float, float]) -> None:
466
"""Change offset during simulation (may cause instability)"""
467
```
468
469
## Segment Shape
470
471
```python { .api }
472
class Segment(Shape):
473
"""
474
Line segment collision shape with thickness.
475
476
Mainly for static shapes like walls, floors, ramps.
477
Can be beveled for thickness.
478
"""
479
480
def __init__(
481
self,
482
body: Optional[Body],
483
a: tuple[float, float],
484
b: tuple[float, float],
485
radius: float
486
) -> None:
487
"""
488
Create a line segment shape.
489
490
Args:
491
body: Body to attach to (can be None)
492
a: First endpoint
493
b: Second endpoint
494
radius: Thickness radius (0 for infinitely thin)
495
496
Example:
497
# Ground segment
498
ground = pymunk.Segment(static_body, (0, 0), (800, 0), 5)
499
500
# Sloped platform
501
ramp = pymunk.Segment(body, (0, 0), (100, 50), 3)
502
"""
503
504
# Properties
505
a: Vec2d
506
"""First endpoint of the segment"""
507
508
b: Vec2d
509
"""Second endpoint of the segment"""
510
511
normal: Vec2d
512
"""Normal vector of the segment (readonly)"""
513
514
radius: float
515
"""Thickness radius of the segment"""
516
517
# Unsafe modification
518
def unsafe_set_endpoints(
519
self,
520
a: tuple[float, float],
521
b: tuple[float, float]
522
) -> None:
523
"""Change endpoints during simulation (may cause instability)"""
524
525
def unsafe_set_radius(self, radius: float) -> None:
526
"""Change radius during simulation (may cause instability)"""
527
528
def set_neighbors(
529
self,
530
prev: tuple[float, float],
531
next: tuple[float, float]
532
) -> None:
533
"""
534
Set neighboring segments for smooth collision.
535
536
Prevents objects from catching on segment joints.
537
538
Args:
539
prev: Previous segment endpoint
540
next: Next segment endpoint
541
"""
542
```
543
544
## Polygon Shape
545
546
```python { .api }
547
class Poly(Shape):
548
"""
549
Convex polygon collision shape.
550
551
Most flexible but slowest collision shape.
552
Automatically converts concave polygons to convex hull.
553
"""
554
555
def __init__(
556
self,
557
body: Optional[Body],
558
vertices: Sequence[tuple[float, float]],
559
transform: Optional[Transform] = None,
560
radius: float = 0
561
) -> None:
562
"""
563
Create a polygon shape.
564
565
Args:
566
body: Body to attach to (can be None)
567
vertices: List of vertices in counter-clockwise order
568
transform: Optional transform applied to vertices
569
radius: Corner rounding radius (reduces catching on edges)
570
571
Note:
572
Vertices should be centered around (0,0) or use transform.
573
Concave polygons are automatically converted to convex hull.
574
575
Example:
576
# Box polygon
577
size = (50, 30)
578
vertices = [
579
(-size[0]/2, -size[1]/2),
580
(size[0]/2, -size[1]/2),
581
(size[0]/2, size[1]/2),
582
(-size[0]/2, size[1]/2)
583
]
584
poly = pymunk.Poly(body, vertices)
585
586
# Triangle
587
vertices = [(0, 20), (-20, -20), (20, -20)]
588
triangle = pymunk.Poly(body, vertices, radius=2)
589
"""
590
591
# Properties
592
radius: float
593
"""Corner rounding radius"""
594
595
# Methods
596
def get_vertices(self) -> list[Vec2d]:
597
"""Get list of vertices in world coordinates"""
598
599
def unsafe_set_vertices(
600
self,
601
vertices: Sequence[tuple[float, float]],
602
transform: Optional[Transform] = None
603
) -> None:
604
"""Change vertices during simulation (may cause instability)"""
605
606
def unsafe_set_radius(self, radius: float) -> None:
607
"""Change radius during simulation (may cause instability)"""
608
609
# Static factory methods
610
@staticmethod
611
def create_box(
612
body: Optional[Body],
613
size: tuple[float, float],
614
radius: float = 0
615
) -> 'Poly':
616
"""
617
Create a box polygon.
618
619
Args:
620
body: Body to attach to
621
size: (width, height) of box
622
radius: Corner rounding radius
623
624
Example:
625
box = pymunk.Poly.create_box(body, (60, 40), radius=2)
626
"""
627
628
@staticmethod
629
def create_box_bb(
630
body: Optional[Body],
631
bb: BB,
632
radius: float = 0
633
) -> 'Poly':
634
"""
635
Create box polygon from bounding box.
636
637
Args:
638
body: Body to attach to
639
bb: Bounding box defining box shape
640
radius: Corner rounding radius
641
"""
642
```
643
644
## Usage Examples
645
646
### Basic Rigid Bodies
647
648
```python { .api }
649
import pymunk
650
import math
651
652
# Create physics space
653
space = pymunk.Space()
654
space.gravity = (0, -981)
655
656
# Static ground
657
ground_body = space.static_body
658
ground = pymunk.Segment(ground_body, (0, 0), (800, 0), 5)
659
ground.friction = 0.7
660
space.add(ground)
661
662
# Dynamic ball
663
ball_mass = 10
664
ball_radius = 25
665
ball_moment = pymunk.moment_for_circle(ball_mass, 0, ball_radius)
666
667
ball_body = pymunk.Body(ball_mass, ball_moment)
668
ball_body.position = 400, 300
669
ball_shape = pymunk.Circle(ball_body, ball_radius)
670
ball_shape.friction = 0.7
671
ball_shape.elasticity = 0.9 # Bouncy
672
673
space.add(ball_body, ball_shape)
674
675
# Dynamic box
676
box_mass = 15
677
box_size = (40, 60)
678
box_moment = pymunk.moment_for_box(box_mass, box_size)
679
680
box_body = pymunk.Body(box_mass, box_moment)
681
box_body.position = 200, 400
682
box_shape = pymunk.Poly.create_box(box_body, box_size)
683
box_shape.friction = 0.5
684
685
space.add(box_body, box_shape)
686
```
687
688
### Material Properties
689
690
```python { .api }
691
import pymunk
692
693
# Different material types
694
def create_material_shapes(body):
695
# Ice - slippery, bouncy
696
ice_shape = pymunk.Circle(body, 20)
697
ice_shape.friction = 0.1 # Very slippery
698
ice_shape.elasticity = 0.8 # Quite bouncy
699
700
# Rubber - grippy, very bouncy
701
rubber_shape = pymunk.Circle(body, 20)
702
rubber_shape.friction = 1.2 # Very grippy
703
rubber_shape.elasticity = 0.95 # Almost perfect bounce
704
705
# Metal - medium friction, little bounce
706
metal_shape = pymunk.Circle(body, 20)
707
metal_shape.friction = 0.7 # Moderate friction
708
metal_shape.elasticity = 0.2 # Little bounce
709
710
# Wood - medium properties
711
wood_shape = pymunk.Circle(body, 20)
712
wood_shape.friction = 0.4 # Some friction
713
wood_shape.elasticity = 0.3 # Some bounce
714
715
# Conveyor belt effect
716
conveyor = pymunk.Segment(static_body, (100, 50), (300, 50), 5)
717
conveyor.surface_velocity = (100, 0) # Move objects rightward
718
conveyor.friction = 0.8 # Need friction for surface velocity to work
719
```
720
721
### Mass and Density
722
723
```python { .api }
724
import pymunk
725
726
# Method 1: Set body mass directly
727
mass = 10
728
moment = pymunk.moment_for_circle(mass, 0, 25)
729
body = pymunk.Body(mass, moment)
730
circle = pymunk.Circle(body, 25)
731
732
# Method 2: Let shapes calculate mass automatically
733
body = pymunk.Body() # No mass/moment specified
734
735
# Set shape density
736
circle = pymunk.Circle(body, 25)
737
circle.density = 0.1 # Light material
738
739
poly = pymunk.Poly.create_box(body, (50, 50))
740
poly.density = 0.2 # Heavier material
741
742
# Body mass/moment calculated automatically from shapes
743
space.add(body, circle, poly)
744
745
# Method 3: Set shape mass directly
746
circle = pymunk.Circle(body, 25)
747
circle.mass = 5 # 5 units of mass
748
749
poly = pymunk.Poly.create_box(body, (50, 50))
750
poly.mass = 15 # 15 units of mass
751
# Total body mass will be 20
752
```
753
754
### Force Application
755
756
```python { .api }
757
import pymunk
758
import math
759
760
space = pymunk.Space()
761
body = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25))
762
body.position = 400, 300
763
764
# Continuous forces (applied each frame)
765
def apply_thrust():
766
# Rocket thrust at back of ship
767
thrust_force = (0, 1000) # Upward thrust
768
thrust_point = body.position + (0, -25) # Back of ship
769
body.apply_force_at_world_point(thrust_force, thrust_point)
770
771
# Impulse forces (one-time application)
772
def explosion(explosion_center, strength):
773
direction = body.position - explosion_center
774
distance = abs(direction)
775
if distance > 0:
776
# Impulse falls off with distance
777
impulse_magnitude = strength / (distance * distance)
778
impulse = direction.normalized() * impulse_magnitude
779
body.apply_impulse_at_world_point(impulse, explosion_center)
780
781
# Torque for rotation
782
body.torque = 500 # Spin clockwise
783
784
# Local force application
785
# Force applied at local point creates both linear and angular motion
786
local_force = (100, 0) # Rightward force
787
local_point = (0, 25) # Top of object
788
body.apply_force_at_local_point(local_force, local_point)
789
```
790
791
### Kinematic Bodies (Moving Platforms)
792
793
```python { .api }
794
import pymunk
795
import math
796
797
# Create kinematic platform
798
platform_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
799
platform_shape = pymunk.Poly.create_box(platform_body, (100, 20))
800
platform_body.position = 200, 100
801
802
space.add(platform_body, platform_shape)
803
804
# Control kinematic body motion
805
def update_platform(dt):
806
# Sine wave motion
807
time = space.current_time_step
808
809
# Horizontal oscillation
810
platform_body.velocity = (50 * math.cos(time), 0)
811
812
# Or set position directly (less stable)
813
# new_x = 200 + 50 * math.sin(time)
814
# platform_body.position = new_x, 100
815
# space.reindex_shapes_for_body(platform_body)
816
817
# Moving elevator
818
elevator_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
819
elevator_shape = pymunk.Poly.create_box(elevator_body, (80, 15))
820
821
def move_elevator_up():
822
elevator_body.velocity = (0, 100) # Move up
823
824
def move_elevator_down():
825
elevator_body.velocity = (0, -100) # Move down
826
827
def stop_elevator():
828
elevator_body.velocity = (0, 0) # Stop
829
```
830
831
### Sensors and Triggers
832
833
```python { .api }
834
import pymunk
835
836
# Create sensor shape for trigger areas
837
trigger_body = space.static_body
838
trigger_shape = pymunk.Circle(trigger_body, 50, (400, 300))
839
trigger_shape.sensor = True # No physical collision response
840
trigger_shape.collision_type = TRIGGER_TYPE
841
842
# Player shape
843
player_body = pymunk.Body(1, pymunk.moment_for_circle(1, 0, 15))
844
player_shape = pymunk.Circle(player_body, 15)
845
player_shape.collision_type = PLAYER_TYPE
846
847
def trigger_callback(arbiter, space, data):
848
"""Called when player enters/exits trigger area"""
849
trigger_shape, player_shape = arbiter.shapes
850
print("Player in trigger area!")
851
return False # Don't process as physical collision
852
853
space.on_collision(TRIGGER_TYPE, PLAYER_TYPE, begin=trigger_callback)
854
855
# Pickup items (remove on contact)
856
pickup_body = space.static_body
857
pickup_shape = pymunk.Circle(pickup_body, 10, (300, 200))
858
pickup_shape.sensor = True
859
pickup_shape.collision_type = PICKUP_TYPE
860
861
def pickup_callback(arbiter, space, data):
862
pickup_shape, player_shape = arbiter.shapes
863
# Schedule removal after step completes
864
space.add_post_step_callback(remove_pickup, pickup_shape, pickup_shape)
865
return False
866
867
def remove_pickup(space, shape):
868
space.remove(shape)
869
print("Collected pickup!")
870
```
871
872
### Shape Queries and Collision Detection
873
874
```python { .api }
875
import pymunk
876
877
# Test if point is inside shape
878
test_point = (100, 200)
879
query_result = shape.point_query(test_point)
880
if query_result.distance < 0:
881
print("Point is inside shape!")
882
print(f"Distance to surface: {abs(query_result.distance)}")
883
884
# Line of sight / raycast using segment query
885
start = player_body.position
886
end = enemy_body.position
887
line_query = shape.segment_query(start, end, radius=0)
888
if line_query:
889
print(f"Line blocked at {line_query.point}")
890
print(f"Surface normal: {line_query.normal}")
891
else:
892
print("Clear line of sight")
893
894
# Shape vs shape collision test
895
collision_info = shape1.shapes_collide(shape2)
896
if collision_info.points:
897
print("Shapes are colliding!")
898
for point in collision_info.points:
899
print(f"Contact at {point.point_a}")
900
```
901
902
Bodies and shapes provide the foundation for realistic physics simulation with intuitive APIs for mass properties, material behavior, force application, and collision detection suitable for games, simulations, and interactive applications.