Попробовал самую очевидную оптимизацию рендера, теперь отрисовываются только те клетки, которые в камере. Карта корраптится при движении камеры, надо исправить, зато масштаб работает и прирост производительности очень хороший, теперь до 200 объектов обрабатывается при стабильных 60 ФПС. Промежуточный коммит, хочу посмотреть сколько можно ещё выжать кадров на 500 объектах, потом нужно починить движение камеры. Также написал простую функцию спавна эльфов в главном цикле для создания нагрузки. Эльфы создаются в углах карты и по центру каждые сто тактов цикла. Они начинают перемещаться по карте в случайные точки, создавая относительно равномерную нагрузку поиска пути.
This commit is contained in:
10
def_map.json
10
def_map.json
@@ -26,7 +26,7 @@
|
||||
"terrain_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "rock_small"
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {}
|
||||
@@ -928,7 +928,7 @@
|
||||
"terrain_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "rock_small"
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {}
|
||||
@@ -946,7 +946,7 @@
|
||||
"terrain_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "rock_small"
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {}
|
||||
@@ -1839,7 +1839,7 @@
|
||||
"terrain_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "rock_small"
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {}
|
||||
@@ -1848,7 +1848,7 @@
|
||||
"terrain_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "rock_small"
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {}
|
||||
|
||||
131
eb_engine.py
131
eb_engine.py
@@ -119,13 +119,79 @@ class Map:
|
||||
if cell.creature_obj:
|
||||
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):
|
||||
screen_rect = screen.get_rect()
|
||||
terrain_list = []
|
||||
creature_list = []
|
||||
|
||||
# ★ 1 ПАСС: собираем terrain и creatures ★
|
||||
for j in range(len(self.cells)):
|
||||
for i, cell in enumerate(self.cells[j]):
|
||||
# Вычисляем видимую область в координатах карты (аналогично get_cell_at_mouse)
|
||||
left_map = (self.cam_x * self.scale) / self.scale / self.cell_size
|
||||
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_y = j * self.cell_size + self.cam_y
|
||||
|
||||
@@ -155,15 +221,18 @@ class Map:
|
||||
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]):
|
||||
# ★ 4 ПАСС: grid только для видимых ячеек ★
|
||||
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_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)
|
||||
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)
|
||||
@@ -171,6 +240,7 @@ class Map:
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class Engine:
|
||||
sprites: dict = field(default_factory = dict)
|
||||
@@ -301,13 +371,20 @@ class Engine:
|
||||
space_pressed = False
|
||||
spawn = False
|
||||
|
||||
def spawn_elf():
|
||||
elf = eb_objects.Creature(id=f"elf_{random.randint(1000,9999)}", name="Elf", sprite_name="elf_watching", grid_pos = (0, 0))
|
||||
def spawn_elves():
|
||||
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_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])
|
||||
easy_map.cells[0][0].creature_obj = elf
|
||||
|
||||
elf.tasks.append([r_move_short]*5 + [r_move_long])
|
||||
easy_map.cells[row][col].creature_obj = elf
|
||||
|
||||
while running:
|
||||
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")
|
||||
|
||||
if global_counter % 100 == 0 and spawn == True:
|
||||
spawn_elf()
|
||||
elf_count += 1
|
||||
spawn_elves()
|
||||
elf_count += 5
|
||||
|
||||
# poll for events
|
||||
# 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.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:
|
||||
@@ -453,7 +505,6 @@ class Engine:
|
||||
row, col = active_cell
|
||||
easy_map.cells[row][col].is_target = True
|
||||
|
||||
|
||||
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
|
||||
mouse_pos = pygame.mouse.get_pos()
|
||||
console_rect = console_window.get_abs_rect()
|
||||
|
||||
@@ -90,10 +90,10 @@ class Creature(Object):
|
||||
self.render_offset = (0.0, 0.0)
|
||||
return
|
||||
|
||||
self.replan_counter += 1
|
||||
if self.replan_counter >= self.REPLAN_INTERVAL:
|
||||
self.replan_counter = 0
|
||||
self.replan(map_obj.cells, self.start_pos)
|
||||
#self.replan_counter += 1
|
||||
#if self.replan_counter >= self.REPLAN_INTERVAL:
|
||||
# self.replan_counter = 0
|
||||
# self.replan(map_obj.cells, self.start_pos)
|
||||
|
||||
|
||||
if self.current_target is None: return
|
||||
|
||||
22
plan.txt
Normal file
22
plan.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
1. Доработка системы задач: класс Task, новые задачи, проработка старых;
|
||||
2. Доработка движка и приведение его структуры в порядок:
|
||||
- вынос всех функций карты в карту, объекты знают свою позицию и им этого хватит.
|
||||
взаимодействия с картой и другими объектами должно происходить только в методе движения, остальные методы должны абстрагироваться от карты;
|
||||
|
||||
- вынос рендера в класс Render. Карта занимается только своими клеткам. Метод
|
||||
draw_map должен только готовить текстуру для Render, который будет ее рисовать;
|
||||
|
||||
- мини-движок поиска пути. class Pathfinder, который хранит в себе кэши,
|
||||
выбирает оптимальный способ поиска пути для конкретной ситуации. Для этого
|
||||
нужно ещё раз протестировать все способы, привести к единообразию интерфейсов.
|
||||
Задокументировать. Возможно понадобится вынос поиска пути в отдельный движок
|
||||
на другом языке и многопоточностью;
|
||||
|
||||
- привести в порядок Main Loop;
|
||||
|
||||
- вынести хранение и обработку объектов из карты в ObjectManager;
|
||||
|
||||
- сделать фундамент для MapManager, пока не развивать, но надо исходить из того
|
||||
что одновременно может рендериться несколько карт;
|
||||
|
||||
3. Оптимизация.
|
||||
Reference in New Issue
Block a user