pyengine-x 0.1.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.
@@ -0,0 +1,988 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyengine-x
3
+ Version: 0.1.0
4
+ Summary: PyEngine-X is a lightweight Python library that simplifies 2D game development by handling routine tasks.
5
+ Author-email: DaarkAngellmc <daarkangellmc@mail.ru>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/darkangelmc/PyEngine-X
8
+ Project-URL: Source, https://github.com/darkangelmc/PyEngine-X
9
+ Project-URL: Issues, https://github.com/darkangelmc/PyEngine-X/issues
10
+ Keywords: pygame,engine,2d,game,gamedev
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Games/Entertainment
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: pygame-ce
20
+ Dynamic: license-file
21
+
22
+ # PyEngine
23
+
24
+ Библиотека для создания 2D-игр на Python, построенная поверх pygame. PyEngine не заменяет pygame — он дополняет его, беря на себя рутинные задачи: управление окном, обработку ввода, коллизии, камеры, очереди отрисовки, анимации и работу со сценами.
25
+
26
+ Вместо того чтобы каждый раз писать цикл обработки событий, считать дельту времени, вручную проверять пересечения прямоугольников и сдвигать координаты для камеры, ты получаешь готовые методы. Если чего-то не хватает — всегда можно обратиться напрямую к pygame.
27
+
28
+ ## Установка
29
+
30
+ Есть два способа начать работу с PyEngine.
31
+
32
+ ### Способ 1: Через pip (рекомендуется для пользователей)
33
+ Когда библиотека будет опубликована на PyPI, ты сможешь установить её одной командой:
34
+
35
+ ```bash
36
+ pip install pyengine-x
37
+ ```
38
+
39
+ ### Способ 2: Ручная установка (для разработчиков и тестирования)
40
+ Скопируй папку `pyengine` из этого репозитория в папку своего проекта. Затем импортируй нужные модули:
41
+
42
+ ```python
43
+ from pyengine.engine import PyEngine
44
+ from pyengine.assets import Assets
45
+ from pyengine.scene import Scene
46
+ from pyengine.tilemap import Tile, TileSet, TileMap
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Assets
52
+
53
+ Класс с предустановленными цветами и шрифтами, чтобы не гуглить RGB-значения и не помнить названия системных шрифтов.
54
+
55
+ ```python
56
+ from pyengine.assets import Assets
57
+
58
+ # Использование констант
59
+ engine.create_figure('player', 'rect', Assets.BLUE, (100, 100, 32, 32))
60
+ engine.create_font('title', Assets.FONT_ARIAL, 48)
61
+
62
+ # Утилиты для работы с цветами
63
+ dark_red = Assets.darken(Assets.RED, 0.7)
64
+ light_blue = Assets.lighten(Assets.BLUE, 1.3)
65
+ random = Assets.random_color()
66
+ ```
67
+
68
+ ### Константы
69
+ **Цвета:** `WHITE`, `BLACK`, `RED`, `GREEN`, `BLUE`, `YELLOW`, `CYAN`, `MAGENTA`, `GRAY`, `DARK_GRAY`, `ORANGE`, `PURPLE`
70
+ **Шрифты:** `FONT_ARIAL`, `FONT_COMIC`, `FONT_TIMES`, `FONT_COURIER`
71
+
72
+ ### Методы
73
+ **get_color(name)**
74
+ Возвращает кортеж цвета по строковому имени. Если цвет не найден, возвращает чёрный.
75
+ - `name` (str): название цвета, например `'red'`, `'dark_gray'`.
76
+
77
+ **random_color()**
78
+ Генерирует случайный цвет в формате (R, G, B).
79
+
80
+ **darken(color, factor=0.7)**
81
+ Затемняет цвет, умножая каждую RGB-компоненту на коэффициент.
82
+ - `color` (tuple): исходный цвет.
83
+ - `factor` (float): коэффициент затемнения (0.7 = темнее на 30%).
84
+
85
+ **lighten(color, factor=1.3)**
86
+ Осветляет цвет, умножая каждую RGB-компоненту на коэффициент, но не даёт превысить 255.
87
+ - `color` (tuple): исходный цвет.
88
+ - `factor` (float): коэффициент осветления (1.3 = ярче на 30%).
89
+
90
+ ---
91
+
92
+ ## Sound
93
+
94
+ Миксин для работы со звуками и музыкой. Автоматически подключается к `PyEngine`, поэтому все методы доступны через `engine`.
95
+
96
+ ```python
97
+ # Загрузка и воспроизведение звуков
98
+ engine.load_sound('jump', 'sounds/jump.wav')
99
+ engine.play_sound('jump')
100
+
101
+ # Фоновая музыка
102
+ engine.load_music('music/theme.ogg')
103
+ engine.play_music(loop=True)
104
+ engine.set_music_volume(0.5)
105
+ ```
106
+
107
+ ### Звуковые эффекты
108
+ **load_sound(name, path)**
109
+ Загружает звуковой эффект из файла и сохраняет под указанным именем.
110
+ - `name` (str): идентификатор звука.
111
+ - `path` (str): путь к файлу.
112
+
113
+ **play_sound(name, loop=False)**
114
+ Воспроизводит загруженный звуковой эффект.
115
+ - `name` (str): имя звука.
116
+ - `loop` (bool): если True, зацикливает воспроизведение.
117
+
118
+ **stop_sound(name)**
119
+ Останавливает воспроизведение звукового эффекта.
120
+ - `name` (str): имя звука.
121
+
122
+ **set_sound_volume(name, volume)**
123
+ Устанавливает громкость конкретного звукового эффекта.
124
+ - `name` (str): имя звука.
125
+ - `volume` (float): громкость от 0.0 до 1.0.
126
+
127
+ ### Музыка
128
+ **load_music(path)**
129
+ Загружает фоновую музыку из файла. Можно загрузить только один трек одновременно.
130
+ - `path` (str): путь к файлу.
131
+
132
+ **play_music(loop=False)**
133
+ Запускает воспроизведение загруженной музыки.
134
+ - `loop` (bool): если True, зацикливает музыку.
135
+
136
+ **stop_music()**
137
+ Полностью останавливает музыку.
138
+
139
+ **pause_music()**
140
+ Приостанавливает воспроизведение музыки. Продолжить можно через `resume_music`.
141
+
142
+ **resume_music()**
143
+ Продолжает воспроизведение музыки с момента паузы.
144
+
145
+ **set_music_volume(volume)**
146
+ Устанавливает громкость фоновой музыки.
147
+ - `volume` (float): громкость от 0.0 до 1.0.
148
+
149
+ **fade_in_music(seconds=2.0, loop=False)**
150
+ Запускает музыку с плавным нарастанием громкости от нуля.
151
+ - `seconds` (float): длительность нарастания в секундах.
152
+ - `loop` (bool): зацикливать ли музыку.
153
+
154
+ **fade_out_music(seconds=2.0)**
155
+ Плавно затухает музыку до тишины за указанное время.
156
+ - `seconds` (float): длительность затухания в секундах.
157
+
158
+ **set_all_volume(volume)**
159
+ Устанавливает одну громкость сразу для всех звуков и музыки.
160
+ - `volume` (float): громкость от 0.0 до 1.0.
161
+
162
+ ---
163
+
164
+ ## Scene
165
+
166
+ Базовый класс для создания сцен. Сцена — это отдельный экран игры: меню, уровень, пауза, game over. Каждая сцена имеет свой жизненный цикл.
167
+
168
+ ```python
169
+ from pyengine.scene import Scene
170
+
171
+ class MyScene(Scene):
172
+ def on_enter(self, **kwargs): ...
173
+ def on_exit(self): ...
174
+ def events(self, event): ...
175
+ def update(self): ...
176
+ def draw(self): ...
177
+ ```
178
+
179
+ ### Методы жизненного цикла
180
+ **on_enter(**kwargs)**
181
+ Вызывается при активации сцены. Здесь создаются фигуры, загружаются карты, устанавливаются начальные позиции.
182
+ - `**kwargs`: любые аргументы, переданные при переключении через `engine.set_scene('name', key=value)`.
183
+
184
+ **on_exit()**
185
+ Вызывается при выходе из сцены. Используется для очистки ресурсов, остановки музыки, сохранения прогресса.
186
+
187
+ ### Методы каждого кадра
188
+ **events(event)**
189
+ Принимает каждое событие pygame. Обрабатывай здесь ввод: нажатия клавиш, клики мыши, движение курсора.
190
+ - `event` (pygame.event.Event): объект события pygame.
191
+
192
+ **update()**
193
+ Вызывается каждый кадр перед отрисовкой. Здесь логика: движение, физика, проверки коллизий, обновление состояний.
194
+
195
+ **draw()**
196
+ Вызывается каждый кадр. Здесь отрисовка: фигуры, текст, карты, UI.
197
+
198
+ ---
199
+
200
+ ## Figure
201
+
202
+ Универсальный графический объект. Одна фигура может быть прямоугольником, кругом, эллипсом, линией, полигоном или спрайтом.
203
+
204
+ ```python
205
+ # Создание фигур
206
+ engine.create_figure('player', 'rect', Assets.BLUE, (100, 100, 32, 32))
207
+ engine.create_figure('enemy', 'circle', Assets.RED, (300, 200, 20))
208
+ engine.create_figure('coin', 'sprite', Assets.YELLOW, (500, 400, 'coin_img'))
209
+
210
+ # Управление
211
+ engine.figures['player'].set_pos(200, 150)
212
+ engine.figures['player'].move(10, -5)
213
+ engine.figures['player'].clamp_to_screen()
214
+ engine.figures['player'].play('idle_anim') # Запуск анимации
215
+ ```
216
+
217
+ ### Позиция и движение
218
+ **set_pos(x, y)**
219
+ Устанавливает новые координаты фигуры. Автоматически синхронизирует внутренние переменные и параметры отрисовки.
220
+ - `x` (float): новая позиция по X.
221
+ - `y` (float): новая позиция по Y.
222
+
223
+ **move(dx, dy)**
224
+ Смещает фигуру относительно текущей позиции.
225
+ - `dx` (float): смещение по X.
226
+ - `dy` (float): смещение по Y.
227
+
228
+ **clamp_to_screen()**
229
+ Ограничивает позицию фигуры границами экрана. Если фигура выходит за край, возвращает её обратно.
230
+
231
+ ### Отрисовка
232
+ **draw(offset=(0.0, 0.0))**
233
+ Отрисовывает фигуру на экране. Обычно вызывается автоматически движком, но можно вызвать вручную.
234
+ - `offset` (tuple[float, float]): смещение камеры (ox, oy). По умолчанию (0, 0).
235
+
236
+ **set_image(image_name)**
237
+ Меняет изображение спрайта. Работает только для фигур с `shape='sprite'`.
238
+ - `image_name` (str): имя изображения, ранее загруженного через `engine.load_image`.
239
+
240
+ ### Геометрия
241
+ **radius** (свойство)
242
+ Возвращает радиус фигуры. Работает только для круга. Для остальных форм возвращает 0.0.
243
+
244
+ **get_center()**
245
+ Возвращает координаты (x, y) центра фигуры. Для круга — центр, для прямоугольника — середина, для линии — середина отрезка.
246
+
247
+ **get_bounds()**
248
+ Возвращает ограничивающий прямоугольник в формате (x, y, ширина, высота). Работает для любой формы, включая полигон.
249
+
250
+ ### Коллизии
251
+ **push_out(other)**
252
+ Выталкивает текущую фигуру наружу из другой фигуры, если они пересеклись. Работает для любых комбинаций форм.
253
+ - `other` (Figure): фигура, из которой нужно вытолкнуть.
254
+
255
+ ### Анимации
256
+ **play(anim_name)**
257
+ Запускает указанную анимацию, сбрасывая текущий кадр и таймер. Движок автоматически обновляет кадры в главном цикле.
258
+ - `anim_name` (str): Имя анимации, зарегистрированной через `engine.add_animation`.
259
+
260
+ ---
261
+
262
+ ## Engine
263
+
264
+ Главный класс движка. Создаёт окно, управляет сценами, фигурами, вводом, коллизиями и отрисовкой.
265
+
266
+ ```python
267
+ from pyengine.engine import PyEngine
268
+
269
+ engine = PyEngine(
270
+ title='My Game',
271
+ background=Assets.BLACK,
272
+ width=800,
273
+ height=600
274
+ )
275
+ engine.initialize(start_scene='menu')
276
+ engine.run()
277
+ ```
278
+
279
+ ### Конструктор
280
+ **__init__(title='Py-Engine', background=(50, 50, 50), width=800, height=600)**
281
+ Создаёт экземпляр движка. Окно пока не открывается — это делает `initialize`.
282
+ - `title` (str): заголовок окна.
283
+ - `background` (tuple): цвет фона в RGB.
284
+ - `width` (int): ширина окна в пикселях.
285
+ - `height` (int): высота окна в пикселях.
286
+
287
+ ### Жизненный цикл
288
+ **initialize(start_scene=None)**
289
+ Инициализирует pygame, создаёт окно, настраивает таймер. После этого можно вызывать `run`.
290
+ - `start_scene` (str | None): имя сцены, на которую переключиться сразу после запуска.
291
+
292
+ **run()**
293
+ Запускает главный игровой цикл. Сам обрабатывает события, считает dt, очищает экран, вызывает update/draw сцен, обновляет камеру, отрисовывает очередь и ограничивает FPS до 60. Блокирующий метод — работает пока окно не закроют.
294
+
295
+ ### Сцены
296
+ **add_scene(name, scene)**
297
+ Регистрирует сцену под именем. Без регистрации `set_scene` не сработает.
298
+ - `name` (str): уникальное имя сцены.
299
+ - `scene` (Scene): экземпляр класса, наследующего от Scene.
300
+
301
+ **add_scenes(scenes)**
302
+ Регистрирует сразу несколько сцен из словаря.
303
+ - `scenes` (dict[str, Scene]): словарь {имя: экземпляр_сцены}.
304
+
305
+ **set_scene(name, destroy=True, **kwargs)**
306
+ Планирует переключение на другую сцену. Переключение произойдёт безопасно в конце текущего кадра.
307
+ - `name` (str): имя целевой сцены.
308
+ - `destroy` (bool): если True, удаляет все фигуры старой сцены.
309
+ - `**kwargs`: аргументы, которые попадут в `on_enter` новой сцены.
310
+
311
+ ### Фигуры
312
+ **create_figure(name, shape, color, params, width=0, auto_draw=False, use_camera=True)**
313
+ Создаёт фигуру и добавляет в движок.
314
+ - `name` (str): уникальное имя.
315
+ - `shape` (Literal): тип формы — `'rect'`, `'circle'`, `'ellipse'`, `'line'`, `'polygon'`, `'sprite'`.
316
+ - `color` (tuple): цвет RGB.
317
+ - `params` (tuple): параметры формы:
318
+ - rect: `(x, y, ширина, высота)`
319
+ - circle: `(x, y, радиус)`
320
+ - ellipse: `(x, y, ширина, высота)`
321
+ - line: `(x1, y1, x2, y2)`
322
+ - polygon: `(список_точек,)`
323
+ - sprite: `(x, y, имя_картинки)`
324
+ - `width` (int): толщина обводки. 0 = заливка.
325
+ - `auto_draw` (bool): если True, фигура сама добавляется в очередь отрисовки каждый кадр.
326
+ - `use_camera` (bool): если True, фигура двигается вместе с камерой. Для элементов UI (счёт, HP-бар) ставь `False`.
327
+
328
+ **draw_figure(name)**
329
+ Добавляет фигуру в очередь отрисовки на текущий кадр.
330
+ - `name` (str): имя фигуры.
331
+
332
+ **remove_figure(name)**
333
+ Удаляет фигуру из движка.
334
+ - `name` (str): имя фигуры.
335
+
336
+ **clear_figures()**
337
+ Удаляет все фигуры. Обычно не нужно вызывать вручную — `set_scene(destroy=True)` делает это сам.
338
+
339
+ ### Текст и шрифты
340
+ **create_font(name, font='Arial', size=36)**
341
+ Создаёт шрифт и кэширует его.
342
+ - `name` (str): идентификатор для дальнейших вызовов.
343
+ - `font` (str): название системного шрифта.
344
+ - `size` (int): размер в пунктах.
345
+
346
+ **draw_text(font, text, x, y, color=(0, 0, 0), antialias=True, use_camera=False)**
347
+ Рисует текст на экране.
348
+ - `font` (str): имя шрифта, созданного через `create_font`.
349
+ - `text` (str): текст.
350
+ - `x, y` (int): координаты левого верхнего угла текста.
351
+ - `color` (tuple): цвет текста.
352
+ - `antialias` (bool): сглаживание.
353
+ - `use_camera` (bool): двигать ли текст с камерой. По умолчанию `False`, так как текст часто является UI.
354
+
355
+ **draw_text_on_figure(fig_name, font, text, color=(0, 0, 0), use_camera=False)**
356
+ Рисует текст по центру фигуры. Сам считает центр и размеры текста.
357
+ - `fig_name` (str): имя фигуры.
358
+ - `font` (str): имя шрифта.
359
+ - `text` (str): текст.
360
+ - `color` (tuple): цвет текста.
361
+ - `use_camera` (bool): двигать ли текст с камерой.
362
+
363
+ **draw_overlay(color=(0, 0, 0), alpha=180)**
364
+ Рисует полупрозрачный слой поверх всего. Годится для затемнения при паузе или открытия меню. Всегда игнорирует камеру.
365
+ - `color` (tuple): цвет слоя.
366
+ - `alpha` (int): прозрачность от 0 (невидимо) до 255 (глухо).
367
+
368
+ ### Изображения
369
+ **load_image(name, path)**
370
+ Загружает картинку из файла.
371
+ - `name` (str): идентификатор.
372
+ - `path` (str): путь к файлу.
373
+
374
+ **scale_image(name, scale)**
375
+ Масштабирует картинку пропорционально.
376
+ - `name` (str): имя картинки.
377
+ - `scale` (float): коэффициент (0.5 = в два раза меньше, 2.0 = в два раза больше).
378
+
379
+ **resize_image(name, width, height)**
380
+ Растягивает/сжимает картинку до точных размеров. Пропорции не сохраняет.
381
+ - `name` (str): имя картинки.
382
+ - `width, height` (int): новые размеры в пикселях.
383
+
384
+ **flip_image(name, new_name, flip_x=False, flip_y=False)**
385
+ Зеркалит картинку и сохраняет копию под новым именем. Оригинал не трогает.
386
+ - `name` (str): имя исходной картинки.
387
+ - `new_name` (str): имя копии.
388
+ - `flip_x` (bool): отразить по горизонтали.
389
+ - `flip_y` (bool): отразить по вертикали.
390
+
391
+ **rotate_image(name, new_name, angle)**
392
+ Поворачивает картинку и сохраняет копию.
393
+ - `name` (str): имя исходной картинки.
394
+ - `new_name` (str): имя копии.
395
+ - `angle` (float): угол в градусах, против часовой стрелки.
396
+
397
+ ### Ввод
398
+ **get_event(event, etype='keyDown', key=None)**
399
+ Проверяет конкретное событие в цикле обработки.
400
+ - `event` (pygame.event.Event): объект события.
401
+ - `etype` (EventLiteral): тип — `'quit'`, `'keyDown'`, `'keyUp'`, `'mouseButtonDown'`, `'mouseButtonUp'`, `'mouseMotion'`.
402
+ - `key` (KeyLiteral | None): конкретная клавиша. Если None, проверяется только тип.
403
+
404
+ **is_pressed(key)**
405
+ Возвращает True, пока клавиша или кнопка мыши удерживается. Это не событие, а состояние.
406
+ - `key` (KeyLiteral): имя клавиши (`'w'`, `'space'`, `'left'`) или кнопки мыши (`'leftMouseButton'`).
407
+
408
+ **is_mouse_over(name, cooldown=0.0)**
409
+ Возвращает True, если курсор над фигурой.
410
+ - `name` (str): имя фигуры.
411
+ - `cooldown` (float): минимальный интервал между True. Полезно чтобы не спамить наведением.
412
+
413
+ **is_clicked(name, cooldown=0.0, button='leftMouseButton')**
414
+ Возвращает True, если по фигуре кликнули мышью. Встроенный кулдаун не даёт сработать дважды.
415
+ - `name` (str): имя фигуры.
416
+ - `cooldown` (float): интервал в секундах.
417
+ - `button` (MouseButton): `'leftMouseButton'`, `'rightMouseButton'` или `'middleMouseButton'`.
418
+
419
+ ### Коллизии
420
+ **check_collision(name1, name2, cooldown=0.5)**
421
+ Проверяют ли две фигуры друг друга. Встроенный кулдаун не даст вернуть True дважды за один "контакт". Поддерживает точную проверку круг-прямоугольник.
422
+ - `name1, name2` (str): имена фигур.
423
+ - `cooldown` (float): время в секундах до повторного True.
424
+
425
+ **can_move_to(name, new_x, new_y, obstacles)**
426
+ Симулирует перемещение и проверяет, не врежется ли фигура в препятствия. Если врежется — возвращает старые координаты.
427
+ - `name` (str): имя фигуры.
428
+ - `new_x, new_y` (float): куда хотим переместить.
429
+ - `obstacles` (list[str]): список имён фигур-препятствий.
430
+ - Возвращает: `(bool, float, float)` — (можно_ли, итоговый_x, итоговый_y).
431
+
432
+ **resolve_collisions(movers, walls)**
433
+ Проходит по списку movers и выталкивает каждого из каждого wall, если они пересекаются.
434
+ - `movers` (list[str]): имена подвижных фигур.
435
+ - `walls` (list[str]): имена статичных препятствий.
436
+
437
+ ### Движение
438
+ **move(name, speed, dx=0.0, dy=0.0, target=None, map_name=None, obstacles=None, use_dt=True, grid_mode=False)**
439
+ Универсальный метод. Двигает фигуру, сам умножает скорость на dt, сам проверяет коллизии с тайлами карты и другими фигурами.
440
+ - `name` (str): имя фигуры.
441
+ - `speed` (float): скорость. Если `use_dt=True` — пикселей в секунду. Если `False` — пикселей за кадр.
442
+ - `dx, dy` (float): направление. Обычно -1, 0 или 1. В `grid_mode` игнорируются, если фигура уже движется к текущей цели.
443
+ - `target` (tuple[float, float] | None): координаты цели. Фигура плавно пойдёт к ним. Игнорируется в `grid_mode`.
444
+ - `map_name` (str | None): имя TileMap для коллизий с тайлами. Обязательно для `grid_mode`.
445
+ - `obstacles` (list[str] | None): имена фигур-препятствий.
446
+ - `use_dt` (bool): учитывать ли dt. False для UI-анимаций.
447
+ - `grid_mode` (bool): если True, включает пошаговое движение по сетке тайлов. Фигура будет останавливаться в центре каждого тайла и ждать нового ввода.
448
+
449
+ *Пример использования:*
450
+ ```python
451
+ # Стандартное непрерывное движение (игрок бежит, врезается в стены)
452
+ dx, dy = 0, 0
453
+ if engine.is_pressed('left'): dx -= 1
454
+ if engine.is_pressed('right'): dx += 1
455
+ engine.move('player', speed=200, dx=dx, dy=dy, map_name='level')
456
+
457
+ # Пошаговое движение по тайлам (герой идет клетка за клеткой)
458
+ dx, dy = 0, 0
459
+ if engine.is_pressed('left'): dx = -1
460
+ if engine.is_pressed('right'): dx = 1
461
+ if engine.is_pressed('up'): dy = -1
462
+ if engine.is_pressed('down'): dy = 1
463
+ # Фигура плавно перейдет в центр соседнего тайла и остановится там
464
+ engine.move('hero', speed=150, dx=dx, dy=dy, map_name='level', grid_mode=True)
465
+ ```
466
+
467
+ **move_towards(current_x, current_y, target_x, target_y, speed=0.1)**
468
+ Линейная интерполяция (lerp). Возвращает новую точку, которая ближе к цели на долю speed.
469
+ - `current_x, current_y` (float): текущие координаты.
470
+ - `target_x, target_y` (float): целевые координаты.
471
+ - `speed` (float): коэффициент от 0.0 до 1.0 (1.0 = телепорт, 0.05 = медленное скольжение).
472
+ - Возвращает: `(int, int)`.
473
+
474
+ **move_at_speed(current_x, current_y, target_x, target_y, speed=5.0)**
475
+ Сдвигает точку ровно на speed пикселей в сторону цели. Если до цели ближе чем speed — ставит точно на цель.
476
+ - `current_x, current_y` (float): текущие координаты.
477
+ - `target_x, target_y` (float): целевые координаты.
478
+ - `speed` (float): смещение в пикселях за один вызов.
479
+ - Возвращает: `(int, int)`.
480
+
481
+ ### TileMap и камера
482
+ **add_tilemap(name, tilemap)**
483
+ Регистрирует карту в движке. После этого `move` сможет сам проверять коллизии с ней.
484
+ - `name` (str): имя карты.
485
+ - `tilemap` (TileMap): экземпляр TileMap.
486
+
487
+ **set_camera_target(figure_name)**
488
+ Камера автоматически следует за фигурой, держа её в центре экрана.
489
+ - `figure_name` (str): имя фигуры.
490
+
491
+ **set_camera_bounds(bounds_type, bounds_data)**
492
+ Ограничивает движение камеры, чтобы она не показывала пустоту за пределами мира.
493
+ - `bounds_type` (str): `'map'` (ограничить картой) или `'window'` (не уходить в отрицательные координаты).
494
+ - `bounds_data`: экземпляр `TileMap` (если `bounds_type='map'`).
495
+
496
+ **clamp_to_map(name, map_name)**
497
+ Не даёт фигуре вылезти за границы карты. Аналог `clamp_to_screen`, но для размеров карты.
498
+ - `name` (str): имя фигуры.
499
+ - `map_name` (str): имя карты.
500
+
501
+ **check_tile_collision(name, map_name, tile_id)**
502
+ Проверяет, стоит ли фигура на тайле с указанным ID.
503
+ - `name` (str): имя фигуры.
504
+ - `map_name` (str): имя карты.
505
+ - `tile_id` (int): ID тайла.
506
+
507
+ *Пример использования:*
508
+ ```python
509
+ # Камера следит за игроком и не выходит за границы карты
510
+ engine.set_camera_target('player')
511
+ engine.set_camera_bounds('map', level)
512
+
513
+ # Игрок двигается с коллизиями
514
+ engine.move('player', speed=200, dx=dx, dy=dy, map_name='level')
515
+ engine.clamp_to_map('player', 'level')
516
+
517
+ # Проверка подбора монетки
518
+ if engine.check_tile_collision('player', 'level', tile_id=10):
519
+ level.remove_tile(tx, ty, layer_index=1)
520
+ score += 1
521
+ ```
522
+
523
+ ### Анимации
524
+ **add_animation(name, frames, fps=8.0)**
525
+ Регистрирует анимацию для использования фигурами.
526
+ - `name` (str): Уникальное имя анимации.
527
+ - `frames` (list[str]): Список имен изображений (кадров), загруженных через `load_image`.
528
+ - `fps` (float): Количество кадров в секунду.
529
+
530
+ *Пример использования:*
531
+ ```python
532
+ engine.load_image('p1', 'assets/player_1.png')
533
+ engine.load_image('p2', 'assets/player_2.png')
534
+ engine.add_animation('player_idle', frames=['p1', 'p2'], fps=8)
535
+ engine.figures['hero'].play('player_idle') # Движок сам меняет кадры
536
+ ```
537
+
538
+ ### Утилиты
539
+ **check_cooldown(name, seconds)**
540
+ Таймер-защитка. Возвращает True, если прошло нужное время с последнего вызова. Иначе False.
541
+ - `name` (str): уникальный ID таймера.
542
+ - `seconds` (float): длительность в секундах.
543
+
544
+ *Пример использования:*
545
+ ```python
546
+ # Выстрел не чаще раза в полсекунды
547
+ if engine.is_pressed('space') and engine.check_cooldown('shoot', seconds=0.5):
548
+ print("Бах!")
549
+ ```
550
+
551
+ **get_text_pos(font, text, align_x, align_y, offset_x=0, offset_y=0)**
552
+ Считает координаты для текста с учётом выравнивания и размера текста.
553
+ - `font` (str): имя шрифта (нужен для измерения текста).
554
+ - `text` (str): текст.
555
+ - `align_x` (AlignX): `'left'`, `'middle'` или `'right'`.
556
+ - `align_y` (AlignY): `'up'`, `'middle'` или `'down'`.
557
+ - `offset_x, offset_y` (int): дополнительный сдвиг.
558
+ - Возвращает: `(int, int)`.
559
+
560
+ **get_shape_pos(shape_w, shape_h, align_x, align_y, offset_x=0, offset_y=0)**
561
+ Считает координаты для фигуры с учётом её размеров и выравнивания.
562
+ - `shape_w, shape_h` (int): ширина и высота.
563
+ - `align_x` (AlignX): `'left'`, `'middle'` или `'right'`.
564
+ - `align_y` (AlignY): `'up'`, `'middle'` или `'down'`.
565
+ - `offset_x, offset_y` (int): дополнительный сдвиг.
566
+ - Возвращает: `(int, int)`.
567
+
568
+ *Пример использования:*
569
+ ```python
570
+ # Кнопка по центру экрана
571
+ btn_pos = engine.get_shape_pos(200, 60, 'middle', 'middle')
572
+ engine.create_figure('btn', 'rect', Assets.GREEN, btn_pos)
573
+ ```
574
+
575
+ ---
576
+
577
+ ## TileMap
578
+
579
+ Система тайловых карт с поддержкой нескольких слоёв. Тайлы — это статичная основа мира: пол, стены, декорации. Динамические объекты (игрок, враги, монетки) — это `Figure`.
580
+
581
+ ```python
582
+ from pyengine.tilemap import Tile, TileSet, TileMap
583
+
584
+ # 1. Создаём набор тайлов
585
+ tileset = TileSet(tile_width=32, tile_height=32)
586
+ tileset.add_tile(Tile(1, Assets.GREEN, solid=False)) # Земля (не твёрдая)
587
+ tileset.add_tile(Tile(2, Assets.GRAY, solid=True)) # Стена (твёрдая)
588
+ tileset.add_tile(Tile(3, Assets.YELLOW)) # Монетка
589
+
590
+ # 2. Создаём карту и слои
591
+ level = TileMap(engine, tileset, width=30, height=20)
592
+ ground_layer = level.add_layer() # Индекс 0
593
+ object_layer = level.add_layer() # Индекс 1
594
+
595
+ # Указываем слой для коллизий
596
+ level.collision_layer_index = ground_layer
597
+
598
+ # 3. Быстрая загрузка из строк (прототипирование)
599
+ level.load_from_lines([
600
+ "######################",
601
+ "#....................#",
602
+ "#..22222.............#",
603
+ "######################",
604
+ ], legend={'#': 2, '.': 1, '2': 1}, layer_index=ground_layer)
605
+
606
+ # 4. Конвертация координат
607
+ px, py = level.cell_to_pixel(5, 2) # Вернёт (160, 64)
608
+ col, row = level.pixel_to_cell(170, 70) # Вернёт (5, 2)
609
+
610
+ engine.add_tilemap('level', level)
611
+ ```
612
+
613
+ ### Tile
614
+ Один тип тайла.
615
+ **__init__(tile_id, color=None, image_name=None, solid=False, exceptions=None)**
616
+ - `tile_id` (int): уникальный ID тайла.
617
+ - `color` (tuple | None): цвет тайла (используется, если нет `image_name`).
618
+ - `image_name` (str | None): имя изображения из `engine.images` (приоритетнее цвета).
619
+ - `solid` (bool): если True, фигуры не смогут проходить сквозь этот тайл.
620
+ - `exceptions` (list[str] | None): список названий фигур, которые могут проходить сквозь этот тайл даже если `solid=True`.
621
+
622
+ ### TileSet
623
+ Набор тайлов с общим размером.
624
+ **__init__(tile_width=32, tile_height=32)**
625
+ - `tile_width` (int): ширина одного тайла в пикселях.
626
+ - `tile_height` (int): высота одного тайла в пикселях.
627
+
628
+ **add_tile(tile)**
629
+ Добавляет тайл в набор. Если тайл с таким ID уже существует, он будет перезаписан.
630
+ - `tile` (Tile): экземпляр Tile.
631
+
632
+ ### TileLayer
633
+ Один слой тайлов. Карта может содержать несколько слоёв для разделения пола, объектов, переднего плана.
634
+ **__init__(width, height, default_tile=0)**
635
+ - `width` (int): ширина слоя в тайлах.
636
+ - `height` (int): высота слоя в тайлах.
637
+ - `default_tile` (int): ID тайла по умолчанию (используется при создании и удалении).
638
+
639
+ **set_tile(x, y, tile_id)**
640
+ Устанавливает тайл с указанным ID в позицию (x, y). Если координаты выходят за границы слоя, метод ничего не делает.
641
+ - `x, y` (int): позиция в тайлах.
642
+ - `tile_id` (int): ID тайла.
643
+
644
+ **get_tile(x, y)**
645
+ Возвращает ID тайла в позиции (x, y). Если координаты выходят за границы слоя, возвращает 0.
646
+ - `x, y` (int): позиция в тайлах.
647
+
648
+ **remove_tile(x, y)**
649
+ Удаляет тайл из позиции (x, y), делая ячейку пустой (ID=0). Пустые тайлы не отрисовываются, что позволяет видеть слои под ними. Если координаты выходят за границы слоя, метод ничего не делает.
650
+ - `x, y` (int): позиция в тайлах.
651
+
652
+ ### TileMap
653
+ Двумерная карта тайлов с поддержкой нескольких слоёв.
654
+ **__init__(engine, tileset, width, height)**
655
+ - `engine` (PyEngine): экземпляр PyEngine.
656
+ - `tileset` (TileSet): набор тайлов.
657
+ - `width` (int): ширина карты в тайлах.
658
+ - `height` (int): высота карты в тайлах.
659
+
660
+ **add_layer(default_tile=0)**
661
+ Добавляет новый слой тайлов в карту. Возвращает индекс созданного слоя (начинается с 0).
662
+ - `default_tile` (int): ID тайла по умолчанию для этого слоя.
663
+
664
+ **set_tile(x, y, tile_id, layer_index=0)**
665
+ Устанавливает тайл с указанным ID в позицию (x, y) на заданном слое. Если `layer_index` не указан, используется слой 0.
666
+ - `x, y` (int): позиция в тайлах.
667
+ - `tile_id` (int): ID тайла.
668
+ - `layer_index` (int): индекс слоя.
669
+
670
+ **get_tile(x, y, layer_index=0)**
671
+ Возвращает ID тайла в позиции (x, y) на заданном слое. Если `layer_index` не указан, используется слой 0. Если координаты или индекс слоя некорректны, возвращает 0.
672
+ - `x, y` (int): позиция в тайлах.
673
+ - `layer_index` (int): индекс слоя.
674
+
675
+ **remove_tile(x, y, layer_index=0)**
676
+ Удаляет тайл из позиции (x, y) на заданном слое, делая ячейку пустой. Пустые тайлы не отрисовываются, позволяя видеть нижние слои. Если `layer_index` не указан, используется слой 0.
677
+ - `x, y` (int): позиция в тайлах.
678
+ - `layer_index` (int): индекс слоя.
679
+
680
+ **is_solid(x, y, figure_name=None)**
681
+ Проверяет, является ли тайл в позиции (x, y) непроходимым. Проверка выполняется только на слое `collision_layer_index`. Если `figure_name` указан и находится в списке `exceptions` тайла, возвращается False.
682
+ - `x, y` (int): позиция в тайлах.
683
+ - `figure_name` (str | None): имя фигуры для проверки исключений.
684
+
685
+ **draw()**
686
+ Отрисовывает все слои карты на экран с учётом смещения камеры. Слои рисуются по порядку их добавления (от 0 до последнего). Оптимизировано: рисуются только видимые тайлы.
687
+
688
+ **load_from_lines(lines, legend, layer_index=0)**
689
+ Загружает карту из списка строк (текстовый уровень).
690
+ - `lines` (list[str]): Список строк, где каждый символ представляет тайл.
691
+ - `legend` (dict[str, int]): Словарь соответствия символа и ID тайла (например, `{'#': 1, '.': 0}`).
692
+ - `layer_index` (int): Индекс слоя для записи данных.
693
+
694
+ **cell_to_pixel(col, row)**
695
+ Преобразует координаты ячейки (col, row) в пиксельные координаты (x, y).
696
+ - `col, row` (int): координаты ячейки.
697
+ - Возвращает: `(int, int)`.
698
+
699
+ **pixel_to_cell(x, y)**
700
+ Преобразует пиксельные координаты (x, y) в координаты ячейки (col, row).
701
+ - `x, y` (float): пиксельные координаты.
702
+ - Возвращает: `(int, int)`.
703
+
704
+ ---
705
+
706
+ ## Мини-игры
707
+
708
+ Четыре примера, показывающие разные комбинации функционала.
709
+
710
+ ### Игра 1. Без сцен, без TileMap
711
+ Простая игра: убегай от красного круга.
712
+
713
+ ```python
714
+ from pyengine.engine import PyEngine
715
+ from pyengine.assets import Assets
716
+
717
+ engine = PyEngine('Убеги!', Assets.BLACK, 640, 480)
718
+ engine.initialize()
719
+
720
+ engine.create_figure('player', 'rect', Assets.BLUE, (300, 220, 24, 24))
721
+ engine.create_figure('enemy', 'circle', Assets.RED, (50, 50, 16))
722
+ engine.create_font('f', Assets.FONT_ARIAL, 20)
723
+ alive = True
724
+
725
+ def update():
726
+ global alive
727
+ if not alive: return
728
+ dx = dy = 0
729
+ if engine.is_pressed('left'): dx -= 1
730
+ if engine.is_pressed('right'): dx += 1
731
+ if engine.is_pressed('up'): dy -= 1
732
+ if engine.is_pressed('down'): dy += 1
733
+ engine.move('player', speed=250, dx=dx, dy=dy)
734
+ engine.figures['player'].clamp_to_screen()
735
+ px, py = engine.figures['player'].get_center()
736
+ engine.move('enemy', speed=120, target=(px, py))
737
+ if engine.check_collision('player', 'enemy', cooldown=999):
738
+ alive = False
739
+
740
+ def draw():
741
+ engine.draw_figure('player')
742
+ engine.draw_figure('enemy')
743
+ if not alive:
744
+ engine.draw_overlay(Assets.BLACK, 180)
745
+ engine.draw_text('f', 'Game Over', 250, 220, Assets.RED, use_camera=False)
746
+
747
+ engine.update = update
748
+ engine.draw = draw
749
+ engine.run()
750
+ ```
751
+
752
+ ### Игра 2. Без сцен, с TileMap
753
+ Собирай монетки в лабиринте. Камера следит за игроком и ограничена границами карты.
754
+
755
+ ```python
756
+ from pyengine.engine import PyEngine
757
+ from pyengine.assets import Assets
758
+ from pyengine.tilemap import Tile, TileSet, TileMap
759
+
760
+ engine = PyEngine('Монетки', Assets.BLACK, 480, 320)
761
+ engine.initialize()
762
+
763
+ ts = TileSet(32, 32)
764
+ ts.add_tile(Tile(1, Assets.DARK_GRAY, solid=True))
765
+ ts.add_tile(Tile(2, Assets.YELLOW))
766
+
767
+ level = TileMap(engine, ts, 30, 20)
768
+ ground = level.add_layer()
769
+ objects = level.add_layer()
770
+ level.collision_layer_index = ground
771
+
772
+ level.load_from_lines([
773
+ "##############################",
774
+ "#............................#",
775
+ "#...####...........####......#",
776
+ "#...#................#....2..#",
777
+ "##############################",
778
+ ], legend={'#': 1, '.': 0, '2': 2}, layer_index=ground)
779
+
780
+ engine.add_tilemap('level', level)
781
+ engine.create_figure('p', 'rect', Assets.CYAN, (64, 64, 24, 24))
782
+ engine.set_camera_target('p')
783
+ engine.set_camera_bounds('map', level) # Камера не покажет пустоту за краями
784
+ engine.create_font('f', Assets.FONT_ARIAL, 18)
785
+ score = 0
786
+
787
+ def update():
788
+ global score
789
+ dx = dy = 0
790
+ if engine.is_pressed('left'): dx -= 1
791
+ if engine.is_pressed('right'): dx += 1
792
+ if engine.is_pressed('up'): dy -= 1
793
+ if engine.is_pressed('down'): dy += 1
794
+
795
+ engine.move('p', speed=180, dx=dx, dy=dy, map_name='level')
796
+
797
+ # Простая проверка: если коснулись тайла 2, меняем его на 0 (пустоту)
798
+ if engine.check_tile_collision('p', 'level', 2):
799
+ cx, cy = engine.figures['p'].get_center()
800
+ tx, ty = level.pixel_to_cell(cx, cy)
801
+ level.remove_tile(tx, ty, layer_index=objects)
802
+ score += 1
803
+
804
+ def draw():
805
+ level.draw()
806
+ engine.draw_figure('p')
807
+ engine.draw_text('f', f'Монетки: {score}', 10, 10, Assets.WHITE, use_camera=False)
808
+
809
+ engine.update = update
810
+ engine.draw = draw
811
+ engine.run()
812
+ ```
813
+
814
+ ### Игра 3. Со сценами, без TileMap
815
+ Три сцены: меню, арена, game over.
816
+
817
+ ```python
818
+ from pyengine.engine import PyEngine
819
+ from pyengine.scene import Scene
820
+ from pyengine.assets import Assets
821
+
822
+ engine = PyEngine('Арена', Assets.BLACK, 480, 320)
823
+
824
+ class Menu(Scene):
825
+ def on_enter(self, **kw):
826
+ self.engine.create_figure('btn', 'rect', Assets.GREEN,
827
+ self.engine.get_shape_pos(180, 50, 'middle', 'middle'))
828
+ self.engine.create_font('f', Assets.FONT_ARIAL, 24)
829
+ def events(self, event):
830
+ if self.engine.get_event(event, 'mouseButtonDown', 'leftMouseButton'):
831
+ if self.engine.is_clicked('btn', cooldown=0.3):
832
+ self.engine.set_scene('arena')
833
+ def update(self): pass
834
+ def draw(self):
835
+ self.engine.draw_figure('btn')
836
+ self.engine.draw_text_on_figure('btn', 'f', 'Играть', Assets.WHITE, use_camera=False)
837
+
838
+ class Arena(Scene):
839
+ def on_enter(self, **kw):
840
+ self.engine.create_figure('p', 'circle', Assets.BLUE, (100, 160, 18))
841
+ self.engine.create_figure('e', 'circle', Assets.RED, (380, 160, 18))
842
+ self.engine.create_font('f', Assets.FONT_ARIAL, 16)
843
+ def events(self, event): pass
844
+ def update(self):
845
+ px, py = self.engine.figures['p'].get_center()
846
+ self.engine.move('e', speed=100, target=(px, py))
847
+ dx = dy = 0
848
+ if self.engine.is_pressed('left'): dx -= 1
849
+ if self.engine.is_pressed('right'): dx += 1
850
+ if self.engine.is_pressed('up'): dy -= 1
851
+ if self.engine.is_pressed('down'): dy += 1
852
+ self.engine.move('p', speed=200, dx=dx, dy=dy)
853
+ self.engine.figures['p'].clamp_to_screen()
854
+ if self.engine.check_collision('p', 'e', cooldown=999):
855
+ self.engine.set_scene('dead')
856
+ def draw(self):
857
+ self.engine.draw_figure('p')
858
+ self.engine.draw_figure('e')
859
+
860
+ class Dead(Scene):
861
+ def on_enter(self, **kw):
862
+ self.engine.create_font('f', Assets.FONT_ARIAL, 36)
863
+ def events(self, event): pass
864
+ def update(self):
865
+ if self.engine.is_pressed('space'):
866
+ self.engine.set_scene('menu')
867
+ def draw(self):
868
+ self.engine.draw_text('f', 'Ты умер. Пробел = меню', 80, 140, Assets.RED, use_camera=False)
869
+
870
+ engine.add_scenes({'menu': Menu(engine), 'arena': Arena(engine), 'dead': Dead(engine)})
871
+ engine.initialize(start_scene='menu')
872
+ engine.run()
873
+ ```
874
+
875
+ ### Игра 4. Со сценами, с TileMap и пошаговым движением
876
+ Меню → подземелье → победа. Герой двигается строго по клеткам.
877
+
878
+ ```python
879
+ from pyengine.engine import PyEngine
880
+ from pyengine.scene import Scene
881
+ from pyengine.assets import Assets
882
+ from pyengine.tilemap import Tile, TileSet, TileMap
883
+
884
+ engine = PyEngine('Подземелье', Assets.BLACK, 480, 320)
885
+
886
+ class Menu(Scene):
887
+ def on_enter(self, **kw):
888
+ self.engine.create_figure('btn', 'rect', Assets.ORANGE,
889
+ self.engine.get_shape_pos(200, 50, 'middle', 'middle'))
890
+ self.engine.create_font('f', Assets.FONT_ARIAL, 22)
891
+ def events(self, event):
892
+ if self.engine.get_event(event, 'mouseButtonDown', 'leftMouseButton'):
893
+ if self.engine.is_clicked('btn', 0.3):
894
+ self.engine.set_scene('dungeon')
895
+ def update(self): pass
896
+ def draw(self):
897
+ self.engine.draw_figure('btn')
898
+ self.engine.draw_text_on_figure('btn', 'f', 'Войти в подземелье', Assets.WHITE, use_camera=False)
899
+
900
+ class Dungeon(Scene):
901
+ def on_enter(self, **kw):
902
+ ts = TileSet(32, 32)
903
+ ts.add_tile(Tile(1, Assets.GRAY, solid=True))
904
+ ts.add_tile(Tile(2, Assets.GREEN))
905
+
906
+ self.level = TileMap(self.engine, ts, 15, 10)
907
+ ground = self.level.add_layer()
908
+ self.level.collision_layer_index = ground
909
+
910
+ self.level.load_from_lines([
911
+ "###############",
912
+ "#.............#",
913
+ "#.#####.......#",
914
+ "#.#...#.......#",
915
+ "#.#.2.#.......#",
916
+ "#.#...#.......#",
917
+ "#.#####.......#",
918
+ "#.............#",
919
+ "#.............#",
920
+ "###############",
921
+ ], legend={'#': 1, '.': 0, '2': 2}, layer_index=ground)
922
+
923
+ self.engine.add_tilemap('dungeon', self.level)
924
+ self.engine.create_figure('p', 'rect', Assets.CYAN, (48, 48, 24, 24))
925
+ self.engine.set_camera_target('p')
926
+ self.engine.set_camera_bounds('map', self.level)
927
+ self.engine.create_font('f', Assets.FONT_ARIAL, 14)
928
+
929
+ def events(self, event): pass
930
+
931
+ def update(self):
932
+ dx = dy = 0
933
+ if self.engine.is_pressed('left'): dx = -1
934
+ if self.engine.is_pressed('right'): dx = 1
935
+ if self.engine.is_pressed('up'): dy = -1
936
+ if self.engine.is_pressed('down'): dy = 1
937
+
938
+ # grid_mode=True: герой сделает один шаг и остановится в центре тайла
939
+ self.engine.move('p', speed=200, dx=dx, dy=dy, map_name='dungeon', grid_mode=True)
940
+
941
+ if self.engine.check_tile_collision('p', 'dungeon', 2):
942
+ self.engine.set_scene('win')
943
+
944
+ def draw(self):
945
+ self.level.draw()
946
+ self.engine.draw_figure('p')
947
+ self.engine.draw_text('f', 'Найди зелёный выход (пошагово!)', 10, 10, Assets.WHITE, use_camera=False)
948
+
949
+ class Win(Scene):
950
+ def on_enter(self, **kw):
951
+ self.engine.create_figure('btn', 'rect', Assets.PURPLE,
952
+ self.engine.get_shape_pos(180, 50, 'middle', 'middle'))
953
+ self.engine.create_font('f', Assets.FONT_ARIAL, 28)
954
+ def events(self, event):
955
+ if self.engine.get_event(event, 'mouseButtonDown', 'leftMouseButton'):
956
+ if self.engine.is_clicked('btn', 0.3):
957
+ self.engine.set_scene('menu')
958
+ def update(self): pass
959
+ def draw(self):
960
+ self.engine.draw_text('f', 'Ты прошёл!', 170, 100, Assets.GREEN, use_camera=False)
961
+ self.engine.draw_figure('btn')
962
+ self.engine.draw_text_on_figure('btn', 'f', 'Заново', Assets.WHITE, use_camera=False)
963
+
964
+ engine.add_scenes({
965
+ 'menu': Menu(engine),
966
+ 'dungeon': Dungeon(engine),
967
+ 'win': Win(engine)
968
+ })
969
+ engine.initialize(start_scene='menu')
970
+ engine.run()
971
+ ```
972
+
973
+ ---
974
+
975
+ ## Философия PyEngine
976
+
977
+ PyEngine не пытается стать заменой pygame или навязать сложную архитектуру. Его цель — убрать рутину.
978
+
979
+ Ты больше не будешь писать одни и те же 20 строк кода для проверки клика по кнопке. Ты не будешь каждый раз изобретать велосипед для камеры, которая следует за игроком. Ты не будешь тратить вечер на отладку коллизий между кругом и прямоугольником.
980
+
981
+ Библиотека дает готовые строительные блоки. Если нужно что-то специфичное — бери `pygame` и делай как хочешь. Но для 90% задач в 2D-играх у PyEngine уже есть решение в одну строку.
982
+
983
+ **Для кого это:**
984
+ * Для тех, кто хочет прототипировать идеи быстро.
985
+ * Для новичков, которые хотят понять логику игр, а не тонуть в boilerplate-коде.
986
+ * Для опытных разработчиков, которым просто лень каждый раз писать одно и то же.
987
+
988
+ Удачи в разработке.