aether-engine 1.7.0__tar.gz → 1.8.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.
- {aether_engine-1.7.0/aether_engine.egg-info → aether_engine-1.8.0}/PKG-INFO +35 -44
- aether_engine-1.8.0/README.md +74 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/__init__.py +1 -1
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/cli.py +33 -3
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/_identity.py +1 -1
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/camera.py +38 -9
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/mesh_factory.py +68 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/scene/component.py +3 -3
- aether_engine-1.8.0/aether/scene/scene.py +55 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ui/hud.py +30 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/content.py +204 -53
- aether_engine-1.8.0/aether/web/plain_content.py +248 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/registry.py +12 -11
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/templates.py +5 -2
- {aether_engine-1.7.0 → aether_engine-1.8.0/aether_engine.egg-info}/PKG-INFO +35 -44
- {aether_engine-1.7.0 → aether_engine-1.8.0}/examples/01_hello_cube.py +1 -2
- {aether_engine-1.7.0 → aether_engine-1.8.0}/examples/02_sphere_scene.py +1 -2
- {aether_engine-1.7.0 → aether_engine-1.8.0}/examples/03_first_person.py +2 -1
- {aether_engine-1.7.0 → aether_engine-1.8.0}/setup.py +1 -1
- aether_engine-1.7.0/README.md +0 -83
- aether_engine-1.7.0/aether/scene/scene.py +0 -33
- aether_engine-1.7.0/aether/web/plain_content.py +0 -166
- {aether_engine-1.7.0 → aether_engine-1.8.0}/LICENSE +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/MANIFEST.in +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/__main__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/accelerators/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/accelerators/gameplay.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/accelerators/voxel.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ai/behavior_tree.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ai/navmesh.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/animation/skeleton.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/assets/__pycache__/asset_pipeline.cpython-314.pyc +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/assets/asset_pipeline.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/assets/fonts/AetherSans.ttf +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/audio/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/audio/audio_manager.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/audio/music.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/audio/sfx.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/audio/sound.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/achievement_system.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/application.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/boot_splash.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/branding_guard.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/config.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/console.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/engine.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/event_bus.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/input.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/plugin_system.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/save_system.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/time_manager.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/core/window.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/editor/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/environment/environment_system.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/first_person.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/framebuffer.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/light.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/lod_system.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/material.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/mesh.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/particle_system.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/post_process.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/renderer.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/shader.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/shader_library.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/sky_atmosphere.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/terrain.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/texture.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/voxel_mesh.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics/water.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/graphics2d/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/network/network_manager.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/bullet_backend.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/collider.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/physics2d.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/physics3d.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/physics_world.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/raycast.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/physics/rigidbody.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/platform/steam_api.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/asset_database.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/asset_loader.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/loaders/model_loader.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/loaders/obj_loader.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/loaders/texture_loader.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/resource_manager.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/save_load.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/resources/serialization.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/scene/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/scene/entity.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/scene/scene_graph.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/scene/scene_manager.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/tools/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/tools/packager.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ui/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ui/button.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ui/canvas.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/ui/text.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/utils/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/utils/fonts.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/utils/logger.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/utils/math_utils.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/utils/object_pool.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/utils/profiler.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/__init__.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/bot_detect.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/config.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/notifications.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/plain.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/secrets.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/server.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether/web/urls.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether_engine.egg-info/SOURCES.txt +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether_engine.egg-info/dependency_links.txt +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether_engine.egg-info/entry_points.txt +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether_engine.egg-info/requires.txt +4 -4
- {aether_engine-1.7.0 → aether_engine-1.8.0}/aether_engine.egg-info/top_level.txt +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/examples/04_physics2d.py +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/pyproject.toml +0 -0
- {aether_engine-1.7.0 → aether_engine-1.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aether-engine
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, mandatory branding splash
|
|
5
5
|
Home-page: https://pypi.org/project/aether-engine/
|
|
6
6
|
Author: entiti937
|
|
@@ -31,13 +31,13 @@ Requires-Dist: trimesh>=3.21; extra == "models"
|
|
|
31
31
|
Provides-Extra: build
|
|
32
32
|
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
33
33
|
Provides-Extra: full
|
|
34
|
-
Requires-Dist: freetype-py>=2.4; extra == "full"
|
|
35
|
-
Requires-Dist: imageio>=2.31; extra == "full"
|
|
36
34
|
Requires-Dist: pygame-ce>=2.5; extra == "full"
|
|
37
|
-
Requires-Dist: pyyaml>=6.0; extra == "full"
|
|
38
35
|
Requires-Dist: trimesh>=3.21; extra == "full"
|
|
39
|
-
Requires-Dist:
|
|
36
|
+
Requires-Dist: imageio>=2.31; extra == "full"
|
|
40
37
|
Requires-Dist: pyinstaller>=6.0; extra == "full"
|
|
38
|
+
Requires-Dist: pyyaml>=6.0; extra == "full"
|
|
39
|
+
Requires-Dist: pymunk>=6.0; extra == "full"
|
|
40
|
+
Requires-Dist: freetype-py>=2.4; extra == "full"
|
|
41
41
|
Dynamic: author
|
|
42
42
|
Dynamic: classifier
|
|
43
43
|
Dynamic: description
|
|
@@ -52,10 +52,10 @@ Dynamic: summary
|
|
|
52
52
|
# Aether Engine
|
|
53
53
|
|
|
54
54
|
**3D / 2D игровой движок на Python + OpenGL**
|
|
55
|
-
**Разработчик:** entiti937 · **Версия:** 1.
|
|
55
|
+
**Разработчик:** entiti937 · **Версия:** 1.8.0 · **Лицензия:** MIT
|
|
56
56
|
**PyPI:** https://pypi.org/project/aether-engine/
|
|
57
57
|
|
|
58
|
-
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
58
|
+
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md) · сайт: `aether website`
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
@@ -65,38 +65,26 @@ Dynamic: summary
|
|
|
65
65
|
pip install -U "aether-engine[full]"
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- `Sprite.from_image(path)` — быстрый импорт 2D-графики
|
|
72
|
-
- Встроенный редактор **убран** (может вернуться в будущих версиях)
|
|
73
|
-
|
|
74
|
-
## Showcase-игра
|
|
75
|
-
|
|
76
|
-
Minecraft-like sandbox **на этом же движке**:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
aether help
|
|
80
|
-
aether build games/aether_craft.py --name AetherCraft
|
|
81
|
-
python games/aether_craft.py
|
|
68
|
+
```python
|
|
69
|
+
import aether # правильно
|
|
70
|
+
# import aether_engine # НЕПРАВИЛЬНО — такого модуля нет
|
|
82
71
|
```
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
## Другие запуски
|
|
73
|
+
## Что нового в 1.8.0
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```
|
|
75
|
+
- `Scene.remove()` / `despawn()` / `find()`
|
|
76
|
+
- `MeshFactory.create_cylinder(...)`
|
|
77
|
+
- `Camera.look_at(target)` + безопасное присвоение `cam.position = (x,y,z)`
|
|
78
|
+
- HUD: `draw_rect`, `draw_panel`
|
|
79
|
+
- Полный **API Reference** в документации (и `/plain/all` для ИИ)
|
|
94
80
|
|
|
95
81
|
## Минимальный код
|
|
96
82
|
|
|
97
83
|
```python
|
|
98
|
-
from aether import
|
|
99
|
-
|
|
84
|
+
from aether import (
|
|
85
|
+
Engine, Scene, Entity, Transform, MeshRenderer,
|
|
86
|
+
MeshFactory, Material, Camera,
|
|
87
|
+
)
|
|
100
88
|
|
|
101
89
|
engine = Engine(1280, 720, "Game by you — engine by entiti937")
|
|
102
90
|
scene = Scene("Main")
|
|
@@ -105,7 +93,8 @@ scene.set_main_camera(camera)
|
|
|
105
93
|
|
|
106
94
|
cube = Entity("Cube")
|
|
107
95
|
cube.add_component(Transform(position=(0, 1, 0)))
|
|
108
|
-
mat = Material()
|
|
96
|
+
mat = Material()
|
|
97
|
+
mat.albedo[:] = (0.2, 0.85, 1.0)
|
|
109
98
|
cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
|
|
110
99
|
scene.spawn(cube)
|
|
111
100
|
|
|
@@ -114,20 +103,22 @@ engine.enable_first_person(camera)
|
|
|
114
103
|
engine.run()
|
|
115
104
|
```
|
|
116
105
|
|
|
117
|
-
##
|
|
118
|
-
|
|
119
|
-
- PBR 3D + sprites 2D + HUD overlay
|
|
120
|
-
- FPS (`enable_first_person`) и free-look камера
|
|
121
|
-
- Физика 3D/2D + raycast
|
|
122
|
-
- `AssetLoader` / `ModelLoader` — OBJ/GLB/glTF/STL/FBX/STEP/… + PNG/WebP спрайты
|
|
123
|
-
- Procedural SFX, JSON save/load
|
|
124
|
-
- Примеры и игры в репозитории
|
|
106
|
+
## Частые операции
|
|
125
107
|
|
|
126
|
-
|
|
108
|
+
```python
|
|
109
|
+
scene.remove(entity) # удалить объект
|
|
110
|
+
MeshFactory.create_cylinder(0.5, 2) # цилиндр
|
|
111
|
+
cam.look_at(player_tf.position) # смотреть на игрока
|
|
112
|
+
engine.free_camera = False # своя камера (не WASD fly)
|
|
113
|
+
```
|
|
127
114
|
|
|
128
|
-
##
|
|
115
|
+
## Возможности
|
|
129
116
|
|
|
130
|
-
|
|
117
|
+
- PBR 3D + sprites 2D + HUD overlay
|
|
118
|
+
- FPS / free-look / `look_at` follow-камера
|
|
119
|
+
- Физика 2D (pymunk) + лёгкий 3D solver
|
|
120
|
+
- `AssetLoader` — модели и спрайты
|
|
121
|
+
- `aether website` + plain docs для LLM
|
|
131
122
|
|
|
132
123
|
---
|
|
133
124
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Aether Engine
|
|
2
|
+
|
|
3
|
+
**3D / 2D игровой движок на Python + OpenGL**
|
|
4
|
+
**Разработчик:** entiti937 · **Версия:** 1.8.0 · **Лицензия:** MIT
|
|
5
|
+
**PyPI:** https://pypi.org/project/aether-engine/
|
|
6
|
+
|
|
7
|
+
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md) · сайт: `aether website`
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Установка
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -U "aether-engine[full]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import aether # правильно
|
|
19
|
+
# import aether_engine # НЕПРАВИЛЬНО — такого модуля нет
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Что нового в 1.8.0
|
|
23
|
+
|
|
24
|
+
- `Scene.remove()` / `despawn()` / `find()`
|
|
25
|
+
- `MeshFactory.create_cylinder(...)`
|
|
26
|
+
- `Camera.look_at(target)` + безопасное присвоение `cam.position = (x,y,z)`
|
|
27
|
+
- HUD: `draw_rect`, `draw_panel`
|
|
28
|
+
- Полный **API Reference** в документации (и `/plain/all` для ИИ)
|
|
29
|
+
|
|
30
|
+
## Минимальный код
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from aether import (
|
|
34
|
+
Engine, Scene, Entity, Transform, MeshRenderer,
|
|
35
|
+
MeshFactory, Material, Camera,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
engine = Engine(1280, 720, "Game by you — engine by entiti937")
|
|
39
|
+
scene = Scene("Main")
|
|
40
|
+
camera = Camera(position=(0, 2, 6), yaw=-90)
|
|
41
|
+
scene.set_main_camera(camera)
|
|
42
|
+
|
|
43
|
+
cube = Entity("Cube")
|
|
44
|
+
cube.add_component(Transform(position=(0, 1, 0)))
|
|
45
|
+
mat = Material()
|
|
46
|
+
mat.albedo[:] = (0.2, 0.85, 1.0)
|
|
47
|
+
cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
|
|
48
|
+
scene.spawn(cube)
|
|
49
|
+
|
|
50
|
+
engine.load_scene(scene)
|
|
51
|
+
engine.enable_first_person(camera)
|
|
52
|
+
engine.run()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Частые операции
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
scene.remove(entity) # удалить объект
|
|
59
|
+
MeshFactory.create_cylinder(0.5, 2) # цилиндр
|
|
60
|
+
cam.look_at(player_tf.position) # смотреть на игрока
|
|
61
|
+
engine.free_camera = False # своя камера (не WASD fly)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Возможности
|
|
65
|
+
|
|
66
|
+
- PBR 3D + sprites 2D + HUD overlay
|
|
67
|
+
- FPS / free-look / `look_at` follow-камера
|
|
68
|
+
- Физика 2D (pymunk) + лёгкий 3D solver
|
|
69
|
+
- `AssetLoader` — модели и спрайты
|
|
70
|
+
- `aether website` + plain docs для LLM
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
Aether Engine © **entiti937**
|
|
@@ -31,7 +31,7 @@ from aether.audio.sfx import SfxBank
|
|
|
31
31
|
from aether.resources.save_load import save_json, load_json
|
|
32
32
|
from aether.physics.raycast import Raycaster
|
|
33
33
|
|
|
34
|
-
__version__ = "1.
|
|
34
|
+
__version__ = "1.8.0"
|
|
35
35
|
|
|
36
36
|
from aether.graphics.voxel_mesh import ChunkedVoxelMesher
|
|
37
37
|
from aether.accelerators import is_native_available, backend_name
|
|
@@ -15,10 +15,11 @@ QUICK_REFERENCE = f"""
|
|
|
15
15
|
|
|
16
16
|
УСТАНОВКА
|
|
17
17
|
pip install -U "aether-engine[full]"
|
|
18
|
+
# пакет на PyPI: aether-engine
|
|
19
|
+
# импорт в коде: import aether (НЕ aether_engine)
|
|
18
20
|
|
|
19
21
|
МИНИМАЛЬНЫЙ КОД (3D)
|
|
20
|
-
from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
|
|
21
|
-
from aether.scene.component import Transform, MeshRenderer
|
|
22
|
+
from aether import Engine, Scene, Entity, Transform, MeshRenderer, MeshFactory, Material, Camera
|
|
22
23
|
|
|
23
24
|
engine = Engine(1280, 720, "My Game")
|
|
24
25
|
scene = Scene("Main")
|
|
@@ -27,7 +28,8 @@ QUICK_REFERENCE = f"""
|
|
|
27
28
|
|
|
28
29
|
cube = Entity("Cube")
|
|
29
30
|
cube.add_component(Transform(position=(0, 1, 0)))
|
|
30
|
-
mat = Material()
|
|
31
|
+
mat = Material()
|
|
32
|
+
mat.albedo[:] = (0.2, 0.9, 0.3)
|
|
31
33
|
cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
|
|
32
34
|
scene.spawn(cube)
|
|
33
35
|
|
|
@@ -35,6 +37,34 @@ QUICK_REFERENCE = f"""
|
|
|
35
37
|
engine.enable_first_person(cam) # FPS
|
|
36
38
|
engine.run() # ESC — выход
|
|
37
39
|
|
|
40
|
+
УДАЛЕНИЕ ОБЪЕКТОВ
|
|
41
|
+
scene.remove(entity) # убрать из сцены + destroy()
|
|
42
|
+
# или entity.destroy() # только компоненты (лучше scene.remove)
|
|
43
|
+
|
|
44
|
+
ПРИМИТИВЫ MeshFactory
|
|
45
|
+
create_cube(size=1)
|
|
46
|
+
create_plane(size=10)
|
|
47
|
+
create_sphere(radius=1, sectors=24, stacks=16)
|
|
48
|
+
create_cylinder(radius=0.5, height=1, segments=24)
|
|
49
|
+
create_pyramid(size=1, height=1)
|
|
50
|
+
|
|
51
|
+
КАМЕРА
|
|
52
|
+
cam.position = (x, y, z) # numpy array под капотом
|
|
53
|
+
cam.yaw, cam.pitch, cam.fov
|
|
54
|
+
cam.look_at(target) # смотреть на точку/игрока
|
|
55
|
+
cam.rotate(dx, dy)
|
|
56
|
+
engine.free_camera = False # если управляете камерой сами
|
|
57
|
+
|
|
58
|
+
TRANSFORM
|
|
59
|
+
tf.position # np.ndarray float32, можно tf.position[:] = (x,y,z)
|
|
60
|
+
tf.set_position(x,y,z) / set_rotation / set_scale
|
|
61
|
+
tf.translate(dx,dy,dz)
|
|
62
|
+
|
|
63
|
+
HUD
|
|
64
|
+
engine.draw_hud = True
|
|
65
|
+
hud = Hud(engine.renderer2d)
|
|
66
|
+
hud.clear(); hud.draw_crosshair(); hud.draw_bar(...); hud.draw_hotbar(...)
|
|
67
|
+
|
|
38
68
|
FPS / КАМЕРА
|
|
39
69
|
engine.enable_first_person(camera, eye_height=1.7, walk_speed=6)
|
|
40
70
|
engine.free_camera = True # WASD + Tab (мышь)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"""Камера"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Sequence, Union
|
|
5
|
+
|
|
2
6
|
import numpy as np
|
|
3
7
|
|
|
8
|
+
Vec3 = Union[Sequence[float], np.ndarray]
|
|
9
|
+
|
|
4
10
|
|
|
5
11
|
class Camera:
|
|
6
12
|
def __init__(self, position=(0, 2, 10), yaw=-90, pitch=0, fov=60):
|
|
7
|
-
self.
|
|
13
|
+
self._position = np.array(position, dtype=np.float32)
|
|
8
14
|
self.yaw = float(yaw)
|
|
9
15
|
self.pitch = float(pitch)
|
|
10
16
|
self.fov = float(fov)
|
|
@@ -15,6 +21,17 @@ class Camera:
|
|
|
15
21
|
|
|
16
22
|
self._update_vectors()
|
|
17
23
|
|
|
24
|
+
@property
|
|
25
|
+
def position(self) -> np.ndarray:
|
|
26
|
+
return self._position
|
|
27
|
+
|
|
28
|
+
@position.setter
|
|
29
|
+
def position(self, value: Vec3):
|
|
30
|
+
self._position = np.asarray(value, dtype=np.float32).reshape(3)
|
|
31
|
+
|
|
32
|
+
def set_position(self, x: float, y: float, z: float):
|
|
33
|
+
self._position[:] = (x, y, z)
|
|
34
|
+
|
|
18
35
|
def _update_vectors(self):
|
|
19
36
|
front = np.array(
|
|
20
37
|
[
|
|
@@ -36,12 +53,24 @@ class Camera:
|
|
|
36
53
|
self.right = right
|
|
37
54
|
self.up = np.cross(self.right, self.front)
|
|
38
55
|
|
|
56
|
+
def look_at(self, target: Vec3, world_up=(0.0, 1.0, 0.0)):
|
|
57
|
+
"""Point the camera at a world-space target (updates yaw/pitch)."""
|
|
58
|
+
target = np.asarray(target, dtype=np.float32).reshape(3)
|
|
59
|
+
direction = target - self._position
|
|
60
|
+
length = float(np.linalg.norm(direction))
|
|
61
|
+
if length < 1e-6:
|
|
62
|
+
return
|
|
63
|
+
direction = direction / length
|
|
64
|
+
self.pitch = float(np.degrees(np.arcsin(np.clip(direction[1], -1.0, 1.0))))
|
|
65
|
+
self.yaw = float(np.degrees(np.arctan2(direction[2], direction[0])))
|
|
66
|
+
self.pitch = max(-89.0, min(89.0, self.pitch))
|
|
67
|
+
self._update_vectors()
|
|
68
|
+
|
|
39
69
|
def get_view_matrix(self) -> np.ndarray:
|
|
40
|
-
# Column-vector math matrix (uploaded with GL_TRUE / row-major).
|
|
41
70
|
s = self.right
|
|
42
71
|
u = self.up
|
|
43
72
|
f = self.front
|
|
44
|
-
p = self.
|
|
73
|
+
p = self._position
|
|
45
74
|
|
|
46
75
|
view = np.identity(4, dtype=np.float32)
|
|
47
76
|
view[0, :3] = s
|
|
@@ -67,22 +96,22 @@ class Camera:
|
|
|
67
96
|
)
|
|
68
97
|
|
|
69
98
|
def move_forward(self, dt):
|
|
70
|
-
self.
|
|
99
|
+
self._position += self.front * self.speed * dt
|
|
71
100
|
|
|
72
101
|
def move_backward(self, dt):
|
|
73
|
-
self.
|
|
102
|
+
self._position -= self.front * self.speed * dt
|
|
74
103
|
|
|
75
104
|
def move_left(self, dt):
|
|
76
|
-
self.
|
|
105
|
+
self._position -= self.right * self.speed * dt
|
|
77
106
|
|
|
78
107
|
def move_right(self, dt):
|
|
79
|
-
self.
|
|
108
|
+
self._position += self.right * self.speed * dt
|
|
80
109
|
|
|
81
110
|
def move_up(self, dt):
|
|
82
|
-
self.
|
|
111
|
+
self._position[1] += self.speed * dt
|
|
83
112
|
|
|
84
113
|
def move_down(self, dt):
|
|
85
|
-
self.
|
|
114
|
+
self._position[1] -= self.speed * dt
|
|
86
115
|
|
|
87
116
|
def rotate(self, dx, dy):
|
|
88
117
|
self.yaw += dx * self.sensitivity
|
|
@@ -134,6 +134,74 @@ class MeshFactory:
|
|
|
134
134
|
|
|
135
135
|
return Mesh(vertices, indices)
|
|
136
136
|
|
|
137
|
+
@staticmethod
|
|
138
|
+
def create_cylinder(
|
|
139
|
+
radius: float = 0.5,
|
|
140
|
+
height: float = 1.0,
|
|
141
|
+
segments: int = 24,
|
|
142
|
+
capped: bool = True,
|
|
143
|
+
) -> Mesh:
|
|
144
|
+
"""
|
|
145
|
+
Cylinder along Y axis, centered at origin.
|
|
146
|
+
height is full height (from -h/2 to +h/2).
|
|
147
|
+
"""
|
|
148
|
+
segments = max(3, int(segments))
|
|
149
|
+
half = height * 0.5
|
|
150
|
+
vertices: list[float] = []
|
|
151
|
+
indices: list[int] = []
|
|
152
|
+
|
|
153
|
+
# Side rings (top and bottom edge)
|
|
154
|
+
for i in range(segments + 1):
|
|
155
|
+
angle = i * 2.0 * math.pi / segments
|
|
156
|
+
c = math.cos(angle)
|
|
157
|
+
s = math.sin(angle)
|
|
158
|
+
x = radius * c
|
|
159
|
+
z = radius * s
|
|
160
|
+
nx, nz = c, s
|
|
161
|
+
u = i / segments
|
|
162
|
+
# bottom
|
|
163
|
+
vertices.extend([x, -half, z, nx, 0.0, nz, u, 0.0])
|
|
164
|
+
# top
|
|
165
|
+
vertices.extend([x, half, z, nx, 0.0, nz, u, 1.0])
|
|
166
|
+
|
|
167
|
+
for i in range(segments):
|
|
168
|
+
b0 = i * 2
|
|
169
|
+
t0 = b0 + 1
|
|
170
|
+
b1 = (i + 1) * 2
|
|
171
|
+
t1 = b1 + 1
|
|
172
|
+
indices.extend([b0, b1, t1, b0, t1, t0])
|
|
173
|
+
|
|
174
|
+
if capped:
|
|
175
|
+
# Bottom cap center + rim
|
|
176
|
+
center_bottom = len(vertices) // 8
|
|
177
|
+
vertices.extend([0.0, -half, 0.0, 0.0, -1.0, 0.0, 0.5, 0.5])
|
|
178
|
+
rim_start = center_bottom + 1
|
|
179
|
+
for i in range(segments):
|
|
180
|
+
angle = i * 2.0 * math.pi / segments
|
|
181
|
+
x = radius * math.cos(angle)
|
|
182
|
+
z = radius * math.sin(angle)
|
|
183
|
+
vertices.extend([x, -half, z, 0.0, -1.0, 0.0, 0.5 + 0.5 * math.cos(angle), 0.5 + 0.5 * math.sin(angle)])
|
|
184
|
+
for i in range(segments):
|
|
185
|
+
i0 = rim_start + i
|
|
186
|
+
i1 = rim_start + ((i + 1) % segments)
|
|
187
|
+
indices.extend([center_bottom, i1, i0])
|
|
188
|
+
|
|
189
|
+
# Top cap
|
|
190
|
+
center_top = len(vertices) // 8
|
|
191
|
+
vertices.extend([0.0, half, 0.0, 0.0, 1.0, 0.0, 0.5, 0.5])
|
|
192
|
+
rim_start = center_top + 1
|
|
193
|
+
for i in range(segments):
|
|
194
|
+
angle = i * 2.0 * math.pi / segments
|
|
195
|
+
x = radius * math.cos(angle)
|
|
196
|
+
z = radius * math.sin(angle)
|
|
197
|
+
vertices.extend([x, half, z, 0.0, 1.0, 0.0, 0.5 + 0.5 * math.cos(angle), 0.5 + 0.5 * math.sin(angle)])
|
|
198
|
+
for i in range(segments):
|
|
199
|
+
i0 = rim_start + i
|
|
200
|
+
i1 = rim_start + ((i + 1) % segments)
|
|
201
|
+
indices.extend([center_top, i0, i1])
|
|
202
|
+
|
|
203
|
+
return Mesh(vertices, indices)
|
|
204
|
+
|
|
137
205
|
@staticmethod
|
|
138
206
|
def create_sphere(radius: float = 1.0, sectors: int = 24, stacks: int = 16) -> Mesh:
|
|
139
207
|
"""
|
|
@@ -84,8 +84,8 @@ class Transform(Component):
|
|
|
84
84
|
self._matrix_dirty = False
|
|
85
85
|
|
|
86
86
|
def get_model_matrix(self) -> np.ndarray:
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
# Always rebuild so in-place edits like tf.position[0] = x stay correct
|
|
88
|
+
self._update_matrix()
|
|
89
89
|
return self._cached_matrix
|
|
90
90
|
|
|
91
91
|
def set_position(self, x, y, z):
|
|
@@ -109,7 +109,7 @@ class Transform(Component):
|
|
|
109
109
|
self._matrix_dirty = True
|
|
110
110
|
|
|
111
111
|
def look_at(self, target):
|
|
112
|
-
target = np.asarray(target, dtype=np.float32)
|
|
112
|
+
target = np.asarray(target, dtype=np.float32).reshape(3)
|
|
113
113
|
direction = target - self.position
|
|
114
114
|
length = np.linalg.norm(direction)
|
|
115
115
|
if length < 1e-6:
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
"""Add entity to the scene."""
|
|
15
|
+
if entity not in self.entities:
|
|
16
|
+
self.entities.append(entity)
|
|
17
|
+
return entity
|
|
18
|
+
|
|
19
|
+
def remove(self, entity: Entity) -> bool:
|
|
20
|
+
"""Remove entity from the scene and call destroy() on it."""
|
|
21
|
+
if entity not in self.entities:
|
|
22
|
+
return False
|
|
23
|
+
self.entities.remove(entity)
|
|
24
|
+
entity.destroy()
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
def despawn(self, entity: Entity) -> bool:
|
|
28
|
+
"""Alias for remove()."""
|
|
29
|
+
return self.remove(entity)
|
|
30
|
+
|
|
31
|
+
def find(self, name: str) -> Optional[Entity]:
|
|
32
|
+
"""Find first entity by name, or None."""
|
|
33
|
+
for ent in self.entities:
|
|
34
|
+
if ent.name == name:
|
|
35
|
+
return ent
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def set_main_camera(self, camera: Camera):
|
|
39
|
+
self.main_camera = camera
|
|
40
|
+
|
|
41
|
+
def awake(self):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
def start(self):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def update(self, dt: float):
|
|
48
|
+
for entity in list(self.entities):
|
|
49
|
+
if entity.enabled:
|
|
50
|
+
entity.update(dt)
|
|
51
|
+
|
|
52
|
+
def destroy(self):
|
|
53
|
+
for entity in list(self.entities):
|
|
54
|
+
entity.destroy()
|
|
55
|
+
self.entities.clear()
|
|
@@ -63,3 +63,33 @@ class Hud:
|
|
|
63
63
|
layer=81,
|
|
64
64
|
)
|
|
65
65
|
)
|
|
66
|
+
|
|
67
|
+
def draw_rect(
|
|
68
|
+
self,
|
|
69
|
+
x: float,
|
|
70
|
+
y: float,
|
|
71
|
+
width: float,
|
|
72
|
+
height: float,
|
|
73
|
+
color=(1.0, 1.0, 1.0, 1.0),
|
|
74
|
+
layer: int = 70,
|
|
75
|
+
):
|
|
76
|
+
"""Draw a solid screen-space rectangle (center at x,y)."""
|
|
77
|
+
self._add(Sprite(x, y, width, height, color=color, layer=layer))
|
|
78
|
+
|
|
79
|
+
def draw_panel(
|
|
80
|
+
self,
|
|
81
|
+
x: float,
|
|
82
|
+
y: float,
|
|
83
|
+
width: float,
|
|
84
|
+
height: float,
|
|
85
|
+
fill=(0.05, 0.06, 0.08, 0.75),
|
|
86
|
+
border=(0.9, 0.9, 0.95, 0.9),
|
|
87
|
+
border_thickness: float = 2.0,
|
|
88
|
+
):
|
|
89
|
+
"""Draw a simple bordered panel (no text — use window title / print for text)."""
|
|
90
|
+
self.draw_rect(x, y, width, height, color=fill, layer=70)
|
|
91
|
+
t = border_thickness
|
|
92
|
+
self.draw_rect(x, y + height * 0.5, width, t, color=border, layer=71)
|
|
93
|
+
self.draw_rect(x, y - height * 0.5, width, t, color=border, layer=71)
|
|
94
|
+
self.draw_rect(x - width * 0.5, y, t, height, color=border, layer=71)
|
|
95
|
+
self.draw_rect(x + width * 0.5, y, t, height, color=border, layer=71)
|