"""
This file contains the implementation of the class Body: a physics
body and its fixtures, in pixel units, wrapping a single Box2D body
without ever exposing Box2D itself. Created through
World.create_static_body/create_dynamic_body/create_kinematic_body.
Author: Alejandro Mujica (aledrums@gmail.com)
"""
from typing import Any, List, Optional
import pygame
from .body_type import BodyType
from .shapes import BoxShape, CircleShape, PolygonShape
[docs]
class Body:
"""
A physics body, in pixel units. Wraps a single Box2D body (never
exposed) plus its fixtures. Created through World.create_static_body/
create_dynamic_body/create_kinematic_body, never directly.
Usage example:
player = world.create_dynamic_body(100, 50, CircleShape(radius=10))
player.apply_impulse(0, -400) # jump
# Every frame, after world.update(dt):
pygame.draw.circle(surface, "white", player.position, 10)
"""
def __init__(self, b2_body: Any, body_type: int, pixels_per_meter: float) -> None:
"""
:param b2_body: The underlying Box2D body. Internal — build a Body through one of World's create_*_body methods instead.
:param body_type: One of the BodyType constants.
:param pixels_per_meter: The conversion factor this body's owning World uses.
"""
self._b2_body = b2_body
self._b2_body.userData = self
self.body_type: int = body_type
self._ppm: float = pixels_per_meter
self.user_data: Any = None
@property
def position(self) -> pygame.Vector2:
return pygame.Vector2(self._b2_body.position) * self._ppm
@position.setter
def position(self, value) -> None:
x, y = value
self._b2_body.position = (x / self._ppm, y / self._ppm)
@property
def angle(self) -> float:
return self._b2_body.angle
@angle.setter
def angle(self, value: float) -> None:
self._b2_body.angle = value
@property
def velocity(self) -> pygame.Vector2:
return pygame.Vector2(self._b2_body.linearVelocity) * self._ppm
@velocity.setter
def velocity(self, value) -> None:
self.set_velocity(*value)
@property
def angular_velocity(self) -> float:
return self._b2_body.angularVelocity
@angular_velocity.setter
def angular_velocity(self, value: float) -> None:
self._b2_body.angularVelocity = value
[docs]
def set_velocity(self, vx: float, vy: float) -> None:
"""
:param vx: Horizontal velocity, in pixels per second.
:param vy: Vertical velocity, in pixels per second.
"""
self._b2_body.linearVelocity = (vx / self._ppm, vy / self._ppm)
[docs]
def apply_force(self, fx: float, fy: float) -> None:
"""
Apply a continuous force at this body's center of mass (call
every frame while the force should be acting, such as a
thruster).
:param fx: Horizontal force.
:param fy: Vertical force.
"""
self._b2_body.ApplyForceToCenter((fx / self._ppm, fy / self._ppm), wake=True)
[docs]
def apply_torque(self, torque: float) -> None:
"""
:param torque: The torque to apply this step.
"""
self._b2_body.ApplyTorque(torque, wake=True)
[docs]
def apply_impulse(self, ix: float, iy: float) -> None:
"""
Apply an instantaneous change in momentum at this body's
center of mass (a one-off "kick", such as a jump).
:param ix: Horizontal impulse.
:param iy: Vertical impulse.
"""
self._b2_body.ApplyLinearImpulse(
(ix / self._ppm, iy / self._ppm), self._b2_body.worldCenter, wake=True
)
[docs]
def add_circle(self, shape: CircleShape) -> None:
"""
:param shape: The circle fixture to attach to this body.
"""
self._b2_body.CreateCircleFixture(
radius=shape.radius / self._ppm,
pos=(shape.offset[0] / self._ppm, shape.offset[1] / self._ppm),
density=shape.density,
friction=shape.friction,
restitution=shape.restitution,
isSensor=shape.is_sensor,
)
[docs]
def add_box(self, shape: BoxShape) -> None:
"""
:param shape: The box fixture to attach to this body.
"""
self._b2_body.CreatePolygonFixture(
box=(
shape.width / 2 / self._ppm,
shape.height / 2 / self._ppm,
(shape.offset[0] / self._ppm, shape.offset[1] / self._ppm),
0,
),
density=shape.density,
friction=shape.friction,
restitution=shape.restitution,
isSensor=shape.is_sensor,
)
[docs]
def add_polygon(self, shape: PolygonShape) -> None:
"""
:param shape: The polygon fixture to attach to this body.
"""
vertices = [(x / self._ppm, y / self._ppm) for x, y in shape.points]
self._b2_body.CreatePolygonFixture(
vertices=vertices,
density=shape.density,
friction=shape.friction,
restitution=shape.restitution,
isSensor=shape.is_sensor,
)
@property
def touching_bodies(self) -> List["Body"]:
"""
:returns: Every other Body currently in contact with this one (a cheap way to answer "is this body resting on something," with no event bookkeeping needed).
"""
bodies = []
for contact_edge in self._b2_body.contacts:
contact = contact_edge.contact
if contact.touching:
other = contact_edge.other.userData
if isinstance(other, Body):
bodies.append(other)
return bodies
[docs]
def destroy(self) -> None:
"""
Remove this body (and its fixtures) from its World. Do not
use this Body afterwards.
"""
self._b2_body.userData = None
self._b2_body.world.DestroyBody(self._b2_body)