Sprites, Collisions y Animations

Curso de Desarrollo de Juegos con Pygame

Felipe A. Moreno-Vera

🎬 Animations

ΒΏCΓ³mo funciona la animation 2D?

La animation funciona exactamente como un flipbook: una secuencia de imΓ‘genes (frames) que cambian a cierta velocidad para crear la ilusiΓ³n de movimiento.

Frame 0    Frame 1    Frame 2    Frame 3
  🧍         🚢         🧍         🚢
  ↑          ↑          ↑          ↑
 100ms      100ms      100ms      100ms   β†’ repeats

Importante

La velocidad de animation β‰  FPS del juego. A 60 FPS, puedes animar a 10 FPS (cambiar de frame cada 6 frames del juego).

Spritesheet

Un spritesheet es una ΓΊnica imagen que contiene todos los frames de todas las animations. Se usa para:

  • Reducir el nΓΊmero de archivos individuales
  • Cargar la memoria de la GPU de forma mΓ‘s eficiente
  • Simplificar la gestiΓ³n de las animations
spritesheet.png:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ [idle_0][idle_1][idle_2][idle_3]                 β”‚  ← row 0: idle
β”‚ [walk_0][walk_1][walk_2][walk_3][walk_4][walk_5] β”‚  ← row 1: walk
β”‚ [jump_0][jump_1][jump_2]                         β”‚  ← row 2: jump
β”‚ [attack_0][attack_1][attack_2][attack_3]         β”‚  ← row 3: attack
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Spritesheet

Un spritesheet es una ΓΊnica imagen que contiene todos los frames de todas las animations. Se usa para:

  • Reducir el nΓΊmero de archivos individuales
  • Cargar la memoria de la GPU de forma mΓ‘s eficiente
  • Simplificar la gestiΓ³n de las animations
spritesheet.png:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ [idle_0][idle_1][idle_2][idle_3]                 β”‚  ← row 0: idle
β”‚ [walk_0][walk_1][walk_2][walk_3][walk_4][walk_5] β”‚  ← row 1: walk
β”‚ [jump_0][jump_1][jump_2]                         β”‚  ← row 2: jump
β”‚ [attack_0][attack_1][attack_2][attack_3]         β”‚  ← row 3: attack
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Para extraer un frame usa Surface.subsurface(rect):

sheet = pygame.image.load("spritesheet.png").convert_alpha()
SIZE = 64  # Frame size

# Extract frame at column=2, row=1
frame = sheet.subsurface(pygame.Rect(2 * SIZE, 1 * SIZE, SIZE, SIZE))

Animation Timer

El timer controla cuΓ‘ndo avanzar al siguiente frame. Hay dos enfoques:

Animation Timer

El timer controla cuΓ‘ndo avanzar al siguiente frame. Hay dos enfoques:

Enfoque 1 β€” Frame counter (mΓ‘s simple)

self.current_frame = 0
self.counter       = 0
self.anim_speed    = 8  # Change frame every 8 game frames

def update(self):
    self.counter += 1
    if self.counter >= self.anim_speed:
        self.counter = 0
        self.current_frame = (self.current_frame + 1) % len(self.frames)
    self.image = self.frames[self.current_frame]

Animation Timer

El timer controla cuΓ‘ndo avanzar al siguiente frame. Hay dos enfoques:

Enfoque 1 β€” Frame counter (mΓ‘s simple)

self.current_frame = 0
self.counter       = 0
self.anim_speed    = 8  # Change frame every 8 game frames

def update(self):
    self.counter += 1
    if self.counter >= self.anim_speed:
        self.counter = 0
        self.current_frame = (self.current_frame + 1) % len(self.frames)
    self.image = self.frames[self.current_frame]

Enfoque 2 β€” Millisecond timer (recomendado)

self.current_frame  = 0
self.last_update    = pygame.time.get_ticks()  # current ms
self.frame_duration = 100  # ms per frame (100ms = 10 anim FPS)

def update(self):
    now = pygame.time.get_ticks()
    if now - self.last_update >= self.frame_duration:
        self.last_update = now
        self.current_frame = (self.current_frame + 1) % len(self.frames)
    self.image = self.frames[self.current_frame]

Animation Timer

El timer controla cuΓ‘ndo avanzar al siguiente frame.

Tip

Approach 2 es preferido porque si el FPS del juego baja, la animation se sigue reproduciendo a la misma velocidad en tiempo real en pantalla.

Animation State Machine

Un personaje tiene mΓΊltiples animations segΓΊn su state. La state machine decide cuΓ‘l reproducir.

                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚       IDLE          β”‚
                 β”‚  (standing still)   β”‚
                 β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚       β”‚
               move    β”‚       β”‚ airborne
               key     ↓       ↓
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚   WALK    β”‚ β”‚   JUMP    β”‚
               β”‚ (walking) β”‚ β”‚ (jumping) β”‚
               β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                     β”‚ release     β”‚ land
                     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                            ↓
                          IDLE

Animation State Machine – cΓ³digo

class Player(pygame.sprite.Sprite):
    def __init__(self):
        ...
        self.state = 'idle'   # Current state
        self.animations = {
            'idle':   [frame_idle_0, frame_idle_1, frame_idle_2],
            'walk':   [frame_walk_0, frame_walk_1, frame_walk_2, frame_walk_3],
            'jump':   [frame_jump_0, frame_jump_1],
        }

    def change_state(self, new_state):
        if self.state != new_state:        # Only if it actually changed
            self.state = new_state
            self.current_frame = 0         # Reset animation on state change

    def update(self):
        # Determine state
        keys = pygame.key.get_pressed()
        if self.airborne:
            self.change_state('jump')
        elif keys[pygame.K_LEFT] or keys[pygame.K_RIGHT]:
            self.change_state('walk')
        else:
            self.change_state('idle')

        # Animate
        frames = self.animations[self.state]
        self.image = frames[self.current_frame % len(frames)]

State Machine Simulation

SimulaciΓ³n de la state machine:
=============================================
  idle[1/2] β†’ idle[2/2] β†’ idle[0/2]
  walk[1/3] β†’ walk[2/3] β†’ walk[3/3] β†’ walk[0/3] β†’ walk[1/3]
  jump[1/1] β†’ jump[0/1] β†’ jump[1/1] β†’ jump[0/1]
  idle[1/2] β†’ idle[2/2]
  attack[1/3] β†’ attack[2/3] β†’ attack[3/3] β†’ attack[0/3] β†’ attack[1/3]
  idle[1/2] β†’ idle[2/2] β†’ idle[0/2]

Transiciones de state:
  idle β†’ walk
  walk β†’ jump
  jump β†’ idle
  idle β†’ attack
  attack β†’ idle

Animations β€” Resumen

  • Spritesheet = una ΓΊnica imagen que contiene todos los frames
  • ms timer = independiente del FPS (approach preferido)
  • State machine = determina quΓ© animation reproducir
  • Reinicia current_frame = 0 cada vez que cambia el state