Я работал над 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 кадров в секунду медленнее, чем могла бы быть.
Я предполагаю, что константа проверяет каждую итерацию цикла, чтобы можно было оптимизировать плитку на экране, но я не уверен, как это сделать без использования такого цикла.
Если у кого-то есть какие-то рекомендации, я был бы очень признателен.. я застрял на этой проблеме в течение нескольких недель и не сделал никакого реального прогресса с моей игрой вообще 🙁
** [РЕДАКТИРОВАТЬ] **
Большинство рекомендаций, как представляется, направлены на ограничение количества математических выражений. У меня не было возможности проверить это, но, вероятно, просто ограничение количества математики значительно оптимизирует частоту кадров?
Можно извлечь все выражения, включающие константы и
y
вычисляемые только внутри внешнегоfor y
цикла (); например,player.mapY + y - 21
,y * halfGraphicSizeX
,y * halfGraphicSizeY
, и т.д.: компьютер каждый только один раз, вложить переменную, и использовать во всем коде. Аналогично Дляx
, но не совсем так эффективно.Вот обновление первых 19 строк кода, которое должно улучшить производительность. В этом примере все, что я сделал, это уменьшил общее количество раз, когда вы формируете математические операции.