aether-engine 1.1.0__tar.gz → 1.2.0__tar.gz

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 (103) hide show
  1. aether_engine-1.2.0/PKG-INFO +122 -0
  2. aether_engine-1.2.0/README.md +74 -0
  3. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/__init__.py +11 -2
  4. aether_engine-1.2.0/aether/audio/sfx.py +85 -0
  5. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/engine.py +15 -2
  6. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics2d/__init__.py +4 -3
  7. aether_engine-1.2.0/aether/physics/raycast.py +102 -0
  8. aether_engine-1.2.0/aether/resources/save_load.py +20 -0
  9. aether_engine-1.2.0/aether/ui/__init__.py +5 -0
  10. aether_engine-1.2.0/aether/ui/hud.py +65 -0
  11. aether_engine-1.2.0/aether_engine.egg-info/PKG-INFO +122 -0
  12. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether_engine.egg-info/SOURCES.txt +3 -0
  13. {aether_engine-1.1.0 → aether_engine-1.2.0}/setup.py +2 -2
  14. aether_engine-1.1.0/PKG-INFO +0 -155
  15. aether_engine-1.1.0/README.md +0 -107
  16. aether_engine-1.1.0/aether/physics/raycast.py +0 -25
  17. aether_engine-1.1.0/aether/utils/__init__.py +0 -0
  18. aether_engine-1.1.0/aether_engine.egg-info/PKG-INFO +0 -155
  19. {aether_engine-1.1.0 → aether_engine-1.2.0}/LICENSE +0 -0
  20. {aether_engine-1.1.0 → aether_engine-1.2.0}/MANIFEST.in +0 -0
  21. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/ai/behavior_tree.py +0 -0
  22. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/ai/navmesh.py +0 -0
  23. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/animation/skeleton.py +0 -0
  24. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/assets/asset_pipeline.py +0 -0
  25. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/audio/__init__.py +0 -0
  26. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/audio/audio_manager.py +0 -0
  27. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/audio/music.py +0 -0
  28. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/audio/sound.py +0 -0
  29. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/__init__.py +0 -0
  30. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/achievement_system.py +0 -0
  31. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/application.py +0 -0
  32. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/config.py +0 -0
  33. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/console.py +0 -0
  34. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/event_bus.py +0 -0
  35. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/input.py +0 -0
  36. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/plugin_system.py +0 -0
  37. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/save_system.py +0 -0
  38. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/time_manager.py +0 -0
  39. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/core/window.py +0 -0
  40. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/editor/__init__.py +0 -0
  41. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/editor/__main__.py +0 -0
  42. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/editor/gizmo.py +0 -0
  43. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/editor/imgui_integration.py +0 -0
  44. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/editor/model_editor.py +0 -0
  45. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/environment/environment_system.py +0 -0
  46. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/__init__.py +0 -0
  47. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/camera.py +0 -0
  48. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/first_person.py +0 -0
  49. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/framebuffer.py +0 -0
  50. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/light.py +0 -0
  51. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/lod_system.py +0 -0
  52. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/material.py +0 -0
  53. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/mesh.py +0 -0
  54. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/mesh_factory.py +0 -0
  55. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/particle_system.py +0 -0
  56. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/post_process.py +0 -0
  57. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/renderer.py +0 -0
  58. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/shader.py +0 -0
  59. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/shader_library.py +0 -0
  60. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/sky_atmosphere.py +0 -0
  61. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/terrain.py +0 -0
  62. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/texture.py +0 -0
  63. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/graphics/water.py +0 -0
  64. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/network/network_manager.py +0 -0
  65. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/__init__.py +0 -0
  66. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/bullet_backend.py +0 -0
  67. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/collider.py +0 -0
  68. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/physics2d.py +0 -0
  69. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/physics3d.py +0 -0
  70. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/physics_world.py +0 -0
  71. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/physics/rigidbody.py +0 -0
  72. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/platform/steam_api.py +0 -0
  73. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/__init__.py +0 -0
  74. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/asset_database.py +0 -0
  75. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/loaders/model_loader.py +0 -0
  76. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/loaders/obj_loader.py +0 -0
  77. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/loaders/texture_loader.py +0 -0
  78. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/resource_manager.py +0 -0
  79. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/resources/serialization.py +0 -0
  80. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/scene/__init__.py +0 -0
  81. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/scene/component.py +0 -0
  82. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/scene/entity.py +0 -0
  83. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/scene/scene.py +0 -0
  84. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/scene/scene_graph.py +0 -0
  85. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/scene/scene_manager.py +0 -0
  86. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/ui/button.py +0 -0
  87. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/ui/canvas.py +0 -0
  88. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/ui/text.py +0 -0
  89. {aether_engine-1.1.0/aether/ui → aether_engine-1.2.0/aether/utils}/__init__.py +0 -0
  90. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/utils/logger.py +0 -0
  91. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/utils/math_utils.py +0 -0
  92. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/utils/object_pool.py +0 -0
  93. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether/utils/profiler.py +0 -0
  94. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether_engine.egg-info/dependency_links.txt +0 -0
  95. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether_engine.egg-info/entry_points.txt +0 -0
  96. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether_engine.egg-info/requires.txt +3 -3
  97. {aether_engine-1.1.0 → aether_engine-1.2.0}/aether_engine.egg-info/top_level.txt +0 -0
  98. {aether_engine-1.1.0 → aether_engine-1.2.0}/examples/01_hello_cube.py +0 -0
  99. {aether_engine-1.1.0 → aether_engine-1.2.0}/examples/02_sphere_scene.py +0 -0
  100. {aether_engine-1.1.0 → aether_engine-1.2.0}/examples/03_first_person.py +0 -0
  101. {aether_engine-1.1.0 → aether_engine-1.2.0}/examples/04_physics2d.py +0 -0
  102. {aether_engine-1.1.0 → aether_engine-1.2.0}/pyproject.toml +0 -0
  103. {aether_engine-1.1.0 → aether_engine-1.2.0}/setup.cfg +0 -0
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: aether-engine
3
+ Version: 1.2.0
4
+ Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, model editor, HUD
5
+ Home-page: https://pypi.org/project/aether-engine/
6
+ Author: entiti937
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Games/Entertainment
17
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=1.24
22
+ Requires-Dist: glfw>=2.6
23
+ Requires-Dist: PyOpenGL>=3.1.7
24
+ Requires-Dist: Pillow>=10.0
25
+ Provides-Extra: audio
26
+ Requires-Dist: pygame-ce>=2.5; extra == "audio"
27
+ Provides-Extra: physics
28
+ Requires-Dist: pymunk>=6.0; extra == "physics"
29
+ Provides-Extra: models
30
+ Requires-Dist: trimesh>=3.21; extra == "models"
31
+ Provides-Extra: full
32
+ Requires-Dist: pyyaml>=6.0; extra == "full"
33
+ Requires-Dist: pygame-ce>=2.5; extra == "full"
34
+ Requires-Dist: trimesh>=3.21; extra == "full"
35
+ Requires-Dist: pymunk>=6.0; extra == "full"
36
+ Requires-Dist: freetype-py>=2.4; extra == "full"
37
+ Requires-Dist: imageio>=2.31; extra == "full"
38
+ Dynamic: author
39
+ Dynamic: classifier
40
+ Dynamic: description
41
+ Dynamic: description-content-type
42
+ Dynamic: home-page
43
+ Dynamic: license-file
44
+ Dynamic: provides-extra
45
+ Dynamic: requires-dist
46
+ Dynamic: requires-python
47
+ Dynamic: summary
48
+
49
+ # Aether Engine
50
+
51
+ **3D / 2D игровой движок на Python + OpenGL**
52
+ **Разработчик:** entiti937 · **Версия:** 1.2.0 · **Лицензия:** MIT
53
+ **PyPI:** https://pypi.org/project/aether-engine/
54
+
55
+ > Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
56
+
57
+ ---
58
+
59
+ ## Установка
60
+
61
+ ```bash
62
+ pip install -U "aether-engine[full]"
63
+ ```
64
+
65
+ ## Showcase-игра (доказательство)
66
+
67
+ Minecraft-like sandbox **на этом же движке**:
68
+
69
+ ```bash
70
+ python games/aether_craft.py
71
+ ```
72
+
73
+ Там есть: воксельный мир, вид от 1 лица, ломать/ставить блоки, HUD, звуки, день/ночь, сейвы (F5/F9), урон и респавн.
74
+
75
+ ## Другие запуски
76
+
77
+ ```bash
78
+ python -m aether.editor
79
+ python games/orb_rush.py
80
+ python examples/03_first_person.py
81
+ python examples/04_physics2d.py
82
+ ```
83
+
84
+ ## Минимальный код
85
+
86
+ ```python
87
+ from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
88
+ from aether.scene.component import Transform, MeshRenderer
89
+
90
+ engine = Engine(1280, 720, "Game by you — engine by entiti937")
91
+ scene = Scene("Main")
92
+ camera = Camera(position=(0, 2, 6), yaw=-90)
93
+ scene.set_main_camera(camera)
94
+
95
+ cube = Entity("Cube")
96
+ cube.add_component(Transform(position=(0, 1, 0)))
97
+ mat = Material(); mat.albedo[:] = (0.2, 0.85, 1.0)
98
+ cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
99
+ scene.spawn(cube)
100
+
101
+ engine.load_scene(scene)
102
+ engine.enable_first_person(camera)
103
+ engine.run()
104
+ ```
105
+
106
+ ## Возможности 1.2
107
+
108
+ - PBR 3D + sprites 2D + HUD overlay
109
+ - FPS (`enable_first_person`) и free-look камера
110
+ - Физика 3D/2D + raycast
111
+ - `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
112
+ - Встроенный voxel model editor
113
+ - Procedural SFX, JSON save/load
114
+ - Примеры и две игры в репозитории
115
+
116
+ ## Документация
117
+
118
+ Все команды, API и туториалы: **[docs/GUIDE.md](docs/GUIDE.md)**
119
+
120
+ ---
121
+
122
+ Aether Engine © **entiti937**
@@ -0,0 +1,74 @@
1
+ # Aether Engine
2
+
3
+ **3D / 2D игровой движок на Python + OpenGL**
4
+ **Разработчик:** entiti937 · **Версия:** 1.2.0 · **Лицензия:** MIT
5
+ **PyPI:** https://pypi.org/project/aether-engine/
6
+
7
+ > Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
8
+
9
+ ---
10
+
11
+ ## Установка
12
+
13
+ ```bash
14
+ pip install -U "aether-engine[full]"
15
+ ```
16
+
17
+ ## Showcase-игра (доказательство)
18
+
19
+ Minecraft-like sandbox **на этом же движке**:
20
+
21
+ ```bash
22
+ python games/aether_craft.py
23
+ ```
24
+
25
+ Там есть: воксельный мир, вид от 1 лица, ломать/ставить блоки, HUD, звуки, день/ночь, сейвы (F5/F9), урон и респавн.
26
+
27
+ ## Другие запуски
28
+
29
+ ```bash
30
+ python -m aether.editor
31
+ python games/orb_rush.py
32
+ python examples/03_first_person.py
33
+ python examples/04_physics2d.py
34
+ ```
35
+
36
+ ## Минимальный код
37
+
38
+ ```python
39
+ from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
40
+ from aether.scene.component import Transform, MeshRenderer
41
+
42
+ engine = Engine(1280, 720, "Game by you — engine by entiti937")
43
+ scene = Scene("Main")
44
+ camera = Camera(position=(0, 2, 6), yaw=-90)
45
+ scene.set_main_camera(camera)
46
+
47
+ cube = Entity("Cube")
48
+ cube.add_component(Transform(position=(0, 1, 0)))
49
+ mat = Material(); mat.albedo[:] = (0.2, 0.85, 1.0)
50
+ cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
51
+ scene.spawn(cube)
52
+
53
+ engine.load_scene(scene)
54
+ engine.enable_first_person(camera)
55
+ engine.run()
56
+ ```
57
+
58
+ ## Возможности 1.2
59
+
60
+ - PBR 3D + sprites 2D + HUD overlay
61
+ - FPS (`enable_first_person`) и free-look камера
62
+ - Физика 3D/2D + raycast
63
+ - `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
64
+ - Встроенный voxel model editor
65
+ - Procedural SFX, JSON save/load
66
+ - Примеры и две игры в репозитории
67
+
68
+ ## Документация
69
+
70
+ Все команды, API и туториалы: **[docs/GUIDE.md](docs/GUIDE.md)**
71
+
72
+ ---
73
+
74
+ Aether Engine © **entiti937**
@@ -18,9 +18,12 @@ from aether.graphics.first_person import FirstPersonController
18
18
  from aether.resources.loaders.model_loader import ModelLoader
19
19
  from aether.physics import PhysicsWorld2D, PhysicsWorld3D, BoxCollider, SphereCollider
20
20
  from aether.graphics2d import Renderer2D, Sprite, Camera2D
21
- from aether.editor import ModelEditor
21
+ from aether.ui.hud import Hud, HudBar
22
+ from aether.audio.sfx import SfxBank
23
+ from aether.resources.save_load import save_json, load_json
24
+ from aether.physics.raycast import Raycaster
22
25
 
23
- __version__ = "1.1.0"
26
+ __version__ = "1.2.0"
24
27
 
25
28
  __all__ = [
26
29
  "Engine",
@@ -45,5 +48,11 @@ __all__ = [
45
48
  "Sprite",
46
49
  "Camera2D",
47
50
  "ModelEditor",
51
+ "Hud",
52
+ "HudBar",
53
+ "SfxBank",
54
+ "save_json",
55
+ "load_json",
56
+ "Raycaster",
48
57
  "__version__",
49
58
  ]
@@ -0,0 +1,85 @@
1
+ """Procedural sound helpers (no asset files required)."""
2
+ from __future__ import annotations
3
+
4
+ import math
5
+ import struct
6
+ import tempfile
7
+ import wave
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+
12
+ def _write_wav(path: Path, samples, rate: int = 22050):
13
+ with wave.open(str(path), "w") as wf:
14
+ wf.setnchannels(1)
15
+ wf.setsampwidth(2)
16
+ wf.setframerate(rate)
17
+ frames = b"".join(struct.pack("<h", int(max(-1, min(1, s)) * 32767)) for s in samples)
18
+ wf.writeframes(frames)
19
+
20
+
21
+ def make_tone(
22
+ frequency: float = 440.0,
23
+ duration: float = 0.08,
24
+ volume: float = 0.35,
25
+ kind: str = "square",
26
+ rate: int = 22050,
27
+ ) -> Path:
28
+ """Generate a short WAV tone and return its path."""
29
+ n = int(rate * duration)
30
+ samples = []
31
+ for i in range(n):
32
+ t = i / rate
33
+ env = 1.0
34
+ if i < rate * 0.01:
35
+ env = i / (rate * 0.01)
36
+ elif i > n - rate * 0.02:
37
+ env = max(0.0, (n - i) / (rate * 0.02))
38
+ phase = 2 * math.pi * frequency * t
39
+ if kind == "sine":
40
+ val = math.sin(phase)
41
+ elif kind == "noise":
42
+ # deterministic pseudo-noise
43
+ val = math.sin(phase * 12.9898) * 2.0 % 2.0 - 1.0
44
+ else:
45
+ val = 1.0 if math.sin(phase) >= 0 else -1.0
46
+ samples.append(val * volume * env)
47
+
48
+ path = Path(tempfile.gettempdir()) / f"aether_tone_{int(frequency)}_{kind}.wav"
49
+ _write_wav(path, samples, rate)
50
+ return path
51
+
52
+
53
+ class SfxBank:
54
+ """Lazy bank of procedural SFX played through Engine.audio."""
55
+
56
+ def __init__(self, audio_manager):
57
+ self.audio = audio_manager
58
+ self._cache = {}
59
+
60
+ def _get(self, key: str, factory):
61
+ if key not in self._cache:
62
+ path = factory()
63
+ # AudioManager.load_sound(name, path)
64
+ self._cache[key] = self.audio.load_sound(key, str(path))
65
+ return self._cache[key]
66
+
67
+ def play_break(self):
68
+ snd = self._get("break", lambda: make_tone(180, 0.07, 0.4, "noise"))
69
+ if snd:
70
+ self.audio.play_sound("break", volume=0.45)
71
+
72
+ def play_place(self):
73
+ snd = self._get("place", lambda: make_tone(520, 0.06, 0.3, "square"))
74
+ if snd:
75
+ self.audio.play_sound("place", volume=0.4)
76
+
77
+ def play_select(self):
78
+ snd = self._get("select", lambda: make_tone(760, 0.04, 0.25, "sine"))
79
+ if snd:
80
+ self.audio.play_sound("select", volume=0.35)
81
+
82
+ def play_hurt(self):
83
+ snd = self._get("hurt", lambda: make_tone(120, 0.12, 0.45, "square"))
84
+ if snd:
85
+ self.audio.play_sound("hurt", volume=0.5)
@@ -54,7 +54,7 @@ class Engine:
54
54
  self.config.graphics.width = width
55
55
  self.config.graphics.height = height
56
56
 
57
- print("[Aether] Initializing Engine v1.1.0...")
57
+ print("[Aether] Initializing Engine v1.2.0 by entiti937...")
58
58
 
59
59
  self.window = Window(
60
60
  self.config.graphics.width,
@@ -101,6 +101,7 @@ class Engine:
101
101
  self.free_camera = True
102
102
  self.fps_controller: Optional[FirstPersonController] = None
103
103
  self._update_callbacks = []
104
+ self.draw_hud = False # overlay Renderer2D sprites on top of 3D
104
105
 
105
106
  self.event_bus.subscribe("window_resize", self._on_window_resize)
106
107
  self.event_bus.subscribe("engine_pause", self._on_pause)
@@ -327,7 +328,7 @@ class Engine:
327
328
 
328
329
  def _render(self):
329
330
  if self.mode == "2d":
330
- self.renderer2d.begin_frame()
331
+ self.renderer2d.begin_frame(clear=True)
331
332
  self.renderer2d.render()
332
333
  self.renderer2d.end_frame()
333
334
  return
@@ -337,6 +338,18 @@ class Engine:
337
338
  self.renderer.render_scene(self.scene)
338
339
  self.renderer.end_frame()
339
340
 
341
+ if self.draw_hud:
342
+ # Overlay HUD without clearing the 3D framebuffer color
343
+ self.renderer2d.begin_frame(clear=False)
344
+ # Map HUD from pixel-ish coords: camera at 0, zoom so 1 unit ~= 1 px-ish
345
+ self.renderer2d.camera.x = 0
346
+ self.renderer2d.camera.y = 0
347
+ self.renderer2d.camera.zoom = 1.0
348
+ self.renderer2d.camera.width = float(self.config.graphics.width)
349
+ self.renderer2d.camera.height = float(self.config.graphics.height)
350
+ self.renderer2d.render()
351
+ self.renderer2d.end_frame()
352
+
340
353
  def on_update(self, callback):
341
354
  """Регистрирует callback(dt), вызываемый каждый кадр."""
342
355
  self._update_callbacks.append(callback)
@@ -100,12 +100,13 @@ class Renderer2D:
100
100
  self.sprites.append(sprite)
101
101
  return sprite
102
102
 
103
- def begin_frame(self):
103
+ def begin_frame(self, clear: bool = True):
104
104
  glDisable(GL_DEPTH_TEST)
105
105
  glEnable(GL_BLEND)
106
106
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
107
- glClearColor(*self.clear_color)
108
- glClear(GL_COLOR_BUFFER_BIT)
107
+ if clear:
108
+ glClearColor(*self.clear_color)
109
+ glClear(GL_COLOR_BUFFER_BIT)
109
110
 
110
111
  def render(self):
111
112
  self._shader.use()
@@ -0,0 +1,102 @@
1
+ """Raycasting against AABB / sphere colliders and voxel helpers."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+
8
+
9
+ class RaycastHit:
10
+ def __init__(self, point, normal, distance, entity=None, body=None):
11
+ self.point = np.asarray(point, dtype=np.float32)
12
+ self.normal = np.asarray(normal, dtype=np.float32)
13
+ self.distance = float(distance)
14
+ self.entity = entity
15
+ self.body = body
16
+
17
+
18
+ def ray_aabb(origin, direction, bmin, bmax, max_distance=1000.0) -> Optional[float]:
19
+ origin = np.asarray(origin, dtype=np.float32)
20
+ direction = np.asarray(direction, dtype=np.float32)
21
+ length = np.linalg.norm(direction)
22
+ if length < 1e-8:
23
+ return None
24
+ direction = direction / length
25
+ tmin, tmax = 0.0, float(max_distance)
26
+ for i in range(3):
27
+ if abs(direction[i]) < 1e-8:
28
+ if origin[i] < bmin[i] or origin[i] > bmax[i]:
29
+ return None
30
+ continue
31
+ inv = 1.0 / direction[i]
32
+ t1 = (bmin[i] - origin[i]) * inv
33
+ t2 = (bmax[i] - origin[i]) * inv
34
+ if t1 > t2:
35
+ t1, t2 = t2, t1
36
+ tmin = max(tmin, t1)
37
+ tmax = min(tmax, t2)
38
+ if tmin > tmax:
39
+ return None
40
+ return tmin
41
+
42
+
43
+ def ray_sphere(origin, direction, center, radius, max_distance=1000.0) -> Optional[float]:
44
+ origin = np.asarray(origin, dtype=np.float32)
45
+ direction = np.asarray(direction, dtype=np.float32)
46
+ center = np.asarray(center, dtype=np.float32)
47
+ length = np.linalg.norm(direction)
48
+ if length < 1e-8:
49
+ return None
50
+ direction = direction / length
51
+ oc = origin - center
52
+ b = float(np.dot(oc, direction))
53
+ c = float(np.dot(oc, oc) - radius * radius)
54
+ disc = b * b - c
55
+ if disc < 0:
56
+ return None
57
+ s = np.sqrt(disc)
58
+ t0 = -b - s
59
+ t1 = -b + s
60
+ for t in (t0, t1):
61
+ if 0.0 <= t <= max_distance:
62
+ return float(t)
63
+ return None
64
+
65
+
66
+ class Raycaster:
67
+ """High-level raycast helper for PhysicsWorld3D bodies / custom AABBs."""
68
+
69
+ @staticmethod
70
+ def cast(world, origin, direction, max_distance=100.0) -> Optional[RaycastHit]:
71
+ if hasattr(world, "raycast"):
72
+ hit = world.raycast(origin, direction, max_distance)
73
+ if not hit:
74
+ return None
75
+ return RaycastHit(
76
+ point=hit["point"],
77
+ normal=(0, 1, 0),
78
+ distance=hit["distance"],
79
+ entity=hit.get("entity"),
80
+ body=hit.get("body"),
81
+ )
82
+ return None
83
+
84
+ @staticmethod
85
+ def cast_aabb_list(origin, direction, aabbs, max_distance=100.0) -> Optional[RaycastHit]:
86
+ """aabbs: iterable of (bmin, bmax, payload)."""
87
+ best_t = None
88
+ best_payload = None
89
+ for bmin, bmax, payload in aabbs:
90
+ t = ray_aabb(origin, direction, bmin, bmax, max_distance)
91
+ if t is None:
92
+ continue
93
+ if best_t is None or t < best_t:
94
+ best_t = t
95
+ best_payload = payload
96
+ if best_t is None:
97
+ return None
98
+ origin = np.asarray(origin, dtype=np.float32)
99
+ direction = np.asarray(direction, dtype=np.float32)
100
+ direction = direction / max(np.linalg.norm(direction), 1e-6)
101
+ point = origin + direction * best_t
102
+ return RaycastHit(point=point, normal=(0, 1, 0), distance=best_t, entity=best_payload)
@@ -0,0 +1,20 @@
1
+ """Simple JSON world / scene helpers."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any, Dict
7
+
8
+
9
+ def save_json(path, data: Dict[str, Any]):
10
+ path = Path(path)
11
+ path.parent.mkdir(parents=True, exist_ok=True)
12
+ path.write_text(json.dumps(data, indent=2), encoding="utf-8")
13
+ print(f"[Save] Wrote {path}")
14
+
15
+
16
+ def load_json(path) -> Dict[str, Any]:
17
+ path = Path(path)
18
+ if not path.exists():
19
+ raise FileNotFoundError(path)
20
+ return json.loads(path.read_text(encoding="utf-8"))
@@ -0,0 +1,5 @@
1
+ """UI package."""
2
+
3
+ from aether.ui.hud import Hud, HudBar
4
+
5
+ __all__ = ["Hud", "HudBar"]
@@ -0,0 +1,65 @@
1
+ """Minimal immediate-mode HUD overlay (screen-space quads via Renderer2D)."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import List, Tuple
6
+
7
+ from aether.graphics2d import Sprite
8
+
9
+
10
+ @dataclass
11
+ class HudBar:
12
+ label: str
13
+ value: float # 0..1
14
+ color: Tuple[float, float, float, float] = (0.2, 0.85, 0.35, 0.95)
15
+ back: Tuple[float, float, float, float] = (0.1, 0.1, 0.12, 0.7)
16
+
17
+
18
+ class Hud:
19
+ """Build sprites for a simple game HUD (hotbar, bars, crosshair)."""
20
+
21
+ def __init__(self, renderer2d):
22
+ self.r = renderer2d
23
+ self.sprites: List[Sprite] = []
24
+ self._owned: List[Sprite] = []
25
+
26
+ def clear(self):
27
+ for s in self._owned:
28
+ if s in self.r.sprites:
29
+ self.r.sprites.remove(s)
30
+ self._owned.clear()
31
+
32
+ def _add(self, sprite: Sprite) -> Sprite:
33
+ self.r.add_sprite(sprite)
34
+ self._owned.append(sprite)
35
+ return sprite
36
+
37
+ def draw_crosshair(self, size: float = 10.0, thickness: float = 2.0):
38
+ c = (1, 1, 1, 0.9)
39
+ self._add(Sprite(0, 0, size, thickness, color=c, layer=100))
40
+ self._add(Sprite(0, 0, thickness, size, color=c, layer=100))
41
+
42
+ def draw_hotbar(self, slots: int, selected: int, colors, y: float = -220):
43
+ slot_w, slot_h, gap = 44, 44, 6
44
+ total = slots * slot_w + (slots - 1) * gap
45
+ start_x = -total * 0.5 + slot_w * 0.5
46
+ for i in range(slots):
47
+ x = start_x + i * (slot_w + gap)
48
+ border = (1, 1, 0.3, 1) if i == selected else (0.15, 0.15, 0.18, 0.85)
49
+ self._add(Sprite(x, y, slot_w + 4, slot_h + 4, color=border, layer=90))
50
+ col = colors[i] if i < len(colors) else (0.4, 0.4, 0.4, 1)
51
+ self._add(Sprite(x, y, slot_w - 6, slot_h - 6, color=(*col[:3], 1.0), layer=91))
52
+
53
+ def draw_bar(self, x: float, y: float, width: float, height: float, bar: HudBar):
54
+ self._add(Sprite(x, y, width, height, color=bar.back, layer=80))
55
+ fill = max(0.0, min(1.0, bar.value)) * (width - 4)
56
+ self._add(
57
+ Sprite(
58
+ x - (width - fill) * 0.5 + 2,
59
+ y,
60
+ max(1.0, fill),
61
+ height - 4,
62
+ color=bar.color,
63
+ layer=81,
64
+ )
65
+ )
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: aether-engine
3
+ Version: 1.2.0
4
+ Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, model editor, HUD
5
+ Home-page: https://pypi.org/project/aether-engine/
6
+ Author: entiti937
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Games/Entertainment
17
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=1.24
22
+ Requires-Dist: glfw>=2.6
23
+ Requires-Dist: PyOpenGL>=3.1.7
24
+ Requires-Dist: Pillow>=10.0
25
+ Provides-Extra: audio
26
+ Requires-Dist: pygame-ce>=2.5; extra == "audio"
27
+ Provides-Extra: physics
28
+ Requires-Dist: pymunk>=6.0; extra == "physics"
29
+ Provides-Extra: models
30
+ Requires-Dist: trimesh>=3.21; extra == "models"
31
+ Provides-Extra: full
32
+ Requires-Dist: pyyaml>=6.0; extra == "full"
33
+ Requires-Dist: pygame-ce>=2.5; extra == "full"
34
+ Requires-Dist: trimesh>=3.21; extra == "full"
35
+ Requires-Dist: pymunk>=6.0; extra == "full"
36
+ Requires-Dist: freetype-py>=2.4; extra == "full"
37
+ Requires-Dist: imageio>=2.31; extra == "full"
38
+ Dynamic: author
39
+ Dynamic: classifier
40
+ Dynamic: description
41
+ Dynamic: description-content-type
42
+ Dynamic: home-page
43
+ Dynamic: license-file
44
+ Dynamic: provides-extra
45
+ Dynamic: requires-dist
46
+ Dynamic: requires-python
47
+ Dynamic: summary
48
+
49
+ # Aether Engine
50
+
51
+ **3D / 2D игровой движок на Python + OpenGL**
52
+ **Разработчик:** entiti937 · **Версия:** 1.2.0 · **Лицензия:** MIT
53
+ **PyPI:** https://pypi.org/project/aether-engine/
54
+
55
+ > Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
56
+
57
+ ---
58
+
59
+ ## Установка
60
+
61
+ ```bash
62
+ pip install -U "aether-engine[full]"
63
+ ```
64
+
65
+ ## Showcase-игра (доказательство)
66
+
67
+ Minecraft-like sandbox **на этом же движке**:
68
+
69
+ ```bash
70
+ python games/aether_craft.py
71
+ ```
72
+
73
+ Там есть: воксельный мир, вид от 1 лица, ломать/ставить блоки, HUD, звуки, день/ночь, сейвы (F5/F9), урон и респавн.
74
+
75
+ ## Другие запуски
76
+
77
+ ```bash
78
+ python -m aether.editor
79
+ python games/orb_rush.py
80
+ python examples/03_first_person.py
81
+ python examples/04_physics2d.py
82
+ ```
83
+
84
+ ## Минимальный код
85
+
86
+ ```python
87
+ from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
88
+ from aether.scene.component import Transform, MeshRenderer
89
+
90
+ engine = Engine(1280, 720, "Game by you — engine by entiti937")
91
+ scene = Scene("Main")
92
+ camera = Camera(position=(0, 2, 6), yaw=-90)
93
+ scene.set_main_camera(camera)
94
+
95
+ cube = Entity("Cube")
96
+ cube.add_component(Transform(position=(0, 1, 0)))
97
+ mat = Material(); mat.albedo[:] = (0.2, 0.85, 1.0)
98
+ cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
99
+ scene.spawn(cube)
100
+
101
+ engine.load_scene(scene)
102
+ engine.enable_first_person(camera)
103
+ engine.run()
104
+ ```
105
+
106
+ ## Возможности 1.2
107
+
108
+ - PBR 3D + sprites 2D + HUD overlay
109
+ - FPS (`enable_first_person`) и free-look камера
110
+ - Физика 3D/2D + raycast
111
+ - `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
112
+ - Встроенный voxel model editor
113
+ - Procedural SFX, JSON save/load
114
+ - Примеры и две игры в репозитории
115
+
116
+ ## Документация
117
+
118
+ Все команды, API и туториалы: **[docs/GUIDE.md](docs/GUIDE.md)**
119
+
120
+ ---
121
+
122
+ Aether Engine © **entiti937**