CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pygame

Cross-platform library for developing multimedia applications and video games in Python built on top of SDL

Overview
Eval results
Files

time-animation.mddocs/

Time and Animation

Timing control, frame rate management, and animation support for smooth gameplay. Pygame's time module provides essential tools for controlling game timing, measuring elapsed time, and creating consistent frame rates across different hardware.

Capabilities

Time Measurement

Get timing information and measure elapsed time.

def get_ticks() -> int:
    """
    Get milliseconds since pygame.init() was called.

    Returns:
        int: Milliseconds elapsed since pygame initialization
    """

def wait(milliseconds: int) -> int:
    """
    Pause program execution for specified time.

    Args:
        milliseconds (int): Time to wait in milliseconds

    Returns:
        int: Actual time waited (may be slightly different)
    """

def delay(milliseconds: int) -> int:
    """
    Pause program with CPU time release (more CPU friendly than wait).

    Args:
        milliseconds (int): Time to delay in milliseconds

    Returns:
        int: Actual time delayed
    """

Frame Rate Control

Manage game frame rate for consistent gameplay across different hardware.

class Clock:
    def __init__(self):
        """Create clock object for frame rate management."""

    def tick(self, framerate: int = 0) -> int:
        """
        Control frame rate and get frame time.

        Args:
            framerate (int): Target frames per second (0 for unlimited)

        Returns:
            int: Milliseconds since last call
        """

    def tick_busy_loop(self, framerate: int = 0) -> int:
        """
        Accurate frame rate control using busy loop (more CPU intensive).

        Args:
            framerate (int): Target frames per second

        Returns:
            int: Milliseconds since last call
        """

    def get_time(self) -> int:
        """
        Get time since last tick() call.

        Returns:
            int: Milliseconds since last tick
        """

    def get_rawtime(self) -> int:
        """
        Get actual time since last tick() (ignoring delays).

        Returns:
            int: Raw milliseconds since last tick
        """

    def get_fps(self) -> float:
        """
        Get current frame rate.

        Returns:
            float: Frames per second averaged over last 10 frames
        """

Timer Events

Schedule events to be posted at regular intervals.

def set_timer(eventid: int, milliseconds: int) -> None:
    """
    Repeatedly post an event at regular intervals.

    Args:
        eventid (int): Event type to post
        milliseconds (int): Interval in milliseconds (0 to stop)
    """

def set_timer(eventid: int, milliseconds: int, loops: int) -> None:
    """
    Post event with limited number of repetitions.

    Args:
        eventid (int): Event type to post
        milliseconds (int): Interval in milliseconds
        loops (int): Number of times to post event (0 for infinite)
    """

Animation Utilities

Helper functions and classes for creating smooth animations.

Linear Interpolation

def lerp(start: float, end: float, t: float) -> float:
    """
    Linear interpolation between two values.

    Args:
        start (float): Starting value
        end (float): Ending value
        t (float): Interpolation factor (0.0 to 1.0)

    Returns:
        float: Interpolated value
    """

def smooth_step(t: float) -> float:
    """
    Smooth interpolation curve (ease in and out).

    Args:
        t (float): Input value (0.0 to 1.0)

    Returns:
        float: Smoothed value using 3t² - 2t³
    """

def ease_in_quad(t: float) -> float:
    """
    Quadratic ease-in animation curve.

    Args:
        t (float): Time parameter (0.0 to 1.0)

    Returns:
        float: Eased value
    """

def ease_out_quad(t: float) -> float:
    """
    Quadratic ease-out animation curve.

    Args:
        t (float): Time parameter (0.0 to 1.0)

    Returns:
        float: Eased value
    """

def ease_in_out_quad(t: float) -> float:
    """
    Quadratic ease-in-out animation curve.

    Args:
        t (float): Time parameter (0.0 to 1.0)

    Returns:
        float: Eased value
    """

Animation Classes

Object-oriented animation system for managing multiple animations.

class Animation:
    def __init__(self, start_value: float, end_value: float, duration: int, easing_func = None):
        """
        Create animation between two values.

        Args:
            start_value (float): Starting value
            end_value (float): Target value
            duration (int): Animation duration in milliseconds
            easing_func: Easing function (default: linear)
        """

    def update(self, dt: int) -> None:
        """
        Update animation progress.

        Args:
            dt (int): Delta time in milliseconds
        """

    def get_value(self) -> float:
        """
        Get current animated value.

        Returns:
            float: Current value based on animation progress
        """

    def is_finished(self) -> bool:
        """
        Check if animation is complete.

        Returns:
            bool: True if animation has finished
        """

    def reset(self) -> None:
        """Reset animation to beginning."""

    def reverse(self) -> None:
        """Reverse animation direction."""

class AnimationManager:
    def __init__(self):
        """Manage multiple animations."""

    def add_animation(self, name: str, animation: Animation) -> None:
        """
        Add animation to manager.

        Args:
            name (str): Animation identifier
            animation (Animation): Animation object
        """

    def update_all(self, dt: int) -> None:
        """
        Update all managed animations.

        Args:
            dt (int): Delta time in milliseconds
        """

    def get_value(self, name: str) -> float:
        """
        Get value of named animation.

        Args:
            name (str): Animation name

        Returns:
            float: Current animation value
        """

    def remove_finished(self) -> None:
        """Remove all completed animations."""

Usage Examples

Basic Frame Rate Control

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))

# Create clock for frame rate control
clock = pygame.time.Clock()
FPS = 60

# Game state
player_x = 400
player_speed = 200  # pixels per second

running = True
while running:
    # Get delta time in seconds
    dt = clock.tick(FPS) / 1000.0

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Frame-rate independent movement
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player_x -= player_speed * dt
    if keys[pygame.K_RIGHT]:
        player_x += player_speed * dt

    # Keep player on screen
    player_x = max(0, min(800, player_x))

    # Draw
    screen.fill((0, 0, 0))
    pygame.draw.circle(screen, (255, 255, 255), (int(player_x), 300), 20)

    # Display FPS
    fps_text = f"FPS: {clock.get_fps():.1f}"
    print(fps_text)  # In real game, render to screen

    pygame.display.flip()

pygame.quit()

Timer Events

import pygame
import random

pygame.init()
screen = pygame.display.set_mode((800, 600))

# Create custom events
SPAWN_ENEMY = pygame.event.custom_type()
FLASH_SCREEN = pygame.event.custom_type()
GAME_TIMER = pygame.event.custom_type()

# Set up timers
pygame.time.set_timer(SPAWN_ENEMY, 2000)  # Spawn enemy every 2 seconds
pygame.time.set_timer(FLASH_SCREEN, 500)  # Flash every 0.5 seconds
pygame.time.set_timer(GAME_TIMER, 1000)   # Game timer every second

# Game state
enemies = []
flash_color = (0, 0, 0)
game_time = 0
clock = pygame.time.Clock()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        elif event.type == SPAWN_ENEMY:
            # Spawn enemy at random position
            enemy_pos = (random.randint(0, 800), random.randint(0, 600))
            enemies.append(enemy_pos)
            print(f"Enemy spawned at {enemy_pos}")

        elif event.type == FLASH_SCREEN:
            # Toggle flash color
            flash_color = (50, 0, 0) if flash_color == (0, 0, 0) else (0, 0, 0)

        elif event.type == GAME_TIMER:
            # Update game timer
            game_time += 1
            print(f"Game time: {game_time} seconds")

            # Stop spawning after 10 seconds
            if game_time >= 10:
                pygame.time.set_timer(SPAWN_ENEMY, 0)  # Stop timer

    # Draw
    screen.fill(flash_color)

    # Draw enemies
    for enemy_pos in enemies:
        pygame.draw.circle(screen, (255, 0, 0), enemy_pos, 15)

    # Draw timer
    font = pygame.font.Font(None, 36)
    timer_text = font.render(f"Time: {game_time}", True, (255, 255, 255))
    screen.blit(timer_text, (50, 50))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Smooth Animation System

import pygame
import math

# Easing functions
def ease_in_quad(t):
    return t * t

def ease_out_quad(t):
    return 1 - (1 - t) * (1 - t)

def ease_in_out_quad(t):
    if t < 0.5:
        return 2 * t * t
    else:
        return 1 - 2 * (1 - t) * (1 - t)

def ease_in_bounce(t):
    return 1 - ease_out_bounce(1 - t)

def ease_out_bounce(t):
    if t < 1 / 2.75:
        return 7.5625 * t * t
    elif t < 2 / 2.75:
        t -= 1.5 / 2.75
        return 7.5625 * t * t + 0.75
    elif t < 2.5 / 2.75:
        t -= 2.25 / 2.75
        return 7.5625 * t * t + 0.9375
    else:
        t -= 2.625 / 2.75
        return 7.5625 * t * t + 0.984375

class Animation:
    def __init__(self, start_value, end_value, duration, easing_func=None):
        self.start_value = start_value
        self.end_value = end_value
        self.duration = duration
        self.easing_func = easing_func or (lambda t: t)  # Linear by default
        self.elapsed = 0
        self.finished = False

    def update(self, dt):
        if not self.finished:
            self.elapsed += dt
            if self.elapsed >= self.duration:
                self.elapsed = self.duration
                self.finished = True

    def get_value(self):
        if self.duration == 0:
            return self.end_value

        t = self.elapsed / self.duration
        eased_t = self.easing_func(t)

        return self.start_value + (self.end_value - self.start_value) * eased_t

    def is_finished(self):
        return self.finished

    def reset(self):
        self.elapsed = 0
        self.finished = False

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Create animations
animations = {
    'x': Animation(100, 700, 2000, ease_out_bounce),  # X movement with bounce
    'y': Animation(100, 500, 1500, ease_in_out_quad),  # Y movement smooth
    'size': Animation(10, 50, 1000, ease_in_quad),     # Size growth
    'rotation': Animation(0, 360, 3000),               # Rotation
}

# Animation state
running = True
animation_started = False
start_time = 0

while running:
    dt = clock.tick(60)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                # Reset and start animations
                for anim in animations.values():
                    anim.reset()
                animation_started = True

    # Update animations
    if animation_started:
        for anim in animations.values():
            anim.update(dt)

    # Get current values
    x = animations['x'].get_value()
    y = animations['y'].get_value()
    size = animations['size'].get_value()
    rotation = animations['rotation'].get_value()

    # Draw
    screen.fill((0, 0, 0))

    # Draw animated circle
    pygame.draw.circle(screen, (255, 255, 255), (int(x), int(y)), int(size))

    # Draw rotating line from circle
    line_length = size * 2
    end_x = x + line_length * math.cos(math.radians(rotation))
    end_y = y + line_length * math.sin(math.radians(rotation))
    pygame.draw.line(screen, (255, 0, 0), (x, y), (end_x, end_y), 3)

    # Draw instructions
    font = pygame.font.Font(None, 36)
    text = font.render("Press SPACE to start animation", True, (255, 255, 255))
    screen.blit(text, (50, 50))

    # Show animation progress
    all_finished = all(anim.is_finished() for anim in animations.values())
    if animation_started:
        progress_text = "Animation finished!" if all_finished else "Animating..."
        progress = font.render(progress_text, True, (255, 255, 0))
        screen.blit(progress, (50, 100))

    pygame.display.flip()

pygame.quit()

Time-based Particle System

import pygame
import random
import math

class Particle:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.vel_x = random.uniform(-100, 100)  # pixels per second
        self.vel_y = random.uniform(-200, -50)  # upward bias
        self.life = random.uniform(2.0, 4.0)    # seconds
        self.max_life = self.life
        self.size = random.uniform(2, 6)
        self.color = [255, random.randint(100, 255), 0]  # Orange-ish

    def update(self, dt):
        # Physics (time-based)
        gravity = 200  # pixels per second squared
        self.vel_y += gravity * dt

        # Update position
        self.x += self.vel_x * dt
        self.y += self.vel_y * dt

        # Update life
        self.life -= dt

        # Fade out as life decreases
        life_ratio = self.life / self.max_life
        self.color[0] = int(255 * life_ratio)
        self.color[1] = int(self.color[1] * life_ratio)

    def is_dead(self):
        return self.life <= 0

    def draw(self, screen):
        if self.life > 0:
            pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), int(self.size * (self.life / self.max_life)))

class ParticleSystem:
    def __init__(self):
        self.particles = []
        self.spawn_timer = 0
        self.spawn_rate = 0.1  # seconds between spawns

    def update(self, dt, mouse_pos):
        # Spawn particles
        self.spawn_timer -= dt
        if self.spawn_timer <= 0:
            # Spawn particle at mouse position
            self.particles.append(Particle(mouse_pos[0], mouse_pos[1]))
            self.spawn_timer = self.spawn_rate

        # Update particles
        for particle in self.particles[:]:
            particle.update(dt)
            if particle.is_dead() or particle.y > 700:  # Remove if dead or off-screen
                self.particles.remove(particle)

    def draw(self, screen):
        for particle in self.particles:
            particle.draw(screen)

    def get_count(self):
        return len(self.particles)

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

particle_system = ParticleSystem()
font = pygame.font.Font(None, 36)

running = True
while running:
    dt = clock.tick(60) / 1000.0  # Convert to seconds

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    mouse_pos = pygame.mouse.get_pos()

    # Update particle system
    particle_system.update(dt, mouse_pos)

    # Draw
    screen.fill((0, 0, 30))  # Dark blue background

    # Draw particle system
    particle_system.draw(screen)

    # Draw UI
    particle_count = font.render(f"Particles: {particle_system.get_count()}", True, (255, 255, 255))
    screen.blit(particle_count, (10, 10))

    fps_text = font.render(f"FPS: {clock.get_fps():.1f}", True, (255, 255, 255))
    screen.blit(fps_text, (10, 50))

    instructions = font.render("Move mouse to emit particles", True, (255, 255, 255))
    screen.blit(instructions, (10, 90))

    pygame.display.flip()

pygame.quit()

Delta Time Comparison

import pygame
import math

pygame.init()
screen = pygame.display.set_mode((800, 600))
font = pygame.font.Font(None, 36)

# Two circles - one frame-dependent, one time-dependent
circle1_angle = 0  # Frame-dependent rotation
circle2_angle = 0  # Time-dependent rotation

rotation_speed_degrees_per_second = 90  # 90 degrees per second

clock = pygame.time.Clock()
target_fps = 60
actual_fps_limit = 60  # Can be changed to test different frame rates

running = True
while running:
    dt = clock.tick(actual_fps_limit) / 1000.0  # Delta time in seconds

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_1:
                actual_fps_limit = 30
            elif event.key == pygame.K_2:
                actual_fps_limit = 60
            elif event.key == pygame.K_3:
                actual_fps_limit = 120

    # Frame-dependent rotation (BAD - speed depends on frame rate)
    circle1_angle += 1.5  # Fixed increment per frame

    # Time-dependent rotation (GOOD - consistent speed regardless of frame rate)
    circle2_angle += rotation_speed_degrees_per_second * dt

    # Calculate positions
    center_x = 400
    radius = 150

    circle1_x = center_x - 100 + radius * math.cos(math.radians(circle1_angle))
    circle1_y = 300 + radius * math.sin(math.radians(circle1_angle))

    circle2_x = center_x + 100 + radius * math.cos(math.radians(circle2_angle))
    circle2_y = 300 + radius * math.sin(math.radians(circle2_angle))

    # Draw
    screen.fill((0, 0, 0))

    # Draw orbits
    pygame.draw.circle(screen, (50, 50, 50), (center_x - 100, 300), radius, 2)
    pygame.draw.circle(screen, (50, 50, 50), (center_x + 100, 300), radius, 2)

    # Draw centers
    pygame.draw.circle(screen, (100, 100, 100), (center_x - 100, 300), 5)
    pygame.draw.circle(screen, (100, 100, 100), (center_x + 100, 300), 5)

    # Draw rotating circles
    pygame.draw.circle(screen, (255, 100, 100), (int(circle1_x), int(circle1_y)), 15)  # Red - frame-dependent
    pygame.draw.circle(screen, (100, 255, 100), (int(circle2_x), int(circle2_y)), 15)  # Green - time-dependent

    # Labels
    label1 = font.render("Frame-dependent (RED)", True, (255, 100, 100))
    screen.blit(label1, (50, 50))

    label2 = font.render("Time-dependent (GREEN)", True, (100, 255, 100))
    screen.blit(label2, (450, 50))

    # FPS info
    fps_text = font.render(f"FPS: {clock.get_fps():.1f}", True, (255, 255, 255))
    screen.blit(fps_text, (50, 500))

    instructions = font.render("Press 1 (30fps), 2 (60fps), 3 (120fps)", True, (255, 255, 255))
    screen.blit(instructions, (50, 530))

    explanation = font.render("Green circle maintains consistent speed at any FPS", True, (255, 255, 255))
    screen.blit(explanation, (50, 560))

    pygame.display.flip()

pygame.quit()

Install with Tessl CLI

npx tessl i tessl/pypi-pygame

docs

advanced-drawing.md

audio-sound.md

core-system.md

drawing-shapes.md

event-input.md

game-objects.md

graphics-display.md

index.md

input-devices.md

joystick-gamepad.md

math-utils.md

surface-image.md

text-font.md

time-animation.md

transform-image.md

tile.json