Physics e AI de Inimigos

Curso de Desenvolvimento de Jogos com Pygame

Felipe A. Moreno-Vera

Nota

Nestes módulos você vai aprender:

  • Gravity, velocidade, aceleração, atrito, altura de salto variável, coyote time, jump buffering, plataformas de mão única, plataformas móveis, e wall jumping
  • Steering behaviors (seek, flee, patrol, wander), line of sight, vision cones, e uma Finite State Machine completa para inimigos

⚙️ Physics Básica

O Physics Update Loop

A physics de plataformas se baseia em duas equações fundamentais aplicadas a cada frame:

velocity += acceleration × dt      # Euler integration
position += velocity   × dt

O Physics Update Loop

A physics de plataformas se baseia em duas equações fundamentais aplicadas a cada frame:

velocity += acceleration × dt      # Euler integration
position += velocity   × dt

dt (delta time) é o tempo decorrido desde o último frame, em segundos. Usar dt torna a simulação independente do frame rate: o mesmo comportamento a 30 FPS e a 120 FPS.

# Every frame:
vy    += GRAVITY * dt       # Gravity accelerates the fall
y     += vy * dt            # Velocity moves the position

O Physics Update Loop

A physics de plataformas se baseia em duas equações fundamentais aplicadas a cada frame:

velocity += acceleration × dt      # Euler integration
position += velocity   × dt

dt (delta time) é o tempo decorrido desde o último frame, em segundos. Usar dt torna a simulação independente do frame rate: o mesmo comportamento a 30 FPS e a 120 FPS.

# Every frame:
vy    += GRAVITY * dt       # Gravity accelerates the fall
y     += vy * dt            # Velocity moves the position

Importante

Sempre multiplique forças e velocidades por dt. Sem isso, seu jogo vai rodar em velocidades diferentes dependendo do hardware.

Gravity

Gravity é simplesmente uma aceleração constante para baixo aplicada a cada frame:

GRAVITY      = 900.0    # px/s²  — feel free to tune
TERMINAL_VEL = 850.0    # cap: prevents infinite acceleration

vy = min(vy + GRAVITY * dt, TERMINAL_VEL)
y  += vy * dt

Gravity

Gravity é simplesmente uma aceleração constante para baixo aplicada a cada frame:

GRAVITY      = 900.0    # px/s²  — feel free to tune
TERMINAL_VEL = 850.0    # cap: prevents infinite acceleration

vy = min(vy + GRAVITY * dt, TERMINAL_VEL)
y  += vy * dt
Simulação de queda livre (60 FPS, gravity=900 px/s²)
  frame    vy (px/s)      y (px)       notas
  ----------------------------------------------------
  0        15.0           0.2          
  10       165.0          16.5         
  20       315.0          57.8         
  30       465.0          124.0        
  40       615.0          215.2        
  50       765.0          331.5        
  60       850.0          469.8        terminal velocity atingida

Jumping

Um salto é simplesmente uma velocidade instantânea para cima aplicada quando o jogador está no chão:

JUMP_VY = -460.0    # Negative = upward (Y grows downward in Pygame)

if player_pressed_jump and on_ground:
    vy = JUMP_VY

Variable Jump Height

Segure o botão de pulo para um salto mais alto; solte antes para um salto curto. O truque: reduzir a gravity enquanto o botão está pressionado e o jogador ainda está subindo.

JUMP_FRAMES_MAX = 20   # Max frames of reduced gravity
JUMP_FRAMES_MIN = 6    # Must hold at least this long

if jumping:
    jump_frames += 1
    if not keys[K_SPACE] and jump_frames > JUMP_FRAMES_MIN:
        jumping = False          # Cut jump short
    if jump_frames >= JUMP_FRAMES_MAX:
        jumping = False

grav_mult = 0.4 if jumping else 1.0   # Reduced gravity = higher jump
vy += GRAVITY * grav_mult * dt

Coyote Time

O período de graça do coyote time permite que o jogador pule durante alguns frames depois de andar para fora de uma plataforma — parece justo e responsivo:

COYOTE_FRAMES = 8

if on_ground:
    coyote_timer = COYOTE_FRAMES
elif coyote_timer > 0:
    coyote_timer -= 1

can_jump = on_ground or coyote_timer > 0

Jump Buffering

Se o jogador pressionar pular pouco antes de aterrissar, guarde o input em um buffer e execute-o ao entrar em contato:

BUFFER_FRAMES = 10

# On KEYDOWN event:
jump_buffer = BUFFER_FRAMES

# In update():
if jump_buffer > 0:
    jump_buffer -= 1
if jump_buffer > 0 and can_jump:
    vy = JUMP_VY
    jump_buffer = 0

Jump Buffering

Valores de Coyote time e jump buffer
=============================================
  Coyote frames  : 8 frames = 133 ms
  Buffer frames  : 10 frames = 167 ms

Por que isso importa:
  Sem coyote: andar para fora da borda → não é possível pular (parece injusto)
  Com coyote:     8 frames de graça → parece responsive

  Sem buffer: pressionar pular 1 frame antes → nada acontece
  Com buffer:     input mantido por 10 frames → dispara automaticamente ao aterrissar

Collision Resolution

O padrão mais confiável: mova-se em um eixo, resolva a collision, e depois mova-se no outro.

# Step 1: move horizontally
x += vx * dt
for platform in platforms:
    if player_rect.colliderect(platform.rect):
        if vx > 0: x = platform.rect.left  - player_w   # Pushed left
        if vx < 0: x = platform.rect.right               # Pushed right
        vx = 0

# Step 2: move vertically
y += vy * dt
on_ground = False
for platform in platforms:
    if player_rect.colliderect(platform.rect):
        if vy > 0:                                        # Falling
            y        = platform.rect.top - player_h
            vy       = 0
            on_ground = True
        elif vy < 0:                                      # Rising
            y  = platform.rect.bottom
            vy = 0

Collision Resolution

O padrão mais confiável: mova-se em um eixo, resolva a collision, e depois mova-se no outro.

# Step 1: move horizontally
x += vx * dt
for platform in platforms:
    if player_rect.colliderect(platform.rect):
        if vx > 0: x = platform.rect.left  - player_w   # Pushed left
        if vx < 0: x = platform.rect.right               # Pushed right
        vx = 0

# Step 2: move vertically
y += vy * dt
on_ground = False
for platform in platforms:
    if player_rect.colliderect(platform.rect):
        if vy > 0:                                        # Falling
            y        = platform.rect.top - player_h
            vy       = 0
            on_ground = True
        elif vy < 0:                                      # Rising
            y  = platform.rect.bottom
            vy = 0

Dica

Sempre resolva X e Y separadamente. Resolver os dois juntos faz o jogador atravessar cantos e ficar preso nas paredes de forma incorreta.

One-Way Platforms

Sólidas apenas por cima — o jogador pode atravessá-las pulando por baixo:

for platform in one_way_platforms:
    if player_rect.colliderect(platform.rect):
        # Only resolve if falling AND was above the platform top last frame
        if vy > 0 and (prev_y + player_h) <= platform.rect.top + 4:
            y        = platform.rect.top - player_h
            vy       = 0
            on_ground = True
        # If rising: ignore (pass through)

Moving Platforms — Velocity Inheritance

Quando o jogador está parado sobre uma plataforma em movimento, some a velocidade da plataforma ao jogador:

class MovingPlatform:
    def update(self, dt):
        prev_x     = self.rect.x
        self.rect.x += self.direction * self.speed * dt
        self.vel_x  = (self.rect.x - prev_x) / dt   # Derived velocity

# In player update:
if on_ground and standing_on:
    x += standing_on.vel_x * dt   # Inherit platform movement

Acceleration-Based Movement

A velocidade instantânea parece rígida. A aceleração dá peso e momentum ao movimento:

MOVE_ACCEL   = 1800.0   # px/s² — ground acceleration
AIR_ACCEL    = 900.0    # Less control in the air
MAX_VX       = 240.0

accel = MOVE_ACCEL if on_ground else AIR_ACCEL

if pressing_right:
    vx += (MAX_VX - vx) * (accel / MAX_VX) * dt   # Accelerate toward target
else:
    vx -= math.copysign(MOVE_ACCEL * dt, vx)       # Decelerate

Acceleration-Based Movement

A velocidade instantânea parece rígida. A aceleração dá peso e momentum ao movimento:

MOVE_ACCEL   = 1800.0   # px/s² — ground acceleration
AIR_ACCEL    = 900.0    # Less control in the air
MAX_VX       = 240.0

accel = MOVE_ACCEL if on_ground else AIR_ACCEL

if pressing_right:
    vx += (MAX_VX - vx) * (accel / MAX_VX) * dt   # Accelerate toward target
else:
    vx -= math.copysign(MOVE_ACCEL * dt, vx)       # Decelerate
Velocity profile em 30 frames
  Frame    Instantânea  Accelerated
  ----------------------------------
  0        240.0        30.0
  5        240.0        132.3
  10       240.0        184.8
  15       240.0        211.7
  20       240.0        225.5
  25       240.0        232.5

O movimento Accelerated alcança max speed de forma gradual → parece weighted

Physics — Resumo

  • velocity += acceleration × dt e depois position += velocity × dt — sempre use dt
  • Gravity = aceleração constante para baixo + limite de terminal velocity
  • Variable jump = reduzir a gravity enquanto o botão de pulo está pressionado
  • Coyote time = período de graça de salto (8 frames) depois de andar para fora de uma plataforma
  • Jump buffering = lembrar o input de salto (10 frames) antes de aterrissar
  • Resolução separada de X e Y para evitar que o jogador atravesse cantos
  • One-way platforms: resolver a collision apenas ao cair e estar acima da borda superior
  • Moving platforms: derivar vel_x a partir do delta de posição, herdar no jogador
  • Acceleration-based movement: vx += (target - vx) * accel_factor * dt