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