aether-engine 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aether/__init__.py +54 -0
- aether/audio/__init__.py +0 -0
- aether/audio/audio_manager.py +211 -0
- aether/audio/music.py +6 -0
- aether/audio/sound.py +6 -0
- aether/core/__init__.py +211 -0
- aether/core/achievement_system.py +304 -0
- aether/core/application.py +19 -0
- aether/core/config.py +75 -0
- aether/core/console.py +366 -0
- aether/core/engine.py +50 -0
- aether/core/event_bus.py +24 -0
- aether/core/input.py +58 -0
- aether/core/plugin_system.py +93 -0
- aether/core/save_system.py +409 -0
- aether/core/time_manager.py +25 -0
- aether/core/window.py +57 -0
- aether/graphics/__init__.py +0 -0
- aether/graphics/camera.py +69 -0
- aether/graphics/framebuffer.py +119 -0
- aether/graphics/light.py +32 -0
- aether/graphics/lod_system.py +170 -0
- aether/graphics/material.py +25 -0
- aether/graphics/mesh.py +83 -0
- aether/graphics/mesh_factory.py +67 -0
- aether/graphics/particle_system.py +268 -0
- aether/graphics/post_process.py +279 -0
- aether/graphics/renderer.py +135 -0
- aether/graphics/shader.py +61 -0
- aether/graphics/shader_library.py +157 -0
- aether/graphics/sky_atmosphere.py +309 -0
- aether/graphics/terrain.py +408 -0
- aether/graphics/texture.py +34 -0
- aether/graphics/water.py +432 -0
- aether/physics/__init__.py +0 -0
- aether/physics/bullet_backend.py +395 -0
- aether/physics/collider.py +22 -0
- aether/physics/physics_world.py +436 -0
- aether/physics/raycast.py +25 -0
- aether/physics/rigidbody.py +7 -0
- aether/resources/__init__.py +0 -0
- aether/resources/asset_database.py +76 -0
- aether/resources/resource_manager.py +147 -0
- aether/resources/serialization.py +114 -0
- aether/scene/__init__.py +0 -0
- aether/scene/component.py +368 -0
- aether/scene/entity.py +46 -0
- aether/scene/scene.py +33 -0
- aether/scene/scene_graph.py +252 -0
- aether/scene/scene_manager.py +191 -0
- aether/ui/__init__.py +0 -0
- aether/ui/button.py +297 -0
- aether/ui/canvas.py +11 -0
- aether/ui/text.py +286 -0
- aether/utils/__init__.py +0 -0
- aether/utils/logger.py +16 -0
- aether/utils/math_utils.py +31 -0
- aether/utils/object_pool.py +137 -0
- aether/utils/profiler.py +187 -0
- aether_engine-1.0.0.dist-info/METADATA +71 -0
- aether_engine-1.0.0.dist-info/RECORD +64 -0
- aether_engine-1.0.0.dist-info/WHEEL +5 -0
- aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
- aether_engine-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Граф сцены с иерархией объектов и трансформациями"""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional, List, Set, Dict
|
|
4
|
+
from aether.scene.entity import Entity
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SceneNode:
|
|
8
|
+
"""Узел графа сцены"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, entity: Entity):
|
|
11
|
+
self.entity = entity
|
|
12
|
+
self.parent: Optional['SceneNode'] = None
|
|
13
|
+
self.children: List['SceneNode'] = []
|
|
14
|
+
|
|
15
|
+
# Кэшированная мировая матрица
|
|
16
|
+
self._world_matrix_dirty = True
|
|
17
|
+
self._cached_world_matrix = np.identity(4, dtype=np.float32)
|
|
18
|
+
|
|
19
|
+
# Мировые трансформации (кэш)
|
|
20
|
+
self._world_position = np.array([0.0, 0.0, 0.0], dtype=np.float32)
|
|
21
|
+
self._world_rotation = np.array([0.0, 0.0, 0.0], dtype=np.float32)
|
|
22
|
+
self._world_scale = np.array([1.0, 1.0, 1.0], dtype=np.float32)
|
|
23
|
+
|
|
24
|
+
def add_child(self, child: 'SceneNode'):
|
|
25
|
+
"""Добавляет дочерний узел"""
|
|
26
|
+
if child.parent:
|
|
27
|
+
child.parent.remove_child(child)
|
|
28
|
+
|
|
29
|
+
child.parent = self
|
|
30
|
+
self.children.append(child)
|
|
31
|
+
child._mark_dirty()
|
|
32
|
+
|
|
33
|
+
def remove_child(self, child: 'SceneNode'):
|
|
34
|
+
"""Удаляет дочерний узел"""
|
|
35
|
+
if child in self.children:
|
|
36
|
+
self.children.remove(child)
|
|
37
|
+
child.parent = None
|
|
38
|
+
child._mark_dirty()
|
|
39
|
+
|
|
40
|
+
def set_parent(self, parent: Optional['SceneNode']):
|
|
41
|
+
"""Устанавливает родительский узел"""
|
|
42
|
+
if parent:
|
|
43
|
+
parent.add_child(self)
|
|
44
|
+
elif self.parent:
|
|
45
|
+
self.parent.remove_child(self)
|
|
46
|
+
|
|
47
|
+
def _mark_dirty(self):
|
|
48
|
+
"""Помечает матрицу как устаревшую (рекурсивно)"""
|
|
49
|
+
self._world_matrix_dirty = True
|
|
50
|
+
for child in self.children:
|
|
51
|
+
child._mark_dirty()
|
|
52
|
+
|
|
53
|
+
def get_world_matrix(self) -> np.ndarray:
|
|
54
|
+
"""Получает мировую матрицу с учётом иерархии"""
|
|
55
|
+
if not self._world_matrix_dirty:
|
|
56
|
+
return self._cached_world_matrix
|
|
57
|
+
|
|
58
|
+
# Получаем локальную матрицу
|
|
59
|
+
transform = self.entity.get_component("Transform")
|
|
60
|
+
if transform:
|
|
61
|
+
local_matrix = transform.get_model_matrix()
|
|
62
|
+
else:
|
|
63
|
+
local_matrix = np.identity(4, dtype=np.float32)
|
|
64
|
+
|
|
65
|
+
# Применяем родительскую матрицу
|
|
66
|
+
if self.parent:
|
|
67
|
+
parent_matrix = self.parent.get_world_matrix()
|
|
68
|
+
self._cached_world_matrix = parent_matrix @ local_matrix
|
|
69
|
+
else:
|
|
70
|
+
self._cached_world_matrix = local_matrix.copy()
|
|
71
|
+
|
|
72
|
+
# Извлекаем мировые компоненты
|
|
73
|
+
self._extract_world_components()
|
|
74
|
+
|
|
75
|
+
self._world_matrix_dirty = False
|
|
76
|
+
return self._cached_world_matrix
|
|
77
|
+
|
|
78
|
+
def _extract_world_components(self):
|
|
79
|
+
"""Извлекает позицию, поворот и масштаб из мировой матрицы"""
|
|
80
|
+
m = self._cached_world_matrix
|
|
81
|
+
|
|
82
|
+
# Позиция
|
|
83
|
+
self._world_position = m[3, :3].copy()
|
|
84
|
+
|
|
85
|
+
# Масштаб
|
|
86
|
+
scale_x = np.linalg.norm(m[0, :3])
|
|
87
|
+
scale_y = np.linalg.norm(m[1, :3])
|
|
88
|
+
scale_z = np.linalg.norm(m[2, :3])
|
|
89
|
+
self._world_scale = np.array([scale_x, scale_y, scale_z], dtype=np.float32)
|
|
90
|
+
|
|
91
|
+
# Поворот (упрощённо)
|
|
92
|
+
if scale_x > 0 and scale_y > 0 and scale_z > 0:
|
|
93
|
+
rot_matrix = m[:3, :3].copy()
|
|
94
|
+
rot_matrix[0, :3] /= scale_x
|
|
95
|
+
rot_matrix[1, :3] /= scale_y
|
|
96
|
+
rot_matrix[2, :3] /= scale_z
|
|
97
|
+
|
|
98
|
+
# Извлекаем углы Эйлера
|
|
99
|
+
self._world_rotation = self._matrix_to_euler(rot_matrix)
|
|
100
|
+
|
|
101
|
+
def _matrix_to_euler(self, m: np.ndarray) -> np.ndarray:
|
|
102
|
+
"""Конвертирует матрицу поворота в углы Эйлера"""
|
|
103
|
+
sy = np.sqrt(m[0, 0] ** 2 + m[1, 0] ** 2)
|
|
104
|
+
|
|
105
|
+
if sy > 1e-6:
|
|
106
|
+
x = np.arctan2(m[2, 1], m[2, 2])
|
|
107
|
+
y = np.arctan2(-m[2, 0], sy)
|
|
108
|
+
z = np.arctan2(m[1, 0], m[0, 0])
|
|
109
|
+
else:
|
|
110
|
+
x = np.arctan2(-m[1, 2], m[1, 1])
|
|
111
|
+
y = np.arctan2(-m[2, 0], sy)
|
|
112
|
+
z = 0.0
|
|
113
|
+
|
|
114
|
+
return np.degrees(np.array([x, y, z], dtype=np.float32))
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def world_position(self) -> np.ndarray:
|
|
118
|
+
"""Мировая позиция"""
|
|
119
|
+
self.get_world_matrix()
|
|
120
|
+
return self._world_position.copy()
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def world_rotation(self) -> np.ndarray:
|
|
124
|
+
"""Мировой поворот"""
|
|
125
|
+
self.get_world_matrix()
|
|
126
|
+
return self._world_rotation.copy()
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def world_scale(self) -> np.ndarray:
|
|
130
|
+
"""Мировой масштаб"""
|
|
131
|
+
self.get_world_matrix()
|
|
132
|
+
return self._world_scale.copy()
|
|
133
|
+
|
|
134
|
+
def forward(self) -> np.ndarray:
|
|
135
|
+
"""Направление вперёд в мировом пространстве"""
|
|
136
|
+
m = self.get_world_matrix()
|
|
137
|
+
forward = -m[2, :3]
|
|
138
|
+
return forward / np.linalg.norm(forward)
|
|
139
|
+
|
|
140
|
+
def right(self) -> np.ndarray:
|
|
141
|
+
"""Направление вправо в мировом пространстве"""
|
|
142
|
+
m = self.get_world_matrix()
|
|
143
|
+
right = m[0, :3]
|
|
144
|
+
return right / np.linalg.norm(right)
|
|
145
|
+
|
|
146
|
+
def up(self) -> np.ndarray:
|
|
147
|
+
"""Направление вверх в мировом пространстве"""
|
|
148
|
+
m = self.get_world_matrix()
|
|
149
|
+
up = m[1, :3]
|
|
150
|
+
return up / np.linalg.norm(up)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SceneGraph:
|
|
154
|
+
"""Граф сцены"""
|
|
155
|
+
|
|
156
|
+
def __init__(self):
|
|
157
|
+
self._nodes: Dict[str, SceneNode] = {}
|
|
158
|
+
self._root_nodes: List[SceneNode] = []
|
|
159
|
+
self._dirty = True
|
|
160
|
+
|
|
161
|
+
def register_entity(self, entity: Entity) -> SceneNode:
|
|
162
|
+
"""Регистрирует Entity в графе"""
|
|
163
|
+
if entity.id in self._nodes:
|
|
164
|
+
return self._nodes[entity.id]
|
|
165
|
+
|
|
166
|
+
node = SceneNode(entity)
|
|
167
|
+
self._nodes[entity.id] = node
|
|
168
|
+
self._root_nodes.append(node)
|
|
169
|
+
self._dirty = True
|
|
170
|
+
|
|
171
|
+
return node
|
|
172
|
+
|
|
173
|
+
def unregister_entity(self, entity: Entity):
|
|
174
|
+
"""Удаляет Entity из графа"""
|
|
175
|
+
if entity.id in self._nodes:
|
|
176
|
+
node = self._nodes[entity.id]
|
|
177
|
+
|
|
178
|
+
# Отсоединяем от родителя
|
|
179
|
+
if node.parent:
|
|
180
|
+
node.parent.remove_child(node)
|
|
181
|
+
|
|
182
|
+
# Удаляем из корневых
|
|
183
|
+
if node in self._root_nodes:
|
|
184
|
+
self._root_nodes.remove(node)
|
|
185
|
+
|
|
186
|
+
# Переносим детей в корень
|
|
187
|
+
for child in node.children[:]:
|
|
188
|
+
node.remove_child(child)
|
|
189
|
+
self._root_nodes.append(child)
|
|
190
|
+
|
|
191
|
+
del self._nodes[entity.id]
|
|
192
|
+
self._dirty = True
|
|
193
|
+
|
|
194
|
+
def set_parent(self, child_entity: Entity, parent_entity: Entity):
|
|
195
|
+
"""Устанавливает родительскую связь"""
|
|
196
|
+
child_node = self._nodes.get(child_entity.id)
|
|
197
|
+
parent_node = self._nodes.get(parent_entity.id)
|
|
198
|
+
|
|
199
|
+
if child_node and parent_node:
|
|
200
|
+
if child_node in self._root_nodes:
|
|
201
|
+
self._root_nodes.remove(child_node)
|
|
202
|
+
|
|
203
|
+
parent_node.add_child(child_node)
|
|
204
|
+
self._dirty = True
|
|
205
|
+
|
|
206
|
+
def get_node(self, entity: Entity) -> Optional[SceneNode]:
|
|
207
|
+
"""Получает узел по Entity"""
|
|
208
|
+
return self._nodes.get(entity.id)
|
|
209
|
+
|
|
210
|
+
def get_root_nodes(self) -> List[SceneNode]:
|
|
211
|
+
"""Возвращает корневые узлы"""
|
|
212
|
+
return self._root_nodes.copy()
|
|
213
|
+
|
|
214
|
+
def get_all_nodes(self) -> List[SceneNode]:
|
|
215
|
+
"""Возвращает все узлы"""
|
|
216
|
+
return list(self._nodes.values())
|
|
217
|
+
|
|
218
|
+
def update(self):
|
|
219
|
+
"""Обновляет все мировые матрицы"""
|
|
220
|
+
for root in self._root_nodes:
|
|
221
|
+
self._update_node_recursive(root)
|
|
222
|
+
|
|
223
|
+
self._dirty = False
|
|
224
|
+
|
|
225
|
+
def _update_node_recursive(self, node: SceneNode):
|
|
226
|
+
"""Рекурсивно обновляет узел и его детей"""
|
|
227
|
+
node._mark_dirty()
|
|
228
|
+
node.get_world_matrix()
|
|
229
|
+
|
|
230
|
+
for child in node.children:
|
|
231
|
+
self._update_node_recursive(child)
|
|
232
|
+
|
|
233
|
+
def find_entity_at_point(self, point: np.ndarray) -> Optional[Entity]:
|
|
234
|
+
"""Находит Entity в заданной точке (упрощённо)"""
|
|
235
|
+
for node in reversed(self.get_all_nodes()):
|
|
236
|
+
collider = node.entity.get_component("Collider") or \
|
|
237
|
+
node.entity.get_component("BoxCollider") or \
|
|
238
|
+
node.entity.get_component("SphereCollider")
|
|
239
|
+
|
|
240
|
+
if collider:
|
|
241
|
+
world_pos = node.world_position
|
|
242
|
+
# Простая проверка AABB (можно улучшить)
|
|
243
|
+
if isinstance(collider, BoxCollider):
|
|
244
|
+
min_point = world_pos + collider.center - collider.size / 2
|
|
245
|
+
max_point = world_pos + collider.center + collider.size / 2
|
|
246
|
+
|
|
247
|
+
if (min_point[0] <= point[0] <= max_point[0] and
|
|
248
|
+
min_point[1] <= point[1] <= max_point[1] and
|
|
249
|
+
min_point[2] <= point[2] <= max_point[2]):
|
|
250
|
+
return node.entity
|
|
251
|
+
|
|
252
|
+
return None
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Менеджер сцен — загрузка, выгрузка, переходы"""
|
|
2
|
+
from typing import Dict, Optional, List
|
|
3
|
+
from aether.scene.scene import Scene
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SceneTransition:
|
|
10
|
+
"""Переход между сценами"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, from_scene: str, to_scene: str,
|
|
13
|
+
transition_time: float = 1.0):
|
|
14
|
+
self.from_scene = from_scene
|
|
15
|
+
self.to_scene = to_scene
|
|
16
|
+
self.transition_time = transition_time
|
|
17
|
+
self.elapsed = 0.0
|
|
18
|
+
self.completed = False
|
|
19
|
+
self.progress = 0.0
|
|
20
|
+
|
|
21
|
+
def update(self, dt: float):
|
|
22
|
+
"""Обновляет прогресс перехода"""
|
|
23
|
+
self.elapsed += dt
|
|
24
|
+
self.progress = min(1.0, self.elapsed / self.transition_time)
|
|
25
|
+
if self.progress >= 1.0:
|
|
26
|
+
self.completed = True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SceneManager:
|
|
30
|
+
"""Менеджер сцен"""
|
|
31
|
+
|
|
32
|
+
_instance = None
|
|
33
|
+
|
|
34
|
+
def __new__(cls):
|
|
35
|
+
if cls._instance is None:
|
|
36
|
+
cls._instance = super().__new__(cls)
|
|
37
|
+
cls._instance._initialize()
|
|
38
|
+
return cls._instance
|
|
39
|
+
|
|
40
|
+
def _initialize(self):
|
|
41
|
+
"""Инициализация менеджера"""
|
|
42
|
+
self._scenes: Dict[str, Scene] = {}
|
|
43
|
+
self._active_scene: Optional[Scene] = None
|
|
44
|
+
self._scene_queue: List[str] = []
|
|
45
|
+
self._transition: Optional[SceneTransition] = None
|
|
46
|
+
self._loading = False
|
|
47
|
+
|
|
48
|
+
# Пути для сохранения сцен
|
|
49
|
+
self._scenes_path = Path("./scenes")
|
|
50
|
+
|
|
51
|
+
print("[SceneManager] Initialized")
|
|
52
|
+
|
|
53
|
+
def register_scene(self, scene: Scene):
|
|
54
|
+
"""Регистрирует сцену"""
|
|
55
|
+
self._scenes[scene.name] = scene
|
|
56
|
+
print(f"[SceneManager] Registered scene: {scene.name}")
|
|
57
|
+
|
|
58
|
+
def unregister_scene(self, name: str):
|
|
59
|
+
"""Удаляет сцену"""
|
|
60
|
+
if name in self._scenes:
|
|
61
|
+
if self._active_scene and self._active_scene.name == name:
|
|
62
|
+
self._active_scene.destroy()
|
|
63
|
+
self._active_scene = None
|
|
64
|
+
|
|
65
|
+
del self._scenes[name]
|
|
66
|
+
|
|
67
|
+
def load_scene(self, name: str, additive: bool = False):
|
|
68
|
+
"""Загружает сцену"""
|
|
69
|
+
if name not in self._scenes:
|
|
70
|
+
print(f"[SceneManager] Scene '{name}' not found!")
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
# Если есть активная сцена и не аддитивная загрузка
|
|
74
|
+
if self._active_scene and not additive:
|
|
75
|
+
self._active_scene.destroy()
|
|
76
|
+
|
|
77
|
+
scene = self._scenes[name]
|
|
78
|
+
|
|
79
|
+
if not additive:
|
|
80
|
+
self._active_scene = scene
|
|
81
|
+
else:
|
|
82
|
+
# Аддитивная загрузка — добавляем объекты к текущей сцене
|
|
83
|
+
if self._active_scene:
|
|
84
|
+
for entity in scene.entities:
|
|
85
|
+
self._active_scene.spawn(entity)
|
|
86
|
+
|
|
87
|
+
scene.awake()
|
|
88
|
+
scene.start()
|
|
89
|
+
|
|
90
|
+
print(f"[SceneManager] Loaded scene: {name}")
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def load_scene_async(self, name: str):
|
|
94
|
+
"""Асинхронная загрузка сцены"""
|
|
95
|
+
import threading
|
|
96
|
+
|
|
97
|
+
def _load():
|
|
98
|
+
self._loading = True
|
|
99
|
+
success = self.load_scene(name)
|
|
100
|
+
self._loading = False
|
|
101
|
+
if success:
|
|
102
|
+
print(f"[SceneManager] Async load complete: {name}")
|
|
103
|
+
|
|
104
|
+
thread = threading.Thread(target=_load, daemon=True)
|
|
105
|
+
thread.start()
|
|
106
|
+
|
|
107
|
+
def transition_to(self, name: str, transition_time: float = 1.0):
|
|
108
|
+
"""Переход к новой сцене с эффектом"""
|
|
109
|
+
if name not in self._scenes:
|
|
110
|
+
print(f"[SceneManager] Scene '{name}' not found!")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if self._active_scene:
|
|
114
|
+
self._transition = SceneTransition(
|
|
115
|
+
self._active_scene.name, name, transition_time
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def update(self, dt: float):
|
|
119
|
+
"""Обновляет менеджер"""
|
|
120
|
+
if self._transition:
|
|
121
|
+
self._transition.update(dt)
|
|
122
|
+
|
|
123
|
+
if self._transition.completed:
|
|
124
|
+
self.load_scene(self._transition.to_scene)
|
|
125
|
+
self._transition = None
|
|
126
|
+
|
|
127
|
+
def get_active_scene(self) -> Optional[Scene]:
|
|
128
|
+
"""Возвращает активную сцену"""
|
|
129
|
+
return self._active_scene
|
|
130
|
+
|
|
131
|
+
def get_scene(self, name: str) -> Optional[Scene]:
|
|
132
|
+
"""Возвращает сцену по имени"""
|
|
133
|
+
return self._scenes.get(name)
|
|
134
|
+
|
|
135
|
+
def get_all_scenes(self) -> Dict[str, Scene]:
|
|
136
|
+
"""Возвращает все сцены"""
|
|
137
|
+
return self._scenes.copy()
|
|
138
|
+
|
|
139
|
+
def save_scene(self, name: str, path: Optional[str] = None):
|
|
140
|
+
"""Сохраняет сцену в файл"""
|
|
141
|
+
scene = self._scenes.get(name)
|
|
142
|
+
if not scene:
|
|
143
|
+
print(f"[SceneManager] Scene '{name}' not found!")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
if path is None:
|
|
147
|
+
os.makedirs(self._scenes_path, exist_ok=True)
|
|
148
|
+
path = self._scenes_path / f"{name}.json"
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
from aether.resources.serialization import SceneSerializer, Serializer
|
|
152
|
+
|
|
153
|
+
serializer = Serializer()
|
|
154
|
+
scene_serializer = SceneSerializer(serializer)
|
|
155
|
+
scene_serializer.save_scene(scene, str(path))
|
|
156
|
+
|
|
157
|
+
print(f"[SceneManager] Saved scene '{name}' to {path}")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print(f"[SceneManager] Failed to save scene: {e}")
|
|
160
|
+
|
|
161
|
+
def load_scene_from_file(self, path: str) -> Optional[Scene]:
|
|
162
|
+
"""Загружает сцену из файла"""
|
|
163
|
+
try:
|
|
164
|
+
from aether.resources.serialization import SceneSerializer, Serializer
|
|
165
|
+
|
|
166
|
+
serializer = Serializer()
|
|
167
|
+
scene_serializer = SceneSerializer(serializer)
|
|
168
|
+
scene = scene_serializer.load_scene(path)
|
|
169
|
+
|
|
170
|
+
self.register_scene(scene)
|
|
171
|
+
print(f"[SceneManager] Loaded scene from {path}")
|
|
172
|
+
|
|
173
|
+
return scene
|
|
174
|
+
except Exception as e:
|
|
175
|
+
print(f"[SceneManager] Failed to load scene: {e}")
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def is_transitioning(self) -> bool:
|
|
180
|
+
"""Идёт ли переход между сценами"""
|
|
181
|
+
return self._transition is not None
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def is_loading(self) -> bool:
|
|
185
|
+
"""Идёт ли загрузка сцены"""
|
|
186
|
+
return self._loading
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def transition_progress(self) -> float:
|
|
190
|
+
"""Прогресс перехода (0.0 - 1.0)"""
|
|
191
|
+
return self._transition.progress if self._transition else 0.0
|
aether/ui/__init__.py
ADDED
|
File without changes
|