Попробовал самую очевидную оптимизацию рендера, теперь отрисовываются только те клетки, которые в камере. Карта корраптится при движении камеры, надо исправить, зато масштаб работает и прирост производительности очень хороший, теперь до 200 объектов обрабатывается при стабильных 60 ФПС. Промежуточный коммит, хочу посмотреть сколько можно ещё выжать кадров на 500 объектах, потом нужно починить движение камеры. Также написал простую функцию спавна эльфов в главном цикле для создания нагрузки. Эльфы создаются в углах карты и по центру каждые сто тактов цикла. Они начинают перемещаться по карте в случайные точки, создавая относительно равномерную нагрузку поиска пути.

This commit is contained in:
shiva404
2026-03-05 22:00:32 +03:00
parent 45f2c71cb8
commit fa189a4c3b
4 changed files with 123 additions and 50 deletions

View File

@@ -26,7 +26,7 @@
"terrain_obj": { "terrain_obj": {
"id": "1", "id": "1",
"name": "2", "name": "2",
"sprite_name": "rock_small" "sprite_name": "grass_small"
}, },
"item_obj": {}, "item_obj": {},
"creature_obj": {} "creature_obj": {}
@@ -928,7 +928,7 @@
"terrain_obj": { "terrain_obj": {
"id": "1", "id": "1",
"name": "2", "name": "2",
"sprite_name": "rock_small" "sprite_name": "grass_small"
}, },
"item_obj": {}, "item_obj": {},
"creature_obj": {} "creature_obj": {}
@@ -946,7 +946,7 @@
"terrain_obj": { "terrain_obj": {
"id": "1", "id": "1",
"name": "2", "name": "2",
"sprite_name": "rock_small" "sprite_name": "grass_small"
}, },
"item_obj": {}, "item_obj": {},
"creature_obj": {} "creature_obj": {}
@@ -1839,7 +1839,7 @@
"terrain_obj": { "terrain_obj": {
"id": "1", "id": "1",
"name": "2", "name": "2",
"sprite_name": "rock_small" "sprite_name": "grass_small"
}, },
"item_obj": {}, "item_obj": {},
"creature_obj": {} "creature_obj": {}
@@ -1848,7 +1848,7 @@
"terrain_obj": { "terrain_obj": {
"id": "1", "id": "1",
"name": "2", "name": "2",
"sprite_name": "rock_small" "sprite_name": "grass_small"
}, },
"item_obj": {}, "item_obj": {},
"creature_obj": {} "creature_obj": {}

View File

@@ -119,13 +119,79 @@ class Map:
if cell.creature_obj: if cell.creature_obj:
cell.creature_obj.update(time_delta, self.cell_size, self) # self! cell.creature_obj.update(time_delta, self.cell_size, self) # self!
# def draw_map(self, screen, current_frame, grid=True):
# terrain_list = []
# creature_list = []
#
# # ★ 1 ПАСС: собираем terrain и creatures ★
# for j in range(len(self.cells)):
# for i, cell in enumerate(self.cells[j]):
# base_x = i * self.cell_size + self.cam_x
# base_y = j * self.cell_size + self.cam_y
#
# # Terrain данные
# terrain_dd = {
# "x": int(base_x * self.scale), "y": int(base_y * self.scale),
# "w": int(self.cell_size * self.scale - self.cell_dist),
# "h": int(self.cell_size * self.scale - self.cell_dist),
# "spr_up": current_frame % self.sprites_refresh,
# "sprites": self.sprites, "scale": self.scale, "screen": screen
# }
# terrain_list.append((cell.terrain_obj, terrain_dd))
#
# # Creature данные (если есть)
# if cell.creature_obj:
# offset_x, offset_y = cell.creature_obj.render_offset
# creature_dd = terrain_dd.copy()
# creature_dd["x"] = int((base_x + offset_x) * self.scale)
# creature_dd["y"] = int((base_y + offset_y) * self.scale)
# creature_list.append((cell.creature_obj, creature_dd))
#
# # ★ 2 ПАСС: рисуем terrain ★
# for obj, dd in terrain_list:
# obj.draw(dd)
#
# # ★ 3 ПАСС: рисуем ВСЕХ creatures ПОСЛЕ terrain ★
# for obj, dd in creature_list:
# obj.draw(dd)
#
# # ★ 4 ПАСС: grid поверх всего ★
# for j in range(len(self.cells)):
# for i, cell in enumerate(self.cells[j]):
# base_x = i * self.cell_size + self.cam_x
# base_y = j * self.cell_size + self.cam_y
# grid_rect = pygame.Rect(
# int(base_x * self.scale), int(base_y * self.scale),
# int(self.cell_size * self.scale - self.cell_dist),
# int(self.cell_size * self.scale - self.cell_dist)
# )
# color = self.target_color if cell.is_target else self.color
# pygame.draw.rect(screen, color, grid_rect, self.bord)
def draw_map(self, screen, current_frame, grid=True): def draw_map(self, screen, current_frame, grid=True):
screen_rect = screen.get_rect()
terrain_list = [] terrain_list = []
creature_list = [] creature_list = []
# ★ 1 ПАСС: собираем terrain и creatures ★ # Вычисляем видимую область в координатах карты (аналогично get_cell_at_mouse)
for j in range(len(self.cells)): left_map = (self.cam_x * self.scale) / self.scale / self.cell_size
for i, cell in enumerate(self.cells[j]): top_map = (self.cam_y * self.scale) / self.scale / self.cell_size
right_map = ((self.cam_x * self.scale + screen_rect.width) / self.scale / self.cell_size)
bottom_map = ((self.cam_y * self.scale + screen_rect.height) / self.scale / self.cell_size)
min_row = max(0, int(top_map - 1)) # -1 для буфера
max_row = min(len(self.cells), int(bottom_map + 2)) # +2 для буфера
min_col = max(0, int(left_map - 1))
max_col = min(len(self.cells[0]) if self.cells and self.cells[0] else 0, int(right_map + 2))
# ★ 1 ПАСС: собираем только видимые terrain и creatures ★
for j in range(min_row, max_row):
if j not in self.cells: continue
row_cells = self.cells[j]
for i in range(min_col, min(max_col, len(row_cells))):
cell = row_cells[i]
base_x = i * self.cell_size + self.cam_x base_x = i * self.cell_size + self.cam_x
base_y = j * self.cell_size + self.cam_y base_y = j * self.cell_size + self.cam_y
@@ -155,15 +221,18 @@ class Map:
for obj, dd in creature_list: for obj, dd in creature_list:
obj.draw(dd) obj.draw(dd)
# ★ 4 ПАСС: grid поверх всего # ★ 4 ПАСС: grid только для видимых ячеек
for j in range(len(self.cells)): for j in range(min_row, max_row):
for i, cell in enumerate(self.cells[j]): if j not in self.cells: continue
row_cells = self.cells[j]
for i in range(min_col, min(max_col, len(row_cells))):
cell = row_cells[i]
base_x = i * self.cell_size + self.cam_x base_x = i * self.cell_size + self.cam_x
base_y = j * self.cell_size + self.cam_y base_y = j * self.cell_size + self.cam_y
grid_rect = pygame.Rect( grid_rect = pygame.Rect(
int(base_x * self.scale), int(base_y * self.scale), int(base_x * self.scale), int(base_y * self.scale),
int(self.cell_size * self.scale - self.cell_dist), int(self.cell_size * self.scale - self.cell_dist),
int(self.cell_size * self.scale - self.cell_dist) int(self.cell_size * self.scale - self.cell_dist) # ИСПРАВЛЕНО
) )
color = self.target_color if cell.is_target else self.color color = self.target_color if cell.is_target else self.color
pygame.draw.rect(screen, color, grid_rect, self.bord) pygame.draw.rect(screen, color, grid_rect, self.bord)
@@ -171,6 +240,7 @@ class Map:
@dataclass @dataclass
class Engine: class Engine:
sprites: dict = field(default_factory = dict) sprites: dict = field(default_factory = dict)
@@ -301,13 +371,20 @@ class Engine:
space_pressed = False space_pressed = False
spawn = False spawn = False
def spawn_elf(): def spawn_elves():
elf = eb_objects.Creature(id=f"elf_{random.randint(1000,9999)}", name="Elf", sprite_name="elf_watching", grid_pos = (0, 0)) spawn_positions = [(0,0), (0,99), (99,0), (99,99), (50,50)]
for pos in spawn_positions:
row, col = pos
if (row < len(easy_map.cells) and
col < len(easy_map.cells[row]) and
easy_map.cells[row][col].creature_obj is None):
elf = eb_objects.Creature(id=f"elf_{random.randint(1000,9999)}", name="Elf", sprite_name="elf_watching", grid_pos = pos)
r_move_short = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.patrol, easy_map.cells, 3), duration=0.01) r_move_short = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.patrol, easy_map.cells, 3), duration=0.01)
r_move_long = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.move_rand, easy_map.cells, 0, 99), duration=0.01) r_move_long = eb_objects.Action(sprite_name="elf_watching", func=partial(elf.move_rand, easy_map.cells, 0, 99), duration=0.01)
elf.tasks.append([r_move_short, r_move_short, r_move_short, r_move_short, r_move_short, r_move_long]) elf.tasks.append([r_move_short]*5 + [r_move_long])
easy_map.cells[0][0].creature_obj = elf easy_map.cells[row][col].creature_obj = elf
while running: while running:
time_delta = clock.tick(60)/1000.0 time_delta = clock.tick(60)/1000.0
@@ -318,8 +395,8 @@ class Engine:
print(f"Leak: {mem_after - mem_before:.1f} KB per 1000 frames") print(f"Leak: {mem_after - mem_before:.1f} KB per 1000 frames")
if global_counter % 100 == 0 and spawn == True: if global_counter % 100 == 0 and spawn == True:
spawn_elf() spawn_elves()
elf_count += 1 elf_count += 5
# poll for events # poll for events
# pygame.QUIT event means the user clicked X to close your window # pygame.QUIT event means the user clicked X to close your window
@@ -410,31 +487,6 @@ class Engine:
self.spr_scale -= self.scale_step self.spr_scale -= self.scale_step
self.scale_sprites() self.scale_sprites()
# # Находим свободные клетки
# # ★ МАССОВЫЙ СПАВН 50 эльфов ★
# free_cells = []
# for j in range(len(easy_map.cells)): # ★ ЛИМИТ строк ★
# for i in range(len(easy_map.cells[j])): # ★ ЛИМИТ колонок ★
# cell = easy_map.cells[j][i]
# if (cell.creature_obj is None and
# cell.terrain_obj and
# cell.terrain_obj.sprite_name == "grass_small"):
# free_cells.append((j, i))
#
# spawn_count = min(50, len(free_cells)) # ★ Не больше свободных клеток ★
# for _ in range(spawn_count):
# row, col = random.choice(free_cells)
# elf = eb_objects.Creature(id=f"elf_{random.randint(1000,9999)}", name="Elf", sprite_name="elf_watching")
# easy_map.cells[row][col].creature_obj = elf
#
# # ★ БЕЗОПАСНЫЙ выбор цели ★
# possible_targets = [c for c in free_cells if c != (row, col)]
# if possible_targets:
# target_cell = random.choice(possible_targets)
# elf.move(easy_map.cells, (row, col), target_cell)
#
# free_cells.remove((row, col)) # Убираем занятые клетки
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
@@ -453,7 +505,6 @@ class Engine:
row, col = active_cell row, col = active_cell
easy_map.cells[row][col].is_target = True easy_map.cells[row][col].is_target = True
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3: if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
mouse_pos = pygame.mouse.get_pos() mouse_pos = pygame.mouse.get_pos()
console_rect = console_window.get_abs_rect() console_rect = console_window.get_abs_rect()

View File

@@ -90,10 +90,10 @@ class Creature(Object):
self.render_offset = (0.0, 0.0) self.render_offset = (0.0, 0.0)
return return
self.replan_counter += 1 #self.replan_counter += 1
if self.replan_counter >= self.REPLAN_INTERVAL: #if self.replan_counter >= self.REPLAN_INTERVAL:
self.replan_counter = 0 # self.replan_counter = 0
self.replan(map_obj.cells, self.start_pos) # self.replan(map_obj.cells, self.start_pos)
if self.current_target is None: return if self.current_target is None: return

22
plan.txt Normal file
View File

@@ -0,0 +1,22 @@
1. Доработка системы задач: класс Task, новые задачи, проработка старых;
2. Доработка движка и приведение его структуры в порядок:
- вынос всех функций карты в карту, объекты знают свою позицию и им этого хватит.
взаимодействия с картой и другими объектами должно происходить только в методе движения, остальные методы должны абстрагироваться от карты;
- вынос рендера в класс Render. Карта занимается только своими клеткам. Метод
draw_map должен только готовить текстуру для Render, который будет ее рисовать;
- мини-движок поиска пути. class Pathfinder, который хранит в себе кэши,
выбирает оптимальный способ поиска пути для конкретной ситуации. Для этого
нужно ещё раз протестировать все способы, привести к единообразию интерфейсов.
Задокументировать. Возможно понадобится вынос поиска пути в отдельный движок
на другом языке и многопоточностью;
- привести в порядок Main Loop;
- вынести хранение и обработку объектов из карты в ObjectManager;
- сделать фундамент для MapManager, пока не развивать, но надо исходить из того
что одновременно может рендериться несколько карт;
3. Оптимизация.