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.
- pyengine/__init__.py +21 -0
- pyengine/assets.py +65 -0
- pyengine/engine.py +910 -0
- pyengine/figure.py +231 -0
- pyengine/scene.py +51 -0
- pyengine/sound.py +102 -0
- pyengine/tilemap.py +224 -0
- pyengine_x-0.1.0.dist-info/METADATA +988 -0
- pyengine_x-0.1.0.dist-info/RECORD +12 -0
- pyengine_x-0.1.0.dist-info/WHEEL +5 -0
- pyengine_x-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyengine_x-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
Удачи в разработке.
|