Source code for gale.physics.body

"""
This file contains the implementation of the class Body: a physics
body and its fixtures, in pixel units, wrapping a single pymunk body
without ever exposing pymunk 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
import pymunk

from .body_type import BodyType
from .shapes import BoxShape, CircleShape, PolygonShape


[docs] class Body: """ A physics body, in pixel units. Wraps a single pymunk 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, pm_body: Any, world: Any, body_type: int, pixels_per_meter: float, ) -> None: """ :param pm_body: The underlying pymunk body. Internal — build a Body through one of World's create_*_body methods instead. :param world: The World pm_body lives in. Internal — needed so add_circle/add_box/add_polygon/destroy/touching_bodies can reach its pymunk space and contact-tracking. :param body_type: One of the BodyType constants. :param pixels_per_meter: The conversion factor this body's owning World uses. """ self._pm_body = pm_body self._pm_body.gale_body = self self._world = world self.body_type: int = body_type self._ppm: float = pixels_per_meter self.user_data: Any = None self._linear_damping: float = 0.0 self._angular_damping: float = 0.0 @property def _pm_space(self) -> Any: return self._world._space @property def position(self) -> pygame.Vector2: return pygame.Vector2(*self._pm_body.position) * self._ppm @position.setter def position(self, value) -> None: x, y = value self._pm_body.position = (x / self._ppm, y / self._ppm) @property def angle(self) -> float: return self._pm_body.angle @angle.setter def angle(self, value: float) -> None: self._pm_body.angle = value @property def velocity(self) -> pygame.Vector2: return pygame.Vector2(*self._pm_body.velocity) * self._ppm @velocity.setter def velocity(self, value) -> None: self.set_velocity(*value) @property def angular_velocity(self) -> float: return self._pm_body.angular_velocity @angular_velocity.setter def angular_velocity(self, value: float) -> None: self._pm_body.angular_velocity = 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._pm_body.velocity = (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._pm_body.apply_force_at_local_point( (fx / self._ppm, fy / self._ppm), (0, 0) )
[docs] def apply_torque(self, torque: float) -> None: """ :param torque: The torque to apply this step. """ self._pm_body.torque += torque
[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._pm_body.apply_impulse_at_local_point( (ix / self._ppm, iy / self._ppm), (0, 0) )
[docs] def add_circle(self, shape: CircleShape) -> None: """ :param shape: The circle fixture to attach to this body. """ ppm = self._ppm pm_shape = pymunk.Circle( self._pm_body, radius=shape.radius / ppm, offset=(shape.offset[0] / ppm, shape.offset[1] / ppm), ) self._configure_shape(pm_shape, shape)
[docs] def add_box(self, shape: BoxShape) -> None: """ :param shape: The box fixture to attach to this body. """ ppm = self._ppm half_width = shape.width / 2 / ppm half_height = shape.height / 2 / ppm ox = shape.offset[0] / ppm oy = shape.offset[1] / ppm vertices = [ (-half_width + ox, -half_height + oy), (half_width + ox, -half_height + oy), (half_width + ox, half_height + oy), (-half_width + ox, half_height + oy), ] pm_shape = pymunk.Poly(self._pm_body, vertices) self._configure_shape(pm_shape, shape)
[docs] def add_polygon(self, shape: PolygonShape) -> None: """ :param shape: The polygon fixture to attach to this body. """ ppm = self._ppm vertices = [(x / ppm, y / ppm) for x, y in shape.points] pm_shape = pymunk.Poly(self._pm_body, vertices) self._configure_shape(pm_shape, shape)
def _configure_shape(self, pm_shape: Any, shape: Any) -> None: pm_shape.density = shape.density pm_shape.friction = shape.friction pm_shape.elasticity = shape.restitution pm_shape.sensor = shape.is_sensor self._pm_space.add(pm_shape) @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). Includes sensor overlaps, same as on_collision_begin/on_collision_end. """ return list(self._world._touching.get(self, {}).keys())
[docs] def set_damping( self, linear_damping: float = 0.0, angular_damping: float = 0.0 ) -> None: """ Slow this body's linear/angular velocity down over time, e.g. to approximate air resistance — every fixed_update(), velocity is scaled by 1 / (1 + fixed_timestep * damping). 0 (the default for every new Body) means no damping at all. :param linear_damping: Damping applied to velocity. The default value is 0.0. :param angular_damping: Damping applied to angular_velocity. The default value is 0.0. """ self._linear_damping = linear_damping self._angular_damping = angular_damping self._pm_body.velocity_func = self._apply_velocity_and_damping
def _apply_velocity_and_damping( self, pm_body: Any, gravity: Any, damping: float, dt: float ) -> None: pymunk.Body.update_velocity(pm_body, gravity, damping, dt) pm_body.velocity = pm_body.velocity / (1 + dt * self._linear_damping) pm_body.angular_velocity = pm_body.angular_velocity / ( 1 + dt * self._angular_damping )
[docs] def destroy(self) -> None: """ Remove this body (and its fixtures) from its World. Do not use this Body afterwards. """ self._pm_body.gale_body = None self._world._forget_touching(self) for pm_shape in list(self._pm_body.shapes): self._pm_space.remove(pm_shape) self._pm_space.remove(self._pm_body)