The first steps towards enemy generators
[koperkapel.git] / koperkapel / scenes / base.py
1 """ Scene utilities. """
2
3 import functools
4
5
6 def apply_events(f):
7     """ Decorator that applies events to an engine. """
8     @functools.wraps(f)
9     def wrap(self, *args, **kw):
10         events = f(self, *args, **kw)
11         self._apply_events(events)
12     return wrap
13
14
15 class Engine:
16     """ A holder for game state & scene management.
17         """
18
19     def __init__(self, app, scene, world):
20         self._app = app
21         self._scene = scene
22         self._world = world
23         self._update_vehicle = False
24
25     def _apply_events(self, events):
26         if not events:
27             return
28         for ev in events:
29             ev.apply(self)
30
31     def change_scene(self, scene):
32         self._apply_events(self._scene.exit(self._world.proxy()))
33         self._scene = scene
34         self._apply_events(self._scene.enter(self._world.proxy()))
35
36     def change_world(self, *args, **kw):
37         self._world.apply_event(*args, **kw)
38
39     def quit_game(self):
40         from pgzero.game import exit
41         exit()
42
43     def move_screen(self, offset):
44         self._scene.move_screen(offset)
45
46     @apply_events
47     def update(self, dt):
48         return self._scene.update(self._world.proxy(), self, dt)
49
50     def draw(self):
51         self._scene.draw(self._app.screen)
52
53     @apply_events
54     def on_mouse_down(self, pos, button):
55         return self._scene.on_mouse_down(pos, button)
56
57     @apply_events
58     def on_mouse_up(self, pos, button):
59         return self._scene.on_mouse_up(pos, button)
60
61     @apply_events
62     def on_key_down(self, key, mod, unicode):
63         return self._scene.on_key_down(key, mod, unicode)
64
65     @apply_events
66     def on_key_up(self, key, mod):
67         return self._scene.on_key_up(key, mod)
68
69     @apply_events
70     def on_music_end(self):
71         return self._scene.on_music_end()
72
73
74 class Event:
75     """ Base class for events. """
76
77     ENGINE_METHOD = "unknown_event"
78
79     def __init__(self, *args, **kw):
80         self._args = args
81         self._kw = kw
82
83     def apply(self, engine):
84         getattr(engine, self.ENGINE_METHOD)(*self._args, **self._kw)
85
86
87 class ChangeSceneEvent(Event):
88     """ Change to a new scene. """
89
90     ENGINE_METHOD = "change_scene"
91
92
93 class WorldEvent(Event):
94     """ Be a hero. Change the world. """
95
96     ENGINE_METHOD = "change_world"
97
98
99 class QuitEvent(Event):
100     """ Quit the game. """
101
102     ENGINE_METHOD = "quit_game"
103
104
105 class MoveViewportEvent(Event):
106     """ Change to a new scene. """
107
108     ENGINE_METHOD = "move_screen"
109
110
111 class Layer:
112     """ A single layer of actors. """
113
114     def __init__(self, name):
115         self.name = name
116         self.actors = []
117
118     def __iter__(self):
119         return iter(self.actors)
120
121     def __getitem__(self, i):
122         return self.actors[i]
123
124     def __len__(self):
125         return len(self.actors)
126
127     def add(self, actor):
128         self.actors.append(actor)
129         return actor
130
131     def clear(self):
132         self.actors.clear()
133
134     def remove(self, actor):
135         self.actors.remove(actor)
136         return actor
137
138
139 class Actors:
140     """ Layers of actors.
141
142     Actors may be rendered in different layers. Layers with lower levels
143     are rendered lower than layers with higher ones.
144     """
145
146     def __init__(self):
147         self._ordered_layers = []
148         self._layers = {}
149         self.add_layer("default", 0)
150
151     def __getattr__(self, name):
152         return self._layers[name]
153
154     def add_layer(self, name, level):
155         layer = self._layers[name] = Layer(name)
156         self._ordered_layers.append((level, name))
157         self._ordered_layers.sort()
158         return layer
159
160     def add(self, actor, layer="default"):
161         return self._layers[layer].add(actor)
162
163     def remove(self, actor, layer="default"):
164         return self._layers[layer].remove(actor)
165
166     def draw(self, screen):
167         for lvl, name in self._ordered_layers:
168             for actor in self._layers[name]:
169                 # actor.draw doesn't allow blitting to anything other than
170                 # the game scene
171                 screen.blit(actor._surf, actor.topleft)
172
173
174 def defer_to_update(f):
175     """ Defers a function until the next update run. """
176     @functools.wraps(f)
177     def wrapper(self, *args, **kw):
178         self._deferred_updates.append((f, args, kw))
179     return wrapper
180
181
182 class Scene:
183     """ Base class for scenes. """
184
185     def __init__(self):
186         self.actors = Actors()
187         self.viewport = (0, 0)
188         self._deferred_updates = []
189
190     def move_screen(self, offset):
191         self.viewport = (self.viewport[0] + offset[0],
192                          self.viewport[1] + offset[1])
193
194     def calc_offset(self, x, y):
195         """ Return a position offset by the viewport. """
196         return x - self.viewport[0], y - self.viewport[1]
197
198     def enter(self, world):
199         pass
200
201     def exit(self, world):
202         pass
203
204     def update(self, world, engine, dt):
205         deferred_updates, self._deferred_updates = self._deferred_updates, []
206         for f, args, kw in deferred_updates:
207             f(self, world, *args, **kw)
208
209     def draw(self, screen):
210         screen.clear()
211         self.actors.draw(screen)
212
213     def on_mouse_down(self, pos, button):
214         pass
215
216     def on_mouse_up(self, pos, button):
217         pass
218
219     def on_key_down(self, key, mod, unicode):
220         pass
221
222     def on_key_up(self, key, mod):
223         pass
224
225     def on_music_end(self):
226         pass