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