简体   繁体   English

转换为 PySDL2 会使我的应用程序运行得比在 PyGame 下运行得更快吗?

[英]Will converting to PySDL2 make my app run faster than it does under PyGame?

I've written a little toy in Python using Pygame. It generates critters (a circle with a directional line, not an image) to wander around the screen.我在 Python 中使用 Pygame 编写了一个小玩具。它会生成小动物(带有方向线的圆圈,而不是图像)在屏幕上四处游荡。 I'm interested in making it more sophisticated, but I'm running into serious performance problems.我有兴趣让它更复杂,但我遇到了严重的性能问题。 As the number of critters on the screen passes 20, the frame rate drops rapidly from 60fps as far as 11fps with 50 on the screen.当屏幕上的小动物数量超过 20 时,帧速率从 60fps 迅速下降到屏幕上有 50 个时的 11fps。 I've gone over my (very simple) code a number of different ways, even profiling with cProfile, without finding any way to optimize.我已经通过多种不同的方式检查了我的(非常简单的)代码,甚至使用 cProfile 进行了分析,但没有找到任何优化方法。

To make a long story somewhat less long, I think I've concluded PyGame just isn't cut out for what I'm asking it to do.长话短说,我想我已经得出结论,PyGame 并不适合我要求它做的事情。 Consequently, I'm looking to convert to something else.因此,我希望转换为其他东西。 C++ is the obvious answer, but as this is just a toy I'd rather code in Python, if possible. C++ 是显而易见的答案,但由于这只是一个玩具,如果可能的话,我宁愿在 Python 中编码。 Especially because it's already written.特别是因为它已经写好了。

In looking at C++, I discovered that there's an SDL (wrapper? bindings? Not sure the term) for Python: PySDL2.在查看 C++ 时,我发现有一个用于 Python 的 SDL(包装器?绑定?不确定该术语):PySDL2。

Thanks for sticking with me.谢谢你和我在一起。 Now the payoff: is there any reason to believe that converting my app to use PySDL2 will make it faster?现在回报:是否有任何理由相信将我的应用程序转换为使用 PySDL2 会使其更快? Especially considering PyGame apparently uses SDL under the hood (somehow).特别是考虑到 PyGame 显然在幕后使用 SDL(以某种方式)。

EDIT: As requested:编辑:根据要求:

import pygame
from pygame import gfxdraw
import pygame.locals
import os
import math
import random
import time

(INSERT CONTENTS OF VECTOR.PY FROM https://gist.github.com/mcleonard/5351452 HERE)


pygame.init()

#some global constants
BLUE = (0, 0, 255)
WHITE = (255,255,255)
diagnostic = False
SPAWN_TIME = 1 #number of seconds between creating new critters
FLOCK_LIMIT = 30 #number of critters at which the flock begins being culled
GUIDs = [0] #list of guaranteed unique IDs for identifying each critter

# Set the position of the OS window
position = (30, 30)
os.environ['SDL_VIDEO_WINDOW_POS'] = str(position[0]) + "," + str(position[1])

# Set the position, width and height of the screen [width, height]
size_x = 1000
size_y = 500
size = (size_x, size_y)
FRAMERATE = 60
SECS_FOR_DYING = 1
screen = pygame.display.set_mode(size)
screen.set_alpha(None)
pygame.display.set_caption("My Game")

# Used to manage how fast the screen updates
clock = pygame.time.Clock()


def random_float(lower, upper):
    num = random.randint(lower*1000, upper*1000)
    return num/1000


def new_GUID():
    num = GUIDs[-1]
    num = num + 1
    while num in GUIDs:
        num += 1
    GUIDs.append(num)
    return num


class HeatBlock:
    def __init__(self,_tlx,_tly,h,w):
        self.tlx = int(_tlx)
        self.tly = int(_tly)
        self.height = int(h)+1
        self.width = int(w)
        self.heat = 255.0
        self.registered = False

    def register_tresspasser(self):
        self.registered = True
        self.heat = max(self.heat - 1, 0)

    def cool_down(self):
        if not self.registered:
            self.heat = min(self.heat + 0.1, 255)
        self.registered = False

    def hb_draw_self(self):
        screen.fill((255,int(self.heat),int(self.heat)), [self.tlx, self.tly, self.width, self.height])


class HeatMap:
    def __init__(self, _h, _v):
        self.h_freq = _h #horizontal frequency
        self.h_rez = size_x/self.h_freq #horizontal resolution
        self.v_freq = _v #vertical frequency
        self.v_rez = size_y/self.v_freq #vertical resolution
        self.blocks = [] 

    def make_map(self):
        h_size = size_x/self.h_freq
        v_size = size_y/self.v_freq
        for h_count in range(0, self.h_freq):
            TLx = h_count * h_size #TopLeft corner, x
            col = []
            for v_count in range(0, self.v_freq):
                TLy = v_count * v_size #TopLeft corner, y
                col.append(HeatBlock(TLx,TLy,v_size,h_size))
            self.blocks.append(col)

    def hm_draw_self(self):
        for col in self.blocks:
            for block in col:
                block.cool_down()
                block.hb_draw_self()

    def register(self, x, y):
        #convert the given coordinates of the trespasser into a col/row block index
        col = max(int(math.floor(x / self.h_rez)),0)
        row = max(int(math.floor(y / self.v_rez)),0)
        self.blocks[col][row].register_tresspasser()


class Critter:
    def __init__(self):
        self.color = (random.randint(1, 200), random.randint(1, 200), random.randint(1, 200))
        self.linear_speed = random_float(20, 100)
        self.radius = int(round(10 * (100/self.linear_speed)))
        self.angular_speed = random_float(0.1, 2)
        self.x = int(random.randint(self.radius*2, size_x - (self.radius*2)))
        self.y = int(random.randint(self.radius*2, size_y - (self.radius*2)))
        self.orientation = Vector(0, 1).rotate(random.randint(-180, 180))
        self.sensor = Vector(0, 20)
        self.sensor_length = 20
        self.new_orientation = self.orientation
        self.draw_bounds = False
        self.GUID = new_GUID()
        self.condition = 0 #0 = alive, [1-fps] = dying, >fps = dead
        self.delete_me = False

    def c_draw_self(self):
        #if we're alive and not dying, draw our normal self
        if self.condition == 0:
            #diagnostic
            if self.draw_bounds:
                pygame.gfxdraw.rectangle(screen, [int(self.x), int(self.y), 1, 1], BLUE)
                temp = self.orientation * (self.linear_speed * 20)
                pygame.gfxdraw.line(screen, int(self.x), int(self.y), int(self.x + temp[0]), int(self.y + temp[1]), BLUE)
            #if there's a new orientation, match it gradually
            temp = self.new_orientation * self.linear_speed
            #draw my body
            pygame.gfxdraw.aacircle(screen, int(self.x), int(self.y), self.radius, self.color)
            #draw a line indicating my new direction
            pygame.gfxdraw.line(screen, int(self.x), int(self.y), int(self.x + temp[0]), int(self.y + temp[1]), BLUE)
            #draw my sensor (a line pointing forward)
            self.sensor = self.orientation.normalize() * self.sensor_length
            pygame.gfxdraw.line(screen, int(self.x), int(self.y), int(self.x + self.sensor[0]), int(self.y + self.sensor[1]), BLUE)
        #otherwise we're dying, draw our dying animation
        elif 1 <= self.condition <= FRAMERATE*SECS_FOR_DYING:
            #draw some lines in a spinningi circle
            for num in range(0,10):
                line = Vector(0, 1).rotate((num*(360/10))+(self.condition*23))
                line = line*self.radius
                pygame.gfxdraw.line(screen, int(self.x), int(self.y), int(self.x+line[0]), int(self.y+line[1]), self.color)

    def print_self(self):
        #diagnostic
        print("==============")
        print("radius:", self.radius)
        print("color:", self.color)
        print("linear_speed:", self.linear_speed)
        print("angular_speed:", self.angular_speed)
        print("x:", self.x)
        print("y:", int(self.y))
        print("orientation:", self.orientation)

    def avoid_others(self, _flock):
        for _critter in _flock:
            #if the critter isn't ME...
            if _critter.GUID is not self.GUID and _critter.condition == 0:
                #and it's touching me...
                if self.x - _critter.x <= self.radius + _critter.radius:
                    me = Vector(self.x, int(self.y))
                    other_guy = Vector(_critter.x, _critter.y)
                    distance = me - other_guy

                    #give me new orientation that's away from the other guy
                    if distance.norm() <= ((self.radius) + (_critter.radius)):
                        new_direction = me - other_guy
                        self.orientation = self.new_orientation = new_direction.normalize()

    def update_location(self, elapsed):
        boundary = '?'
        while boundary != 'X':
            boundary = self.out_of_bounds()
            if boundary == 'N':
                self.orientation = self.new_orientation = Vector(0, 1).rotate(random.randint(-20, 20))
                self.y = (self.radius) + 2
            elif boundary == 'S':
                self.orientation = self.new_orientation = Vector(0,-1).rotate(random.randint(-20, 20))
                self.y = (size_y - (self.radius)) - 2
            elif boundary == 'E':
                self.orientation = self.new_orientation = Vector(-1,0).rotate(random.randint(-20, 20))
                self.x = (size_x - (self.radius)) - 2
            elif boundary == 'W':
                self.orientation = self.new_orientation = Vector(1,0).rotate(random.randint(-20, 20))
                self.x = (self.radius) + 2
            point = Vector(self.x, self.y)
            self.x, self.y = (point + (self.orientation * (self.linear_speed*(elapsed/1000))))
            boundary = self.out_of_bounds()

    def update_orientation(self, elapsed):
        #randomly choose a new direction, from time to time
        if random.randint(0, 100) > 98:
            self.choose_new_orientation()
        difference = self.orientation.argument() - self.new_orientation.argument()
        self.orientation = self.orientation.rotate((difference * (self.angular_speed*(elapsed/1000))))

    def still_alive(self, elapsed):
        return_value = True #I am still alive
        if self.condition == 0:
            return_value = True
        elif self.condition <= FRAMERATE*SECS_FOR_DYING:
            self.condition = self.condition + (elapsed/17)
            return_value = True
        if self.condition > FRAMERATE*SECS_FOR_DYING:
            return_value = False

        return return_value

    def choose_new_orientation(self):
        if self.new_orientation:
            if (self.orientation.argument() - self.new_orientation.argument()) < 5:
                rotation = random.randint(-300, 300)
                self.new_orientation = self.orientation.rotate(rotation)

    def out_of_bounds(self):
        if self.x >= (size_x - (self.radius)):
            return 'E'
        elif self.y >= (size_y - (self.radius)):
            return 'S'
        elif self.x <= (0 + (self.radius)):
            return 'W'
        elif self.y <= (0 + (self.radius)):
            return 'N'
        else:
            return 'X'


# -------- Main Program Loop -----------
# generate critters
flock = [Critter()]
heatMap = HeatMap(60, 40)
heatMap.make_map()
last_spawn = time.clock()
run_time = time.perf_counter()
frame_count = 0
max_time = 0
ms_elapsed = 1
avg_fps = [1]
# Loop until the user clicks the close button.
done = False
while not done:
    # --- Main event loop only processes one event
    frame_count = frame_count + 1
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    # --- Game logic should go here
    #check if it's time to make another critter
    if time.clock() - last_spawn > SPAWN_TIME:
        flock.append(Critter())
        last_spawn = time.clock()
    if len(flock) >= FLOCK_LIMIT:
        #if we're over the flock limit, cull the herd
        counter = FLOCK_LIMIT
        for critter in flock[0:len(flock)-FLOCK_LIMIT]:
            #this code allows a critter to be "dying" for a while, to play an animation
            if critter.condition == 0:
                critter.condition = 1
            elif not critter.still_alive(ms_elapsed):
                critter.delete_me = True
    counter = 0

    #delete all the critters that have finished dying
    while counter < len(flock):
        if flock[counter].delete_me:
            del flock[counter]
        else:
            counter = counter+1

    #----loop on all critters once, doing all functions for each critter
    for critter in flock:
        if critter.condition == 0:
            critter.avoid_others(flock)
            if critter.condition == 0:
                heatMap.register(critter.x, critter.y)
            critter.update_location(ms_elapsed)
            critter.update_orientation(ms_elapsed)
            if diagnostic:
                critter.print_self()

    #----alternately, loop for each function. Speed seems to be similar either way
    #for critter in flock:
    #    if critter.condition == 0:
    #        critter.update_location(ms_elapsed)
    #for critter in flock:
    #    if critter.condition == 0:
    #        critter.update_orientation(ms_elapsed)

    # --- Screen-clearing code goes here

    # Here, we clear the screen to white. Don't put other drawing commands
    screen.fill(WHITE)

    # --- Drawing code should go here
    #draw the heat_map
    heatMap.hm_draw_self()
    for critter in flock:
        critter.c_draw_self()

    #draw the framerate
    myfont = pygame.font.SysFont("monospace", 15)
    #average the framerate over 60 frames
    temp = sum(avg_fps)/float(len(avg_fps)) 
    text = str(round(((1/temp)*1000),0))+"FPS | "+str(len(flock))+" Critters"
    label = myfont.render(text, 1, (0, 0, 0))
    screen.blit(label, (5, 5))

    # --- Go ahead and update the screen with what we've drawn.
    pygame.display.update()

    # --- Limit to 60 frames per second
    #only run for 30 seconds
    if time.perf_counter()-run_time >= 30:
        done = True
    #limit to 60fps
    #add this frame's time to the list
    avg_fps.append(ms_elapsed)
    #remove any old frames
    while len(avg_fps) > 60:
        del avg_fps[0]
    ms_elapsed = clock.tick(FRAMERATE)
    #track longest frame
    if ms_elapsed > max_time:
        max_time = ms_elapsed

#print some stats once the program is finished
print("Count:", frame_count)
print("Max time since last flip:", str(max_time)+"ms")
print("Total Time:", str(int(time.perf_counter()-run_time))+"s")
print("Average time for a flip:", str(int(((time.perf_counter()-run_time)/frame_count)*1000))+"ms")
# Close the window and quit.
pygame.quit()

Do not create the font object in each frame.不要在每一帧中创建字体 object。 Creating a font object is a very expensive operation because the font must be read from the resource and decoded.创建字体 object 是一项非常昂贵的操作,因为必须从资源中读取字体并进行解码。 Create the font before the application loop, but use it in the application loop:在应用程序循环之前创建字体,但在应用程序循环中使用它:

myfont = pygame.font.SysFont("monospace", 15)                 # <-- INSERT

done = False
while not done:
    # [...]

    # myfont = pygame.font.SysFont("monospace", 15)             <-- DELETE

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM