Skip to content

Commit 9cd3d11

Browse files
committed
sound/music, set ground color, script key changes
1 parent 76daf63 commit 9cd3d11

File tree

10 files changed

+128
-55
lines changed

10 files changed

+128
-55
lines changed

game/base/entity.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def play_sound(self, filename, callback=None, *args):
164164
channel = pygame.mixer.find_channel()
165165
if not channel:
166166
return None, None, None
167+
channel.set_volume(SOUND_VOLUME)
167168
if callback:
168169
slot = self.scene.when.once(self.sounds[0].get_length(), callback)
169170
self.slots.add(slot)

game/base/script.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ def __init__(self, app, ctx, script, use_input=True, script_args=None):
2525

2626
# these are accumulated between yields
2727
# this is different from get_pressed()
28-
self.key_down = set()
29-
self.key_up = set()
28+
self.keys = set()
29+
self.keys_down = set()
30+
self.keys_up = set()
3031

3132
if use_input:
3233
self.event_slot = self.app.on_event.connect(self.event)
@@ -48,9 +49,14 @@ def resume(self):
4849

4950
def event(self, ev):
5051
if ev.type == pygame.KEYDOWN:
51-
self.key_down.add(ev.key)
52+
self.keys_down.add(ev.key)
53+
self.keys.add(ev.key)
5254
elif ev.type == pygame.KEYUP:
53-
self.key_up.add(ev.key)
55+
self.keys_up.add(ev.key)
56+
try:
57+
self.keys.remove(ev.key)
58+
except KeyError:
59+
pass
5460

5561
def running(self):
5662
return self._script is not None
@@ -66,8 +72,19 @@ def key(self, k):
6672
assert self.event_slot # input needs to be enabled (default)
6773

6874
if isinstance(k, str):
69-
return self.key_down[ord(k)]
70-
return self.key_down[k]
75+
return ord(k) in self.keys
76+
return k in self.keys
77+
78+
def key_down(self, k):
79+
# if we're in a script: return keys since last script yield
80+
# assert self.script.inside
81+
82+
assert self.inside # please only use this in scripts
83+
assert self.event_slot # input needs to be enabled (default)
84+
85+
if isinstance(k, str):
86+
return ord(k) in self.keys_down
87+
return k in self.keys_down
7188

7289
def key_up(self, k):
7390
# if we're in a script: return keys since last script yield
@@ -77,23 +94,23 @@ def key_up(self, k):
7794
assert self.event_slot # input needs to be enabled (default)
7895

7996
if isinstance(k, str):
80-
return self.key_up[ord(k)]
81-
return self.key_up[k]
97+
return ord(k) in self.keys_up
98+
return k in self.keys_up
8299

83100
# This makes scripting cleaner than checking script.keys directly
84101
# We need these so scripts can do "keys = script.keys"
85102
# and then call keys(), since it changes
86-
def keys(self):
87-
# return key downs since last script yield
88-
assert self.inside # please only use this in scripts
89-
assert self.event_slot # input needs to be enabled (default)
90-
return self.key_down
91-
92-
def keys_up(self):
93-
# return key ups since last script yield
94-
assert self.inside # please only use this in scripts
95-
assert self.event_slot # input needs to be enabled (default)
96-
return self.key_up
103+
# def keys(self):
104+
# # return key downs since last script yield
105+
# assert self.inside # please only use this in scripts
106+
# assert self.event_slot # input needs to be enabled (default)
107+
# return self._keys
108+
109+
# def keys_up(self):
110+
# # return key ups since last script yield
111+
# assert self.inside # please only use this in scripts
112+
# assert self.event_slot # input needs to be enabled (default)
113+
# return self._key_up
97114

98115
@property
99116
def script(self):

game/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
MUSIC_DIR = os.path.join(ASSETS_DIR, "music")
1717
FONTS_DIR = ASSETS_DIR
1818

19+
SOUND_VOLUME = 0.1
20+
MUSIC_VOLUME = 1.0
21+
1922
# Images should all be in the SPRITES_DIR
2023
SHIP_IMAGE_PATH = "ship.png"
2124
CROSSHAIR_IMAGE_PATH = "crosshair.png"

game/entities/ground.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Ground(Entity):
1111
def __init__(self, app, scene, height):
1212
super().__init__(app, scene)
1313
self.position = vec3(0, height, float("-inf"))
14+
self.color = GREEN
1415

1516
def render(self, camera: Camera):
1617
super().render(camera)
@@ -53,4 +54,4 @@ def render(self, camera: Camera):
5354

5455
if len(poly) > 2:
5556
poly = [tuple(camera.world_to_screen(p)) for p in poly]
56-
pygame.draw.polygon(self.app.screen, GREEN, poly)
57+
pygame.draw.polygon(self.app.screen, self.color, poly)

game/scene.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22

33
import functools
4-
from game.base.signal import Signal, Slot
4+
from game.base.signal import Signal, Slot, SlotList
55
from game.base.when import When
66
from os import path
77
from pygame import Color
@@ -22,12 +22,15 @@ def __init__(self, app, state, script=None, script_args=None):
2222
super().__init__()
2323
self.app = app
2424
self.state = state
25-
self._when = When()
25+
self.when = When()
26+
self.slotlist = SlotList()
2627

2728
# self.script_paused = False
2829
# self.script_slots = []
2930
self.sky_color = None
3031
self.dt = 0
32+
self.sounds = {}
33+
3134
# self.script_fn = script
3235
# self.event_slot = self.app.on_event.connect(self.event)
3336

@@ -51,14 +54,50 @@ def __init__(self, app, state, script=None, script_args=None):
5154
else:
5255
self._script = None
5356

54-
@property
55-
def when(self):
56-
if self._script and self._script.running():
57-
# Sanity Check:
58-
# Don't use scene.when() when inside a script.
59-
# Use script.when()
60-
assert not self._script.inside
61-
return self._when
57+
def remove_sound(self, filename):
58+
if filename in self.sounds:
59+
self.sounds[filename][0].stop()
60+
del self.sounds[filename]
61+
return True
62+
return False
63+
64+
def ensure_sound(self, filename, callback=None, *args):
65+
"""
66+
Ensure a sound is playing. If it isn't, play it.
67+
"""
68+
if filename in self.sounds:
69+
return None, None, None
70+
return self.play_sound(filename, callback, *args)
71+
72+
def play_sound(self, filename, callback=None, *args):
73+
"""
74+
Plays the sound with the given filename (relative to SOUNDS_DIR).
75+
Returns sound, channel, and callback slot.
76+
"""
77+
78+
if filename in self.sounds:
79+
self.sounds[filename][0].stop()
80+
del self.sounds[filename]
81+
82+
filename = path.join(SOUNDS_DIR, filename)
83+
sound = self.app.load(filename, lambda: pygame.mixer.Sound(filename))
84+
if not sound:
85+
return None, None, None
86+
channel = pygame.mixer.find_channel()
87+
if not channel:
88+
return None, None, None
89+
channel.set_volume(SOUND_VOLUME)
90+
if callback:
91+
slot = self.when.once(self.sounds[0].get_length(), callback)
92+
self.slotlist += slot
93+
else:
94+
slot = None
95+
self.sounds[filename] = (sound, channel, slot)
96+
channel.play(sound, *args)
97+
self.slotlist += self.when.once(
98+
sound.get_length(), lambda: self.remove_sound(sound)
99+
)
100+
return sound, channel, slot
62101

63102
@property
64103
def script(self):
@@ -76,6 +115,17 @@ def script(self, fn):
76115
def music(self):
77116
return self._music
78117

118+
@property
119+
def ground_color(self):
120+
return self.color(self.app.state.ground.color)
121+
122+
@ground_color.setter
123+
def ground_color(self, color):
124+
c = self.color(color)
125+
self.app.state.ground.color = pygame.Color(
126+
int(c[0] * 255), int(c[1] * 255), int(c[2] * 255)
127+
)
128+
79129
@music.setter
80130
def music(self, filename):
81131
self._music = filename
@@ -88,15 +138,18 @@ def color(self, c):
88138
Given a color string, a pygame color, or vec3,
89139
return that as a normalized vec4 color
90140
"""
91-
# print(c)
92141
if isinstance(c, str):
93142
c = vec4(*pygame.Color(c)) / 255.0
143+
elif isinstance(c, tuple):
144+
c = vec4(*c, 0) / 255.0
94145
elif isinstance(c, pygame.Color):
95-
c = vec4(*c) / 255.0
146+
c = vec4(*c, 0) / 255.0
96147
elif isinstance(c, vec3):
97148
c = vec4(*c, 0)
98149
elif isinstance(c, (float, int)):
99150
c = vec4(c, c, c, 0)
151+
elif c is None:
152+
c = vec4(0)
100153
return c
101154

102155
def mix(self, a, b, t):
@@ -170,11 +223,14 @@ def sky_color(self):
170223

171224
@sky_color.setter
172225
def sky_color(self, c):
173-
self._sky_color = self.color(c)
226+
self._sky_color = self.color(c) if c else None
174227

175228
# for scripts to call when.fade(1, set_sky_color)
176229
def set_sky_color(self, c):
177-
self._sky_color = self.color(c)
230+
self._sky_color = self.color(c) if c else None
231+
232+
def set_ground_color(self, c):
233+
self.ground_color = self.color(c) if c else None
178234

179235
def remove(self, entity):
180236
super().disconnect(entity)

game/scripts/level.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ def pause(self, duration):
5151

5252
def __call__(self):
5353
self.scene.sky_color = self.sky
54-
self.cloudy()
54+
self.scene.ground_color = self.ground
55+
self.scene.music = self.music
5556

5657
if self.name:
5758
terminal = self.app.state.terminal
58-
typ = pygame.mixer.Sound("data/sounds/type.wav")
5959

6060
left = ivec2((terminal.size.x - len(self.name)) / 2, 5)
6161
for i, letter in enumerate(self.name):
6262
terminal.write(letter, left + (i, 0), "white")
63-
typ.play()
63+
self.scene.play_sound("type.wav")
6464
yield self.pause(0.1)
6565

6666
terminal.clear(left[1])

game/scripts/level1.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
# Yield when you want to wait until the next event.
77
# This is a generator. Using a busy loop will halt the game.
88
from game.scripts.level import Level
9+
from game.constants import GREEN
910

1011

1112
class Level1(Level):
1213
name = "Level 1"
1314
sky = "#59ABE3"
15+
ground = GREEN
16+
music = "butterfly.mp3"
1417

1518
def __call__(self):
19+
1620
yield from super().__call__()
1721

1822
for _ in range(10):

game/states/game.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __init__(self, app, state=None):
2929

3030
self.app.inputs = self.build_inputs()
3131
self.camera = self.scene.add(Camera(app, self.scene, self.app.size))
32-
self.scene.add(Ground(app, self.scene, GROUND_HEIGHT))
32+
self.ground = self.scene.add(Ground(app, self.scene, GROUND_HEIGHT))
3333
self.player = self.scene.add(Player(app, self.scene))
3434
# self.msg = self.scene.add(Message(self.app, self.scene, "HELLO"))
3535

game/states/intro.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ def __call__(self, script):
4545
scene = self.scene
4646
color = self.scene.color
4747
terminal = self.terminal
48-
keys = script.keys
4948

5049
when.fade(3, scene.__class__.sky_color.setter, (vec4(0), vec4(1)))
5150
a = when.fade(
@@ -57,13 +56,11 @@ def __call__(self, script):
5756
)
5857

5958
# scene.sky_color = "black"
60-
typ = pygame.mixer.Sound("data/sounds/type.wav")
61-
6259
msg = "Welcome to Butterfly Destroyers!"
6360
for i in range(len(msg)):
6461
terminal.write(msg[i], (i, 0), "red")
65-
typ.play()
66-
yield script.sleep(0.1)
62+
scene.ensure_sound("type.wav")
63+
yield script.sleep(0.01 if script.keys else 0.05)
6764

6865
msg = [
6966
"In the year, 20XX, the butterfly",
@@ -78,8 +75,8 @@ def __call__(self, script):
7875
for y, line in enumerate(msg):
7976
for x, m in enumerate(line):
8077
terminal.write(m, (x, y * 2 + 3), "white")
81-
typ.play()
82-
yield script.sleep(0.05)
78+
scene.ensure_sound("type.wav")
79+
yield script.sleep(0.01 if script.keys else 0.05)
8380

8481
t = 0
8582
while True:
@@ -93,15 +90,7 @@ def __call__(self, script):
9390
# terminal.offset((x,20), (0, math.sin(t*math.tau*300)*4 - 2))
9491

9592
yield script.sleep(0.2)
96-
if len(keys()):
97-
break
98-
99-
# t += script.dt
100-
101-
terminal.clear(20)
102-
103-
yield script.sleep(0.2)
104-
if len(keys()):
93+
if script.keys_down:
10594
break
10695

10796
self.app.state = "game"

tests/test_when.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def test_once():
4747
slot = s.once(2, lambda: c.increment())
4848
s.update(1)
4949
assert c.x == 0
50-
s.update(1)
50+
s.update(0.5)
51+
assert c.x == 0
52+
s.update(0.5)
5153
assert c.x == 1
5254
s.update(10)
5355
assert c.x == 1

0 commit comments

Comments
 (0)