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.
Files changed (64) hide show
  1. aether/__init__.py +54 -0
  2. aether/audio/__init__.py +0 -0
  3. aether/audio/audio_manager.py +211 -0
  4. aether/audio/music.py +6 -0
  5. aether/audio/sound.py +6 -0
  6. aether/core/__init__.py +211 -0
  7. aether/core/achievement_system.py +304 -0
  8. aether/core/application.py +19 -0
  9. aether/core/config.py +75 -0
  10. aether/core/console.py +366 -0
  11. aether/core/engine.py +50 -0
  12. aether/core/event_bus.py +24 -0
  13. aether/core/input.py +58 -0
  14. aether/core/plugin_system.py +93 -0
  15. aether/core/save_system.py +409 -0
  16. aether/core/time_manager.py +25 -0
  17. aether/core/window.py +57 -0
  18. aether/graphics/__init__.py +0 -0
  19. aether/graphics/camera.py +69 -0
  20. aether/graphics/framebuffer.py +119 -0
  21. aether/graphics/light.py +32 -0
  22. aether/graphics/lod_system.py +170 -0
  23. aether/graphics/material.py +25 -0
  24. aether/graphics/mesh.py +83 -0
  25. aether/graphics/mesh_factory.py +67 -0
  26. aether/graphics/particle_system.py +268 -0
  27. aether/graphics/post_process.py +279 -0
  28. aether/graphics/renderer.py +135 -0
  29. aether/graphics/shader.py +61 -0
  30. aether/graphics/shader_library.py +157 -0
  31. aether/graphics/sky_atmosphere.py +309 -0
  32. aether/graphics/terrain.py +408 -0
  33. aether/graphics/texture.py +34 -0
  34. aether/graphics/water.py +432 -0
  35. aether/physics/__init__.py +0 -0
  36. aether/physics/bullet_backend.py +395 -0
  37. aether/physics/collider.py +22 -0
  38. aether/physics/physics_world.py +436 -0
  39. aether/physics/raycast.py +25 -0
  40. aether/physics/rigidbody.py +7 -0
  41. aether/resources/__init__.py +0 -0
  42. aether/resources/asset_database.py +76 -0
  43. aether/resources/resource_manager.py +147 -0
  44. aether/resources/serialization.py +114 -0
  45. aether/scene/__init__.py +0 -0
  46. aether/scene/component.py +368 -0
  47. aether/scene/entity.py +46 -0
  48. aether/scene/scene.py +33 -0
  49. aether/scene/scene_graph.py +252 -0
  50. aether/scene/scene_manager.py +191 -0
  51. aether/ui/__init__.py +0 -0
  52. aether/ui/button.py +297 -0
  53. aether/ui/canvas.py +11 -0
  54. aether/ui/text.py +286 -0
  55. aether/utils/__init__.py +0 -0
  56. aether/utils/logger.py +16 -0
  57. aether/utils/math_utils.py +31 -0
  58. aether/utils/object_pool.py +137 -0
  59. aether/utils/profiler.py +187 -0
  60. aether_engine-1.0.0.dist-info/METADATA +71 -0
  61. aether_engine-1.0.0.dist-info/RECORD +64 -0
  62. aether_engine-1.0.0.dist-info/WHEEL +5 -0
  63. aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
  64. aether_engine-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,114 @@
1
+ """Сериализация сцен и префабов"""
2
+ import json
3
+ import yaml
4
+ from typing import Dict, Any, Type
5
+ from aether.scene.entity import Entity
6
+ from aether.scene.component import Component
7
+
8
+
9
+ class Serializer:
10
+ """Сериализатор игровых объектов"""
11
+
12
+ def __init__(self):
13
+ self._component_serializers: Dict[str, Type['ComponentSerializer']] = {}
14
+
15
+ def register_component(self, component_class, serializer):
16
+ """Регистрирует сериализатор для компонента"""
17
+ self._component_serializers[component_class.__name__] = serializer
18
+
19
+ def serialize_entity(self, entity: Entity) -> dict:
20
+ """Сериализует Entity в словарь"""
21
+ data = {
22
+ 'id': entity.id,
23
+ 'name': entity.name,
24
+ 'tag': entity.tag,
25
+ 'enabled': entity.enabled,
26
+ 'components': {}
27
+ }
28
+
29
+ for comp_name, component in entity.components.items():
30
+ if comp_name in self._component_serializers:
31
+ serializer = self._component_serializers[comp_name]
32
+ data['components'][comp_name] = serializer.serialize(component)
33
+
34
+ if entity.children:
35
+ data['children'] = [
36
+ self.serialize_entity(child)
37
+ for child in entity.children
38
+ ]
39
+
40
+ return data
41
+
42
+ def deserialize_entity(self, data: dict) -> Entity:
43
+ """Создаёт Entity из словаря"""
44
+ entity = Entity(name=data['name'], tag=data.get('tag', ''))
45
+ entity.id = data.get('id', entity.id)
46
+ entity.enabled = data.get('enabled', True)
47
+
48
+ for comp_name, comp_data in data.get('components', {}).items():
49
+ if comp_name in self._component_serializers:
50
+ serializer = self._component_serializers[comp_name]
51
+ component = serializer.deserialize(comp_data)
52
+ entity.add_component(component)
53
+
54
+ for child_data in data.get('children', []):
55
+ child = self.deserialize_entity(child_data)
56
+ entity.add_child(child)
57
+
58
+ return entity
59
+
60
+
61
+ class ComponentSerializer(ABC):
62
+ """Базовый класс сериализатора компонентов"""
63
+
64
+ @abstractmethod
65
+ def serialize(self, component: Component) -> dict:
66
+ pass
67
+
68
+ @abstractmethod
69
+ def deserialize(self, data: dict) -> Component:
70
+ pass
71
+
72
+
73
+ class SceneSerializer:
74
+ """Сериализатор целых сцен"""
75
+
76
+ def __init__(self, entity_serializer: Serializer):
77
+ self.entity_serializer = entity_serializer
78
+
79
+ def save_scene(self, scene, path: str):
80
+ """Сохраняет сцену в файл"""
81
+ data = {
82
+ 'name': scene.name,
83
+ 'version': '1.0',
84
+ 'entities': []
85
+ }
86
+
87
+ for entity in scene.entities:
88
+ entity_data = self.entity_serializer.serialize_entity(entity)
89
+ data['entities'].append(entity_data)
90
+
91
+ with open(path, 'w') as f:
92
+ if path.endswith('.json'):
93
+ json.dump(data, f, indent=2)
94
+ elif path.endswith('.yaml') or path.endswith('.yml'):
95
+ yaml.dump(data, f, default_flow_style=False)
96
+
97
+ def load_scene(self, path: str) -> 'Scene':
98
+ """Загружает сцену из файла"""
99
+ with open(path, 'r') as f:
100
+ if path.endswith('.json'):
101
+ data = json.load(f)
102
+ elif path.endswith('.yaml') or path.endswith('.yml'):
103
+ data = yaml.safe_load(f)
104
+ else:
105
+ raise ValueError(f"Unsupported format: {path}")
106
+
107
+ from aether.scene.scene import Scene
108
+ scene = Scene(name=data['name'])
109
+
110
+ for entity_data in data.get('entities', []):
111
+ entity = self.entity_serializer.deserialize_entity(entity_data)
112
+ scene.spawn(entity)
113
+
114
+ return scene
File without changes
@@ -0,0 +1,368 @@
1
+ """Компоненты для Entity"""
2
+ import numpy as np
3
+ from abc import ABC, abstractmethod
4
+ from OpenGL.GL import *
5
+ from aether.graphics.shader import Shader
6
+ from aether.graphics.shader_library import ShaderLibrary
7
+
8
+
9
+ class Component(ABC):
10
+ """Базовый класс компонента"""
11
+
12
+ def __init__(self):
13
+ self.entity = None
14
+ self.enabled = True
15
+ self._started = False
16
+
17
+ def awake(self):
18
+ """Вызывается при добавлении компонента"""
19
+ pass
20
+
21
+ def start(self):
22
+ """Вызывается один раз перед первым update"""
23
+ self._started = True
24
+
25
+ @abstractmethod
26
+ def update(self, dt: float):
27
+ """Вызывается каждый кадр"""
28
+ pass
29
+
30
+ def on_enable(self):
31
+ """Вызывается при включении компонента"""
32
+ pass
33
+
34
+ def on_disable(self):
35
+ """Вызывается при выключении компонента"""
36
+ pass
37
+
38
+ def destroy(self):
39
+ """Вызывается при удалении компонента"""
40
+ pass
41
+
42
+
43
+ class Transform(Component):
44
+ """Компонент трансформации"""
45
+
46
+ def __init__(self, position=(0, 0, 0), rotation=(0, 0, 0), scale=(1, 1, 1)):
47
+ super().__init__()
48
+ self.position = np.array(position, dtype=np.float32)
49
+ self.rotation = np.array(rotation, dtype=np.float32) # Euler angles
50
+ self.scale = np.array(scale, dtype=np.float32)
51
+
52
+ # Кэшированная матрица
53
+ self._matrix_dirty = True
54
+ self._cached_matrix = np.identity(4, dtype=np.float32)
55
+
56
+ def update(self, dt: float):
57
+ if self._matrix_dirty:
58
+ self._update_matrix()
59
+
60
+ def _update_matrix(self):
61
+ """Обновляет модельную матрицу"""
62
+ # Translate
63
+ m = np.identity(4, dtype=np.float32)
64
+ m[3, :3] = self.position
65
+
66
+ # Rotation
67
+ rx, ry, rz = np.radians(self.rotation)
68
+
69
+ # Rotate X
70
+ cx, sx = np.cos(rx), np.sin(rx)
71
+ rot_x = np.array([
72
+ [1, 0, 0],
73
+ [0, cx, -sx],
74
+ [0, sx, cx]
75
+ ], dtype=np.float32)
76
+
77
+ # Rotate Y
78
+ cy, sy = np.cos(ry), np.sin(ry)
79
+ rot_y = np.array([
80
+ [cy, 0, sy],
81
+ [0, 1, 0],
82
+ [-sy, 0, cy]
83
+ ], dtype=np.float32)
84
+
85
+ # Rotate Z
86
+ cz, sz = np.cos(rz), np.sin(rz)
87
+ rot_z = np.array([
88
+ [cz, -sz, 0],
89
+ [sz, cz, 0],
90
+ [0, 0, 1]
91
+ ], dtype=np.float32)
92
+
93
+ # Комбинированная ротация
94
+ rot = rot_z @ rot_y @ rot_x
95
+
96
+ # Применяем ротацию и скейл
97
+ m[:3, :3] = rot * self.scale
98
+
99
+ self._cached_matrix = m
100
+ self._matrix_dirty = False
101
+
102
+ def get_model_matrix(self) -> np.ndarray:
103
+ """Возвращает модельную матрицу (с кэшированием)"""
104
+ if self._matrix_dirty:
105
+ self._update_matrix()
106
+ return self._cached_matrix
107
+
108
+ def translate(self, dx, dy, dz):
109
+ """Перемещает объект"""
110
+ self.position += np.array([dx, dy, dz], dtype=np.float32)
111
+ self._matrix_dirty = True
112
+
113
+ def set_position(self, x, y, z):
114
+ """Устанавливает позицию"""
115
+ self.position = np.array([x, y, z], dtype=np.float32)
116
+ self._matrix_dirty = True
117
+
118
+ def rotate(self, rx, ry, rz):
119
+ """Поворачивает объект"""
120
+ self.rotation += np.array([rx, ry, rz], dtype=np.float32)
121
+ self._matrix_dirty = True
122
+
123
+ def look_at(self, target: np.ndarray):
124
+ """Поворачивает объект в сторону цели"""
125
+ direction = target - self.position
126
+ direction = direction / np.linalg.norm(direction)
127
+
128
+ # Вычисляем углы Эйлера из направления
129
+ self.rotation[0] = np.degrees(np.arcsin(-direction[1]))
130
+ self.rotation[1] = np.degrees(np.arctan2(-direction[0], -direction[2]))
131
+ self._matrix_dirty = True
132
+
133
+
134
+ class MeshRenderer(Component):
135
+ """Компонент рендеринга меша"""
136
+
137
+ def __init__(self, mesh=None, material=None, cast_shadows=True, receive_shadows=True):
138
+ super().__init__()
139
+ self.mesh = mesh
140
+ self.material = material
141
+ self.visible = True
142
+ self.cast_shadows = cast_shadows
143
+ self.receive_shadows = receive_shadows
144
+ self._shader_library = ShaderLibrary()
145
+
146
+ def update(self, dt: float):
147
+ pass
148
+
149
+ def render(self, view: np.ndarray, projection: np.ndarray):
150
+ """Рендерит меш"""
151
+ if not self.mesh or not self.material or not self.visible:
152
+ return
153
+
154
+ if not self.entity:
155
+ return
156
+
157
+ transform = self.entity.get_component("Transform")
158
+ if not transform:
159
+ return
160
+
161
+ # Получаем шейдер
162
+ shader = self._shader_library.get_pbr_shader()
163
+ if not shader:
164
+ return
165
+
166
+ shader.use()
167
+
168
+ # Устанавливаем матрицы
169
+ model = transform.get_model_matrix()
170
+ shader.set_mat4("model", model)
171
+ shader.set_mat4("view", view)
172
+ shader.set_mat4("projection", projection)
173
+
174
+ # Устанавливаем свойства материала
175
+ shader.set_vec3("albedo", self.material.albedo)
176
+ shader.set_float("metallic", self.material.metallic)
177
+ shader.set_float("roughness", self.material.roughness)
178
+ shader.set_float("ao", self.material.ao)
179
+
180
+ # Устанавливаем текстуры
181
+ if self.material.albedo_map:
182
+ self.material.albedo_map.bind(0)
183
+ shader.set_int("albedoMap", 0)
184
+
185
+ if self.material.normal_map:
186
+ self.material.normal_map.bind(1)
187
+ shader.set_int("normalMap", 1)
188
+
189
+ if self.material.metallic_map:
190
+ self.material.metallic_map.bind(2)
191
+ shader.set_int("metallicMap", 2)
192
+
193
+ if self.material.roughness_map:
194
+ self.material.roughness_map.bind(3)
195
+ shader.set_int("roughnessMap", 3)
196
+
197
+ # Рендерим меш
198
+ self.mesh.draw()
199
+
200
+ def destroy(self):
201
+ """Очищает ресурсы"""
202
+ if self.mesh:
203
+ self.mesh.cleanup()
204
+ self.mesh = None
205
+ self.material = None
206
+
207
+
208
+ class CameraComponent(Component):
209
+ """Компонент камеры"""
210
+
211
+ def __init__(self, camera=None):
212
+ super().__init__()
213
+ self.camera = camera
214
+ self.is_main = False
215
+
216
+ def update(self, dt: float):
217
+ pass
218
+
219
+ def set_as_main(self):
220
+ """Делает камеру основной в сцене"""
221
+ if self.entity and hasattr(self.entity, 'scene'):
222
+ self.entity.scene.set_main_camera(self.camera)
223
+ self.is_main = True
224
+
225
+
226
+ class LightComponent(Component):
227
+ """Компонент источника света"""
228
+
229
+ def __init__(self, light=None):
230
+ super().__init__()
231
+ self.light = light
232
+ self.intensity_multiplier = 1.0
233
+
234
+ def update(self, dt: float):
235
+ if self.light and self.entity:
236
+ transform = self.entity.get_component("Transform")
237
+ if transform:
238
+ self.light.position = transform.position
239
+ self.light.direction = -transform.position / np.linalg.norm(transform.position)
240
+
241
+
242
+ class RigidbodyComponent(Component):
243
+ """Компонент физического тела"""
244
+
245
+ def __init__(self, mass=1.0, use_gravity=True, is_kinematic=False):
246
+ super().__init__()
247
+ self.mass = mass
248
+ self.use_gravity = use_gravity
249
+ self.is_kinematic = is_kinematic
250
+ self.velocity = np.array([0.0, 0.0, 0.0], dtype=np.float32)
251
+ self.angular_velocity = np.array([0.0, 0.0, 0.0], dtype=np.float32)
252
+ self.drag = 0.0
253
+ self.angular_drag = 0.05
254
+
255
+ def update(self, dt: float):
256
+ if self.is_kinematic:
257
+ return
258
+
259
+ transform = self.entity.get_component("Transform")
260
+ if not transform:
261
+ return
262
+
263
+ # Применяем drag
264
+ self.velocity *= (1.0 - self.drag * dt)
265
+ self.angular_velocity *= (1.0 - self.angular_drag * dt)
266
+
267
+ # Обновляем позицию
268
+ transform.position += self.velocity * dt
269
+ transform.rotation += self.angular_velocity * dt
270
+ transform._matrix_dirty = True
271
+
272
+ def add_force(self, force: np.ndarray):
273
+ """Добавляет силу"""
274
+ if not self.is_kinematic and self.mass > 0:
275
+ self.velocity += force / self.mass
276
+
277
+ def add_impulse(self, impulse: np.ndarray):
278
+ """Мгновенный импульс"""
279
+ if not self.is_kinematic and self.mass > 0:
280
+ self.velocity += impulse / self.mass
281
+
282
+
283
+ class ScriptComponent(Component):
284
+ """Компонент для пользовательских скриптов"""
285
+
286
+ def __init__(self):
287
+ super().__init__()
288
+ self._scripts = []
289
+
290
+ def add_script(self, script):
291
+ """Добавляет скрипт"""
292
+ self._scripts.append(script)
293
+ if hasattr(script, 'entity'):
294
+ script.entity = self.entity
295
+ if hasattr(script, 'awake'):
296
+ script.awake()
297
+ if hasattr(script, 'start'):
298
+ script.start()
299
+
300
+ def update(self, dt: float):
301
+ for script in self._scripts:
302
+ if hasattr(script, 'update'):
303
+ script.update(dt)
304
+
305
+
306
+ class AnimatorComponent(Component):
307
+ """Компонент анимации"""
308
+
309
+ def __init__(self):
310
+ super().__init__()
311
+ self.animations = {}
312
+ self.current_animation = None
313
+ self.playback_speed = 1.0
314
+ self._time = 0.0
315
+ self._playing = False
316
+ self._loop = True
317
+
318
+ def add_animation(self, name: str, animation):
319
+ """Добавляет анимацию"""
320
+ self.animations[name] = animation
321
+
322
+ def play(self, name: str, loop: bool = True):
323
+ """Проигрывает анимацию"""
324
+ if name in self.animations:
325
+ self.current_animation = name
326
+ self._time = 0.0
327
+ self._playing = True
328
+ self._loop = loop
329
+
330
+ def stop(self):
331
+ """Останавливает анимацию"""
332
+ self._playing = False
333
+ self._time = 0.0
334
+
335
+ def pause(self):
336
+ """Ставит на паузу"""
337
+ self._playing = False
338
+
339
+ def resume(self):
340
+ """Продолжает проигрывание"""
341
+ self._playing = True
342
+
343
+ def update(self, dt: float):
344
+ if not self._playing or not self.current_animation:
345
+ return
346
+
347
+ anim = self.animations.get(self.current_animation)
348
+ if not anim:
349
+ return
350
+
351
+ self._time += dt * self.playback_speed
352
+
353
+ if self._time >= anim.duration:
354
+ if self._loop:
355
+ self._time %= anim.duration
356
+ else:
357
+ self._time = anim.duration
358
+ self._playing = False
359
+
360
+ # Применяем анимацию к трансформу
361
+ transform = self.entity.get_component("Transform")
362
+ if transform:
363
+ frame_data = anim.get_frame(self._time)
364
+ if frame_data:
365
+ transform.position = frame_data.get('position', transform.position)
366
+ transform.rotation = frame_data.get('rotation', transform.rotation)
367
+ transform.scale = frame_data.get('scale', transform.scale)
368
+ transform._matrix_dirty = True
aether/scene/entity.py ADDED
@@ -0,0 +1,46 @@
1
+ """Игровой объект"""
2
+ from typing import Dict, Optional, List
3
+ import uuid
4
+
5
+
6
+ class Entity:
7
+ def __init__(self, name="Entity", tag=""):
8
+ self.id = str(uuid.uuid4())
9
+ self.name = name
10
+ self.tag = tag
11
+ self.enabled = True
12
+ self.components: Dict[str, 'Component'] = {}
13
+ self.children: List['Entity'] = []
14
+ self.parent: Optional['Entity'] = None
15
+
16
+ def add_component(self, component: 'Component'):
17
+ component.entity = self
18
+ self.components[component.__class__.__name__] = component
19
+ component.awake()
20
+ component.start()
21
+ return component
22
+
23
+ def get_component(self, name: str) -> Optional['Component']:
24
+ return self.components.get(name)
25
+
26
+ def remove_component(self, name: str):
27
+ if name in self.components:
28
+ self.components[name].destroy()
29
+ del self.components[name]
30
+
31
+ def add_child(self, child: 'Entity'):
32
+ child.parent = self
33
+ self.children.append(child)
34
+
35
+ def update(self, dt: float):
36
+ for comp in self.components.values():
37
+ if comp.enabled:
38
+ comp.update(dt)
39
+ for child in self.children:
40
+ child.update(dt)
41
+
42
+ def destroy(self):
43
+ for comp in list(self.components.values()):
44
+ comp.destroy()
45
+ for child in self.children:
46
+ child.destroy()
aether/scene/scene.py ADDED
@@ -0,0 +1,33 @@
1
+ """Сцена"""
2
+ from typing import List, Optional
3
+ from aether.scene.entity import Entity
4
+ from aether.graphics.camera import Camera
5
+
6
+
7
+ class Scene:
8
+ def __init__(self, name="Scene"):
9
+ self.name = name
10
+ self.entities: List[Entity] = []
11
+ self.main_camera: Optional[Camera] = None
12
+
13
+ def spawn(self, entity: Entity):
14
+ self.entities.append(entity)
15
+ return entity
16
+
17
+ def set_main_camera(self, camera: Camera):
18
+ self.main_camera = camera
19
+
20
+ def awake(self):
21
+ pass
22
+
23
+ def start(self):
24
+ pass
25
+
26
+ def update(self, dt: float):
27
+ for entity in self.entities:
28
+ if entity.enabled:
29
+ entity.update(dt)
30
+
31
+ def destroy(self):
32
+ for entity in self.entities:
33
+ entity.destroy()