From fa189a4c3b6089d54e1832f7cc86be4aa5becfed Mon Sep 17 00:00:00 2001 From: shiva404 Date: Thu, 5 Mar 2026 22:00:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20=D1=81=D0=B0=D0=BC=D1=83=D1=8E=20=D0=BE?= =?UTF-8?q?=D1=87=D0=B5=D0=B2=D0=B8=D0=B4=D0=BD=D1=83=D1=8E=20=D0=BE=D0=BF?= =?UTF-8?q?=D1=82=D0=B8=D0=BC=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B4=D0=B5=D1=80=D0=B0,=20=D1=82=D0=B5=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D1=8C=20=D0=BE=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D1=82=D0=B5=20=D0=BA=D0=BB=D0=B5=D1=82?= =?UTF-8?q?=D0=BA=D0=B8,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=B0=D0=BC=D0=B5=D1=80=D0=B5.=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=80=D1=82=D0=B0=20=D0=BA=D0=BE=D1=80=D1=80=D0=B0=D0=BF=D1=82?= =?UTF-8?q?=D0=B8=D1=82=D1=81=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=B4=D0=B2?= =?UTF-8?q?=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=BA=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=D1=8B,=20=D0=BD=D0=B0=D0=B4=D0=BE=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D1=82=D1=8C,=20=D0=B7=D0=B0=D1=82?= =?UTF-8?q?=D0=BE=20=D0=BC=D0=B0=D1=81=D1=88=D1=82=D0=B0=D0=B1=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D1=80=D0=BE=D1=81=D1=82=20=D0=BF=D1=80=D0=BE=D0=B8?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D0=BE=D1=87=D0=B5=D0=BD=D1=8C=20?= =?UTF-8?q?=D1=85=D0=BE=D1=80=D0=BE=D1=88=D0=B8=D0=B9,=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B4=D0=BE=20200=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=B0=D1=82=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D1=81=D1=82=D0=B0=D0=B1=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=2060=20=D0=A4=D0=9F=D0=A1.=20=D0=9F=D1=80?= =?UTF-8?q?=D0=BE=D0=BC=D0=B5=D0=B6=D1=83=D1=82=D0=BE=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82,=20=D1=85=D0=BE?= =?UTF-8?q?=D1=87=D1=83=20=D0=BF=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D1=82=D1=8C=20=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D0=B5=D1=89=D1=91=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B6=D0=B0=D1=82=D1=8C=20=D0=BA=D0=B0=D0=B4=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BD=D0=B0=20500=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=B0=D1=85,=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BC=20=D0=BD?= =?UTF-8?q?=D1=83=D0=B6=D0=BD=D0=BE=20=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BA=D0=B0=D0=BC=D0=B5=D1=80=D1=8B.=20=D0=A2=D0=B0=D0=BA?= =?UTF-8?q?=D0=B6=D0=B5=20=D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=81=D1=82=D1=83=D1=8E=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D1=8E=20=D1=81=D0=BF=D0=B0=D0=B2=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=8D=D0=BB=D1=8C=D1=84=D0=BE=D0=B2=20=D0=B2=20=D0=B3=D0=BB?= =?UTF-8?q?=D0=B0=D0=B2=D0=BD=D0=BE=D0=BC=20=D1=86=D0=B8=D0=BA=D0=BB=D0=B5?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BD=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8?= =?UTF-8?q?.=20=D0=AD=D0=BB=D1=8C=D1=84=D1=8B=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=B2=20=D1=83=D0=B3=D0=BB?= =?UTF-8?q?=D0=B0=D1=85=20=D0=BA=D0=B0=D1=80=D1=82=D1=8B=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=86=D0=B5=D0=BD=D1=82=D1=80=D1=83=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=B6=D0=B4=D1=8B=D0=B5=20=D1=81=D1=82=D0=BE=20=D1=82=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D0=B2=20=D1=86=D0=B8=D0=BA=D0=BB=D0=B0.=20?= =?UTF-8?q?=D0=9E=D0=BD=D0=B8=20=D0=BD=D0=B0=D1=87=D0=B8=D0=BD=D0=B0=D1=8E?= =?UTF-8?q?=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D1=89=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=D1=81=D1=8F=20=D0=BF=D0=BE=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=B5=20=D0=B2=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=82=D0=BE=D1=87=D0=BA=D0=B8,=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=B2=D0=B0=D1=8F=20=D0=BE=D1=82=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BD=D0=BE=D0=BC=D0=B5=D1=80=D0=BD=D1=83=D1=8E=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D1=83=20=D0=BF=D0=BE=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=20=D0=BF=D1=83=D1=82=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- def_map.json | 10 ++-- eb_engine.py | 133 ++++++++++++++++++++++++++++++++++---------------- eb_objects.py | 8 +-- plan.txt | 22 +++++++++ 4 files changed, 123 insertions(+), 50 deletions(-) create mode 100644 plan.txt diff --git a/def_map.json b/def_map.json index a3a1bad..4aadc5c 100644 --- a/def_map.json +++ b/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": {} diff --git a/eb_engine.py b/eb_engine.py index 75819bc..dc08499 100644 --- a/eb_engine.py +++ b/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)) - 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 + 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]*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() diff --git a/eb_objects.py b/eb_objects.py index d1dd457..915ae48 100644 --- a/eb_objects.py +++ b/eb_objects.py @@ -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 diff --git a/plan.txt b/plan.txt new file mode 100644 index 0000000..0aa803b --- /dev/null +++ b/plan.txt @@ -0,0 +1,22 @@ +1. Доработка системы задач: класс Task, новые задачи, проработка старых; +2. Доработка движка и приведение его структуры в порядок: + - вынос всех функций карты в карту, объекты знают свою позицию и им этого хватит. + взаимодействия с картой и другими объектами должно происходить только в методе движения, остальные методы должны абстрагироваться от карты; + + - вынос рендера в класс Render. Карта занимается только своими клеткам. Метод + draw_map должен только готовить текстуру для Render, который будет ее рисовать; + + - мини-движок поиска пути. class Pathfinder, который хранит в себе кэши, + выбирает оптимальный способ поиска пути для конкретной ситуации. Для этого + нужно ещё раз протестировать все способы, привести к единообразию интерфейсов. + Задокументировать. Возможно понадобится вынос поиска пути в отдельный движок + на другом языке и многопоточностью; + + - привести в порядок Main Loop; + + - вынести хранение и обработку объектов из карты в ObjectManager; + + - сделать фундамент для MapManager, пока не развивать, но надо исходить из того + что одновременно может рендериться несколько карт; + +3. Оптимизация. \ No newline at end of file