Нужен совет по оптимизации рендеринга Python 2d tileset

Я работал над 2D-изометрическим MORPG на основе плитки в течение нескольких месяцев и понял, что мой игровой экран рендеринга на очень низкой частоте кадров.
Я исследовал и тестировал в течение нескольких недель и могу только получить незначительный прирост частоты кадров. Я использовал cProfile и проверил частоту кадров, и я могу достичь 100+ FPS на программе обычно, но как только моя функция «render()» называется, она падает до 5 FPS.
Вот (несколько) сокращенная версия этой функции:

for y in range(0, 42):
        for x in range(0, 42):
            if (player.mapY + y - 21 > 0) and (player.mapY + y - 21 < 128) and (player.mapX + x - 21 > 0) and (
                                player.mapX + x - 21 < 128):
                if (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) > -64 and (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX)+halfGraphicSizeX < 1024+32 and
                    (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY) > -32 and (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY)+halfGraphicSizeY < 600+32:
                    if self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21)) is not 0:

                        canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY),
                                            image=groundGraphics[
                                                self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21))],
                                            anchor=NW)

                    if (self.getObjectAtYX(player.mapY + (y - 21), player.mapX + (x - 21)) is not 0):
                        canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
                                            # -34 for img height diff between ground & objects
                                            image=objectGraphics[
                                                self.getObjectAtYX(player.mapY + (y - 21), player.mapX + (x - 21))],
                                            anchor=NW)


            ghostCopy = list(gameState.itemsOnGround)
            for i in range(0, len(ghostCopy)):
                if ghostCopy[i].idNum > 0:
                    if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
                        ghostCopy[i].mapY):
                        canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY),
                                            image=itemGraphics[ghostCopy[i].idNum],
                                            anchor=NW)

            ghostCopy = ""
            ghostCopy = list(gameState.monster)
            for i in range(0, len(ghostCopy)):
                if ghostCopy[i].active == True and ghostCopy[i].hp > 0:
                    if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
                        ghostCopy[i].mapY):
                        canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
                                            # -34 for img height diff between ground & objects
                                            image=monsterGraphics[ghostCopy[i].type],
                                            anchor=NW)
                        canvas.create_rectangle(
                            (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 15,
                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 35),
                            (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 16 + 33,
                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 29), fill="black",
                            width=0)
                        canvas.create_rectangle(
                            (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 16,
                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 30),
                            (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX) + 16 + (
                                32 * (ghostCopy[i].hp / ghostCopy[i].maxHp)),
                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34), fill="green",
                            width=0)

            ghostCopy = ""
            ghostCopy = list(gameState.sprite)
            for i in range(0, len(ghostCopy)):
                if ghostCopy[i].graphic[0:1] == "0":

                if ghostCopy[i].active == True and ghostCopy[i].username != "ME":
                    if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
                        ghostCopy[i].mapY):
                        #"graphicToDraw" variable is derived from an animation state but has
                        #been removed from here to make it easier to read
                        canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
                                            # -34 for img height diff between ground & objects
                                            image=graphicToDraw,
                                            anchor=NW)

            if (y == 21):
                if (x == 21):
                    #"graphicToDraw" variable is derived from an animation state but has
                    #been removed from here to make it easier to read
                    canvas.create_image(
                        (startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                        (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
                        # -34 for img height diff between ground & sprites
                        image=graphicToDraw,
                        anchor=NW)

            ghostCopy = ""
            ghostCopy = list(gameState.spells)
            for i in range(0, len(ghostCopy)):
                if ghostCopy[i].active:
                    if (player.mapX - 21 + x == ghostCopy[i].mapX and player.mapY - 21 + y ==
                        ghostCopy[i].mapY):
                        canvas.create_image((startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX),
                                            (startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY - 34),
                                            image=spellGraphics[ghostCopy[i].id],
                                            anchor=NW)

render()функция принадлежит mapобъекту (selfотносится к карте в данном сегменте кода). Она эффектно идет через плитки -21 .. 21 на оси x,y и если плитка находится в пределах границ плитки maps (0 .. 128) и плитка находится в пределах размера экрана (1024×600) он рисует его на экран.

GhostCopy делает снимок текущего gamestateэлемента (например, заклинания), чтобы он не обновлялся потоком, получающим данные сервера в середине итерации.

В некоторых тестах оптимизации я уменьшил диапазон y, x в начале, чтобы минимизировать количество полных итераций цикла.
Я прочитал, что использование атласа текстур/таблицы спрайтов может улучшить скорость рендеринга, но я не мог сделать улучшение с помощью одного.

Я попытался просто вручную нарисовать количество изображений, которые обычно визуализируются в общей сцене в цикле for, и получил около 30+ fps. Таким образом, моя функция рендеринга на 25 кадров в секунду медленнее, чем могла бы быть.

Я предполагаю, что константа проверяет каждую итерацию цикла, чтобы можно было оптимизировать плитку на экране, но я не уверен, как это сделать без использования такого цикла.

Если у кого-то есть какие-то рекомендации, я был бы очень признателен.. я застрял на этой проблеме в течение нескольких недель и не сделал никакого реального прогресса с моей игрой вообще 🙁

** [РЕДАКТИРОВАТЬ] **
Большинство рекомендаций, как представляется, направлены на ограничение количества математических выражений. У меня не было возможности проверить это, но, вероятно, просто ограничение количества математики значительно оптимизирует частоту кадров?

2 ответа

  1. Можно извлечь все выражения, включающие константы и yвычисляемые только внутри внешнего for yцикла (); например, player.mapY + y - 21, y * halfGraphicSizeX, y * halfGraphicSizeY, и т.д.: компьютер каждый только один раз, вложить переменную, и использовать во всем коде. Аналогично Дляx, но не совсем так эффективно.

  2. Вот обновление первых 19 строк кода, которое должно улучшить производительность. В этом примере все, что я сделал, это уменьшил общее количество раз, когда вы формируете математические операции.

    for y in range(0, 42):
        for x in range(0, 42):
            player_y = player.mapY + y - 21
            player_x = player.mapX + x -21
    
            if  player_y > 0 and player_y < 128 and player_x > 0 and player_x < 128:
                start_drawing_x_half_graphic_size = startDrawingPosX + x * halfGraphicSizeX - y * halfGraphicSizeX
                start_drawing_y_half_graphic_size = startDrawingPosY + x * halfGraphicSizeY + y * halfGraphicSizeY
    
                if start_drawing_x_half_graphic_size > -64 and start_drawing_x_half_graphic_size + halfGraphicSizeX < 1024+32 and\
                    start_drawing_y_half_graphic_size > -32 and start_drawing_y_half_graphic_size + halfGraphicSizeY < 600+32:
    
                    if self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21)) is not 0:
    
                        canvas.create_image(start_drawing_x_half_graphic_size,
                                            start_drawing_y_half_graphic_size,
                                            image=groundGraphics[
                                                self.getGroundAtYX(player.mapY + (y - 21), player.mapX + (x - 21))],
                                            anchor=NW)