Ir para o conteúdo

Usando dentro do pygame

O FastObjects pode renderizar dentro de uma janela criada por outra biblioteca. O host (pygame aqui) é dono da janela, do loop de eventos, do input e do som; o FastObjects é dono da inserção, atualização, remoção e desenho dos objetos. O desenho clássico do pygame compõe por cima.

O único requisito: uma janela OpenGL

O pygame precisa criar a janela com contexto OpenGL — é a única mudança em relação a um setup pygame normal:

import pygame
import fastobjects as fo

pygame.init()
pygame.display.set_mode((1280, 720), pygame.OPENGL | pygame.DOUBLEBUF)
ext = fo.attach(view_size=(1280, 720))

fo.attach() conecta o FastObjects ao contexto OpenGL corrente do host e o registra como janela atual, então batches criados depois funcionam exatamente como no modo nativo. Chame-o uma vez por janela. Se não houver contexto GL, ele levanta um RuntimeError mandando adicionar a flag OPENGL.

Quem é dono de quê

Responsabilidade Dono
Janela, loop de eventos, flip() pygame
Teclado, mouse, som pygame
Estado de sprites/formas, update, render FastObjects
Desenho 2D clássico (pygame.draw, pygame.font) pygame → composto via SurfaceLayer

O ExternalWindow (retornado por attach) expõe de propósito apenas .ctx, .width, .height, .clear(r, g, b) e .close() — sem loop de frames, sem input, porque isso pertence ao host.

Compondo o desenho do pygame: SurfaceLayer

Desenhe seu HUD, texto ou arte vetorial numa pygame.Surface normal e componha-a na GPU junto com os batches:

hud_surface = pygame.Surface((1280, 720), pygame.SRCALPHA)
hud = fo.SurfaceLayer(hud_surface)
font = pygame.font.Font(None, 28)

# a cada frame:
hud_surface.fill((0, 0, 0, 0))
hud_surface.blit(font.render("score: 42", True, (255, 255, 255)), (10, 10))
hud.update()   # sobe a surface para a GPU (um upload)
hud.draw()     # compõe (um draw call)

update() precisa do pygame instalado (importado de forma preguiçosa, então o pygame nunca vira dependência do próprio FastObjects).

Loop completo

import pygame
import fastobjects as fo

pygame.init()
pygame.display.set_mode((1280, 720), pygame.OPENGL | pygame.DOUBLEBUF)
ext = fo.attach(view_size=(1280, 720))

batch = fo.SpriteBatch("player.png", capacity=200_000)
groups = []

hud_surface = pygame.Surface((1280, 720), pygame.SRCALPHA)
hud = fo.SurfaceLayer(hud_surface)
font = pygame.font.Font(None, 28)

clock = pygame.time.Clock()
running = True
while running:
    dt = clock.tick() / 1000.0
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            groups.append(batch.spawn(100, x=float(event.pos[0]), y=float(event.pos[1])))
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_d and groups:
            batch.despawn(groups.pop())   # remoção real, devolve capacity

    hud_surface.fill((0, 0, 0, 0))
    hud_surface.blit(font.render(f"sprites: {batch.count}", True, (255, 255, 255)), (10, 10))

    ext.clear(0.08, 0.08, 0.10)
    batch.draw()
    hud.update()
    hud.draw()
    pygame.display.flip()

pygame.quit()

A versão completa executável é examples/pygame_interop.py.

Por que não fazer blit do FastObjects sobre o pygame clássico?

Uma janela OpenGL assume a apresentação — a surface de software do SDL não chega mais à tela (isso vale para qualquer API de GPU, Vulkan incluído). Então a composição vai no sentido inverso: o pygame desenha numa Surface e o FastObjects a compõe. Você mantém todas as ferramentas de desenho do pygame; só apresenta via OpenGL.

Outros hosts

O fo.attach() funciona com qualquer contexto OpenGL corrente, então o pygame é só o exemplo documentado. Estes hosts foram validados com janela real (o spike está no repositório, resultados em benchmarks/RESULTS.md):

Host Status
pygame (OpenGL) ✅ Suportado
pyglet ✅ Suportado
arcade ✅ Suportado
raylib (pyray) ❌ Não suportado

pyglet — uma janela pyglet já é OpenGL, então o attach simplesmente funciona:

import pyglet
import fastobjects as fo

win = pyglet.window.Window(900, 600)
ext = fo.attach(view_size=(900, 600))
batch = fo.SpriteBatch("player.png", capacity=10_000)
batch.spawn(1000, x=450, y=300)

while not win.has_exit:
    win.switch_to()
    win.dispatch_events()
    ext.clear(0.1, 0.1, 0.1)
    batch.draw()      # objetos do FastObjects
    win.flip()

Veja examples/pyglet_interop.py (coelhos quicando + um HUD nativo pyglet.text.Label).

arcade — desenhe os batches do FastObjects dentro do on_draw; o desenho nativo do arcade (arcade.Text, formas) convive ao lado deles:

import arcade
import fastobjects as fo

class Demo(arcade.Window):
    def __init__(self):
        super().__init__(900, 600, "fastobjects + arcade")
        self.ext = fo.attach(view_size=(900, 600))
        self.batch = fo.SpriteBatch("player.png", capacity=10_000)
        self.batch.spawn(1000, x=450, y=300)

    def on_draw(self):
        self.clear()
        self.batch.draw()   # objetos do FastObjects

Demo()
arcade.run()

Veja examples/arcade_interop.py.

raylib não é suportado

O attach conecta ao contexto GL do raylib (um clear funciona), mas o draw instanciado do FastObjects não produz saída: a camada rlgl do raylib é dona do estado GL (pilha de matrizes, shader, VAO e sistema de batch próprios), e um segundo pipeline de renderização no mesmo contexto não renderiza. Reconciliar os dois exigiria patchar internals do raylib, o que está fora de escopo. Use pygame, pyglet ou arcade como host.