Files
Elvenbane/eb_engine.py

406 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from common import os, json, uuid, deepcopy, dataclass, field
from common import pygame, pygame_gui
import eb_objects
import eb_terrain_objects
import eb_creature_objects
#from pympler import muppy, summary
import gc, psutil, os
cell_classes = {"grass_small": eb_terrain_objects.Ground,
"sword_default": eb_objects.Item, "elf_watching": eb_creature_objects.Unit,
"rock_small": eb_terrain_objects.Rock}
main_dir = os.path.dirname(os.path.abspath(__file__))
sprites_dir = os.path.join(main_dir, "res", "sprites")
#class Render
#class ObjectManager
#class MapManager
#class Event
#class EventManager
#class Control
@dataclass
class Cell:
terrain_obj: None = None
item_obj: None = None
creature_obj: None = None
is_target: bool = False
render_offset: tuple = (0.0, 0.0)
@dataclass
class Map:
name: str
sprites: dict
sprites_refresh: int = 60
cells: dict = field(default_factory = dict)
color: str = "gray57"
target_color: str = "gold"
cell_size: int = 150
bord: int = 3
scale: float = 1
cam_x: int = 0
cam_y: int = 0
cell_dist: int = 1
#action_time_multiplier
def __post_init__(self):
self.cells = {}
with open(self.name, 'r') as file:
buff = json.load(file)
for line in range(len(buff)):
self.cells[line] = []
for cell in buff[str(line)]:
final_cell = Cell(cell_classes[cell["terrain_obj"]["sprite_name"]](**cell["terrain_obj"]))
if cell["item_obj"]:
final_cell.item_obj = cell_classes[cell["item_obj"]["sprite_name"]](**cell["item_obj"])
if cell["creature_obj"]:
final_cell.creature_obj = cell_classes[cell["creature_obj"]["sprite_name"]](**cell["creature_obj"])
self.cells[line].append(final_cell)
def move_obj(self, type, start, goal):
"""Перемещает объект типа 'terrain_obj', 'item_obj' или 'creature_obj'
из клетки start=(row, col) в goal=(row, col)"""
s_y, s_x = start
d_y, d_x = goal
# Проверка границ
if (s_y >= len(self.cells) or s_x >= len(self.cells[s_y]) or
d_y >= len(self.cells) or d_x >= len(self.cells[d_y])):
return False
source_cell = self.cells[s_y][s_x]
dest_cell = self.cells[d_y][d_x]
obj = getattr(source_cell, type)
if obj is None:
return False
setattr(source_cell, type, None)
setattr(dest_cell, type, obj)
return True
def get_cell_at_mouse(self, mouse_pos):
"""Возвращает индексы клетки (row, col) по позиции мыши или None если вне карты"""
mx, my = mouse_pos
# Переводим экранные координаты в координаты карты с учетом камеры и масштаба
map_x = (mx - self.cam_x * self.scale) / self.scale / self.cell_size
map_y = (my - self.cam_y * self.scale) / self.scale / self.cell_size
# Получаем индексы ближайшей клетки
col = int(map_x)
row = int(map_y)
# Проверяем границы карты
if (row < 0 or row >= len(self.cells) or
col < 0 or col >= len(self.cells[row])):
return None
return (row, col)
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:
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)
@dataclass
class Engine:
sprites: dict = field(default_factory = dict)
cached_sprites: dict = field(default_factory = dict)
screen: pygame.Surface = ((1, 1))
width: int = 1600
height: int = 800
camera_step: int = 10
scale_step: float = 0.01
spr_scale: float = 1
def __post_init__(self):
self.sprites = {}
pygame.init()
pygame.display.set_caption('Elvenbane')
self.screen = pygame.display.set_mode((self.width, self.height), pygame.HWSURFACE | pygame.DOUBLEBUF)
print("The engine has started. Sprites were successfully loaded.\n")
self.load_sprites()
print("Sprites were successfully loaded.\n")
self.cached_sprites = deepcopy(self.sprites)
print("Sprites were successfully cached.\n")
def load_sprites(self, folder_path = sprites_dir):
self.sprites = {}
files = [f for f in os.listdir(folder_path) if f.lower().endswith('.png')]
#TOTAL SLOR - REWRITE THIS FUNC PLS
groups = {}
for f in files:
name = os.path.splitext(f)[0]
if '_' in name and name.rsplit('_', 1)[0].count('_') >= 1:
prefix = name.rsplit('_', 1)[0]
num = int(name.rsplit('_', 1)[1])
groups.setdefault(prefix, []).append((num, f))
for prefix, items in groups.items():
items.sort()
self.sprites[prefix] = [
pygame.image.load(os.path.join(folder_path, f)).convert_alpha()
for num, f in items
]
def scale_image(self, image):
orig_size = image.get_size()
new_size = (int(orig_size[0] * self.spr_scale), int(orig_size[1] * self.spr_scale))
return pygame.transform.smoothscale(image, new_size)
def scale_sprites(self):
for sp_name, sprite_list in self.sprites.items():
for i, sprite in enumerate(sprite_list):
scaled = self.scale_image(sprite)
self.cached_sprites[sp_name][i] = scaled
def create_console(self, manager):
console_window = pygame_gui.elements.UIWindow(
pygame.Rect(100, 100, 400, 300),
manager=manager,
window_display_title='Console',
resizable=True
)
input_entry = pygame_gui.elements.UITextEntryLine(
relative_rect=pygame.Rect(10, 250, 380, 30),
container=console_window,
manager=manager
)
# ★ UIScrollingContainer ★
scroll_container = pygame_gui.elements.UIScrollingContainer(
relative_rect=pygame.Rect(10, 10, 380, 230),
container=console_window,
manager=manager,
allow_scroll_x=False
)
# ★ UITextBox ВНУТРИ контейнера ★
output_box = pygame_gui.elements.UITextBox(
html_text=">>> hlwrld1\n",
relative_rect=pygame.Rect(0, 0, 380, 1000), # ← Увеличьте высоту!
container=scroll_container,
manager=manager
)
scroll_container.set_scrollable_area_dimensions((380, 2000))
return console_window, input_entry, scroll_container, output_box
def main_loop(self):
easy_map = Map("def_map.json", self.cached_sprites)
#print(easy_map.find_way((1, 1), (5, 5)))
#print(easy_map.find_way((0, 0), (1, 1)))
#print(easy_map.find_way((0, 0), (0, 1)))
#print(easy_map.find_way((0, 0), (0, 0)))
#sp = eb_objects.Sprite(self.sprites, "elf_watching")
#gr = pygame.image.load(file_path).convert_alpha()
background = pygame.Surface((1600, 800))
background.fill("chartreuse4")
manager = pygame_gui.UIManager((1600, 800))
#hello_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((350, 275), (100, 50)),
# text='Say Hello',
# manager=manager)
console_window, input_entry, scroll_container, output_box = self.create_console(manager)
output_log = ">>> hlwrld1\n"
console_active = False
clock = pygame.time.Clock()
running = True
unlock = True
current_frame = 0
max_fps = 60
global_counter = 0
global_counter_cap = 100000
active_cell = None
# profiling
process = psutil.Process(os.getpid())
gc.collect()
mem_before = process.memory_info().rss / 1024
while running:
time_delta = clock.tick(60)/1000.0
#pygame.event.clear()
if global_counter % 1000 == 0:
gc.collect()
mem_after = process.memory_info().rss / 1024
print(f"Leak: {mem_after - mem_before:.1f} KB per 1000 frames")
# 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_gui.UI_TEXT_ENTRY_FINISHED and event.ui_element == input_entry:
user_text = input_entry.get_text()
exec(user_text)
if user_text.strip():
output_log += f">>> {user_text}\n"
output_box.set_text(output_log)
input_entry.set_text("")
user_text = ""
console_active = False
console_active = bool(input_entry.get_text().strip())
if event.type == pygame.VIDEORESIZE:
new_size = event.size
self.screen = pygame.display.set_mode(new_size, pygame.RESIZABLE)
manager.clear_and_set_new_size((new_size[0], new_size[1]))
# ★ ПЕРЕСОЗДАНИЕ ★
console_window.kill()
input_entry.kill()
scroll_container.kill()
output_box.kill()
console_window, input_entry, scroll_container, output_box = self.create_console(manager)
manager.process_events(event)
manager.update(time_delta)
# fill the screen with a color to wipe away anything from last frame
#self.screen.fill("chartreuse4")
self.screen.blit(background, (0, 0))
easy_map.update_map(time_delta)
easy_map.draw_map(self.screen, current_frame + 1)
manager.draw_ui(self.screen)
if not console_active:
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
easy_map.cam_y += self.camera_step
if keys[pygame.K_s]:
easy_map.cam_y -= self.camera_step
if keys[pygame.K_a]:
easy_map.cam_x += self.camera_step
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 event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
mouse_pos = pygame.mouse.get_pos()
console_rect = console_window.get_abs_rect()
if not console_rect.collidepoint(mouse_pos):
cell_coords = easy_map.get_cell_at_mouse(mouse_pos)
if cell_coords:
# Сбрасываем предыдущую цель
if active_cell is not None:
row, col = active_cell
if row in easy_map.cells and col < len(easy_map.cells[row]):
easy_map.cells[row][col].is_target = False
active_cell = cell_coords # Теперь кортеж (row, col)
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()
if (not console_rect.collidepoint(mouse_pos) and
active_cell is not None and
easy_map.cells[active_cell[0]][active_cell[1]].creature_obj is not None):
cell_coords = easy_map.get_cell_at_mouse(mouse_pos)
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)
if keys[pygame.K_ESCAPE]:
running = False
current_frame = (current_frame + 1) % max_fps
if global_counter < global_counter_cap:
global_counter += 1
else:
global_counter = 0
# flip() the display to put your work on screen
pygame.display.update()
if global_counter % 10 == 0:
current_fps = clock.get_fps()
#print(f"Current FPS: {current_fps:.2f}")
pygame.quit()