Добавил систему задач для юнитов, она реализована в методе update класса Creature. Подчистил код, пофиксил по мелочам баги. Остался ещё техдолг Егору и задачи из main.
This commit is contained in:
@@ -4,9 +4,7 @@ import uuid
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from copy import deepcopy
|
||||
|
||||
from collections import defaultdict
|
||||
from heapq import heappush, heappop
|
||||
from functools import partial
|
||||
|
||||
import pygame
|
||||
import pygame_gui
|
||||
@@ -15,12 +13,10 @@ from classes import Rock
|
||||
|
||||
from pathfinding.core.grid import Grid
|
||||
from pathfinding.finder.a_star import AStarFinder
|
||||
|
||||
from collections import defaultdict
|
||||
from heapq import heappush, heappop
|
||||
from math import sqrt, inf
|
||||
|
||||
|
||||
def path_exists(data, path):
|
||||
current = data
|
||||
for key in path:
|
||||
@@ -371,7 +367,7 @@ def find_way(cells, start, goal):
|
||||
queue[queue_size] = [nr, nc]
|
||||
queue_size += 1
|
||||
|
||||
print(f"Путь не найден: {start} -> {goal}")
|
||||
#print(f"Путь не найден: {start} -> {goal}")
|
||||
return None
|
||||
|
||||
def can_move_diagonal(r, c, nr, nc, rocks_only, rows, cols):
|
||||
|
||||
12
def_map.json
12
def_map.json
@@ -20,11 +20,7 @@
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "elf_watching"
|
||||
}
|
||||
"creature_obj": {}
|
||||
},
|
||||
{
|
||||
"terrain_obj": {
|
||||
@@ -926,11 +922,7 @@
|
||||
"sprite_name": "grass_small"
|
||||
},
|
||||
"item_obj": {},
|
||||
"creature_obj": {
|
||||
"id": "1",
|
||||
"name": "2",
|
||||
"sprite_name": "elf_watching"
|
||||
}
|
||||
"creature_obj": {}
|
||||
},
|
||||
{
|
||||
"terrain_obj": {
|
||||
|
||||
107
eb_engine.py
107
eb_engine.py
@@ -1,5 +1,5 @@
|
||||
from common import os, json, uuid, deepcopy, random
|
||||
from common import dataclass, field
|
||||
from common import dataclass, field, partial
|
||||
from common import pygame, pygame_gui
|
||||
import eb_objects
|
||||
import eb_terrain_objects
|
||||
@@ -47,6 +47,7 @@ class Map:
|
||||
cam_y: int = 0
|
||||
cell_dist: int = 1
|
||||
|
||||
#effects[]
|
||||
#action_time_multiplier
|
||||
|
||||
def __post_init__(self):
|
||||
@@ -55,7 +56,7 @@ class Map:
|
||||
buff = json.load(file)
|
||||
for line in range(len(buff)):
|
||||
self.cells[line] = []
|
||||
for cell in buff[str(line)]:
|
||||
for col, cell in enumerate(buff[str(line)]):
|
||||
final_cell = Cell(cell_classes[cell["terrain_obj"]["sprite_name"]](**cell["terrain_obj"]))
|
||||
|
||||
if cell["item_obj"]:
|
||||
@@ -63,6 +64,7 @@ class Map:
|
||||
|
||||
if cell["creature_obj"]:
|
||||
final_cell.creature_obj = cell_classes[cell["creature_obj"]["sprite_name"]](**cell["creature_obj"])
|
||||
final_cell.creature_obj.grid_pos = (line, col)
|
||||
|
||||
self.cells[line].append(final_cell)
|
||||
|
||||
@@ -90,6 +92,7 @@ class Map:
|
||||
|
||||
setattr(source_cell, type, None)
|
||||
setattr(dest_cell, type, obj)
|
||||
#obj.grid_pos = goal
|
||||
return True
|
||||
|
||||
def get_cell_at_mouse(self, mouse_pos):
|
||||
@@ -113,7 +116,7 @@ class Map:
|
||||
def update_map(self, time_delta):
|
||||
for j in range(len(self.cells)):
|
||||
for cell in self.cells[j]:
|
||||
if cell.creature_obj and cell.creature_obj.current_target:
|
||||
if cell.creature_obj:
|
||||
cell.creature_obj.update(time_delta, self.cell_size, self) # self!
|
||||
|
||||
def draw_map(self, screen, current_frame, grid=True):
|
||||
@@ -293,8 +296,17 @@ class Engine:
|
||||
mem_before = process.memory_info().rss / 1024
|
||||
|
||||
|
||||
NUM_ELVES = 1000
|
||||
NUM_ELVES = 2000
|
||||
elf_count = 0
|
||||
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
|
||||
|
||||
|
||||
while running:
|
||||
@@ -305,12 +317,29 @@ class Engine:
|
||||
mem_after = process.memory_info().rss / 1024
|
||||
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
|
||||
|
||||
# poll for events
|
||||
# pygame.QUIT event means the user clicked X to close your window
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_SPACE and elf_count < NUM_ELVES and not space_pressed:
|
||||
#spawn_elf()
|
||||
#elf_count += 1
|
||||
if spawn == False:
|
||||
spawn = True
|
||||
else: spawn = False
|
||||
space_pressed = True
|
||||
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == pygame.K_SPACE:
|
||||
space_pressed = False
|
||||
|
||||
if event.type == pygame.MOUSEWHEEL:
|
||||
scroll_y = event.y
|
||||
|
||||
@@ -372,41 +401,39 @@ class Engine:
|
||||
if keys[pygame.K_d]:
|
||||
easy_map.cam_x -= self.camera_step
|
||||
|
||||
#if keys[pygame.K_q]:
|
||||
# easy_map.scale += self.scale_step
|
||||
# self.spr_scale += self.scale_step
|
||||
# self.scale_sprites()
|
||||
#if keys[pygame.K_e] and easy_map.scale >= self.scale_step:
|
||||
# easy_map.scale -= self.scale_step
|
||||
# self.spr_scale -= self.scale_step
|
||||
# self.scale_sprites()
|
||||
if keys[pygame.K_q]:
|
||||
easy_map.scale += self.scale_step
|
||||
self.spr_scale += self.scale_step
|
||||
self.scale_sprites()
|
||||
if keys[pygame.K_e] and easy_map.scale >= self.scale_step:
|
||||
easy_map.scale -= self.scale_step
|
||||
self.spr_scale -= self.scale_step
|
||||
self.scale_sprites()
|
||||
|
||||
|
||||
if keys[pygame.K_SPACE] and elf_count < NUM_ELVES:
|
||||
# Находим свободные клетки
|
||||
# ★ МАССОВЫЙ СПАВН 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)) # Убираем занятые клетки
|
||||
# # Находим свободные клетки
|
||||
# # ★ МАССОВЫЙ СПАВН 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)) # Убираем занятые клетки
|
||||
|
||||
|
||||
|
||||
@@ -437,7 +464,7 @@ class Engine:
|
||||
if cell_coords:
|
||||
#print(f"Движение: {active_cell} -> {cell_coords}")
|
||||
easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move(
|
||||
easy_map.cells, active_cell, cell_coords)
|
||||
easy_map.cells, cell_coords)
|
||||
|
||||
if keys[pygame.K_ESCAPE]:
|
||||
running = False
|
||||
@@ -453,6 +480,6 @@ class Engine:
|
||||
|
||||
if global_counter % 10 == 0:
|
||||
current_fps = clock.get_fps()
|
||||
print(f"Current FPS: {current_fps:.2f}")
|
||||
print(f"Elves count: {elf_count} - Current FPS: {current_fps:.2f}")
|
||||
|
||||
pygame.quit()
|
||||
185
eb_objects.py
185
eb_objects.py
@@ -1,4 +1,21 @@
|
||||
from common import deepcopy, dataclass, field
|
||||
from common import deepcopy, dataclass, field, random
|
||||
|
||||
@dataclass
|
||||
class Action:
|
||||
sprite_name: str
|
||||
func: function
|
||||
duration: float
|
||||
progress: float = 0.0
|
||||
#status: str = "pending"
|
||||
#взаимодействие с инвентарем
|
||||
#взять предмет
|
||||
#бросить предмет
|
||||
#передать предмет
|
||||
#собрать ресурс
|
||||
#активное действие с оружием
|
||||
#колдовство
|
||||
|
||||
#class Task
|
||||
|
||||
@dataclass
|
||||
class Object:
|
||||
@@ -6,6 +23,7 @@ class Object:
|
||||
name: str
|
||||
sprite_name: str
|
||||
sprite_state: int = 0
|
||||
grid_pos: tuple = None
|
||||
# current_map
|
||||
# pos
|
||||
# weight
|
||||
@@ -31,21 +49,30 @@ class Terrain(Object):
|
||||
|
||||
@dataclass
|
||||
class Creature(Object):
|
||||
waypoints: list = field(default_factory = list)
|
||||
quick_actions: list = field(default_factory = list)
|
||||
tasks: list = field(default_factory = list)
|
||||
inventory: dict = field(default_factory = dict)
|
||||
|
||||
direction: int = 0 # tuple?
|
||||
move_progress: float = 0.0 # 0.0 = старт клетки, 1.0 = конец клетки
|
||||
current_target: tuple = None # (row, col) следующая клетка
|
||||
final_goal: tuple = None
|
||||
move_speed: float = 0.02 # пикселей/кадр (настройте)
|
||||
move_speed: float = 0.02 # пикселей/кадр
|
||||
render_offset: tuple = (0.0, 0.0)
|
||||
start_pos: tuple = None # (row, col) начальная позиция сегмента пути
|
||||
|
||||
replan_counter: int = 0
|
||||
REPLAN_INTERVAL: int = 300
|
||||
REPLAN_INTERVAL: int = 30000
|
||||
waypoints: list = field(default_factory = list)
|
||||
|
||||
inventory: dict = field(default_factory = dict)
|
||||
quick_actions: list = field(default_factory = list)
|
||||
tasks: list = field(default_factory = list)
|
||||
|
||||
action: Action = None
|
||||
action_time: float = 0.0
|
||||
action_counter: int = 0
|
||||
task_counter: int = 0
|
||||
|
||||
new_task: list = field(default_factory = list)
|
||||
interrupt_task: list = field(default_factory = list)
|
||||
|
||||
interrupt_action_status: str = "completed"
|
||||
|
||||
def replan(self, cells, pos):
|
||||
from common import find_way
|
||||
@@ -56,21 +83,9 @@ class Creature(Object):
|
||||
else:
|
||||
self.waypoints.clear()
|
||||
self.current_target = None
|
||||
self.final_goal = None
|
||||
|
||||
def move(self, cells, start, goal):
|
||||
from common import find_way
|
||||
self.final_goal = goal
|
||||
self.start_pos = start
|
||||
path = find_way(cells, start, goal)
|
||||
if path and len(path) > 1:
|
||||
self.waypoints = path[1:] # Убираем текущую позицию
|
||||
self.current_target = self.waypoints[0]
|
||||
self.move_progress = 0.0
|
||||
self.start_pos = start # ★ ТУТ - текущая позиция как стартовая для первого шага ★
|
||||
self.render_offset = (0.0, 0.0)
|
||||
|
||||
|
||||
def update(self, time_delta, cell_size, map_obj):
|
||||
def calc_step(self, time_delta, cell_size, map_obj):
|
||||
if self.current_target is None or not self.waypoints:
|
||||
self.render_offset = (0.0, 0.0)
|
||||
return
|
||||
@@ -98,28 +113,143 @@ class Creature(Object):
|
||||
|
||||
if self.move_progress >= 1.0:
|
||||
map_obj.move_obj('creature_obj', self.start_pos, self.current_target)
|
||||
self.grid_pos = self.current_target
|
||||
|
||||
if self.waypoints: self.waypoints.pop(0)
|
||||
if self.waypoints:
|
||||
self.start_pos = self.current_target # Новая клетка как старт
|
||||
self.start_pos = self.current_target
|
||||
self.current_target = self.waypoints[0]
|
||||
self.move_progress = 0.0
|
||||
self.render_offset = (0.0, 0.0) # ← ДОБАВИТЬ!
|
||||
self.render_offset = (0.0, 0.0)
|
||||
else:
|
||||
#print(111111111111111)
|
||||
self.current_target = None
|
||||
self.final_goal = None
|
||||
self.render_offset = (0.0, 0.0)
|
||||
return
|
||||
|
||||
# ★ ТОЛЬКО интерполяция offset ★
|
||||
start_row, start_col = self.start_pos #or (0, 0)
|
||||
if self.current_target is None:
|
||||
self.render_offset = (0.0, 0.0)
|
||||
return
|
||||
|
||||
start_row, start_col = self.start_pos
|
||||
target_row, target_col = self.current_target
|
||||
offset_x = (target_col - start_col) * cell_size * self.move_progress
|
||||
offset_y = (target_row - start_row) * cell_size * self.move_progress
|
||||
self.render_offset = (offset_x, offset_y)
|
||||
|
||||
def move(self, cells, goal):
|
||||
from common import find_way
|
||||
self.final_goal = goal
|
||||
path = find_way(cells, self.grid_pos, goal)
|
||||
if path and len(path) > 1:
|
||||
self.waypoints = path[1:]
|
||||
self.current_target = self.waypoints[0]
|
||||
self.move_progress = 0.0
|
||||
self.start_pos = self.grid_pos
|
||||
self.render_offset = (0.0, 0.0)
|
||||
else:
|
||||
self.final_goal = None
|
||||
|
||||
def create_task(self):
|
||||
self.new_task = []
|
||||
|
||||
#add actions_default_durations dict {func1: dur1, ...}
|
||||
def add_action(self, sprite_name: str, func: function, duration: float):
|
||||
self.new_task.append(Action(sprite_name, func, duration))
|
||||
|
||||
def add_task(self):
|
||||
self.tasks.append(self.new_task)
|
||||
|
||||
def add_interr_task(self):
|
||||
pass
|
||||
#param resume_on_interrupt, Bool
|
||||
#if True
|
||||
# save goal to buffer
|
||||
#if False
|
||||
# action/task_counter = 0
|
||||
#clear goal/wp
|
||||
|
||||
def update(self, time_delta, cell_size, map_obj):
|
||||
#quick_actions? here?
|
||||
#print(self.waypoints, self.final_goal, self.action_counter, self.task_counter)
|
||||
if self.final_goal is not None:
|
||||
#print(2)
|
||||
self.calc_step(time_delta, cell_size, map_obj)
|
||||
return
|
||||
|
||||
if self.interrupt_task and self.interrupt_action_status == "completed":
|
||||
#print(3)
|
||||
self.action_time = 0.0
|
||||
self.action = self.interrupt_task.pop(0)
|
||||
self.interrupt_action_status = "active"
|
||||
|
||||
#print(f" DEBUG: tasks={len(self.tasks)}, "
|
||||
# f"task_len={len(self.tasks[self.task_counter]) if self.tasks else 0}, "
|
||||
# f"action={self.action is not None}")
|
||||
|
||||
if self.action:
|
||||
#print(self.action_counter, self.task_counter)
|
||||
self.action_time += time_delta
|
||||
self.action.progress = min(1.0, self.action_time / self.action.duration)
|
||||
|
||||
if self.action_time >= self.action.duration:
|
||||
self.action.func()
|
||||
if self.interrupt_action_status == "active":
|
||||
self.interrupt_action_status == "completed"
|
||||
#if not self.inter_task and goal: move to buff_goal from self.pos (add to Object)
|
||||
self.action = None
|
||||
self.action_time = 0.0
|
||||
|
||||
|
||||
elif self.tasks:
|
||||
#print(6)
|
||||
if self.action_counter < len(self.tasks[self.task_counter]):
|
||||
self.action = self.tasks[self.task_counter][self.action_counter]
|
||||
self.action_counter += 1
|
||||
else:
|
||||
self.task_counter += 1
|
||||
self.action_counter = 0
|
||||
if self.task_counter == len(self.tasks):
|
||||
self.task_counter = 0
|
||||
|
||||
self.action = self.tasks[self.task_counter][self.action_counter]
|
||||
self.action_counter += 1
|
||||
self.action_time = 0.0
|
||||
|
||||
|
||||
#elif self.tasks:
|
||||
# if self.action_counter >= len(self.tasks[self.task_counter]):
|
||||
# self.task_counter += 1
|
||||
# self.action_counter = 0
|
||||
# if self.task_counter >= len(self.tasks):
|
||||
# self.task_counter = 0
|
||||
#
|
||||
# self.action = self.tasks[self.task_counter][self.action_counter]
|
||||
# self.action_counter += 1
|
||||
# self.action_time = 0.0
|
||||
|
||||
def patrol(self, cells, area):
|
||||
goal = (random.randint(self.grid_pos[0] - area,
|
||||
self.grid_pos[0] + area),
|
||||
random.randint(self.grid_pos[1] - area,
|
||||
self.grid_pos[1] + area))
|
||||
while goal == self.grid_pos:
|
||||
goal = (random.randint(self.grid_pos[0] - area,
|
||||
self.grid_pos[0] + area),
|
||||
random.randint(self.grid_pos[1] - area,
|
||||
self.grid_pos[1] + area))
|
||||
self.move(cells, goal)
|
||||
|
||||
|
||||
def move_rand(self, cells, area_start, area_end):
|
||||
goal = (random.randint(area_start, area_end), random.randint(area_start, area_end))
|
||||
while goal == self.grid_pos:
|
||||
goal = (random.randint(area_start, area_end), random.randint(area_start, area_end))
|
||||
self.move(cells, goal)
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item(Object):
|
||||
@@ -132,7 +262,6 @@ class Container(Item):
|
||||
# content = {}
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Building(Object):
|
||||
pass
|
||||
27
main.py
27
main.py
@@ -11,8 +11,35 @@ if __name__ == "__main__":
|
||||
# todo:
|
||||
# прокрутка баг консоль и карта
|
||||
# ОПТИМИЗАЦИИ
|
||||
# перепроверять путь пореже, или только после столкновения, или локальный поиск
|
||||
# очередь задач и задача рандомного патруля
|
||||
# устроить краш тест поиску пути, запустив много объектов на маленьком поле, успел заметить баги
|
||||
# добавить функцию движения за каким-то объектом
|
||||
# сделать, чтобы в случае отменненого движения не телепортировался назад, а плавно
|
||||
# приступаем к логике
|
||||
# сделать по аналогии с текущей клеткой текущий объект
|
||||
|
||||
|
||||
|
||||
|
||||
# посмотреть как в clock = pygame.time.Clock() работает фпс
|
||||
# перемещать оъект в другую клетку при половине офсета
|
||||
|
||||
# техдолг Егору
|
||||
# убрать cells и mapobject creature - перенести нужную логику в методы Map
|
||||
# система имен спрайтов и Action - реализовать
|
||||
# рисовать группой спрайтов как в перпл
|
||||
# нужен ли теперь start_pos? grid_pos?
|
||||
# class Task с проверками выполнения экшонов
|
||||
|
||||
#вернуть назад апдейт. вернул - работает, сравнить с новым и решить
|
||||
#final_goal = None - check all!
|
||||
#рефактор goal = (random.randint(self.grid_pos[0] - area,
|
||||
# self.grid_pos[0] + area),
|
||||
# random.randint(self.grid_pos[1] - area,
|
||||
# self.grid_pos[1] + area))
|
||||
|
||||
# отловить баг - иногда всё равно встают - похоже из-за реплана и коллизий с эльфами
|
||||
# вроде починил, добавив final_goal = None в реплан, проверить
|
||||
|
||||
# совет ксюши - не считать коллизии с объектами, только при перемещении в клетку проверять
|
||||
Reference in New Issue
Block a user