我在 PyGame 中进行的模拟性能极差

Extremely poor performance in my simulation made in PyGame

我在学校的一个项目中遵循了 PyGame 基于图块的教程,但我从未打算制作游戏,而是模拟生态系统。不幸的是,当我 运行 我的程序性能非常糟糕,它只能 运行 几秒钟,然后 windows 停止回答。

我现在唯一想做的就是在一片草地的能量达到80时放置一片新的草地。

有什么好玩的?一切都在更新方法中是不是很糟糕?我可以使用事件或其他方式来延长检查时间吗?我知道每一帧都有很多数学运算,但不知道如何用其他方式来计算。

这是我的代码:

main.py:

#!/usr/bin/python3

#Importing necessary libraries
import pygame as pg, sys, random
from settings import *
from sprites import *

class Sim:
    #Initialize the game window, etc.
    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()

        self.running = True

    def new_grass(self, pos):
        for g in self.grass:
            if pos != g.pos:
                Grass(self, pos)

    #Start a new generation
    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.grass = pg.sprite.Group()
        Grass(self, (10, 15))
        self.run()

    #Main loop
    def run(self):
        self.simulating = True
        while self.simulating:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()

    #Update things on screen
    def update(self):
        self.all_sprites.update()

    #Draw a grid on screen
    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, BLACK, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, BLACK, (0, y), (WIDTH, y))

    #Draw things on screen
    def draw(self):
        pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
        self.screen.fill(DARK_GREEN)
        self.draw_grid()
        self.all_sprites.draw(self.screen)
        #After drawing everything, flip the display
        pg.display.flip()

    #Events that might happen
    def events(self):
        for event in pg.event.get():
            #Check for the user closing the window
            if event.type == pg.QUIT:
                if self.simulating:
                    self.simulating = False
                self.running = False

s = Sim()
while s.running:
    s.new()
pg.quit()

sprites.py:

#!/usr/bin/python3

import pygame as pg, random
from settings import *
vec = pg.math.Vector2

class Grass(pg.sprite.Sprite):
    def __init__(self, sim, cord):
        self.groups = sim.all_sprites, sim.grass
        pg.sprite.Sprite.__init__(self, self.groups)
        self.sim = sim
        self.image = pg.Surface((TILESIZE/2, TILESIZE/2))
        self.image.fill(GREEN)
        self.cord = cord
        self.rect = self.image.get_rect()
        self.pos = vec(cord) * TILESIZE / 2
        self.rect.topleft = self.pos
        self.spread = vec(random.randint(-1, 1), random.randint(-1, 1))
        self.energy = 20

    def update(self):
        if self.energy <= 80:
            self.energy += 10

        if self.energy >= 80:
            self.sim.new_grass((self.cord + self.spread))

settings.py:

#Options/settings
TITLE = "EcoSim"
WIDTH = 480
HEIGHT = 600
FPS = 30
TILESIZE = 32
GRID_WIDTH = WIDTH / TILESIZE
GRID_HEIGHT = HEIGHT / TILESIZE

#Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 100, 0)
BROWN = (150,75,0)

主要问题是方法 Sim.new_grass:

class Sim:
   # [...]

   def new_grass(self, pos):
       for g in self.grass:
           if pos != g.pos:
               Grass(self, pos)

该方法生成的 Grass 个对象比预期的多。它事件在同一位置生成多个草对象。对于 pos != g.pos 的每个对象 (g),都会构造一个 Grass 的新实例。

如果所需位置没有 any Grass 对象,您需要创建一个 Grass 的新实例:

class Sim:
   # [...]

   def new_grass(self, pos):
        if not any(pos == g.pos for g in self.grass):
            Grass(self, pos)

问题出在这里:

def new_grass(self, pos):
    for g in self.grass:
        if pos != g.pos:
            Grass(self, pos)

这是因为每次在另一个位置上已经存在草时,您都会添加一个 Grass 对象。我想你是想在根本不存在该职位的情况下添加一个。

你的程序有一些错误,主要是上面提到的那个,而且参数pos实际上应该是coord。我已经突出显示了您的代码并提供了一些改进意见:

#!/usr/bin/python3

import pygame as pg, random

TITLE = "EcoSim"
WIDTH = 480
HEIGHT = 600
FPS = 30
TILESIZE = 32
GRID_WIDTH = WIDTH / TILESIZE
GRID_HEIGHT = HEIGHT / TILESIZE

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 100, 0)
BROWN = (150,75,0)

vec = pg.math.Vector2

class Grass(pg.sprite.Sprite):
    # This creates the image just once!
    IMAGE = pg.Surface((TILESIZE/2, TILESIZE/2))
    IMAGE.fill(GREEN)

    def __init__(self, cord):  # Remove 'sim'.
        # self.groups = sim.all_sprites, sim.grass
        # pg.sprite.Sprite.__init__(self, self.groups)
        # self.sim = sim
        super().__init__()
        self.image = Grass.IMAGE  # All reference the same image.
        self.cord = cord
        self.rect = self.image.get_rect()
        self.pos = vec(cord) * TILESIZE / 2
        self.rect.topleft = self.pos
        self.energy = 20
        self.spread = vec(random.randint(-1, 1), random.randint(-1, 1))

    def update(self):
        if self.energy <= 80:
            self.energy += 10

        # Make Sim check for this.
        # if self.energy >= 80:
        #     self.sim.new_grass((self.cord + self.spread))


class Sim:
    def __init__(self):
        pg.init()
        pg.display.set_caption(TITLE)
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        self.clock = pg.time.Clock()
        self.all_sprites = pg.sprite.Group()  # Create *ALL* attributes in `__init__`
        self.grass = pg.sprite.Group()
        self.running = True
        self.simulating = False

    # You're passing coord here, not pos! And you also want to add
    # the grass only if the coord is not already present in the list.
    def new_grass(self, coord):
        if coord not in (x.cord for x in self.grass):
            grass = Grass(coord)
            self.grass.add(grass)
            self.all_sprites.add(grass)

    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.grass = pg.sprite.Group()
        grass = Grass((10, 15))  # Grass is now pure and doesn't have any side-effects, which makes the code much clearer.
        self.grass.add(grass)
        self.all_sprites.add(grass)
        self.run()

    def run(self):
        self.simulating = True
        while self.simulating:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()

    def update(self):
        self.all_sprites.update()
        # Let Sim decide the fate of the grass. Don't let Grass add
        # itself.
        for grass in self.grass:
            if grass.energy >= 80:
                self.new_grass((grass.cord + grass.spread))

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, BLACK, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, BLACK, (0, y), (WIDTH, y))

    def draw(self):
        pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
        self.screen.fill(DARK_GREEN)
        self.draw_grid()
        self.all_sprites.draw(self.screen)
        pg.display.flip()

    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.simulating = False  # There was an unnecessary if here.
                self.running = False

s = Sim()
while s.running:
    s.new()
pg.quit()