aether-engine 1.3.0__tar.gz → 1.4.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.3.0/aether_engine.egg-info → aether_engine-1.4.0}/PKG-INFO +16 -10
- {aether_engine-1.3.0 → aether_engine-1.4.0}/README.md +9 -6
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/__init__.py +4 -1
- aether_engine-1.4.0/aether/__main__.py +6 -0
- aether_engine-1.4.0/aether/cli.py +145 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/_identity.py +1 -1
- aether_engine-1.4.0/aether/core/boot_splash.py +217 -0
- aether_engine-1.4.0/aether/core/branding_guard.py +32 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/engine.py +3 -19
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/mesh.py +21 -0
- aether_engine-1.4.0/aether/graphics/voxel_mesh.py +173 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/serialization.py +19 -9
- aether_engine-1.4.0/aether/tools/__init__.py +1 -0
- aether_engine-1.4.0/aether/tools/packager.py +68 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ui/text.py +8 -2
- {aether_engine-1.3.0 → aether_engine-1.4.0/aether_engine.egg-info}/PKG-INFO +16 -10
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether_engine.egg-info/SOURCES.txt +5 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether_engine.egg-info/entry_points.txt +1 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether_engine.egg-info/requires.txt +7 -3
- {aether_engine-1.3.0 → aether_engine-1.4.0}/setup.py +24 -5
- aether_engine-1.3.0/aether/core/boot_splash.py +0 -141
- aether_engine-1.3.0/aether/core/branding_guard.py +0 -103
- {aether_engine-1.3.0 → aether_engine-1.4.0}/LICENSE +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/MANIFEST.in +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ai/behavior_tree.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ai/navmesh.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/animation/skeleton.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/assets/asset_pipeline.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/audio/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/audio/audio_manager.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/audio/music.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/audio/sfx.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/audio/sound.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/achievement_system.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/application.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/config.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/console.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/event_bus.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/input.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/plugin_system.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/save_system.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/time_manager.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/core/window.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/editor/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/editor/__main__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/editor/gizmo.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/editor/imgui_integration.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/editor/model_editor.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/environment/environment_system.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/camera.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/first_person.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/framebuffer.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/light.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/lod_system.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/material.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/mesh_factory.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/particle_system.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/post_process.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/renderer.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/shader.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/shader_library.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/sky_atmosphere.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/terrain.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/texture.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics/water.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/graphics2d/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/network/network_manager.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/bullet_backend.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/collider.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/physics2d.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/physics3d.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/physics_world.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/raycast.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/physics/rigidbody.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/platform/steam_api.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/asset_database.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/loaders/model_loader.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/loaders/obj_loader.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/loaders/texture_loader.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/resource_manager.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/resources/save_load.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/scene/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/scene/component.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/scene/entity.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/scene/scene.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/scene/scene_graph.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/scene/scene_manager.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ui/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ui/button.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ui/canvas.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/ui/hud.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/utils/__init__.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/utils/fonts.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/utils/logger.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/utils/math_utils.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/utils/object_pool.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether/utils/profiler.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether_engine.egg-info/dependency_links.txt +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/aether_engine.egg-info/top_level.txt +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/examples/01_hello_cube.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/examples/02_sphere_scene.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/examples/03_first_person.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/examples/04_physics2d.py +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.0}/pyproject.toml +0 -0
- {aether_engine-1.3.0 → aether_engine-1.4.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.4.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
|
|
@@ -28,13 +28,16 @@ Provides-Extra: physics
|
|
|
28
28
|
Requires-Dist: pymunk>=6.0; extra == "physics"
|
|
29
29
|
Provides-Extra: models
|
|
30
30
|
Requires-Dist: trimesh>=3.21; extra == "models"
|
|
31
|
+
Provides-Extra: build
|
|
32
|
+
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
31
33
|
Provides-Extra: full
|
|
34
|
+
Requires-Dist: pymunk>=6.0; extra == "full"
|
|
35
|
+
Requires-Dist: pygame-ce>=2.5; extra == "full"
|
|
36
|
+
Requires-Dist: imageio>=2.31; extra == "full"
|
|
32
37
|
Requires-Dist: trimesh>=3.21; extra == "full"
|
|
33
38
|
Requires-Dist: freetype-py>=2.4; extra == "full"
|
|
34
|
-
Requires-Dist:
|
|
39
|
+
Requires-Dist: pyinstaller>=6.0; extra == "full"
|
|
35
40
|
Requires-Dist: pyyaml>=6.0; extra == "full"
|
|
36
|
-
Requires-Dist: pymunk>=6.0; extra == "full"
|
|
37
|
-
Requires-Dist: imageio>=2.31; extra == "full"
|
|
38
41
|
Dynamic: author
|
|
39
42
|
Dynamic: classifier
|
|
40
43
|
Dynamic: description
|
|
@@ -49,7 +52,7 @@ Dynamic: summary
|
|
|
49
52
|
# Aether Engine
|
|
50
53
|
|
|
51
54
|
**3D / 2D игровой движок на Python + OpenGL**
|
|
52
|
-
**Разработчик:** entiti937 · **Версия:** 1.
|
|
55
|
+
**Разработчик:** entiti937 · **Версия:** 1.4.0 · **Лицензия:** MIT
|
|
53
56
|
**PyPI:** https://pypi.org/project/aether-engine/
|
|
54
57
|
|
|
55
58
|
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
@@ -62,18 +65,21 @@ Dynamic: summary
|
|
|
62
65
|
pip install -U "aether-engine[full]"
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
## Что нового в 1.
|
|
68
|
+
## Что нового в 1.4.0
|
|
66
69
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
70
|
+
- Splash как в Unreal (fade-in) — **только при загрузке**, не в игре
|
|
71
|
+
- `aether help` / `aether build` — справка и сборка в `.exe`
|
|
72
|
+
- `ChunkedVoxelMesher` — оптимизация вокселей на уровне движка
|
|
73
|
+
- Исправлен текст splash (кириллица через Pillow)
|
|
74
|
+
- Убран постоянный водяной знак во время геймплея
|
|
71
75
|
|
|
72
76
|
## Showcase-игра
|
|
73
77
|
|
|
74
78
|
Minecraft-like sandbox **на этом же движке**:
|
|
75
79
|
|
|
76
80
|
```bash
|
|
81
|
+
aether help
|
|
82
|
+
aether build games/aether_craft.py --name AetherCraft
|
|
77
83
|
python games/aether_craft.py
|
|
78
84
|
```
|
|
79
85
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Aether Engine
|
|
2
2
|
|
|
3
3
|
**3D / 2D игровой движок на Python + OpenGL**
|
|
4
|
-
**Разработчик:** entiti937 · **Версия:** 1.
|
|
4
|
+
**Разработчик:** entiti937 · **Версия:** 1.4.0 · **Лицензия:** MIT
|
|
5
5
|
**PyPI:** https://pypi.org/project/aether-engine/
|
|
6
6
|
|
|
7
7
|
> Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
|
|
@@ -14,18 +14,21 @@
|
|
|
14
14
|
pip install -U "aether-engine[full]"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## Что нового в 1.
|
|
17
|
+
## Что нового в 1.4.0
|
|
18
18
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
19
|
+
- Splash как в Unreal (fade-in) — **только при загрузке**, не в игре
|
|
20
|
+
- `aether help` / `aether build` — справка и сборка в `.exe`
|
|
21
|
+
- `ChunkedVoxelMesher` — оптимизация вокселей на уровне движка
|
|
22
|
+
- Исправлен текст splash (кириллица через Pillow)
|
|
23
|
+
- Убран постоянный водяной знак во время геймплея
|
|
23
24
|
|
|
24
25
|
## Showcase-игра
|
|
25
26
|
|
|
26
27
|
Minecraft-like sandbox **на этом же движке**:
|
|
27
28
|
|
|
28
29
|
```bash
|
|
30
|
+
aether help
|
|
31
|
+
aether build games/aether_craft.py --name AetherCraft
|
|
29
32
|
python games/aether_craft.py
|
|
30
33
|
```
|
|
31
34
|
|
|
@@ -31,7 +31,9 @@ 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.4.0"
|
|
35
|
+
|
|
36
|
+
from aether.graphics.voxel_mesh import ChunkedVoxelMesher
|
|
35
37
|
|
|
36
38
|
__all__ = [
|
|
37
39
|
"Engine",
|
|
@@ -62,5 +64,6 @@ __all__ = [
|
|
|
62
64
|
"save_json",
|
|
63
65
|
"load_json",
|
|
64
66
|
"Raycaster",
|
|
67
|
+
"ChunkedVoxelMesher",
|
|
65
68
|
"__version__",
|
|
66
69
|
]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Aether Engine CLI — help, docs, build."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from aether import __version__
|
|
9
|
+
from aether.core._identity import creator, engine_name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
QUICK_REFERENCE = f"""
|
|
13
|
+
{engine_name()} v{__version__} — разработчик: {creator()}
|
|
14
|
+
================================================================
|
|
15
|
+
|
|
16
|
+
УСТАНОВКА
|
|
17
|
+
pip install -U "aether-engine[full]"
|
|
18
|
+
|
|
19
|
+
МИНИМАЛЬНЫЙ КОД (3D)
|
|
20
|
+
from aether import Engine, Scene, Entity, MeshFactory, Material, Camera
|
|
21
|
+
from aether.scene.component import Transform, MeshRenderer
|
|
22
|
+
|
|
23
|
+
engine = Engine(1280, 720, "My Game")
|
|
24
|
+
scene = Scene("Main")
|
|
25
|
+
cam = Camera(position=(0, 2, 6), yaw=-90)
|
|
26
|
+
scene.set_main_camera(cam)
|
|
27
|
+
|
|
28
|
+
cube = Entity("Cube")
|
|
29
|
+
cube.add_component(Transform(position=(0, 1, 0)))
|
|
30
|
+
mat = Material(); mat.albedo[:] = (0.2, 0.9, 0.3)
|
|
31
|
+
cube.add_component(MeshRenderer(mesh=MeshFactory.create_cube(), material=mat))
|
|
32
|
+
scene.spawn(cube)
|
|
33
|
+
|
|
34
|
+
engine.load_scene(scene)
|
|
35
|
+
engine.enable_first_person(cam) # FPS
|
|
36
|
+
engine.run() # ESC — выход
|
|
37
|
+
|
|
38
|
+
FPS / КАМЕРА
|
|
39
|
+
engine.enable_first_person(camera, eye_height=1.7, walk_speed=6)
|
|
40
|
+
engine.free_camera = True # WASD + Tab (мышь)
|
|
41
|
+
|
|
42
|
+
2D РЕЖИМ
|
|
43
|
+
engine.set_mode("2d")
|
|
44
|
+
engine.renderer2d.add_sprite(Sprite(x=0, y=0, width=40, height=40, color=(1,0,0,1)))
|
|
45
|
+
|
|
46
|
+
МОДЕЛИ
|
|
47
|
+
from aether import ModelLoader
|
|
48
|
+
mesh = ModelLoader.load("hero.glb") # obj/stl/glb/gltf/fbx...
|
|
49
|
+
|
|
50
|
+
ВОКСЕЛИ (ОПТИМИЗАЦИЯ)
|
|
51
|
+
from aether.graphics.voxel_mesh import ChunkedVoxelMesher
|
|
52
|
+
mesher = ChunkedVoxelMesher(world.get_block)
|
|
53
|
+
mesher.notify_block_change(x, y, z)
|
|
54
|
+
meshes = mesher.flush_dirty() # только изменённые чанки
|
|
55
|
+
|
|
56
|
+
ФИЗИКА
|
|
57
|
+
engine.physics3d.create_body_for_entity(ent, collider=BoxCollider())
|
|
58
|
+
engine.physics2d.create_box(x, y, w, h, mass=1.0)
|
|
59
|
+
|
|
60
|
+
ВВОД
|
|
61
|
+
import glfw
|
|
62
|
+
engine.input.get_key(glfw.KEY_W)
|
|
63
|
+
engine.input.get_key_down(glfw.KEY_SPACE)
|
|
64
|
+
|
|
65
|
+
АПДЕЙТ
|
|
66
|
+
engine.on_update(lambda dt: ...)
|
|
67
|
+
|
|
68
|
+
СБОРКА В EXE
|
|
69
|
+
aether build my_game.py
|
|
70
|
+
aether build my_game.py --name MyGame --console
|
|
71
|
+
|
|
72
|
+
ИНСТРУМЕНТЫ
|
|
73
|
+
aether help — эта справка
|
|
74
|
+
aether docs — краткий гайд по API
|
|
75
|
+
aether-editor — 3D редактор моделей
|
|
76
|
+
python games/aether_craft.py
|
|
77
|
+
|
|
78
|
+
ПОЛНАЯ ДОКУМЕНТАЦИЯ: docs/GUIDE.md
|
|
79
|
+
PyPI: https://pypi.org/project/aether-engine/
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def cmd_help(_args):
|
|
84
|
+
print(QUICK_REFERENCE)
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_docs(_args):
|
|
89
|
+
guide = Path(__file__).resolve().parents[2] / "docs" / "GUIDE.md"
|
|
90
|
+
if guide.exists():
|
|
91
|
+
print(guide.read_text(encoding="utf-8"))
|
|
92
|
+
else:
|
|
93
|
+
print(QUICK_REFERENCE)
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def cmd_build(args):
|
|
98
|
+
from aether.tools.packager import build_exe
|
|
99
|
+
|
|
100
|
+
return build_exe(
|
|
101
|
+
args.script,
|
|
102
|
+
name=args.name,
|
|
103
|
+
onefile=not args.dir,
|
|
104
|
+
windowed=not args.console,
|
|
105
|
+
icon=args.icon,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def cmd_editor(_args):
|
|
110
|
+
from aether.editor.model_editor import main as editor_main
|
|
111
|
+
|
|
112
|
+
editor_main()
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main(argv=None):
|
|
117
|
+
parser = argparse.ArgumentParser(
|
|
118
|
+
prog="aether",
|
|
119
|
+
description=f"{engine_name()} CLI by {creator()}",
|
|
120
|
+
)
|
|
121
|
+
parser.add_argument("--version", action="version", version=f"{engine_name()} {__version__}")
|
|
122
|
+
|
|
123
|
+
sub = parser.add_subparsers(dest="command")
|
|
124
|
+
|
|
125
|
+
sub.add_parser("help", help="Краткая справка по API и командам").set_defaults(func=cmd_help)
|
|
126
|
+
sub.add_parser("docs", help="Полная документация").set_defaults(func=cmd_docs)
|
|
127
|
+
sub.add_parser("editor", help="3D редактор моделей").set_defaults(func=cmd_editor)
|
|
128
|
+
|
|
129
|
+
p_build = sub.add_parser("build", help="Собрать игру в .exe (PyInstaller)")
|
|
130
|
+
p_build.add_argument("script", help="Путь к main.py игры")
|
|
131
|
+
p_build.add_argument("--name", help="Имя exe", default=None)
|
|
132
|
+
p_build.add_argument("--console", action="store_true", help="Показать консоль")
|
|
133
|
+
p_build.add_argument("--dir", action="store_true", help="Папка вместо onefile")
|
|
134
|
+
p_build.add_argument("--icon", help="Иконка .ico", default=None)
|
|
135
|
+
p_build.set_defaults(func=cmd_build)
|
|
136
|
+
|
|
137
|
+
args = parser.parse_args(argv)
|
|
138
|
+
if not args.command:
|
|
139
|
+
cmd_help(args)
|
|
140
|
+
return 0
|
|
141
|
+
return args.func(args)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
sys.exit(main())
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""UE-style boot splash — fade-in text on black, preload only."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import glfw
|
|
8
|
+
import numpy as np
|
|
9
|
+
from OpenGL.GL import *
|
|
10
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
11
|
+
|
|
12
|
+
from aether.core._identity import creator, splash_line1, splash_line2, verify_token
|
|
13
|
+
from aether.utils.fonts import resolve_system_font
|
|
14
|
+
|
|
15
|
+
_SPLASH_VERT = """
|
|
16
|
+
#version 330 core
|
|
17
|
+
layout(location = 0) in vec2 aPos;
|
|
18
|
+
layout(location = 1) in vec2 aUV;
|
|
19
|
+
out vec2 vUV;
|
|
20
|
+
uniform float uAlpha;
|
|
21
|
+
void main() {
|
|
22
|
+
vUV = aUV;
|
|
23
|
+
gl_Position = vec4(aPos, 0.0, 1.0);
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_SPLASH_FRAG = """
|
|
28
|
+
#version 330 core
|
|
29
|
+
in vec2 vUV;
|
|
30
|
+
out vec4 FragColor;
|
|
31
|
+
uniform sampler2D uTex;
|
|
32
|
+
uniform float uAlpha;
|
|
33
|
+
void main() {
|
|
34
|
+
vec4 c = texture(uTex, vUV);
|
|
35
|
+
FragColor = vec4(c.rgb, c.a * uAlpha);
|
|
36
|
+
}
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BootSplash:
|
|
41
|
+
"""Animated preload splash (Unreal-style fade). Shown ONLY during Engine init."""
|
|
42
|
+
|
|
43
|
+
MIN_SHOW_SEC = 2.4
|
|
44
|
+
FADE_IN_SEC = 0.85
|
|
45
|
+
FADE_OUT_SEC = 0.55
|
|
46
|
+
|
|
47
|
+
def __init__(self, window):
|
|
48
|
+
self.window = window
|
|
49
|
+
self.width = window.width
|
|
50
|
+
self.height = window.height
|
|
51
|
+
self._started = time.monotonic()
|
|
52
|
+
self._progress = 0.0
|
|
53
|
+
self._status = ""
|
|
54
|
+
self._font = None
|
|
55
|
+
self._font_small = None
|
|
56
|
+
self._load_fonts()
|
|
57
|
+
self._init_gl()
|
|
58
|
+
self.draw(0.0, "Загрузка...")
|
|
59
|
+
|
|
60
|
+
def _load_fonts(self):
|
|
61
|
+
path = resolve_system_font()
|
|
62
|
+
if not path:
|
|
63
|
+
return
|
|
64
|
+
try:
|
|
65
|
+
self._font = ImageFont.truetype(path, 36)
|
|
66
|
+
self._font_small = ImageFont.truetype(path, 22)
|
|
67
|
+
except Exception:
|
|
68
|
+
self._font = ImageFont.load_default()
|
|
69
|
+
self._font_small = self._font
|
|
70
|
+
|
|
71
|
+
def _init_gl(self):
|
|
72
|
+
from aether.graphics.shader import Shader
|
|
73
|
+
|
|
74
|
+
self._shader = Shader(_SPLASH_VERT, _SPLASH_FRAG)
|
|
75
|
+
self._vao = glGenVertexArrays(1)
|
|
76
|
+
self._vbo = glGenBuffers(1)
|
|
77
|
+
self._tex = glGenTextures(1)
|
|
78
|
+
quad = np.array(
|
|
79
|
+
[
|
|
80
|
+
-1, -1, 0, 0,
|
|
81
|
+
1, -1, 1, 0,
|
|
82
|
+
1, 1, 1, 1,
|
|
83
|
+
-1, -1, 0, 0,
|
|
84
|
+
1, 1, 1, 1,
|
|
85
|
+
-1, 1, 0, 1,
|
|
86
|
+
],
|
|
87
|
+
dtype=np.float32,
|
|
88
|
+
)
|
|
89
|
+
glBindVertexArray(self._vao)
|
|
90
|
+
glBindBuffer(GL_ARRAY_BUFFER, self._vbo)
|
|
91
|
+
glBufferData(GL_ARRAY_BUFFER, quad.nbytes, quad, GL_STATIC_DRAW)
|
|
92
|
+
glEnableVertexAttribArray(0)
|
|
93
|
+
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(0))
|
|
94
|
+
glEnableVertexAttribArray(1)
|
|
95
|
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(8))
|
|
96
|
+
glBindVertexArray(0)
|
|
97
|
+
|
|
98
|
+
def _fade_alpha(self) -> float:
|
|
99
|
+
t = time.monotonic() - self._started
|
|
100
|
+
if t < self.FADE_IN_SEC:
|
|
101
|
+
return self._ease_out(t / self.FADE_IN_SEC)
|
|
102
|
+
if self._progress >= 0.99 and t > self.MIN_SHOW_SEC:
|
|
103
|
+
out_t = t - self.MIN_SHOW_SEC
|
|
104
|
+
if out_t < self.FADE_OUT_SEC:
|
|
105
|
+
return 1.0 - self._ease_in(out_t / self.FADE_OUT_SEC)
|
|
106
|
+
return 0.0
|
|
107
|
+
return 1.0
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _ease_out(x: float) -> float:
|
|
111
|
+
return 1.0 - (1.0 - x) ** 3
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _ease_in(x: float) -> float:
|
|
115
|
+
return x * x * x
|
|
116
|
+
|
|
117
|
+
def _compose_image(self, alpha: float) -> np.ndarray:
|
|
118
|
+
w, h = self.width, self.height
|
|
119
|
+
img = Image.new("RGBA", (w, h), (0, 0, 0, 255))
|
|
120
|
+
|
|
121
|
+
if self._font:
|
|
122
|
+
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
|
123
|
+
draw = ImageDraw.Draw(overlay)
|
|
124
|
+
|
|
125
|
+
line1 = splash_line1()
|
|
126
|
+
line2 = splash_line2()
|
|
127
|
+
a = int(255 * max(0.0, min(1.0, alpha)))
|
|
128
|
+
|
|
129
|
+
# Slight slide-up like UE
|
|
130
|
+
slide = int((1.0 - min(1.0, alpha * 1.2)) * 28)
|
|
131
|
+
|
|
132
|
+
def tw(text, font):
|
|
133
|
+
bb = draw.textbbox((0, 0), text, font=font)
|
|
134
|
+
return bb[2] - bb[0], bb[3] - bb[1]
|
|
135
|
+
|
|
136
|
+
t1w, t1h = tw(line1, self._font)
|
|
137
|
+
t2w, t2h = tw(line2, self._font_small)
|
|
138
|
+
cy = h // 2 - slide
|
|
139
|
+
|
|
140
|
+
draw.text(((w - t1w) // 2, cy - t1h - 8), line1, font=self._font, fill=(255, 255, 255, a))
|
|
141
|
+
draw.text(((w - t2w) // 2, cy + 6), line2, font=self._font_small, fill=(230, 230, 230, a))
|
|
142
|
+
|
|
143
|
+
if self._status:
|
|
144
|
+
sw, sh = tw(self._status, self._font_small)
|
|
145
|
+
draw.text(
|
|
146
|
+
((w - sw) // 2, cy + t2h + 36),
|
|
147
|
+
self._status,
|
|
148
|
+
font=self._font_small,
|
|
149
|
+
fill=(160, 160, 160, a),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Progress bar
|
|
153
|
+
bar_w = min(420, int(w * 0.5))
|
|
154
|
+
bar_h = 4
|
|
155
|
+
bx = (w - bar_w) // 2
|
|
156
|
+
by = int(h * 0.62)
|
|
157
|
+
draw.rectangle([bx, by, bx + bar_w, by + bar_h], fill=(40, 40, 40, a))
|
|
158
|
+
fill = int(bar_w * self._progress)
|
|
159
|
+
if fill > 0:
|
|
160
|
+
draw.rectangle([bx, by, bx + fill, by + bar_h], fill=(255, 255, 255, a))
|
|
161
|
+
|
|
162
|
+
img = Image.alpha_composite(img, overlay)
|
|
163
|
+
|
|
164
|
+
return np.array(img, dtype=np.uint8)
|
|
165
|
+
|
|
166
|
+
def draw(self, progress: float, status: str = ""):
|
|
167
|
+
if not verify_token():
|
|
168
|
+
raise RuntimeError("Aether Engine identity verification failed")
|
|
169
|
+
|
|
170
|
+
self._progress = max(self._progress, progress)
|
|
171
|
+
self._status = status
|
|
172
|
+
alpha = self._fade_alpha()
|
|
173
|
+
|
|
174
|
+
glViewport(0, 0, self.width, self.height)
|
|
175
|
+
glClearColor(0, 0, 0, 1)
|
|
176
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
177
|
+
|
|
178
|
+
data = self._compose_image(alpha)
|
|
179
|
+
glBindTexture(GL_TEXTURE_2D, self._tex)
|
|
180
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
|
181
|
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
|
182
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
|
|
183
|
+
|
|
184
|
+
glDisable(GL_DEPTH_TEST)
|
|
185
|
+
glEnable(GL_BLEND)
|
|
186
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
187
|
+
|
|
188
|
+
self._shader.use()
|
|
189
|
+
self._shader.set_float("uAlpha", 1.0)
|
|
190
|
+
glActiveTexture(GL_TEXTURE0)
|
|
191
|
+
glBindTexture(GL_TEXTURE_2D, self._tex)
|
|
192
|
+
self._shader.set_int("uTex", 0)
|
|
193
|
+
|
|
194
|
+
glBindVertexArray(self._vao)
|
|
195
|
+
glDrawArrays(GL_TRIANGLES, 0, 6)
|
|
196
|
+
glBindVertexArray(0)
|
|
197
|
+
|
|
198
|
+
glDisable(GL_BLEND)
|
|
199
|
+
glEnable(GL_DEPTH_TEST)
|
|
200
|
+
|
|
201
|
+
self.window.swap_buffers()
|
|
202
|
+
glfw.poll_events()
|
|
203
|
+
|
|
204
|
+
def step(self, progress: float, status: str):
|
|
205
|
+
self.draw(progress, status)
|
|
206
|
+
time.sleep(0.016)
|
|
207
|
+
|
|
208
|
+
def finish(self):
|
|
209
|
+
# Fade-out animation
|
|
210
|
+
t0 = time.monotonic()
|
|
211
|
+
while time.monotonic() - t0 < self.FADE_OUT_SEC + 0.05:
|
|
212
|
+
self._progress = 1.0
|
|
213
|
+
self.draw(1.0, "Готово")
|
|
214
|
+
self.draw(1.0, "")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
import ctypes # noqa: E402
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Runtime integrity checks — no on-screen watermark during gameplay."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from aether.core._identity import verify_token
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BrandingGuard:
|
|
8
|
+
"""Silent integrity guard. Branding is shown only on boot splash."""
|
|
9
|
+
|
|
10
|
+
_tamper_count = 0
|
|
11
|
+
|
|
12
|
+
def __init__(self, engine):
|
|
13
|
+
self.engine = engine
|
|
14
|
+
self._frame = 0
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def assert_integrity(cls, engine) -> bool:
|
|
18
|
+
if not verify_token():
|
|
19
|
+
cls._tamper_count += 1
|
|
20
|
+
return False
|
|
21
|
+
if getattr(engine, "_brand_bypass", False):
|
|
22
|
+
cls._tamper_count += 1
|
|
23
|
+
engine._brand_bypass = False
|
|
24
|
+
return False
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
def tick(self):
|
|
28
|
+
self._frame += 1
|
|
29
|
+
if self._frame % 120 == 0 and not verify_token():
|
|
30
|
+
raise RuntimeError(
|
|
31
|
+
"Aether Engine: integrity check failed. Reinstall: pip install -U aether-engine"
|
|
32
|
+
)
|
|
@@ -276,7 +276,7 @@ class Engine:
|
|
|
276
276
|
traceback.print_exc()
|
|
277
277
|
|
|
278
278
|
if self._frame_count % 180 == 0 and not BrandingGuard.assert_integrity(self):
|
|
279
|
-
|
|
279
|
+
raise RuntimeError("Aether Engine integrity check failed")
|
|
280
280
|
|
|
281
281
|
profiler.end_frame()
|
|
282
282
|
self._frame_count += 1
|
|
@@ -360,7 +360,8 @@ class Engine:
|
|
|
360
360
|
self.renderer2d.begin_frame(clear=True)
|
|
361
361
|
self.renderer2d.render()
|
|
362
362
|
self.renderer2d.end_frame()
|
|
363
|
-
self.
|
|
363
|
+
if hasattr(self, "_branding") and self._branding:
|
|
364
|
+
self._branding.tick()
|
|
364
365
|
return
|
|
365
366
|
|
|
366
367
|
self.renderer.begin_frame()
|
|
@@ -380,23 +381,6 @@ class Engine:
|
|
|
380
381
|
self.renderer2d.render()
|
|
381
382
|
self.renderer2d.end_frame()
|
|
382
383
|
|
|
383
|
-
self._render_branding()
|
|
384
|
-
|
|
385
|
-
def _render_branding(self):
|
|
386
|
-
"""Mandatory watermark — always drawn."""
|
|
387
|
-
if not hasattr(self, "_branding") or self._branding is None:
|
|
388
|
-
return
|
|
389
|
-
try:
|
|
390
|
-
from OpenGL.GL import glDisable, glEnable, GL_BLEND, GL_DEPTH_TEST
|
|
391
|
-
|
|
392
|
-
glDisable(GL_DEPTH_TEST)
|
|
393
|
-
glEnable(GL_BLEND)
|
|
394
|
-
self._branding.draw_watermark()
|
|
395
|
-
self._branding.tick()
|
|
396
|
-
glEnable(GL_DEPTH_TEST)
|
|
397
|
-
except Exception:
|
|
398
|
-
pass
|
|
399
|
-
|
|
400
384
|
def on_update(self, callback):
|
|
401
385
|
"""Регистрирует callback(dt), вызываемый каждый кадр."""
|
|
402
386
|
self._update_callbacks.append(callback)
|
|
@@ -75,6 +75,27 @@ class Mesh:
|
|
|
75
75
|
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
76
76
|
self._gpu_ready = True
|
|
77
77
|
|
|
78
|
+
def update_data(self, vertices, indices=None):
|
|
79
|
+
"""Update GPU buffers in-place (fast path for voxel edits)."""
|
|
80
|
+
self.vertices = np.array(vertices, dtype=np.float32).reshape(-1)
|
|
81
|
+
if indices is not None:
|
|
82
|
+
self.indices = np.array(indices, dtype=np.uint32)
|
|
83
|
+
self.vertex_count = len(self.vertices) // 8
|
|
84
|
+
self.index_count = 0 if self.indices is None else len(self.indices)
|
|
85
|
+
|
|
86
|
+
if not self._gpu_ready:
|
|
87
|
+
self._create_gl_buffers()
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
91
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_DYNAMIC_DRAW)
|
|
92
|
+
if self.indices is not None and self.index_count > 0:
|
|
93
|
+
if not self.ebo:
|
|
94
|
+
self.ebo = glGenBuffers(1)
|
|
95
|
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
|
|
96
|
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.indices.nbytes, self.indices, GL_DYNAMIC_DRAW)
|
|
97
|
+
glBindVertexArray(0)
|
|
98
|
+
|
|
78
99
|
def draw(self):
|
|
79
100
|
self.ensure_gpu()
|
|
80
101
|
if not self.vao:
|