Files
Elvenbane/eb_engine.py
2026-02-20 02:09:43 +03:00

369 lines
14 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
@dataclass
class Map:
name: str
sprites: dict
sprites_refresh: int = 60
cells: dict = field(default_factory = dict)
color: str = "gray57"
target_color: str = "gold"
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.size
map_y = (my - self.cam_y * self.scale) / self.scale / self.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 draw_map(self, screen, current_frame, grid = True):
for j in range(len(self.cells)):
for i, cell in enumerate(self.cells[j]):
dd = {"x": int((i * self.size + self.cam_x) * self.scale),
"y": int((j * self.size + self.cam_y) * self.scale),
"w": int(self.size * self.scale - self.cell_dist),
"h": int(self.size * self.scale - self.cell_dist),
"spr_up": current_frame % self.sprites_refresh,
"sprites": self.sprites, "scale": self.scale,
"screen": screen}
cell.terrain_obj.draw(dd)
if cell.item_obj:
cell.item_obj.draw(dd)
if cell.creature_obj:
cell.creature_obj.draw(dd)
if grid:
if cell.is_target:
pygame.draw.rect(screen, self.target_color, pygame.Rect(dd["x"], dd["y"], dd["w"], dd["h"]), self.bord)
else:
pygame.draw.rect(screen, self.color, pygame.Rect(dd["x"], dd["y"], dd["w"], dd["h"]), self.bord)
def update_map(self, current_frame):
for j in range(len(self.cells)):
for i, cell in enumerate(self.cells[j]):
if cell.creature_obj is not None and len(cell.creature_obj.waypoints) > 1 and current_frame % 57 == 0:
del cell.creature_obj.waypoints[0]
self.move_obj("creature_obj", (j, i), (cell.creature_obj.waypoints[0]))
@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 = []
# 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(global_counter)
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:
easy_map.cells[active_cell[0]][active_cell[1]].is_target = False
active_cell = cell_coords
easy_map.cells[active_cell[0]][active_cell[1]].is_target = True
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
mouse_pos = pygame.mouse.get_pos()
cell_coords = easy_map.get_cell_at_mouse(mouse_pos)
console_rect = console_window.get_abs_rect()
if not console_rect.collidepoint(mouse_pos) and easy_map.cells[active_cell[0]][active_cell[1]].creature_obj is not None:
easy_map.cells[active_cell[0]][active_cell[1]].creature_obj.move(easy_map.cells, (active_cell[0], active_cell[1]), (cell_coords[0], cell_coords[1]))
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()