Make layers iterable.
[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._viewport = (0, 0)
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._viewport = (self._viewport[0] + offset[0],
45                           self._viewport[1] + offset[1])
46
47     @apply_events
48     def update(self, dt):
49         return self._scene.update(self._world.proxy(), dt)
50
51     def draw(self):
52         self._scene.draw(self._app.screen, self._viewport)
53
54     @apply_events
55     def on_mouse_down(self, pos, button):
56         return self._scene.on_mouse_down(pos, button)
57
58     @apply_events
59     def on_mouse_up(self, pos, button):
60         return self._scene.on_mouse_up(pos, button)
61
62     @apply_events
63     def on_key_down(self, key, mod, unicode):
64         return self._scene.on_key_down(key, mod, unicode)
65
66     @apply_events
67     def on_key_up(self, key, mod):
68         return self._scene.on_key_up(key, mod)
69
70     @apply_events
71     def on_music_end(self):
72         return self._scene.on_music_end()
73
74
75 class Event:
76     """ Base class for events. """
77
78     ENGINE_METHOD = "unknown_event"
79
80     def __init__(self, *args, **kw):
81         self._args = args
82         self._kw = kw
83
84     def apply(self, engine):
85         getattr(engine, self.ENGINE_METHOD)(*self._args, **self._kw)
86
87
88 class ChangeSceneEvent(Event):
89     """ Change to a new scene. """
90
91     ENGINE_METHOD = "change_scene"
92
93
94 class WorldEvent(Event):
95     """ Be a hero. Change the world. """
96
97     ENGINE_METHOD = "change_world"
98
99
100 class QuitEvent(Event):
101     """ Quit the game. """
102
103     ENGINE_METHOD = "quit_game"
104
105
106 class MoveViewportEvent(Event):
107     """ Change to a new scene. """
108
109     ENGINE_METHOD = "move_screen"
110
111
112 class Layer:
113     """ A single layer of actors. """
114
115     def __init__(self, name):
116         self.name = name
117         self.actors = []
118
119     def __iter__(self):
120         return iter(self.actors)
121
122     def add(self, actor):
123         self.actors.append(actor)
124         return actor
125
126     def remove(self, actor):
127         self.actors.remove(actor)
128         return actor
129
130
131 class Actors:
132     """ Layers of actors.
133
134     Actors may be rendered in different layers. Layers with lower levels
135     are rendered lower than layers with higher ones.
136     """
137
138     def __init__(self):
139         self._ordered_layers = []
140         self._layers = {}
141         self.add_layer("default", 0)
142
143     def __getattr__(self, name):
144         return self._layers[name]
145
146     def add_layer(self, name, level):
147         layer = self._layers[name] = Layer(name)
148         self._ordered_layers.append((level, name))
149         self._ordered_layers.sort()
150         return layer
151
152     def add(self, actor, layer="default"):
153         return self._layers[layer].add(actor)
154
155     def remove(self, actor, layer="default"):
156         return self._layers[layer].remove(actor)
157
158     def draw(self, screen):
159         for lvl, name in self._ordered_layers:
160             for actor in self._layers[name]:
161                 actor.draw()  # TODO: allow an option screen to be passed in
162
163
164 class Scene:
165     """ Base class for scenes. """
166
167     def __init__(self):
168         self.actors = Actors()
169
170     def enter(self, world):
171         pass
172
173     def exit(self, world):
174         pass
175
176     def update(self, world, dt):
177         pass
178
179     def draw(self, screen, viewport):
180         screen.clear()
181         self.actors.draw(screen)
182
183     def on_mouse_down(self, pos, button):
184         pass
185
186     def on_mouse_up(self, pos, button):
187         pass
188
189     def on_key_down(self, key, mod, unicode):
190         pass
191
192     def on_key_up(self, key, mod):
193         pass
194
195     def on_music_end(self):
196         pass