Skip to content

Commit 1cfd8f9

Browse files
v1.2.8
1 parent 836f945 commit 1cfd8f9

14 files changed

Lines changed: 546 additions & 72 deletions

docs/game_loop.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,57 @@ while True:
4141
s.update(fill_color=(10, 10, 20))
4242
```
4343

44+
Важно: если переключаете сцену прямо в `update`, завершайте метод сразу после `set_scene_by_name`, чтобы не запускать логику кадра дальше.
45+
46+
```python
47+
def update(self, dt):
48+
if s.input.was_pressed(pygame.K_RETURN):
49+
s.set_scene_by_name("menu")
50+
return
51+
```
52+
4453
Перезапуск сцены:
4554

4655
```python
4756
s.restart_scene() # текущая сцена
4857
s.restart_scene("main") # по имени
4958
```
5059

60+
## Несколько активных сцен
61+
62+
Можно держать активными несколько сцен одновременно (например, игра + UI).
63+
64+
```python
65+
s.set_scene_by_name("game")
66+
s.activate_scene("hud")
67+
print(s.is_scene_active("hud"))
68+
69+
for scene in s.get_active_scenes():
70+
print(scene.name, scene.is_active)
71+
```
72+
73+
Отключение сцены:
74+
75+
```python
76+
s.deactivate_scene("hud")
77+
```
78+
79+
### Порядок обновления и отрисовки
80+
81+
У каждой сцены есть поле `order` (по умолчанию 0). Чем выше значение, тем позже сцена обновляется и рисуется.
82+
83+
```python
84+
game_scene.order = 0
85+
hud_scene.order = 10
86+
s.activate_scene(hud_scene)
87+
```
88+
89+
Или через менеджер:
90+
91+
```python
92+
s.set_scene_order("hud", 10)
93+
```
94+
5195
## Таймеры в сценах
5296

5397
### Вариант 1: Через dt

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "spritepro"
7-
version = "1.2.5"
7+
version = "1.2.8"
88
authors = [
99
{ name="NeoXider", email="neoxider@gmail.com" },
1010
]

spritePro/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,41 @@ def set_scene_by_name(name: str, recreate: bool = False) -> None:
586586
_context.scene_manager.set_scene_by_name(name, _context, recreate=recreate)
587587

588588

589+
def activate_scene(scene_or_name: Scene | str) -> None:
590+
"""Активирует сцену, не отключая другие."""
591+
_context.scene_manager.activate_scene(scene_or_name, _context)
592+
593+
594+
def deactivate_scene(scene_or_name: Scene | str) -> None:
595+
"""Деактивирует сцену."""
596+
_context.scene_manager.deactivate_scene(scene_or_name)
597+
598+
599+
def set_active_scenes(scenes_or_names: list[Scene | str]) -> None:
600+
"""Устанавливает список активных сцен."""
601+
_context.scene_manager.set_active_scenes(scenes_or_names, _context)
602+
603+
604+
def get_active_scenes() -> list[Scene]:
605+
"""Возвращает список активных сцен."""
606+
return _context.scene_manager.get_active_scenes()
607+
608+
609+
def is_scene_active(scene_or_name: Scene | str) -> bool:
610+
"""Проверяет, активна ли сцена."""
611+
return _context.scene_manager.is_scene_active(scene_or_name)
612+
613+
614+
def set_scene_order(scene_or_name: Scene | str, order: int) -> None:
615+
"""Устанавливает порядок отрисовки/обновления сцены."""
616+
_context.scene_manager.set_scene_order(scene_or_name, order)
617+
618+
619+
def get_current_scene() -> Scene | None:
620+
"""Возвращает текущую сцену."""
621+
return _context.scene_manager.current_scene
622+
623+
589624
def restart_scene(name: str | None = None) -> None:
590625
"""Перезапускает сцену по имени или текущую сцену."""
591626
if name is None:

spritePro/audio/audio_manager.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,16 @@ def load_sound(self, name: str, path: str) -> "Sound":
8585
>>> jump_sound = audio.load_sound("jump", "sounds/jump.mp3")
8686
>>> jump_sound.play() # Можно сразу использовать!
8787
"""
88-
try:
89-
cached = resource_cache.load_sound(path)
90-
if cached is None:
91-
cached = pygame.mixer.Sound(path)
92-
self.sounds[name] = cached
93-
return Sound(self, name)
94-
except pygame.error as e:
88+
cached = resource_cache.load_sound(path)
89+
if cached is None:
9590
import spritePro
9691

97-
spritePro.debug_log_warning(f"Sound not loaded '{name}' from '{path}': {e}")
92+
spritePro.debug_log_warning(
93+
f"Sound not loaded '{name}' from '{path}': cache load returned None"
94+
)
9895
return Sound(self, name) # Возвращаем объект даже при ошибке
96+
self.sounds[name] = cached
97+
return Sound(self, name)
9998

10099
def play_sound(self, name_or_path: str, volume: Optional[float] = None) -> None:
101100
"""Воспроизвести звуковой эффект.
@@ -125,15 +124,18 @@ def play_sound(self, name_or_path: str, volume: Optional[float] = None) -> None:
125124
sound.set_volume(volume if volume is not None else self.sfx_volume)
126125
sound.play()
127126
else:
128-
# Пытаемся загрузить и воспроизвести напрямую из файла
129-
try:
130-
sound = pygame.mixer.Sound(name_or_path)
131-
sound.set_volume(volume if volume is not None else self.sfx_volume)
132-
sound.play()
133-
except pygame.error as e:
127+
# Пытаемся загрузить и воспроизвести из кэша
128+
sound = resource_cache.load_sound(name_or_path)
129+
if sound is None:
134130
import spritePro
135131

136-
spritePro.debug_log_warning(f"Sound not played '{name_or_path}': {e}")
132+
spritePro.debug_log_warning(
133+
f"Sound not played '{name_or_path}': cache load returned None"
134+
)
135+
return
136+
self.sounds[name_or_path] = sound
137+
sound.set_volume(volume if volume is not None else self.sfx_volume)
138+
sound.play()
137139

138140
def play_music(
139141
self, path: str, loop: bool = True, volume: Optional[float] = None
@@ -158,7 +160,7 @@ def play_music(
158160
pygame.mixer.music.set_volume(music_vol)
159161
pygame.mixer.music.play(-1 if loop else 0)
160162
self.current_music = path
161-
except pygame.error as e:
163+
except Exception as e:
162164
import spritePro
163165

164166
spritePro.debug_log_warning(f"Music not loaded '{path}': {e}")

spritePro/demoGames/input_events_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def main():
2525
s.events.connect("key_down", on_key_down)
2626
s.events.connect("quit", on_quit)
2727

28-
player = s.Sprite("", (60, 60), (400, 300), speed=260)
28+
player = s.Sprite("", (60, 60), (400, 300), speed=5)
2929
player.set_color((120, 200, 255))
3030
player.angle = 0
3131

spritePro/demoGames/particles_images_demo.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
sys.path.insert(0, str(parent_dir))
1919

2020
import spritePro as s
21+
from spritePro.resources import resource_cache
2122
from spritePro.particles import ParticleConfig, ParticleEmitter
2223

2324

2425
def load_image(name: str) -> pygame.Surface:
2526
sprites_dir = current_dir / "Sprites"
26-
img = pygame.image.load(str(sprites_dir / name)).convert_alpha()
27+
img = resource_cache.load_texture(str(sprites_dir / name))
28+
if img is None:
29+
img = pygame.Surface((16, 16), pygame.SRCALPHA)
30+
img.fill((255, 255, 255, 255))
2731
return img
2832

2933

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
Particles Stress Demo - SpritePro
3+
4+
Spawns 10k image particles every second and shows timings.
5+
"""
6+
7+
import sys
8+
from pathlib import Path
9+
import time
10+
11+
import pygame
12+
13+
current_dir = Path(__file__).parent
14+
parent_dir = current_dir.parent.parent
15+
if str(parent_dir) not in sys.path:
16+
sys.path.insert(0, str(parent_dir))
17+
18+
import spritePro as s # noqa: E402
19+
from spritePro.particles import ParticleConfig, ParticleEmitter # noqa: E402
20+
from spritePro.readySprites import Text_fps # noqa: E402
21+
from spritePro.resources import resource_cache # noqa: E402
22+
23+
24+
TEXTURE_PATH = "spritePro/demoGames/Sprites/c.png"
25+
BURST_COUNT = 1_00
26+
BURST_INTERVAL = 0.1
27+
28+
29+
def _load_particle_image() -> pygame.Surface:
30+
img = resource_cache.load_texture(TEXTURE_PATH)
31+
if img is None:
32+
img = pygame.Surface((24, 24), pygame.SRCALPHA)
33+
img.fill((255, 255, 255, 255))
34+
return img
35+
36+
37+
def main() -> None:
38+
s.init()
39+
screen = s.get_screen((900, 600), "Particles Stress Demo")
40+
center = screen.get_rect().center
41+
42+
image = _load_particle_image()
43+
cfg = ParticleConfig(
44+
amount=BURST_COUNT,
45+
lifetime_range=(1.5, 2.0),
46+
speed_range=(40.0, 160.0),
47+
fade_speed=220.0,
48+
image=image,
49+
image_scale_range=(0.05, 0.4),
50+
angle_range=(0.0, 360.0),
51+
spawn_circle_radius=30,
52+
)
53+
emitter = ParticleEmitter(cfg, use_pool=True, max_pool_size=BURST_COUNT * 2)
54+
55+
fps_counter = Text_fps(
56+
pos=(10, 8), color=(255, 255, 0), prefix="FPS: ", precision=1
57+
)
58+
stats = s.TextSprite("", 20, (200, 220, 255), (450, 40))
59+
_hint = s.TextSprite(
60+
f"Spawn: {BURST_COUNT} particles every {BURST_INTERVAL:.1f}s",
61+
18,
62+
(180, 180, 180),
63+
(450, 70),
64+
)
65+
66+
total_emitted = 0
67+
last_emit_ms = 0.0
68+
last_emit_count = 0
69+
70+
def emit_burst() -> None:
71+
nonlocal total_emitted, last_emit_ms, last_emit_count
72+
start = time.perf_counter()
73+
particles = emitter.emit(center)
74+
last_emit_ms = (time.perf_counter() - start) * 1000.0
75+
last_emit_count = len(particles)
76+
total_emitted += last_emit_count
77+
stats.set_text(
78+
f"Burst: {last_emit_count} | Total: {total_emitted} | Emit: {last_emit_ms:.2f}ms"
79+
)
80+
81+
stats.set_text("Waiting for burst...")
82+
s.Timer(BURST_INTERVAL, callback=emit_burst, repeat=True, autostart=True)
83+
emit_burst()
84+
85+
while True:
86+
fps_counter.update_fps()
87+
s.update(fill_color=(12, 12, 20))
88+
89+
90+
if __name__ == "__main__":
91+
main()

spritePro/demoGames/ping_pong.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -318,16 +318,15 @@ def update_sprite_visibility():
318318
for bt in bts.values():
319319
bt.set_image(round_corners(bt.image, 50))
320320

321+
bg_surface = spritePro.load_texture(path / "Sprites" / "bg.jpg")
322+
if bg_surface is None:
323+
bg_surface = pygame.Surface(spritePro.WH)
324+
bg_surface.fill((20, 20, 20))
325+
bg_scaled = pygame.transform.scale(bg_surface, spritePro.WH)
321326
BGS = {
322-
STATE_MENU: pygame.transform.scale(
323-
pygame.image.load(path / "Sprites" / "bg.jpg"), (spritePro.WH)
324-
),
325-
STATE_SHOP: pygame.transform.scale(
326-
pygame.image.load(path / "Sprites" / "bg.jpg"), (spritePro.WH)
327-
),
328-
STATE_GAME: pygame.transform.scale(
329-
pygame.image.load(path / "Sprites" / "bg.jpg"), (spritePro.WH)
330-
),
327+
STATE_MENU: bg_scaled,
328+
STATE_SHOP: bg_scaled,
329+
STATE_GAME: bg_scaled,
331330
}
332331

333332

0 commit comments

Comments
 (0)