source: nagslang/game_object.py @ 235:831e4f6b3d18

Last change on this file since 235:831e4f6b3d18 was 235:831e4f6b3d18, checked in by Neil Muller <drnlmuller@…>, 7 years ago

Add hints for the level editor

File size: 9.0 KB
Line 
1import pymunk
2import pymunk.pygame_util
3
4from nagslang import puzzle
5from nagslang import render
6from nagslang.constants import (
7    SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW,
8    ZORDER_FLOOR, COLLISION_TYPE_DOOR)
9from nagslang.resources import resources
10from nagslang.events import DoorEvent
11
12
13def get_editable_game_objects():
14    classes = []
15    for cls_name, cls in globals().iteritems():
16        if isinstance(cls, type) and hasattr(cls, 'requires'):
17            classes.append((cls_name, cls))
18    return classes
19
20
21class Physicser(object):
22    def __init__(self, space):
23        self._space = space
24
25    def get_space(self):
26        return self._space
27
28    def set_game_object(self, game_object):
29        self.game_object = game_object
30
31    def get_shape(self):
32        raise NotImplementedError()
33
34    def add_to_space(self):
35        shape = self.get_shape()
36        self.get_space().add(shape)
37        if not shape.body.is_static:
38            self.get_space().add(shape.body)
39
40    def remove_from_space(self):
41        shape = self.get_shape()
42        self.get_space().remove(shape)
43        if not shape.body.is_static:
44            self.get_space().remove(shape.body)
45
46    def get_render_position(self, surface):
47        pos = self.get_shape().body.position
48        return pymunk.pygame_util.to_pygame(pos, surface)
49
50    def get_angle(self):
51        return self.get_shape().body.angle
52
53    def get_velocity(self):
54        return self.get_shape().body.velocity
55
56    def _get_position(self):
57        return self.get_shape().body.position
58
59    def _set_position(self, position):
60        self.get_shape().body.position = position
61
62    position = property(_get_position, _set_position)
63
64    def apply_impulse(self, j, r=(0, 0)):
65        return self.get_shape().body.apply_impulse(j, r)
66
67
68class SingleShapePhysicser(Physicser):
69    def __init__(self, space, shape):
70        super(SingleShapePhysicser, self).__init__(space)
71        self._shape = shape
72        shape.physicser = self
73
74    def get_shape(self):
75        return self._shape
76
77
78def damping_velocity_func(body, gravity, damping, dt):
79    """Apply custom damping to this body's velocity.
80    """
81    damping = getattr(body, 'damping', damping)
82    return pymunk.Body.update_velocity(body, gravity, damping, dt)
83
84
85def make_body(mass, moment, position, damping=None):
86    body = pymunk.Body(mass, moment)
87    body.position = tuple(position)
88    if damping is not None:
89        body.damping = damping
90        body.velocity_func = damping_velocity_func
91    return body
92
93
94class GameObject(object):
95    """A representation of a thing in the game world.
96
97    This has a rendery thing, physicsy things and maybe some other things.
98    """
99
100    zorder = ZORDER_LOW
101    is_moving = False  # `True` if a movement animation should play.
102
103    def __init__(self, physicser, renderer, puzzler=None, overlay=None):
104        self.physicser = physicser
105        physicser.set_game_object(self)
106        self.physicser.add_to_space()
107        self.renderer = renderer
108        renderer.set_game_object(self)
109        self.puzzler = puzzler
110        if puzzler is not None:
111            puzzler.set_game_object(self)
112        self.overlay = overlay
113        if overlay is not None:
114            self.overlay.set_game_object(self)
115
116    def get_space(self):
117        return self.physicser.get_space()
118
119    def get_shape(self):
120        return self.physicser.get_shape()
121
122    def get_render_position(self, surface):
123        return self.physicser.get_render_position(surface)
124
125    def get_render_angle(self):
126        return self.physicser.get_angle()
127
128    def get_facing_direction(self):
129        """Used by rendererd that care what direction an object is facing.
130        """
131        return None
132
133    def render(self, surface):
134        return self.renderer.render(surface)
135
136    def animate(self):
137        self.renderer.animate()
138
139    def collide_with_protagonist(self):
140        """Called as a `pre_solve` collision callback with the protagonist.
141
142        You can return `False` to ignore the collision, anything else
143        (including `None`) to process the collision as normal.
144        """
145        return True
146
147    @classmethod
148    def requires(cls):
149        """Hints for the level editor"""
150        return [("name", "string")]
151
152
153class FloorSwitch(GameObject):
154    zorder = ZORDER_FLOOR
155
156    def __init__(self, space, position):
157        body = make_body(None, None, position)
158        self.shape = pymunk.Circle(body, 30)
159        self.shape.collision_type = COLLISION_TYPE_SWITCH
160        self.shape.sensor = True
161        super(FloorSwitch, self).__init__(
162            SingleShapePhysicser(space, self.shape),
163            render.ImageStateRenderer({
164                True: resources.get_image('objects', 'sensor_on.png'),
165                False: resources.get_image('objects', 'sensor_off.png'),
166            }),
167            puzzle.CollidePuzzler(*SWITCH_PUSHERS),
168        )
169
170    @classmethod
171    def requires(cls):
172        return [("name", "string"), ("position", "coordinates")]
173
174
175class Note(GameObject):
176    zorder = ZORDER_FLOOR
177
178    def __init__(self, space, position, message):
179        body = make_body(None, None, position)
180        self.shape = pymunk.Circle(body, 30)
181        self.shape.sensor = True
182        super(Note, self).__init__(
183            SingleShapePhysicser(space, self.shape),
184            render.ImageRenderer(resources.get_image('objects', 'note.png')),
185            puzzle.CollidePuzzler(),
186            render.TextOverlay(message),
187        )
188
189    @classmethod
190    def requires(cls):
191        return [("name", "string"), ("position", "coordinates"),
192                ("message", "text")]
193
194
195class FloorLight(GameObject):
196    zorder = ZORDER_FLOOR
197
198    def __init__(self, space, position, state_source):
199        body = make_body(None, None, position)
200        self.shape = pymunk.Circle(body, 10)
201        self.shape.collision_type = COLLISION_TYPE_SWITCH
202        self.shape.sensor = True
203        super(FloorLight, self).__init__(
204            SingleShapePhysicser(space, self.shape),
205            render.ImageStateRenderer({
206                True: resources.get_image('objects', 'light_on.png'),
207                False: resources.get_image('objects', 'light_off.png'),
208            }),
209            puzzle.StateProxyPuzzler(state_source),
210        )
211
212    @classmethod
213    def requires(cls):
214        return [("name", "string"), ("position", "coordinates"),
215                ("state_source", "puzzler")]
216
217
218class Box(GameObject):
219    def __init__(self, space, position):
220        body = make_body(10, 10000, position, damping=0.5)
221        self.shape = pymunk.Poly(
222            body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
223        self.shape.friction = 0.5
224        self.shape.collision_type = COLLISION_TYPE_BOX
225        super(Box, self).__init__(
226            SingleShapePhysicser(space, self.shape),
227            render.ImageRenderer(resources.get_image('objects', 'crate.png')),
228        )
229
230    @classmethod
231    def requires(cls):
232        return [("name", "string"), ("position", "coordinates"),
233                ("state_source", "puzzler")]
234
235
236class Door(GameObject):
237    zorder = ZORDER_FLOOR
238
239    def __init__(self, space, position, destination, dest_pos, key_state=None):
240        body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
241        self.shape = pymunk.Poly(
242            body, [(-32, -32), (32, -32), (32, 32), (-32, 32)])
243        self.shape.collision_type = COLLISION_TYPE_DOOR
244        self.shape.sensor = True
245        self.destination = destination
246        self.dest_pos = tuple(dest_pos)
247        if key_state is None:
248            puzzler = puzzle.YesPuzzler()
249        else:
250            puzzler = puzzle.StateProxyPuzzler(key_state)
251        super(Door, self).__init__(
252            SingleShapePhysicser(space, self.shape),
253            render.ImageRenderer(resources.get_image('objects', 'door.png')),
254            puzzler,
255        )
256
257    def collide_with_protagonist(self):
258        if self.puzzler.get_state():
259            DoorEvent.post(self.destination, self.dest_pos)
260
261    @classmethod
262    def requires(cls):
263        return [("name", "string"), ("position", "coordinates"),
264                ("destination", "level name"), ("dest_pos", "coordinate"),
265                ("key_state", "puzzler")]
266
267
268class Bulkhead(GameObject):
269    zorder = ZORDER_FLOOR
270
271    def __init__(self, space, end1, end2, key_state=None):
272        body = make_body(None, None, (0, 0))
273        self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 3)
274        self.shape.collision_type = COLLISION_TYPE_DOOR
275        if key_state is None:
276            puzzler = puzzle.YesPuzzler()
277        else:
278            puzzler = puzzle.StateProxyPuzzler(key_state)
279        super(Bulkhead, self).__init__(
280            SingleShapePhysicser(space, self.shape),
281            render.ShapeStateRenderer(),
282            puzzler,
283        )
284
285    def collide_with_protagonist(self):
286        if self.puzzler.get_state():
287            # Reject the collision, we can walk through.
288            return False
289        return True
290
291    @classmethod
292    def requires(cls):
293        return [("name", "string"), ("end1", "coordinates"),
294                ("end2", "coordinates"), ("key_state", "puzzler")]
Note: See TracBrowser for help on using the repository browser.