aether-engine 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aether/__init__.py +54 -0
- aether/audio/__init__.py +0 -0
- aether/audio/audio_manager.py +211 -0
- aether/audio/music.py +6 -0
- aether/audio/sound.py +6 -0
- aether/core/__init__.py +211 -0
- aether/core/achievement_system.py +304 -0
- aether/core/application.py +19 -0
- aether/core/config.py +75 -0
- aether/core/console.py +366 -0
- aether/core/engine.py +50 -0
- aether/core/event_bus.py +24 -0
- aether/core/input.py +58 -0
- aether/core/plugin_system.py +93 -0
- aether/core/save_system.py +409 -0
- aether/core/time_manager.py +25 -0
- aether/core/window.py +57 -0
- aether/graphics/__init__.py +0 -0
- aether/graphics/camera.py +69 -0
- aether/graphics/framebuffer.py +119 -0
- aether/graphics/light.py +32 -0
- aether/graphics/lod_system.py +170 -0
- aether/graphics/material.py +25 -0
- aether/graphics/mesh.py +83 -0
- aether/graphics/mesh_factory.py +67 -0
- aether/graphics/particle_system.py +268 -0
- aether/graphics/post_process.py +279 -0
- aether/graphics/renderer.py +135 -0
- aether/graphics/shader.py +61 -0
- aether/graphics/shader_library.py +157 -0
- aether/graphics/sky_atmosphere.py +309 -0
- aether/graphics/terrain.py +408 -0
- aether/graphics/texture.py +34 -0
- aether/graphics/water.py +432 -0
- aether/physics/__init__.py +0 -0
- aether/physics/bullet_backend.py +395 -0
- aether/physics/collider.py +22 -0
- aether/physics/physics_world.py +436 -0
- aether/physics/raycast.py +25 -0
- aether/physics/rigidbody.py +7 -0
- aether/resources/__init__.py +0 -0
- aether/resources/asset_database.py +76 -0
- aether/resources/resource_manager.py +147 -0
- aether/resources/serialization.py +114 -0
- aether/scene/__init__.py +0 -0
- aether/scene/component.py +368 -0
- aether/scene/entity.py +46 -0
- aether/scene/scene.py +33 -0
- aether/scene/scene_graph.py +252 -0
- aether/scene/scene_manager.py +191 -0
- aether/ui/__init__.py +0 -0
- aether/ui/button.py +297 -0
- aether/ui/canvas.py +11 -0
- aether/ui/text.py +286 -0
- aether/utils/__init__.py +0 -0
- aether/utils/logger.py +16 -0
- aether/utils/math_utils.py +31 -0
- aether/utils/object_pool.py +137 -0
- aether/utils/profiler.py +187 -0
- aether_engine-1.0.0.dist-info/METADATA +71 -0
- aether_engine-1.0.0.dist-info/RECORD +64 -0
- aether_engine-1.0.0.dist-info/WHEEL +5 -0
- aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
- aether_engine-1.0.0.dist-info/top_level.txt +1 -0
aether/__init__.py
ADDED
|
@@ -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
|
aether/audio/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Менеджер звука на pygame.mixer"""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Sound:
|
|
8
|
+
"""Звуковой эффект"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, path: str = ""):
|
|
11
|
+
self.path = path
|
|
12
|
+
self.volume = 1.0
|
|
13
|
+
self.pitch = 1.0
|
|
14
|
+
self.loop = False
|
|
15
|
+
self._sound = None
|
|
16
|
+
self._channel = None
|
|
17
|
+
|
|
18
|
+
if path:
|
|
19
|
+
self.load(path)
|
|
20
|
+
|
|
21
|
+
def load(self, path: str):
|
|
22
|
+
"""Загружает звук"""
|
|
23
|
+
try:
|
|
24
|
+
import pygame
|
|
25
|
+
self._sound = pygame.mixer.Sound(path)
|
|
26
|
+
self.path = path
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"[Sound] Failed to load {path}: {e}")
|
|
29
|
+
|
|
30
|
+
def play(self, volume: float = 1.0, pitch: float = 1.0):
|
|
31
|
+
"""Проигрывает звук"""
|
|
32
|
+
if self._sound:
|
|
33
|
+
self._sound.set_volume(volume * self.volume)
|
|
34
|
+
self._channel = self._sound.play()
|
|
35
|
+
|
|
36
|
+
def stop(self):
|
|
37
|
+
"""Останавливает звук"""
|
|
38
|
+
if self._channel:
|
|
39
|
+
self._channel.stop()
|
|
40
|
+
|
|
41
|
+
def set_volume(self, volume: float):
|
|
42
|
+
"""Устанавливает громкость"""
|
|
43
|
+
self.volume = max(0.0, min(1.0, volume))
|
|
44
|
+
if self._channel:
|
|
45
|
+
self._channel.set_volume(self.volume)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Music:
|
|
49
|
+
"""Фоновая музыка"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, path: str = ""):
|
|
52
|
+
self.path = path
|
|
53
|
+
self.volume = 1.0
|
|
54
|
+
self.loop = True
|
|
55
|
+
self._loaded = False
|
|
56
|
+
|
|
57
|
+
def load(self, path: str):
|
|
58
|
+
"""Загружает музыку"""
|
|
59
|
+
try:
|
|
60
|
+
import pygame
|
|
61
|
+
pygame.mixer.music.load(path)
|
|
62
|
+
self.path = path
|
|
63
|
+
self._loaded = True
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"[Music] Failed to load {path}: {e}")
|
|
66
|
+
|
|
67
|
+
def play(self, volume: float = 1.0, loop: bool = True):
|
|
68
|
+
"""Проигрывает музыку"""
|
|
69
|
+
if self._loaded:
|
|
70
|
+
try:
|
|
71
|
+
import pygame
|
|
72
|
+
pygame.mixer.music.set_volume(volume * self.volume)
|
|
73
|
+
pygame.mixer.music.play(-1 if loop else 0)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(f"[Music] Failed to play: {e}")
|
|
76
|
+
|
|
77
|
+
def pause(self):
|
|
78
|
+
"""Ставит на паузу"""
|
|
79
|
+
try:
|
|
80
|
+
import pygame
|
|
81
|
+
pygame.mixer.music.pause()
|
|
82
|
+
except:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def resume(self):
|
|
86
|
+
"""Продолжает проигрывание"""
|
|
87
|
+
try:
|
|
88
|
+
import pygame
|
|
89
|
+
pygame.mixer.music.unpause()
|
|
90
|
+
except:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def stop(self):
|
|
94
|
+
"""Останавливает музыку"""
|
|
95
|
+
try:
|
|
96
|
+
import pygame
|
|
97
|
+
pygame.mixer.music.stop()
|
|
98
|
+
except:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AudioManager:
|
|
103
|
+
"""Менеджер звука"""
|
|
104
|
+
|
|
105
|
+
_instance = None
|
|
106
|
+
|
|
107
|
+
def __new__(cls):
|
|
108
|
+
if cls._instance is None:
|
|
109
|
+
cls._instance = super().__new__(cls)
|
|
110
|
+
cls._instance._initialize()
|
|
111
|
+
return cls._instance
|
|
112
|
+
|
|
113
|
+
def _initialize(self):
|
|
114
|
+
"""Инициализация аудио системы"""
|
|
115
|
+
self.sounds: Dict[str, Sound] = {}
|
|
116
|
+
self.current_music: Optional[Music] = None
|
|
117
|
+
|
|
118
|
+
self.master_volume = 1.0
|
|
119
|
+
self.sfx_volume = 1.0
|
|
120
|
+
self.music_volume = 0.8
|
|
121
|
+
|
|
122
|
+
self._initialized = False
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
import pygame
|
|
126
|
+
pygame.mixer.init(frequency=44100, size=-16, channels=32, buffer=512)
|
|
127
|
+
self._initialized = True
|
|
128
|
+
print("[Audio] Initialized (pygame.mixer)")
|
|
129
|
+
except ImportError:
|
|
130
|
+
print("[Audio] pygame not available, audio disabled")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"[Audio] Failed to initialize: {e}")
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def initialized(self) -> bool:
|
|
136
|
+
return self._initialized
|
|
137
|
+
|
|
138
|
+
def load_sound(self, name: str, path: str) -> Sound:
|
|
139
|
+
"""Загружает звуковой эффект"""
|
|
140
|
+
sound = Sound(path)
|
|
141
|
+
self.sounds[name] = sound
|
|
142
|
+
return sound
|
|
143
|
+
|
|
144
|
+
def play_sound(self, name: str, volume: float = 1.0, pitch: float = 1.0):
|
|
145
|
+
"""Проигрывает звук по имени"""
|
|
146
|
+
if not self._initialized:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
sound = self.sounds.get(name)
|
|
150
|
+
if sound:
|
|
151
|
+
sound.play(volume * self.sfx_volume * self.master_volume, pitch)
|
|
152
|
+
|
|
153
|
+
def stop_sound(self, name: str):
|
|
154
|
+
"""Останавливает звук"""
|
|
155
|
+
sound = self.sounds.get(name)
|
|
156
|
+
if sound:
|
|
157
|
+
sound.stop()
|
|
158
|
+
|
|
159
|
+
def play_music(self, path: str, volume: float = 1.0, loop: bool = True):
|
|
160
|
+
"""Проигрывает фоновую музыку"""
|
|
161
|
+
if not self._initialized:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if self.current_music:
|
|
165
|
+
self.current_music.stop()
|
|
166
|
+
|
|
167
|
+
self.current_music = Music(path)
|
|
168
|
+
self.current_music.play(volume * self.music_volume * self.master_volume, loop)
|
|
169
|
+
|
|
170
|
+
def pause_music(self):
|
|
171
|
+
"""Ставит музыку на паузу"""
|
|
172
|
+
if self.current_music:
|
|
173
|
+
self.current_music.pause()
|
|
174
|
+
|
|
175
|
+
def resume_music(self):
|
|
176
|
+
"""Продолжает музыку"""
|
|
177
|
+
if self.current_music:
|
|
178
|
+
self.current_music.resume()
|
|
179
|
+
|
|
180
|
+
def stop_music(self):
|
|
181
|
+
"""Останавливает музыку"""
|
|
182
|
+
if self.current_music:
|
|
183
|
+
self.current_music.stop()
|
|
184
|
+
self.current_music = None
|
|
185
|
+
|
|
186
|
+
def set_master_volume(self, volume: float):
|
|
187
|
+
"""Устанавливает общую громкость"""
|
|
188
|
+
self.master_volume = max(0.0, min(1.0, volume))
|
|
189
|
+
|
|
190
|
+
def set_sfx_volume(self, volume: float):
|
|
191
|
+
"""Устанавливает громкость эффектов"""
|
|
192
|
+
self.sfx_volume = max(0.0, min(1.0, volume))
|
|
193
|
+
|
|
194
|
+
def set_music_volume(self, volume: float):
|
|
195
|
+
"""Устанавливает громкость музыки"""
|
|
196
|
+
self.music_volume = max(0.0, min(1.0, volume))
|
|
197
|
+
if self.current_music:
|
|
198
|
+
self.current_music.play(self.music_volume * self.master_volume)
|
|
199
|
+
|
|
200
|
+
def cleanup(self):
|
|
201
|
+
"""Очищает аудио систему"""
|
|
202
|
+
self.stop_music()
|
|
203
|
+
self.sounds.clear()
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
import pygame
|
|
207
|
+
pygame.mixer.quit()
|
|
208
|
+
except:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
print("[Audio] Cleanup complete")
|
aether/audio/music.py
ADDED
aether/audio/sound.py
ADDED
aether/core/__init__.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Обработка ввода: клавиатура, мышь, геймпад"""
|
|
2
|
+
import glfw
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Input:
|
|
7
|
+
"""Система ввода"""
|
|
8
|
+
|
|
9
|
+
# Константы кнопок мыши
|
|
10
|
+
MOUSE_LEFT = glfw.MOUSE_BUTTON_LEFT
|
|
11
|
+
MOUSE_RIGHT = glfw.MOUSE_BUTTON_RIGHT
|
|
12
|
+
MOUSE_MIDDLE = glfw.MOUSE_BUTTON_MIDDLE
|
|
13
|
+
|
|
14
|
+
# Константы геймпада
|
|
15
|
+
GAMEPAD_A = 0
|
|
16
|
+
GAMEPAD_B = 1
|
|
17
|
+
GAMEPAD_X = 2
|
|
18
|
+
GAMEPAD_Y = 3
|
|
19
|
+
GAMEPAD_LB = 4
|
|
20
|
+
GAMEPAD_RB = 5
|
|
21
|
+
GAMEPAD_START = 7
|
|
22
|
+
GAMEPAD_LEFT_STICK_X = 0
|
|
23
|
+
GAMEPAD_LEFT_STICK_Y = 1
|
|
24
|
+
GAMEPAD_RIGHT_STICK_X = 2
|
|
25
|
+
GAMEPAD_RIGHT_STICK_Y = 3
|
|
26
|
+
GAMEPAD_LEFT_TRIGGER = 4
|
|
27
|
+
GAMEPAD_RIGHT_TRIGGER = 5
|
|
28
|
+
|
|
29
|
+
def __init__(self, window):
|
|
30
|
+
self.window = window
|
|
31
|
+
|
|
32
|
+
# Клавиатура
|
|
33
|
+
self._keys = {}
|
|
34
|
+
self._keys_prev = {}
|
|
35
|
+
|
|
36
|
+
# Мышь
|
|
37
|
+
self._mouse_buttons = {}
|
|
38
|
+
self._mouse_buttons_prev = {}
|
|
39
|
+
self.mouse_x = 0.0
|
|
40
|
+
self.mouse_y = 0.0
|
|
41
|
+
self.mouse_dx = 0.0
|
|
42
|
+
self.mouse_dy = 0.0
|
|
43
|
+
self.mouse_scroll = 0.0
|
|
44
|
+
self._first_mouse = True
|
|
45
|
+
self._cursor_locked = False
|
|
46
|
+
|
|
47
|
+
# Геймпад
|
|
48
|
+
self._gamepad_connected = False
|
|
49
|
+
self._gamepad_id = None
|
|
50
|
+
self._gamepad_buttons = {}
|
|
51
|
+
self._gamepad_buttons_prev = {}
|
|
52
|
+
self._gamepad_axes = {}
|
|
53
|
+
self._gamepad_axes_prev = {}
|
|
54
|
+
|
|
55
|
+
# Настройка коллбэков
|
|
56
|
+
glfw.set_key_callback(window.handle, self._key_callback)
|
|
57
|
+
glfw.set_mouse_button_callback(window.handle, self._mouse_button_callback)
|
|
58
|
+
glfw.set_cursor_pos_callback(window.handle, self._cursor_callback)
|
|
59
|
+
glfw.set_scroll_callback(window.handle, self._scroll_callback)
|
|
60
|
+
|
|
61
|
+
# Проверяем наличие геймпада
|
|
62
|
+
self._check_gamepad()
|
|
63
|
+
|
|
64
|
+
def _key_callback(self, window, key, scancode, action, mods):
|
|
65
|
+
"""Коллбэк клавиатуры"""
|
|
66
|
+
if action == glfw.PRESS:
|
|
67
|
+
self._keys[key] = True
|
|
68
|
+
elif action == glfw.RELEASE:
|
|
69
|
+
self._keys[key] = False
|
|
70
|
+
|
|
71
|
+
def _mouse_button_callback(self, window, button, action, mods):
|
|
72
|
+
"""Коллбэк кнопок мыши"""
|
|
73
|
+
if action == glfw.PRESS:
|
|
74
|
+
self._mouse_buttons[button] = True
|
|
75
|
+
elif action == glfw.RELEASE:
|
|
76
|
+
self._mouse_buttons[button] = False
|
|
77
|
+
|
|
78
|
+
def _cursor_callback(self, window, xpos, ypos):
|
|
79
|
+
"""Коллбэк движения мыши"""
|
|
80
|
+
if self._first_mouse:
|
|
81
|
+
self.mouse_x = xpos
|
|
82
|
+
self.mouse_y = ypos
|
|
83
|
+
self._first_mouse = False
|
|
84
|
+
|
|
85
|
+
self.mouse_dx = xpos - self.mouse_x
|
|
86
|
+
self.mouse_dy = ypos - self.mouse_y
|
|
87
|
+
self.mouse_x = xpos
|
|
88
|
+
self.mouse_y = ypos
|
|
89
|
+
|
|
90
|
+
def _scroll_callback(self, window, xoffset, yoffset):
|
|
91
|
+
"""Коллбэк колёсика мыши"""
|
|
92
|
+
self.mouse_scroll = yoffset
|
|
93
|
+
|
|
94
|
+
def _check_gamepad(self):
|
|
95
|
+
"""Проверяет подключение геймпада"""
|
|
96
|
+
for jid in range(glfw.JOYSTICK_1, glfw.JOYSTICK_16):
|
|
97
|
+
if glfw.joystick_present(jid):
|
|
98
|
+
self._gamepad_connected = True
|
|
99
|
+
self._gamepad_id = jid
|
|
100
|
+
name = glfw.get_joystick_name(jid)
|
|
101
|
+
print(f"[Input] Gamepad connected: {name}")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
self._gamepad_connected = False
|
|
105
|
+
self._gamepad_id = None
|
|
106
|
+
|
|
107
|
+
def update(self):
|
|
108
|
+
"""Обновляет состояние ввода"""
|
|
109
|
+
# Сохраняем предыдущее состояние
|
|
110
|
+
self._keys_prev = self._keys.copy()
|
|
111
|
+
self._mouse_buttons_prev = self._mouse_buttons.copy()
|
|
112
|
+
self._gamepad_buttons_prev = self._gamepad_buttons.copy()
|
|
113
|
+
self._gamepad_axes_prev = self._gamepad_axes.copy()
|
|
114
|
+
|
|
115
|
+
# Сбрасываем дельты мыши
|
|
116
|
+
self.mouse_dx = 0.0
|
|
117
|
+
self.mouse_dy = 0.0
|
|
118
|
+
self.mouse_scroll = 0.0
|
|
119
|
+
|
|
120
|
+
# Обновляем геймпад
|
|
121
|
+
if self._gamepad_connected and self._gamepad_id is not None:
|
|
122
|
+
if not glfw.joystick_present(self._gamepad_id):
|
|
123
|
+
self._gamepad_connected = False
|
|
124
|
+
self._gamepad_id = None
|
|
125
|
+
print("[Input] Gamepad disconnected")
|
|
126
|
+
else:
|
|
127
|
+
buttons = glfw.get_joystick_buttons(self._gamepad_id)
|
|
128
|
+
for i, state in enumerate(buttons):
|
|
129
|
+
self._gamepad_buttons[i] = (state == glfw.PRESS)
|
|
130
|
+
|
|
131
|
+
axes = glfw.get_joystick_axes(self._gamepad_id)
|
|
132
|
+
for i, value in enumerate(axes):
|
|
133
|
+
self._gamepad_axes[i] = value
|
|
134
|
+
|
|
135
|
+
# ---- Клавиатура ----
|
|
136
|
+
|
|
137
|
+
def get_key(self, key) -> bool:
|
|
138
|
+
"""Клавиша нажата (удерживается)"""
|
|
139
|
+
return self._keys.get(key, False)
|
|
140
|
+
|
|
141
|
+
def get_key_down(self, key) -> bool:
|
|
142
|
+
"""Клавиша нажата в этом кадре"""
|
|
143
|
+
return self._keys.get(key, False) and not self._keys_prev.get(key, False)
|
|
144
|
+
|
|
145
|
+
def get_key_up(self, key) -> bool:
|
|
146
|
+
"""Клавиша отпущена в этом кадре"""
|
|
147
|
+
return not self._keys.get(key, False) and self._keys_prev.get(key, False)
|
|
148
|
+
|
|
149
|
+
# ---- Мышь ----
|
|
150
|
+
|
|
151
|
+
def get_mouse_button(self, button) -> bool:
|
|
152
|
+
"""Кнопка мыши нажата (удерживается)"""
|
|
153
|
+
return self._mouse_buttons.get(button, False)
|
|
154
|
+
|
|
155
|
+
def get_mouse_button_down(self, button) -> bool:
|
|
156
|
+
"""Кнопка мыши нажата в этом кадре"""
|
|
157
|
+
return self._mouse_buttons.get(button, False) and not self._mouse_buttons_prev.get(button, False)
|
|
158
|
+
|
|
159
|
+
def get_mouse_button_up(self, button) -> bool:
|
|
160
|
+
"""Кнопка мыши отпущена в этом кадре"""
|
|
161
|
+
return not self._mouse_buttons.get(button, False) and self._mouse_buttons_prev.get(button, False)
|
|
162
|
+
|
|
163
|
+
def get_mouse_position(self) -> tuple:
|
|
164
|
+
"""Возвращает позицию мыши"""
|
|
165
|
+
return (self.mouse_x, self.mouse_y)
|
|
166
|
+
|
|
167
|
+
def get_mouse_delta(self) -> tuple:
|
|
168
|
+
"""Возвращает дельту движения мыши"""
|
|
169
|
+
return (self.mouse_dx, self.mouse_dy)
|
|
170
|
+
|
|
171
|
+
def get_mouse_scroll(self) -> float:
|
|
172
|
+
"""Возвращает значение колёсика"""
|
|
173
|
+
return self.mouse_scroll
|
|
174
|
+
|
|
175
|
+
# ---- Геймпад ----
|
|
176
|
+
|
|
177
|
+
def get_gamepad_button(self, button) -> bool:
|
|
178
|
+
"""Кнопка геймпада нажата (удерживается)"""
|
|
179
|
+
return self._gamepad_buttons.get(button, False)
|
|
180
|
+
|
|
181
|
+
def get_gamepad_button_down(self, button) -> bool:
|
|
182
|
+
"""Кнопка геймпада нажата в этом кадре"""
|
|
183
|
+
return self._gamepad_buttons.get(button, False) and not self._gamepad_buttons_prev.get(button, False)
|
|
184
|
+
|
|
185
|
+
def get_gamepad_axis(self, axis) -> float:
|
|
186
|
+
"""Возвращает значение оси геймпада"""
|
|
187
|
+
value = self._gamepad_axes.get(axis, 0.0)
|
|
188
|
+
# Мёртвая зона
|
|
189
|
+
if abs(value) < 0.15:
|
|
190
|
+
return 0.0
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
def is_gamepad_connected(self) -> bool:
|
|
194
|
+
"""Подключён ли геймпад"""
|
|
195
|
+
return self._gamepad_connected
|
|
196
|
+
|
|
197
|
+
# ---- Управление курсором ----
|
|
198
|
+
|
|
199
|
+
def lock_cursor(self):
|
|
200
|
+
"""Захватывает курсор (для FPS)"""
|
|
201
|
+
glfw.set_input_mode(self.window.handle, glfw.CURSOR, glfw.CURSOR_DISABLED)
|
|
202
|
+
self._cursor_locked = True
|
|
203
|
+
|
|
204
|
+
def unlock_cursor(self):
|
|
205
|
+
"""Освобождает курсор"""
|
|
206
|
+
glfw.set_input_mode(self.window.handle, glfw.CURSOR, glfw.CURSOR_NORMAL)
|
|
207
|
+
self._cursor_locked = False
|
|
208
|
+
|
|
209
|
+
def is_cursor_locked(self) -> bool:
|
|
210
|
+
"""Захвачен ли курсор"""
|
|
211
|
+
return self._cursor_locked
|