python - 优化对象显示(gamedev)

标签 python pygame

相关代码,简化:

class Character:
    def __init__(self):
        self.projectiles = []

    def shoot(self):
        self.projectiles.append(Projectile())

    def draw(self):
        for projectile in self.projectiles:
            pygame.draw.rect()  # args do not seem relevant here

draw() 每当有射弹产生时,游戏就会变得非常慢。

我的问题: 我可以加快列表迭代过程吗? 我可以实现比列表更好的方法吗? 我可以在哪里放置一些多线程?

编辑:

shoot() 每帧最多调用一次(在按键绑定(bind)时触发),并且 projectiles 列表迭代两次(更新和绘制步骤)。

游戏在使用单个射弹时会变得非常滞后,并且当射弹达到一定距离( map 限制)并从列表中删除时,游戏会再次变得流畅。

最佳答案

Can I speed up the list iteration process?

不,但也许您对列表的迭代次数超出了必要的次数。当您调用draw()时,projectiles有多大?

Can I implement a better way than a list?

使用 pygame 时,您应该考虑使用 SpriteGroup 类。使用而不是列表。这提供了一些方便的功能,例如在不同层上绘制 Sprite 、抽象更新和绘制以及使用 kill() 轻松删除 Sprite 等。

Where could I put some multi-threading in this?

我 99.9% 确定您实际上并不需要多线程。


您的问题是您的 Projectile 类的更新方法:

def update(self, camera):
    self.posW += self.speed * self.dirW * Clock().tick(FPS)
    self.posH += self.speed * self.dirH * Clock().tick(FPS)
    self.rect = (self.posW, self.posH, 20, 20)

在这里,您创建一个新的 Clock 实例,并为每个 Projectile 实例每帧调用两次 tick ,这将使您的游戏每帧暂停多次框架。

您应该做的是创建一个Clock实例一次,并且每帧仅调用一次tick,并将结果值传递给您的 Sprite 以调整它们的速度.


我更改了您的代码以利用游戏的 Sprite 类并删除了很多文件:

相机.py:

import pygame

class CameraAwareLayeredUpdates(pygame.sprite.LayeredUpdates):
    def __init__(self, target, world_size, screen_size):
        super().__init__()
        self.target = target
        self.cam = pygame.Vector2(0, 0)
        self.world_size = world_size
        self.screen_size = screen_size
        if self.target:
            self.add(target)

    def update(self, *args):
        super().update(*args)

        if self.target:
            x = -self.target.rect.center[0] + self.screen_size[0]/2
            y = -self.target.rect.center[1] + self.screen_size[1]/2
            self.cam += (pygame.Vector2((x, y)) - self.cam) * 0.07
            self.cam.x = max(-(self.world_size[0]-self.screen_size[0]), min(0, self.cam.x))
            self.cam.y = max(-(self.world_size[1]-self.screen_size[1]), min(0, self.cam.y))

    def draw(self, surface):
        spritedict = self.spritedict
        surface_blit = surface.blit
        dirty = self.lostsprites
        self.lostsprites = []
        dirty_append = dirty.append
        init_rect = self._init_rect
        for spr in self.sprites():
            rec = spritedict[spr]
            newrect = surface_blit(spr.image, spr.rect.move(self.cam))
            if rec is init_rect:
                dirty_append(newrect)
            else:
                if newrect.colliderect(rec):
                    dirty_append(newrect.union(rec))
                else:
                    dirty_append(newrect)
                    dirty_append(rec)
            spritedict[spr] = newrect
        return dirty            

游戏.py:

import pygame

from lib.camera import CameraAwareLayeredUpdates

TILESIZE = 50
WORLD_SIZE_IN_TILES = 30, 16
WORLD_SIZE = [v*TILESIZE for v in WORLD_SIZE_IN_TILES]
SCREEN_SIZE = 800, 500

class Actor(pygame.sprite.Sprite):
    def __init__(self, game, *args, **kwargs):
        super().__init__(*args)
        self.game = game

        self.image = pygame.Surface(kwargs.get('size', (50, 50)))
        self.image.fill(pygame.Color(kwargs.get('color', 'blue')))
        self.rect = self.image.get_rect()
        self.pos = pygame.Vector2(kwargs.get('pos', self.rect.center))
        self.rect.center = [int(x) for x in self.pos]
        self.vel = pygame.Vector2(kwargs.get('vel', (0, 0)))

    def update(self, dt, events):
        self.pos += (self.vel * dt/10)
        self.rect.center = [int(x) for x in self.pos]

class Character(Actor):
    def __init__(self, game, *args, **kwargs):
        super().__init__(game, *args, **kwargs)
        self.facing = pygame.Vector2(0, 1)

    def update(self, dt, events):
        for event in events:
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    self.game.add(Actor, 
                            pos=self.rect.center, 
                            vel=self.facing * 10,
                            color='blue',
                            size=(20, 20))

        pressed = pygame.key.get_pressed()

        self.vel.x, self.vel.y = 0, 0
        if pressed[pygame.K_a]: self.vel.x = -1
        if pressed[pygame.K_d]: self.vel.x = 1
        if pressed[pygame.K_w]: self.vel.y = -1
        if pressed[pygame.K_s]: self.vel.y = 1
        if self.vel.length() > 0:
            self.facing = pygame.Vector2(self.vel)
            self.facing.normalize_ip()
        self.vel *= 5

        super().update(dt, events)

class Tilemap(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self._layer = -100
        self.tilesH = WORLD_SIZE_IN_TILES[1]
        self.tilesW = WORLD_SIZE_IN_TILES[0]
        self.map = [
            'white' for i in range(self.tilesH * self.tilesW)
        ]

        for i in range(self.tilesH):
            for j in range(self.tilesW):
                if i * j == 0 or i == self.tilesH - 1 or j == self.tilesW - 1:
                    self.map[i * j] = 'grey'

        self.image = self.render()
        self.rect = self.image.get_rect()

    def update(self, *args):
        pass

    def render(self):
        surf = pygame.Surface(WORLD_SIZE)
        for i in range(self.tilesH):
            for j in range(self.tilesW):
                rect = (j * TILESIZE, i * TILESIZE, TILESIZE, TILESIZE)
                pygame.draw.rect(surf, pygame.Color(self.map[i * j]), rect)
        return surf

class Game:
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("SANTARENA")
        self.screen = pygame.display.set_mode(SCREEN_SIZE)
        self.clock = pygame.time.Clock()
        player = Character(self, color='red', pos=self.screen.get_rect().center)
        self.all_sprites = CameraAwareLayeredUpdates(player, WORLD_SIZE, SCREEN_SIZE)
        self.all_sprites.add(Tilemap())

    def add(self, clazz, **kwargs):
        instance = clazz(self, self.all_sprites, **kwargs)
        return instance

    def start(self):
        dt = 0
        while True:
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.QUIT:
                    return

            self.all_sprites.update(dt, events)
            self.all_sprites.draw(self.screen)
            pygame.display.update()
            dt = self.clock.tick(60)

enter image description here

关于python - 优化对象显示(gamedev),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58856807/

相关文章:

python - Flask返回响应但继续处理?

python - 将带有日期和时区的字符串解析为 UTC 日期时间

python - Python3中的python-xlib、python3-xlib、pyxlib和xlib有什么区别?

javascript - 通过 JS 或 HTML 调用 Python 脚本

python - 我正在流血图像,但它在一毫秒左右后消失

python - 如何在 Pygame 中退出全屏模式?

python - 如何将滚动条附加到文本小部件?

python - 将不同的调色板索引分配给调色板图像

python - 在 pygame 中创建单独的随机平台

python - 如何在保持程序的其余部分以正常速度运行的同时定期更改变量?