Cross-platform library for developing multimedia applications and video games in Python built on top of SDL
Mathematical functions and vector operations for game calculations. Pygame's math module provides essential mathematical tools including 2D and 3D vectors, utility functions, and geometric calculations commonly needed in game development.
General mathematical functions for common game calculations.
def clamp(value: float, min_value: float, max_value: float) -> float:
"""
Constrain value to range [min_value, max_value].
Args:
value (float): Value to clamp
min_value (float): Minimum allowed value
max_value (float): Maximum allowed value
Returns:
float: Clamped value
"""
def lerp(a: float, b: float, weight: float) -> float:
"""
Linear interpolation between two values.
Args:
a (float): Start value
b (float): End value
weight (float): Interpolation factor (0.0 to 1.0)
Returns:
float: Interpolated value
"""2D vector class for position, velocity, direction calculations.
class Vector2:
def __init__(self, x: float = 0, y: float = 0):
"""
Create 2D vector.
Args:
x (float): X component
y (float): Y component
"""
# Properties
x: float # X component
y: float # Y component
# Vector Operations
def dot(self, vector: 'Vector2') -> float:
"""
Calculate dot product with another vector.
Args:
vector (Vector2): Other vector
Returns:
float: Dot product (v1.x * v2.x + v1.y * v2.y)
"""
def cross(self, vector: 'Vector2') -> float:
"""
Calculate 2D cross product (scalar).
Args:
vector (Vector2): Other vector
Returns:
float: Cross product magnitude
"""
def magnitude(self) -> float:
"""
Get vector length/magnitude.
Returns:
float: Vector magnitude
"""
def magnitude_squared(self) -> float:
"""
Get squared magnitude (faster than magnitude()).
Returns:
float: Squared magnitude
"""
def length(self) -> float:
"""
Alias for magnitude().
Returns:
float: Vector length
"""
def length_squared(self) -> float:
"""
Alias for magnitude_squared().
Returns:
float: Squared length
"""
def normalize(self) -> 'Vector2':
"""
Get normalized vector (length = 1).
Returns:
Vector2: Unit vector in same direction
"""
def normalize_ip(self) -> None:
"""Normalize this vector in place."""
def is_normalized(self) -> bool:
"""
Check if vector is normalized.
Returns:
bool: True if length is approximately 1
"""
def safe_normalize(self) -> 'Vector2':
"""
Normalize vector, returns zero vector if length is zero.
Returns:
Vector2: Normalized vector or zero vector
"""
def scale_to_length(self, length: float) -> None:
"""
Scale vector to specific length.
Args:
length (float): Desired length
"""
# Distance Functions
def distance_to(self, vector: 'Vector2') -> float:
"""
Calculate distance to another vector.
Args:
vector (Vector2): Target vector
Returns:
float: Distance between vectors
"""
def distance_squared_to(self, vector: 'Vector2') -> float:
"""
Calculate squared distance (faster than distance_to()).
Args:
vector (Vector2): Target vector
Returns:
float: Squared distance
"""
# Interpolation
def lerp(self, vector: 'Vector2', t: float) -> 'Vector2':
"""
Linear interpolation to another vector.
Args:
vector (Vector2): Target vector
t (float): Interpolation factor (0.0 to 1.0)
Returns:
Vector2: Interpolated vector
"""
def slerp(self, vector: 'Vector2', t: float) -> 'Vector2':
"""
Spherical linear interpolation (smoother rotation).
Args:
vector (Vector2): Target vector
t (float): Interpolation factor (0.0 to 1.0)
Returns:
Vector2: Interpolated vector
"""
def move_towards(self, vector: 'Vector2', max_distance: float) -> 'Vector2':
"""
Move towards target by maximum distance.
Args:
vector (Vector2): Target vector
max_distance (float): Maximum distance to move
Returns:
Vector2: New position
"""
# Rotation and Reflection
def rotate(self, angle: float) -> 'Vector2':
"""
Rotate vector by angle (degrees).
Args:
angle (float): Rotation angle in degrees
Returns:
Vector2: Rotated vector
"""
def rotate_ip(self, angle: float) -> None:
"""
Rotate vector in place.
Args:
angle (float): Rotation angle in degrees
"""
def rotate_rad(self, angle: float) -> 'Vector2':
"""
Rotate vector by angle (radians).
Args:
angle (float): Rotation angle in radians
Returns:
Vector2: Rotated vector
"""
def rotate_rad_ip(self, angle: float) -> None:
"""
Rotate vector in place (radians).
Args:
angle (float): Rotation angle in radians
"""
def reflect(self, normal: 'Vector2') -> 'Vector2':
"""
Reflect vector across surface normal.
Args:
normal (Vector2): Surface normal vector
Returns:
Vector2: Reflected vector
"""
def reflect_ip(self, normal: 'Vector2') -> None:
"""
Reflect vector in place.
Args:
normal (Vector2): Surface normal vector
"""
# Polar Coordinates
def as_polar(self) -> tuple[float, float]:
"""
Convert to polar coordinates.
Returns:
tuple[float, float]: (radius, angle_in_degrees)
"""
@classmethod
def from_polar(cls, polar: tuple[float, float]) -> 'Vector2':
"""
Create vector from polar coordinates.
Args:
polar (tuple[float, float]): (radius, angle_in_degrees)
Returns:
Vector2: New vector from polar coordinates
"""
# Angles
def angle_to(self, vector: 'Vector2') -> float:
"""
Calculate angle to another vector.
Args:
vector (Vector2): Target vector
Returns:
float: Angle in degrees (-180 to 180)
"""
# Utility
def copy(self) -> 'Vector2':
"""
Create copy of vector.
Returns:
Vector2: Copy of this vector
"""
def update(self, x: float, y: float) -> None:
"""
Update vector components.
Args:
x (float): New X component
y (float): New Y component
"""
def clamp_magnitude(self, min_length: float, max_length: float) -> 'Vector2':
"""
Clamp vector magnitude to range.
Args:
min_length (float): Minimum magnitude
max_length (float): Maximum magnitude
Returns:
Vector2: Vector with clamped magnitude
"""
def clamp_magnitude_ip(self, min_length: float, max_length: float) -> None:
"""
Clamp magnitude in place.
Args:
min_length (float): Minimum magnitude
max_length (float): Maximum magnitude
"""
# Arithmetic Operations (supports +, -, *, /, //, %, **)
def __add__(self, other) -> 'Vector2': ...
def __sub__(self, other) -> 'Vector2': ...
def __mul__(self, scalar: float) -> 'Vector2': ...
def __truediv__(self, scalar: float) -> 'Vector2': ...
def __floordiv__(self, scalar: float) -> 'Vector2': ...
def __mod__(self, scalar: float) -> 'Vector2': ...
def __pow__(self, exponent: float) -> 'Vector2': ...
# Comparison Operations
def __eq__(self, other) -> bool: ...
def __ne__(self, other) -> bool: ...
# Sequence Operations
def __getitem__(self, index: int) -> float: ...
def __setitem__(self, index: int, value: float) -> None: ...
def __len__(self) -> int: ... # Always returns 2
def __iter__(self): ...3D vector class extending Vector2 functionality with Z component.
class Vector3:
def __init__(self, x: float = 0, y: float = 0, z: float = 0):
"""
Create 3D vector.
Args:
x (float): X component
y (float): Y component
z (float): Z component
"""
# Properties
x: float # X component
y: float # Y component
z: float # Z component
# All Vector2 methods plus 3D-specific operations:
def cross(self, vector: 'Vector3') -> 'Vector3':
"""
Calculate 3D cross product.
Args:
vector (Vector3): Other vector
Returns:
Vector3: Cross product vector (perpendicular to both inputs)
"""
def dot(self, vector: 'Vector3') -> float:
"""
Calculate dot product.
Args:
vector (Vector3): Other vector
Returns:
float: Dot product
"""
# 3D Rotation
def rotate_x(self, angle: float) -> 'Vector3':
"""
Rotate around X axis.
Args:
angle (float): Rotation angle in degrees
Returns:
Vector3: Rotated vector
"""
def rotate_x_ip(self, angle: float) -> None:
"""Rotate around X axis in place."""
def rotate_y(self, angle: float) -> 'Vector3':
"""
Rotate around Y axis.
Args:
angle (float): Rotation angle in degrees
Returns:
Vector3: Rotated vector
"""
def rotate_y_ip(self, angle: float) -> None:
"""Rotate around Y axis in place."""
def rotate_z(self, angle: float) -> 'Vector3':
"""
Rotate around Z axis.
Args:
angle (float): Rotation angle in degrees
Returns:
Vector3: Rotated vector
"""
def rotate_z_ip(self, angle: float) -> None:
"""Rotate around Z axis in place."""
def rotate(self, axis: 'Vector3', angle: float) -> 'Vector3':
"""
Rotate around arbitrary axis.
Args:
axis (Vector3): Rotation axis (should be normalized)
angle (float): Rotation angle in degrees
Returns:
Vector3: Rotated vector
"""
def rotate_ip(self, axis: 'Vector3', angle: float) -> None:
"""
Rotate around arbitrary axis in place.
Args:
axis (Vector3): Rotation axis (should be normalized)
angle (float): Rotation angle in degrees
"""
# Spherical Coordinates
def as_spherical(self) -> tuple[float, float, float]:
"""
Convert to spherical coordinates.
Returns:
tuple[float, float, float]: (radius, theta, phi) in degrees
"""
@classmethod
def from_spherical(cls, spherical: tuple[float, float, float]) -> 'Vector3':
"""
Create vector from spherical coordinates.
Args:
spherical (tuple[float, float, float]): (radius, theta, phi) in degrees
Returns:
Vector3: New vector from spherical coordinates
"""
def update(self, x: float, y: float, z: float) -> None:
"""
Update vector components.
Args:
x (float): New X component
y (float): New Y component
z (float): New Z component
"""
# All arithmetic and comparison operations from Vector2
def __len__(self) -> int: ... # Always returns 3import pygame
import pygame.math
# Create vectors
pos = pygame.math.Vector2(100, 50)
velocity = pygame.math.Vector2(5, -3)
target = pygame.math.Vector2(400, 300)
print(f"Position: {pos}")
print(f"Velocity: {velocity}")
# Vector arithmetic
new_pos = pos + velocity
print(f"New position: {new_pos}")
# Vector magnitude
speed = velocity.magnitude()
print(f"Speed: {speed}")
# Distance calculation
distance = pos.distance_to(target)
print(f"Distance to target: {distance}")
# Normalize vector
direction = (target - pos).normalize()
print(f"Direction to target: {direction}")import pygame
import pygame.math
import random
class Entity:
def __init__(self, x, y):
self.position = pygame.math.Vector2(x, y)
self.velocity = pygame.math.Vector2(0, 0)
self.max_speed = 3.0
self.acceleration = pygame.math.Vector2(0, 0)
def seek(self, target):
"""Steer towards target"""
desired = (target - self.position).normalize() * self.max_speed
steer = desired - self.velocity
return steer
def flee(self, target):
"""Steer away from target"""
return -self.seek(target)
def wander(self):
"""Random wandering behavior"""
random_angle = random.uniform(-30, 30)
forward = self.velocity.normalize() if self.velocity.magnitude() > 0 else pygame.math.Vector2(1, 0)
wander_direction = forward.rotate(random_angle)
return wander_direction * 0.5
def update(self):
# Apply acceleration
self.velocity += self.acceleration
# Limit speed
if self.velocity.magnitude() > self.max_speed:
self.velocity.scale_to_length(self.max_speed)
# Update position
self.position += self.velocity
# Reset acceleration
self.acceleration = pygame.math.Vector2(0, 0)
def apply_force(self, force):
self.acceleration += force
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
# Create entities
entities = [Entity(random.randint(50, 750), random.randint(50, 550)) for _ in range(10)]
mouse_pos = pygame.math.Vector2(400, 300)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEMOTION:
mouse_pos = pygame.math.Vector2(event.pos)
screen.fill((0, 0, 0))
for entity in entities:
# Calculate steering forces
seek_force = entity.seek(mouse_pos) * 0.1
wander_force = entity.wander() * 0.5
# Combine forces
total_force = seek_force + wander_force
entity.apply_force(total_force)
entity.update()
# Wrap around screen edges
if entity.position.x < 0:
entity.position.x = 800
elif entity.position.x > 800:
entity.position.x = 0
if entity.position.y < 0:
entity.position.y = 600
elif entity.position.y > 600:
entity.position.y = 0
# Draw entity
pygame.draw.circle(screen, (255, 255, 255), (int(entity.position.x), int(entity.position.y)), 5)
# Draw velocity vector
end_pos = entity.position + entity.velocity * 10
pygame.draw.line(screen, (255, 0, 0), entity.position, end_pos, 2)
# Draw mouse cursor
pygame.draw.circle(screen, (0, 255, 0), (int(mouse_pos.x), int(mouse_pos.y)), 8)
pygame.display.flip()
clock.tick(60)
pygame.quit()import pygame.math
import math
# Create 3D vectors
position = pygame.math.Vector3(1, 2, 3)
forward = pygame.math.Vector3(0, 0, -1) # Looking down negative Z
up = pygame.math.Vector3(0, 1, 0)
print(f"Position: {position}")
print(f"Forward: {forward}")
print(f"Up: {up}")
# Calculate right vector using cross product
right = forward.cross(up)
print(f"Right: {right}")
# Rotate around Y axis (yaw)
rotated_forward = forward.rotate_y(45)
print(f"Rotated 45° around Y: {rotated_forward}")
# Convert to spherical coordinates
spherical = position.as_spherical()
print(f"Spherical (r, theta, phi): {spherical}")
# Create vector from spherical coordinates
from_spherical = pygame.math.Vector3.from_spherical((5, 45, 30))
print(f"From spherical: {from_spherical}")
# 3D distance and interpolation
target = pygame.math.Vector3(5, 8, 2)
distance_3d = position.distance_to(target)
print(f"3D distance: {distance_3d}")
# Interpolate between positions
interpolated = position.lerp(target, 0.5)
print(f"Halfway point: {interpolated}")import pygame
import pygame.math
def circle_collision(pos1, radius1, pos2, radius2):
"""Check collision between two circles using vectors"""
distance = pos1.distance_to(pos2)
return distance <= (radius1 + radius2)
def line_intersection(line1_start, line1_end, line2_start, line2_end):
"""Find intersection point of two lines"""
# Convert to vector form
d1 = line1_end - line1_start
d2 = line2_end - line2_start
d3 = line1_start - line2_start
# Calculate cross products
cross_d2_d3 = d2.cross(d3)
cross_d1_d2 = d1.cross(d2)
if abs(cross_d1_d2) < 1e-10: # Lines are parallel
return None
t1 = cross_d2_d3 / cross_d1_d2
if 0 <= t1 <= 1:
intersection = line1_start + d1 * t1
return intersection
return None
def point_in_triangle(point, a, b, c):
"""Check if point is inside triangle using barycentric coordinates"""
v0 = c - a
v1 = b - a
v2 = point - a
dot00 = v0.dot(v0)
dot01 = v0.dot(v1)
dot02 = v0.dot(v2)
dot11 = v1.dot(v1)
dot12 = v1.dot(v2)
inv_denom = 1 / (dot00 * dot11 - dot01 * dot01)
u = (dot11 * dot02 - dot01 * dot12) * inv_denom
v = (dot00 * dot12 - dot01 * dot02) * inv_denom
return (u >= 0) and (v >= 0) and (u + v <= 1)
# Example usage
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
# Test objects
circle1 = {"pos": pygame.math.Vector2(200, 200), "radius": 30}
circle2 = {"pos": pygame.math.Vector2(400, 300), "radius": 40}
triangle_points = [
pygame.math.Vector2(500, 100),
pygame.math.Vector2(600, 250),
pygame.math.Vector2(450, 200)
]
mouse_pos = pygame.math.Vector2(0, 0)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEMOTION:
mouse_pos = pygame.math.Vector2(event.pos)
screen.fill((0, 0, 0))
# Check circle collision
circles_colliding = circle_collision(
circle1["pos"], circle1["radius"],
circle2["pos"], circle2["radius"]
)
# Draw circles
color1 = (255, 100, 100) if circles_colliding else (100, 100, 255)
color2 = (255, 100, 100) if circles_colliding else (100, 255, 100)
pygame.draw.circle(screen, color1, (int(circle1["pos"].x), int(circle1["pos"].y)), circle1["radius"])
pygame.draw.circle(screen, color2, (int(circle2["pos"].x), int(circle2["pos"].y)), circle2["radius"])
# Check point in triangle
mouse_in_triangle = point_in_triangle(mouse_pos, *triangle_points)
triangle_color = (255, 255, 100) if mouse_in_triangle else (100, 100, 100)
# Draw triangle
triangle_coords = [(int(p.x), int(p.y)) for p in triangle_points]
pygame.draw.polygon(screen, triangle_color, triangle_coords)
# Draw mouse position
pygame.draw.circle(screen, (255, 255, 255), (int(mouse_pos.x), int(mouse_pos.y)), 5)
pygame.display.flip()
clock.tick(60)
pygame.quit()import pygame
import pygame.math
import math
def vector_field(pos):
"""Generate vector field - spiral pattern"""
center = pygame.math.Vector2(400, 300)
to_center = center - pos
if to_center.magnitude() > 0:
# Spiral field
perpendicular = pygame.math.Vector2(-to_center.y, to_center.x).normalize()
radial = to_center.normalize()
spiral_strength = 0.3
radial_strength = 0.1
field_vector = perpendicular * spiral_strength + radial * radial_strength
return field_vector
else:
return pygame.math.Vector2(0, 0)
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
# Create grid of vectors
grid_size = 40
vectors = []
for x in range(0, 800, grid_size):
for y in range(0, 600, grid_size):
pos = pygame.math.Vector2(x, y)
field_vec = vector_field(pos)
vectors.append((pos, field_vec))
# Particles
particles = []
for _ in range(50):
particle = {
'pos': pygame.math.Vector2(
pygame.math.Vector2(400, 300).x + (random.uniform(-100, 100)),
pygame.math.Vector2(400, 300).y + (random.uniform(-100, 100))
),
'vel': pygame.math.Vector2(0, 0),
'trail': []
}
particles.append(particle)
import random
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
# Draw vector field
for pos, field_vec in vectors:
if field_vec.magnitude() > 0.01:
end_pos = pos + field_vec * 30
pygame.draw.line(screen, (50, 50, 50), pos, end_pos)
# Draw arrowhead
arrow_size = 5
if field_vec.magnitude() > 0:
arrow_dir = field_vec.normalize()
left_arrow = end_pos - arrow_dir.rotate(150) * arrow_size
right_arrow = end_pos - arrow_dir.rotate(-150) * arrow_size
pygame.draw.polygon(screen, (50, 50, 50), [end_pos, left_arrow, right_arrow])
# Update and draw particles
for particle in particles:
# Apply field force
field_force = vector_field(particle['pos'])
particle['vel'] += field_force * 0.5
# Apply damping
particle['vel'] *= 0.98
# Update position
particle['pos'] += particle['vel']
# Add to trail
particle['trail'].append(particle['pos'].copy())
if len(particle['trail']) > 20:
particle['trail'].pop(0)
# Wrap around screen
if particle['pos'].x < 0: particle['pos'].x = 800
if particle['pos'].x > 800: particle['pos'].x = 0
if particle['pos'].y < 0: particle['pos'].y = 600
if particle['pos'].y > 600: particle['pos'].y = 0
# Draw trail
for i, trail_pos in enumerate(particle['trail']):
alpha = i / len(particle['trail'])
color_intensity = int(255 * alpha)
pygame.draw.circle(screen, (color_intensity, color_intensity, 255),
(int(trail_pos.x), int(trail_pos.y)), 2)
pygame.display.flip()
clock.tick(60)
pygame.quit()