pygame-ce icon indicating copy to clipboard operation
pygame-ce copied to clipboard

sprites now have limited support for Texture and Renderer

Open oddbookworm opened this issue 1 year ago • 5 comments
trafficstars

@rethanon was playing around with _sdl2.video and tried combining sprites and gpu rendering. Things went wrong, this is my attempt to add some basic intercompat between the two because it's already pretty compatible (at least at a minimal functioning level)

oddbookworm avatar Aug 05 '24 03:08 oddbookworm

Sample code (provided by rethanon on discord)

import random
import time

import pygame
import pygame._sdl2 as sdl2


pygame.init()

WINDOW: pygame.Window = pygame.Window("Sprite Test", (800, 600))
RENDERER: sdl2.Renderer = sdl2.Renderer(WINDOW)
RENDERER.draw_color = "midnightblue"
running: bool = True
frame_start: float = time.perf_counter()
blocks = pygame.sprite.Group()


class Block(pygame.sprite.Sprite):
    def __init__(self) -> None:
        pygame.sprite.Sprite.__init__(self)
        self.image: sdl2.Texture = sdl2.Texture.from_surface(
            RENDERER, pygame.Surface((60, 60))
        )
        self.rect: pygame.FRect = pygame.FRect(
            self.image.get_rect(
                topleft=(
                    random.randint(0, 800 - self.image.width),
                    random.randint(0, 600 - self.image.height),
                )
            )
        )
        self.movement_speed_x: int = random.choice([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])
        self.movement_speed_y: int = random.choice([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])

    def update(self, delta_time: float) -> None:
        self.rect.x += self.movement_speed_x * 60 * delta_time
        self.rect.y += self.movement_speed_y * 60 * delta_time
        self.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
        if (self.rect.left == 0 and self.movement_speed_x < 0) or (
            self.rect.right == 800 and self.movement_speed_x > 0
        ):
            self.movement_speed_x *= -1
        if (self.rect.top == 0 and self.movement_speed_y < 0) or (
            self.rect.bottom == 600 and self.movement_speed_y > 0
        ):
            self.movement_speed_y *= -1


for _ in range(20):
    Block().add(blocks)

while running:
    frame_end: float = time.perf_counter()
    delta_time: float = frame_end - frame_start
    frame_start = time.perf_counter()

    for event in pygame.event.get():
        if event.type == pygame.WINDOWCLOSE:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False

    RENDERER.clear()
    blocks.update(delta_time)
    blocks.draw(RENDERER)

    RENDERER.present()

oddbookworm avatar Aug 05 '24 03:08 oddbookworm

Just a minor nitpick but I'd like whitespace/formatting changes to be a separate PR, preferably with some way to keep it automated with changes to our precommit config

Same lol. My editor is configured to remove trailing whitespace on file save though, so I'd have to either change my editor config just for pygame-ce, or edit files in a different editor when I want to make a pull request. Alternatively, someone can put together a whitespace-fix PR pretty quickly and then those won't show as diffs anymore

oddbookworm avatar Aug 16 '24 00:08 oddbookworm

Also, I have absolutely no idea why this pull is causing segfaults

oddbookworm avatar Aug 16 '24 00:08 oddbookworm

import random
import sys
import time

import pygame
import pygame._sdl2 as sdl2

if "--cpu" in sys.argv:
    USE_GPU = False
else:
    USE_GPU = True
pygame.init()

WINDOW: pygame.Window = pygame.Window("Sprite Test", (800, 600))

if USE_GPU:
    RENDERER: sdl2.Renderer = sdl2.Renderer(WINDOW)
    RENDERER.draw_color = "midnightblue"
else:
    screen = WINDOW.get_surface()
running: bool = True
frame_start: float = time.perf_counter()
blocks = pygame.sprite.Group()

alien = pygame.image.load("examples/data/alien1.png")


class Block(pygame.sprite.Sprite):
    def __init__(self) -> None:
        pygame.sprite.Sprite.__init__(self)
        if USE_GPU:
            self.image = sdl2.Texture.from_surface(RENDERER, alien)
        else:
            self.image = alien.copy()
        self.rect: pygame.FRect = pygame.FRect(
            self.image.get_rect(
                topleft=(
                    random.randint(0, 800 - self.image.width),
                    random.randint(0, 600 - self.image.height),
                )
            )
        )
        self.movement_speed_x: int = random.choice([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])
        self.movement_speed_y: int = random.choice([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])

    def update(self, delta_time: float) -> None:
        self.rect.x += self.movement_speed_x * 60 * delta_time
        self.rect.y += self.movement_speed_y * 60 * delta_time
        self.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))
        if (self.rect.left == 0 and self.movement_speed_x < 0) or (
            self.rect.right == 800 and self.movement_speed_x > 0
        ):
            self.movement_speed_x *= -1
        if (self.rect.top == 0 and self.movement_speed_y < 0) or (
            self.rect.bottom == 600 and self.movement_speed_y > 0
        ):
            self.movement_speed_y *= -1


for _ in range(20):
    Block().add(blocks)

while running:
    frame_end: float = time.perf_counter()
    delta_time: float = frame_end - frame_start
    frame_start = time.perf_counter()

    WINDOW.title = f"fps = {1 / delta_time}"

    for event in pygame.event.get():
        if event.type == pygame.WINDOWCLOSE:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False

    if USE_GPU:
        RENDERER.clear()
    else:
        screen.fill("midnightblue")

    blocks.update(delta_time)

    if USE_GPU:
        blocks.draw(RENDERER)
        RENDERER.present()
    else:
        blocks.draw(screen)
        WINDOW.flip()

Updated test script that lets you run both cpu and gpu

oddbookworm avatar Jun 26 '25 00:06 oddbookworm

Note that the stub changes to _SupportsSprite and Sprite will make sprite typing harder.

First, typing users will have to deal with an extra possible type when accessing image from Sprite. Second, the mutable image attribute type was broadened, which may be incompatible for code that is checked for mutable override in subclasses (a check usually disabled by default, though).

aatle avatar Jun 29 '25 23:06 aatle