aether-engine 1.0.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 (84) hide show
  1. aether_engine-1.0.0/LICENSE +21 -0
  2. aether_engine-1.0.0/MANIFEST.in +14 -0
  3. aether_engine-1.0.0/PKG-INFO +71 -0
  4. aether_engine-1.0.0/README.md +32 -0
  5. aether_engine-1.0.0/aether/__init__.py +54 -0
  6. aether_engine-1.0.0/aether/ai/behavior_tree.py +403 -0
  7. aether_engine-1.0.0/aether/ai/navmesh.py +265 -0
  8. aether_engine-1.0.0/aether/animation/skeleton.py +296 -0
  9. aether_engine-1.0.0/aether/assets/asset_pipeline.py +365 -0
  10. aether_engine-1.0.0/aether/audio/__init__.py +0 -0
  11. aether_engine-1.0.0/aether/audio/audio_manager.py +211 -0
  12. aether_engine-1.0.0/aether/audio/music.py +6 -0
  13. aether_engine-1.0.0/aether/audio/sound.py +6 -0
  14. aether_engine-1.0.0/aether/core/__init__.py +211 -0
  15. aether_engine-1.0.0/aether/core/achievement_system.py +304 -0
  16. aether_engine-1.0.0/aether/core/application.py +19 -0
  17. aether_engine-1.0.0/aether/core/config.py +75 -0
  18. aether_engine-1.0.0/aether/core/console.py +366 -0
  19. aether_engine-1.0.0/aether/core/engine.py +50 -0
  20. aether_engine-1.0.0/aether/core/event_bus.py +24 -0
  21. aether_engine-1.0.0/aether/core/input.py +58 -0
  22. aether_engine-1.0.0/aether/core/plugin_system.py +93 -0
  23. aether_engine-1.0.0/aether/core/save_system.py +409 -0
  24. aether_engine-1.0.0/aether/core/time_manager.py +25 -0
  25. aether_engine-1.0.0/aether/core/window.py +57 -0
  26. aether_engine-1.0.0/aether/editor/gizmo.py +346 -0
  27. aether_engine-1.0.0/aether/editor/imgui_integration.py +468 -0
  28. aether_engine-1.0.0/aether/environment/environment_system.py +291 -0
  29. aether_engine-1.0.0/aether/graphics/__init__.py +0 -0
  30. aether_engine-1.0.0/aether/graphics/camera.py +69 -0
  31. aether_engine-1.0.0/aether/graphics/framebuffer.py +119 -0
  32. aether_engine-1.0.0/aether/graphics/light.py +32 -0
  33. aether_engine-1.0.0/aether/graphics/lod_system.py +170 -0
  34. aether_engine-1.0.0/aether/graphics/material.py +25 -0
  35. aether_engine-1.0.0/aether/graphics/mesh.py +83 -0
  36. aether_engine-1.0.0/aether/graphics/mesh_factory.py +67 -0
  37. aether_engine-1.0.0/aether/graphics/particle_system.py +268 -0
  38. aether_engine-1.0.0/aether/graphics/post_process.py +279 -0
  39. aether_engine-1.0.0/aether/graphics/renderer.py +135 -0
  40. aether_engine-1.0.0/aether/graphics/shader.py +61 -0
  41. aether_engine-1.0.0/aether/graphics/shader_library.py +157 -0
  42. aether_engine-1.0.0/aether/graphics/sky_atmosphere.py +309 -0
  43. aether_engine-1.0.0/aether/graphics/terrain.py +408 -0
  44. aether_engine-1.0.0/aether/graphics/texture.py +34 -0
  45. aether_engine-1.0.0/aether/graphics/water.py +432 -0
  46. aether_engine-1.0.0/aether/network/network_manager.py +395 -0
  47. aether_engine-1.0.0/aether/physics/__init__.py +0 -0
  48. aether_engine-1.0.0/aether/physics/bullet_backend.py +395 -0
  49. aether_engine-1.0.0/aether/physics/collider.py +22 -0
  50. aether_engine-1.0.0/aether/physics/physics_world.py +436 -0
  51. aether_engine-1.0.0/aether/physics/raycast.py +25 -0
  52. aether_engine-1.0.0/aether/physics/rigidbody.py +7 -0
  53. aether_engine-1.0.0/aether/platform/steam_api.py +378 -0
  54. aether_engine-1.0.0/aether/resources/__init__.py +0 -0
  55. aether_engine-1.0.0/aether/resources/asset_database.py +76 -0
  56. aether_engine-1.0.0/aether/resources/loaders/obj_loader.py +104 -0
  57. aether_engine-1.0.0/aether/resources/loaders/texture_loader.py +261 -0
  58. aether_engine-1.0.0/aether/resources/resource_manager.py +147 -0
  59. aether_engine-1.0.0/aether/resources/serialization.py +114 -0
  60. aether_engine-1.0.0/aether/scene/__init__.py +0 -0
  61. aether_engine-1.0.0/aether/scene/component.py +368 -0
  62. aether_engine-1.0.0/aether/scene/entity.py +46 -0
  63. aether_engine-1.0.0/aether/scene/scene.py +33 -0
  64. aether_engine-1.0.0/aether/scene/scene_graph.py +252 -0
  65. aether_engine-1.0.0/aether/scene/scene_manager.py +191 -0
  66. aether_engine-1.0.0/aether/ui/__init__.py +0 -0
  67. aether_engine-1.0.0/aether/ui/button.py +297 -0
  68. aether_engine-1.0.0/aether/ui/canvas.py +11 -0
  69. aether_engine-1.0.0/aether/ui/text.py +286 -0
  70. aether_engine-1.0.0/aether/utils/__init__.py +0 -0
  71. aether_engine-1.0.0/aether/utils/logger.py +16 -0
  72. aether_engine-1.0.0/aether/utils/math_utils.py +31 -0
  73. aether_engine-1.0.0/aether/utils/object_pool.py +137 -0
  74. aether_engine-1.0.0/aether/utils/profiler.py +187 -0
  75. aether_engine-1.0.0/aether_engine.egg-info/PKG-INFO +71 -0
  76. aether_engine-1.0.0/aether_engine.egg-info/SOURCES.txt +82 -0
  77. aether_engine-1.0.0/aether_engine.egg-info/dependency_links.txt +1 -0
  78. aether_engine-1.0.0/aether_engine.egg-info/requires.txt +19 -0
  79. aether_engine-1.0.0/aether_engine.egg-info/top_level.txt +1 -0
  80. aether_engine-1.0.0/examples/01_hello_cube.py +28 -0
  81. aether_engine-1.0.0/examples/02_sphere_scene.py +42 -0
  82. aether_engine-1.0.0/pyproject.toml +3 -0
  83. aether_engine-1.0.0/setup.cfg +4 -0
  84. aether_engine-1.0.0/setup.py +54 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 entiti937
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+
5
+ recursive-include aether *.py
6
+ recursive-include aether *.glsl
7
+ recursive-include aether *.vert
8
+ recursive-include aether *.frag
9
+ recursive-include aether *.json
10
+
11
+ recursive-include examples *.py
12
+
13
+ exclude __pycache__
14
+ exclude *.pyc
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: aether-engine
3
+ Version: 1.0.0
4
+ Summary: Полноценный 3D игровой движок на Python с OpenGL
5
+ Author: entiti937
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy>=1.24
15
+ Requires-Dist: glfw>=2.6
16
+ Requires-Dist: PyOpenGL>=3.1.7
17
+ Requires-Dist: Pillow>=10.0
18
+ Provides-Extra: physics
19
+ Requires-Dist: PyBullet>=3.2.5; extra == "physics"
20
+ Provides-Extra: audio
21
+ Requires-Dist: pygame>=2.5; extra == "audio"
22
+ Provides-Extra: full
23
+ Requires-Dist: PyBullet>=3.2.5; extra == "full"
24
+ Requires-Dist: pygame>=2.5; extra == "full"
25
+ Requires-Dist: freetype-py>=2.4; extra == "full"
26
+ Requires-Dist: imgui>=2.0; extra == "full"
27
+ Requires-Dist: noise>=1.2; extra == "full"
28
+ Requires-Dist: pyyaml>=6.0; extra == "full"
29
+ Requires-Dist: imageio>=2.31; extra == "full"
30
+ Dynamic: author
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: license-file
35
+ Dynamic: provides-extra
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
39
+
40
+ # Aether Engine
41
+
42
+ Полноценный 3D игровой движок на Python с OpenGL 4.6.
43
+
44
+ ## Возможности
45
+ - PBR-рендеринг
46
+ - Система Entity-Component
47
+ - Физика (базовая)
48
+ - Звуковая система
49
+ - UI-фреймворк
50
+ - Загрузка моделей (OBJ)
51
+ - Пост-обработка
52
+
53
+ ## Установка
54
+ ```bash
55
+ pip install -e .
56
+ # Быстрый старт
57
+ from aether import Engine, Scene, Entity, MeshFactory, Material
58
+
59
+ engine = Engine(1280, 720, "Моя игра")
60
+ scene = Scene("Level 1")
61
+
62
+ cube = Entity("Куб")
63
+ cube.add_component(Transform())
64
+ cube.add_component(MeshRenderer(
65
+ mesh=MeshFactory.create_cube(),
66
+ material=Material()
67
+ ))
68
+
69
+ scene.spawn(cube)
70
+ engine.load_scene(scene)
71
+ engine.run()
@@ -0,0 +1,32 @@
1
+ # Aether Engine
2
+
3
+ Полноценный 3D игровой движок на Python с OpenGL 4.6.
4
+
5
+ ## Возможности
6
+ - PBR-рендеринг
7
+ - Система Entity-Component
8
+ - Физика (базовая)
9
+ - Звуковая система
10
+ - UI-фреймворк
11
+ - Загрузка моделей (OBJ)
12
+ - Пост-обработка
13
+
14
+ ## Установка
15
+ ```bash
16
+ pip install -e .
17
+ # Быстрый старт
18
+ from aether import Engine, Scene, Entity, MeshFactory, Material
19
+
20
+ engine = Engine(1280, 720, "Моя игра")
21
+ scene = Scene("Level 1")
22
+
23
+ cube = Entity("Куб")
24
+ cube.add_component(Transform())
25
+ cube.add_component(MeshRenderer(
26
+ mesh=MeshFactory.create_cube(),
27
+ material=Material()
28
+ ))
29
+
30
+ scene.spawn(cube)
31
+ engine.load_scene(scene)
32
+ engine.run()
@@ -0,0 +1,54 @@
1
+ """
2
+ Aether Engine — полноценный 3D игровой движок на Python с OpenGL
3
+ """
4
+
5
+ __version__ = "1.0.0"
6
+ __author__ = "entiti937"
7
+
8
+ # Ядро
9
+ from aether.core.engine import Engine
10
+ from aether.core.application import Application
11
+ from aether.core.config import EngineConfig, GraphicsConfig, PhysicsConfig, AudioConfig
12
+
13
+ # Сцена
14
+ from aether.scene.entity import Entity
15
+ from aether.scene.component import (
16
+ Transform, MeshRenderer, CameraComponent,
17
+ LightComponent, RigidbodyComponent, ScriptComponent, AnimatorComponent
18
+ )
19
+ from aether.scene.scene import Scene
20
+ from aether.scene.scene_manager import SceneManager
21
+
22
+ # Графика
23
+ from aether.graphics.shader import Shader
24
+ from aether.graphics.shader_library import ShaderLibrary
25
+ from aether.graphics.mesh import Mesh
26
+ from aether.graphics.mesh_factory import MeshFactory
27
+ from aether.graphics.material import Material
28
+ from aether.graphics.texture import Texture
29
+ from aether.graphics.camera import Camera
30
+ from aether.graphics.light import DirectionalLight, PointLight, SpotLight
31
+ from aether.graphics.renderer import Renderer
32
+ from aether.graphics.particle_system import ParticleEmitter, ParticleRenderer
33
+
34
+ # Физика
35
+ from aether.physics.physics_world import PhysicsWorld
36
+ from aether.physics.collider import BoxCollider, SphereCollider
37
+ from aether.physics.rigidbody import Rigidbody
38
+
39
+ # Ресурсы
40
+ from aether.resources.resource_manager import ResourceManager
41
+ from aether.resources.asset_database import AssetDatabase
42
+ from aether.resources.loaders.obj_loader import OBJLoader
43
+ from aether.resources.loaders.texture_loader import TextureLoader
44
+
45
+ # UI
46
+ from aether.ui.canvas import Canvas
47
+ from aether.ui.button import Button, Panel, Label
48
+ from aether.ui.text import Font, TextRenderer
49
+
50
+ # Аудио
51
+ from aether.audio.audio_manager import AudioManager, Sound, Music
52
+
53
+ # Утилиты
54
+ from aether.utils.profiler import Profiler, ScopedProfiler
@@ -0,0 +1,403 @@
1
+ """Система деревьев поведения для AI"""
2
+ from enum import Enum
3
+ from typing import List, Dict, Optional, Callable, Any
4
+ import time
5
+
6
+
7
+ class BTStatus(Enum):
8
+ """Статус узла дерева поведения"""
9
+ SUCCESS = 1
10
+ FAILURE = 2
11
+ RUNNING = 3
12
+
13
+
14
+ class BTNode:
15
+ """Базовый узел дерева поведения"""
16
+
17
+ def __init__(self, name: str = ""):
18
+ self.name = name or self.__class__.__name__
19
+ self.parent: Optional['BTNode'] = None
20
+ self._status = BTStatus.FAILURE
21
+
22
+ def tick(self, context: Dict[str, Any]) -> BTStatus:
23
+ """Выполняет узел"""
24
+ self._status = self._execute(context)
25
+ return self._status
26
+
27
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
28
+ """Внутренняя логика (переопределяется)"""
29
+ return BTStatus.SUCCESS
30
+
31
+ def reset(self):
32
+ """Сбрасывает состояние"""
33
+ self._status = BTStatus.FAILURE
34
+
35
+ @property
36
+ def status(self) -> BTStatus:
37
+ return self._status
38
+
39
+
40
+ class Sequence(BTNode):
41
+ """
42
+ Последовательность: выполняет детей по порядку.
43
+ Останавливается при первой FAILURE или RUNNING.
44
+ """
45
+
46
+ def __init__(self, name: str = "Sequence"):
47
+ super().__init__(name)
48
+ self.children: List[BTNode] = []
49
+ self._current_index = 0
50
+
51
+ def add_child(self, node: BTNode) -> 'Sequence':
52
+ node.parent = self
53
+ self.children.append(node)
54
+ return self
55
+
56
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
57
+ while self._current_index < len(self.children):
58
+ child = self.children[self._current_index]
59
+ status = child.tick(context)
60
+
61
+ if status == BTStatus.FAILURE:
62
+ self._current_index = 0
63
+ return BTStatus.FAILURE
64
+
65
+ if status == BTStatus.RUNNING:
66
+ return BTStatus.RUNNING
67
+
68
+ self._current_index += 1
69
+
70
+ self._current_index = 0
71
+ return BTStatus.SUCCESS
72
+
73
+ def reset(self):
74
+ super().reset()
75
+ self._current_index = 0
76
+ for child in self.children:
77
+ child.reset()
78
+
79
+
80
+ class Selector(BTNode):
81
+ """
82
+ Селектор: выполняет детей по порядку.
83
+ Останавливается при первой SUCCESS или RUNNING.
84
+ """
85
+
86
+ def __init__(self, name: str = "Selector"):
87
+ super().__init__(name)
88
+ self.children: List[BTNode] = []
89
+ self._current_index = 0
90
+
91
+ def add_child(self, node: BTNode) -> 'Selector':
92
+ node.parent = self
93
+ self.children.append(node)
94
+ return self
95
+
96
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
97
+ while self._current_index < len(self.children):
98
+ child = self.children[self._current_index]
99
+ status = child.tick(context)
100
+
101
+ if status == BTStatus.SUCCESS:
102
+ self._current_index = 0
103
+ return BTStatus.SUCCESS
104
+
105
+ if status == BTStatus.RUNNING:
106
+ return BTStatus.RUNNING
107
+
108
+ self._current_index += 1
109
+
110
+ self._current_index = 0
111
+ return BTStatus.FAILURE
112
+
113
+ def reset(self):
114
+ super().reset()
115
+ self._current_index = 0
116
+ for child in self.children:
117
+ child.reset()
118
+
119
+
120
+ class Parallel(BTNode):
121
+ """
122
+ Параллельное выполнение всех детей.
123
+ """
124
+
125
+ def __init__(self, name: str = "Parallel",
126
+ success_threshold: int = -1):
127
+ super().__init__(name)
128
+ self.children: List[BTNode] = []
129
+ self.success_threshold = success_threshold # -1 = все должны выполниться
130
+
131
+ def add_child(self, node: BTNode) -> 'Parallel':
132
+ node.parent = self
133
+ self.children.append(node)
134
+ return self
135
+
136
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
137
+ success_count = 0
138
+ failure_count = 0
139
+
140
+ for child in self.children:
141
+ status = child.tick(context)
142
+
143
+ if status == BTStatus.SUCCESS:
144
+ success_count += 1
145
+ elif status == BTStatus.FAILURE:
146
+ failure_count += 1
147
+
148
+ threshold = self.success_threshold
149
+ if threshold < 0:
150
+ threshold = len(self.children)
151
+
152
+ if success_count >= threshold:
153
+ return BTStatus.SUCCESS
154
+ elif failure_count > len(self.children) - threshold:
155
+ return BTStatus.FAILURE
156
+
157
+ return BTStatus.RUNNING
158
+
159
+ def reset(self):
160
+ super().reset()
161
+ for child in self.children:
162
+ child.reset()
163
+
164
+
165
+ class Condition(BTNode):
166
+ """Условие (проверка)"""
167
+
168
+ def __init__(self, name: str, condition_fn: Callable):
169
+ super().__init__(name)
170
+ self.condition_fn = condition_fn
171
+
172
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
173
+ if self.condition_fn(context):
174
+ return BTStatus.SUCCESS
175
+ return BTStatus.FAILURE
176
+
177
+
178
+ class Action(BTNode):
179
+ """Действие"""
180
+
181
+ def __init__(self, name: str, action_fn: Callable):
182
+ super().__init__(name)
183
+ self.action_fn = action_fn
184
+
185
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
186
+ return self.action_fn(context)
187
+
188
+
189
+ class Inverter(BTNode):
190
+ """Инвертирует результат дочернего узла"""
191
+
192
+ def __init__(self, name: str = "Inverter"):
193
+ super().__init__(name)
194
+ self.child: Optional[BTNode] = None
195
+
196
+ def set_child(self, node: BTNode) -> 'Inverter':
197
+ node.parent = self
198
+ self.child = node
199
+ return self
200
+
201
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
202
+ if not self.child:
203
+ return BTStatus.FAILURE
204
+
205
+ status = self.child.tick(context)
206
+
207
+ if status == BTStatus.SUCCESS:
208
+ return BTStatus.FAILURE
209
+ elif status == BTStatus.FAILURE:
210
+ return BTStatus.SUCCESS
211
+
212
+ return BTStatus.RUNNING
213
+
214
+
215
+ class Repeater(BTNode):
216
+ """Повторяет дочерний узел N раз или пока не FAILURE"""
217
+
218
+ def __init__(self, name: str = "Repeater",
219
+ count: int = -1,
220
+ stop_on_failure: bool = True):
221
+ super().__init__(name)
222
+ self.child: Optional[BTNode] = None
223
+ self.count = count # -1 = бесконечно
224
+ self.stop_on_failure = stop_on_failure
225
+ self._iteration = 0
226
+
227
+ def set_child(self, node: BTNode) -> 'Repeater':
228
+ node.parent = self
229
+ self.child = node
230
+ return self
231
+
232
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
233
+ if not self.child:
234
+ return BTStatus.FAILURE
235
+
236
+ status = self.child.tick(context)
237
+
238
+ if self.stop_on_failure and status == BTStatus.FAILURE:
239
+ return BTStatus.FAILURE
240
+
241
+ if status != BTStatus.RUNNING:
242
+ self._iteration += 1
243
+
244
+ if self.count > 0 and self._iteration >= self.count:
245
+ return BTStatus.SUCCESS
246
+
247
+ self.child.reset()
248
+
249
+ return BTStatus.RUNNING
250
+
251
+ def reset(self):
252
+ super().reset()
253
+ self._iteration = 0
254
+ if self.child:
255
+ self.child.reset()
256
+
257
+
258
+ class Wait(BTNode):
259
+ """Ожидание заданное время"""
260
+
261
+ def __init__(self, name: str = "Wait", duration: float = 1.0):
262
+ super().__init__(name)
263
+ self.duration = duration
264
+ self._start_time = 0.0
265
+ self._started = False
266
+
267
+ def _execute(self, context: Dict[str, Any]) -> BTStatus:
268
+ if not self._started:
269
+ self._start_time = time.time()
270
+ self._started = True
271
+
272
+ elapsed = time.time() - self._start_time
273
+
274
+ if elapsed >= self.duration:
275
+ self._started = False
276
+ return BTStatus.SUCCESS
277
+
278
+ return BTStatus.RUNNING
279
+
280
+
281
+ class BehaviorTree:
282
+ """Дерево поведения"""
283
+
284
+ def __init__(self, root: BTNode = None):
285
+ self.root = root
286
+ self._context: Dict[str, Any] = {}
287
+ self._running = True
288
+
289
+ def set_root(self, node: BTNode):
290
+ self.root = node
291
+
292
+ def set_context(self, key: str, value: Any):
293
+ self._context[key] = value
294
+
295
+ def get_context(self, key: str, default: Any = None) -> Any:
296
+ return self._context.get(key, default)
297
+
298
+ def tick(self) -> BTStatus:
299
+ """Один такт дерева поведения"""
300
+ if not self.root or not self._running:
301
+ return BTStatus.FAILURE
302
+
303
+ return self.root.tick(self._context)
304
+
305
+ def reset(self):
306
+ """Сбрасывает дерево"""
307
+ if self.root:
308
+ self.root.reset()
309
+
310
+ def pause(self):
311
+ self._running = False
312
+
313
+ def resume(self):
314
+ self._running = True
315
+
316
+
317
+ # ---- ПРИМЕРЫ ПОВЕДЕНИЯ ----
318
+
319
+ class IsEnemyNearby(Condition):
320
+ """Проверка: есть ли враг рядом"""
321
+ def __init__(self, radius: float = 5.0):
322
+ super().__init__("IsEnemyNearby", self._check)
323
+ self.radius = radius
324
+
325
+ def _check(self, context: Dict[str, Any]) -> bool:
326
+ entity = context.get('entity')
327
+ if not entity:
328
+ return False
329
+
330
+ # Здесь должна быть реальная проверка
331
+ enemies = context.get('enemies', [])
332
+ position = context.get('position')
333
+
334
+ if position is None:
335
+ transform = entity.get_component("Transform")
336
+ if transform:
337
+ position = transform.position
338
+
339
+ for enemy in enemies:
340
+ enemy_pos = enemy.get_component("Transform").position
341
+ if np.linalg.norm(position - enemy_pos) < self.radius:
342
+ return True
343
+
344
+ return False
345
+
346
+
347
+ class MoveToTarget(Action):
348
+ """Движение к цели"""
349
+ def __init__(self, speed: float = 3.0):
350
+ super().__init__("MoveToTarget", self._move)
351
+ self.speed = speed
352
+
353
+ def _move(self, context: Dict[str, Any]) -> BTStatus:
354
+ entity = context.get('entity')
355
+ target = context.get('target')
356
+
357
+ if not entity or not target:
358
+ return BTStatus.FAILURE
359
+
360
+ transform = entity.get_component("Transform")
361
+ target_pos = target.get_component("Transform").position
362
+
363
+ if transform is None:
364
+ return BTStatus.FAILURE
365
+
366
+ direction = target_pos - transform.position
367
+ distance = np.linalg.norm(direction)
368
+
369
+ if distance < 0.5:
370
+ return BTStatus.SUCCESS
371
+
372
+ direction = direction / distance
373
+ transform.position += direction * self.speed * 0.016
374
+ transform._matrix_dirty = True
375
+
376
+ return BTStatus.RUNNING
377
+
378
+
379
+ class AttackTarget(Action):
380
+ """Атака цели"""
381
+ def __init__(self, damage: float = 10.0):
382
+ super().__init__("AttackTarget", self._attack)
383
+ self.damage = damage
384
+ self._cooldown = 1.0
385
+ self._last_attack = 0.0
386
+
387
+ def _attack(self, context: Dict[str, Any]) -> BTStatus:
388
+ target = context.get('target')
389
+ if not target:
390
+ return BTStatus.FAILURE
391
+
392
+ current_time = time.time()
393
+ if current_time - self._last_attack < self._cooldown:
394
+ return BTStatus.RUNNING
395
+
396
+ # Наносим урон
397
+ health = target.get_component("Health")
398
+ if health:
399
+ health.take_damage(self.damage)
400
+
401
+ self._last_attack = current_time
402
+
403
+ return BTStatus.SUCCESS