source: nagslang/game_object.py@ 335:78b805549b4e

Last change on this file since 335:78b805549b4e was 335:78b805549b4e, checked in by Jeremy Thurgood <firxen@…>, 9 years ago

More interesting claw attack.

File size: 14.1 KB
RevLine 
[81]1import pymunk
[93]2import pymunk.pygame_util
[81]3
[263]4import math
5
[281]6from nagslang import environment
[201]7from nagslang import puzzle
[207]8from nagslang import render
[296]9from nagslang.mutators import FLIP_H
[107]10from nagslang.constants import (
[318]11 COLLISION_TYPE_DOOR, COLLISION_TYPE_FURNITURE, COLLISION_TYPE_PROJECTILE,
12 COLLISION_TYPE_SWITCH, COLLISION_TYPE_WEREWOLF_ATTACK,
13 SWITCH_PUSHERS, ZORDER_FLOOR, ZORDER_LOW)
[155]14from nagslang.resources import resources
[180]15from nagslang.events import DoorEvent
[81]16
[82]17
[235]18def get_editable_game_objects():
19 classes = []
20 for cls_name, cls in globals().iteritems():
21 if isinstance(cls, type) and hasattr(cls, 'requires'):
22 classes.append((cls_name, cls))
23 return classes
24
25
[59]26class Physicser(object):
[93]27 def __init__(self, space):
[123]28 self._space = space
29
30 def get_space(self):
31 return self._space
32
[276]33 def set_space(self, new_space):
34 self._space = new_space
35
[123]36 def set_game_object(self, game_object):
37 self.game_object = game_object
38
39 def get_shape(self):
40 raise NotImplementedError()
[93]41
42 def add_to_space(self):
[215]43 shape = self.get_shape()
44 self.get_space().add(shape)
45 if not shape.body.is_static:
46 self.get_space().add(shape.body)
[59]47
[93]48 def remove_from_space(self):
[215]49 shape = self.get_shape()
50 self.get_space().remove(shape)
51 if not shape.body.is_static:
52 self.get_space().remove(shape.body)
[59]53
[93]54 def get_render_position(self, surface):
[215]55 pos = self.get_shape().body.position
56 return pymunk.pygame_util.to_pygame(pos, surface)
[63]57
[93]58 def get_angle(self):
[215]59 return self.get_shape().body.angle
60
[217]61 def get_velocity(self):
62 return self.get_shape().body.velocity
63
[216]64 def _get_position(self):
[215]65 return self.get_shape().body.position
66
[216]67 def _set_position(self, position):
[215]68 self.get_shape().body.position = position
[93]69
[216]70 position = property(_get_position, _set_position)
71
[93]72 def apply_impulse(self, j, r=(0, 0)):
[215]73 return self.get_shape().body.apply_impulse(j, r)
[59]74
75
76class SingleShapePhysicser(Physicser):
[93]77 def __init__(self, space, shape):
78 super(SingleShapePhysicser, self).__init__(space)
[59]79 self._shape = shape
[186]80 shape.physicser = self
[59]81
[123]82 def get_shape(self):
83 return self._shape
84
[59]85
[133]86def damping_velocity_func(body, gravity, damping, dt):
87 """Apply custom damping to this body's velocity.
88 """
89 damping = getattr(body, 'damping', damping)
90 return pymunk.Body.update_velocity(body, gravity, damping, dt)
91
92
93def make_body(mass, moment, position, damping=None):
94 body = pymunk.Body(mass, moment)
[145]95 body.position = tuple(position)
[133]96 if damping is not None:
97 body.damping = damping
98 body.velocity_func = damping_velocity_func
99 return body
100
101
[59]102class GameObject(object):
103 """A representation of a thing in the game world.
104
105 This has a rendery thing, physicsy things and maybe some other things.
106 """
107
[162]108 zorder = ZORDER_LOW
[218]109 is_moving = False # `True` if a movement animation should play.
[162]110
[281]111 def __init__(self, physicser, renderer, puzzler=None, overlay=None,
112 interactible=None):
[333]113 self.lifetime = 0
[93]114 self.physicser = physicser
[123]115 physicser.set_game_object(self)
[93]116 self.physicser.add_to_space()
[59]117 self.renderer = renderer
[123]118 renderer.set_game_object(self)
[81]119 self.puzzler = puzzler
[123]120 if puzzler is not None:
121 puzzler.set_game_object(self)
[191]122 self.overlay = overlay
123 if overlay is not None:
124 self.overlay.set_game_object(self)
[281]125 self.interactible = interactible
126 if interactible is not None:
127 self.interactible.set_game_object(self)
[293]128 self.remove = False # If true, will be removed from drawables
[59]129
[123]130 def get_space(self):
131 return self.physicser.get_space()
132
133 def get_shape(self):
134 return self.physicser.get_shape()
135
[93]136 def get_render_position(self, surface):
137 return self.physicser.get_render_position(surface)
138
139 def get_render_angle(self):
140 return self.physicser.get_angle()
[59]141
[229]142 def get_facing_direction(self):
143 """Used by rendererd that care what direction an object is facing.
144 """
145 return None
146
[59]147 def render(self, surface):
[123]148 return self.renderer.render(surface)
[81]149
[333]150 def update(self, dt):
151 self.lifetime += dt
152 self.renderer.update(dt)
[143]153
[302]154 def hit(self, weapon):
155 '''Was hit with a weapon (such as a bullet)'''
156 pass
157
[256]158 def collide_with_protagonist(self, protagonist):
[186]159 """Called as a `pre_solve` collision callback with the protagonist.
160
161 You can return `False` to ignore the collision, anything else
162 (including `None`) to process the collision as normal.
163 """
[192]164 return True
[186]165
[319]166 def collide_with_furniture(self, furniture):
167 return True
168
[333]169 def collide_with_claw_attack(self, claw_attack):
170 return True
171
[235]172 @classmethod
173 def requires(cls):
174 """Hints for the level editor"""
175 return [("name", "string")]
176
[81]177
178class FloorSwitch(GameObject):
[162]179 zorder = ZORDER_FLOOR
180
[93]181 def __init__(self, space, position):
[145]182 body = make_body(None, None, position)
[81]183 self.shape = pymunk.Circle(body, 30)
184 self.shape.collision_type = COLLISION_TYPE_SWITCH
185 self.shape.sensor = True
186 super(FloorSwitch, self).__init__(
[93]187 SingleShapePhysicser(space, self.shape),
[207]188 render.ImageStateRenderer({
[162]189 True: resources.get_image('objects', 'sensor_on.png'),
190 False: resources.get_image('objects', 'sensor_off.png'),
191 }),
[201]192 puzzle.CollidePuzzler(*SWITCH_PUSHERS),
[81]193 )
194
[235]195 @classmethod
196 def requires(cls):
197 return [("name", "string"), ("position", "coordinates")]
198
[106]199
[191]200class Note(GameObject):
201 zorder = ZORDER_FLOOR
202
203 def __init__(self, space, position, message):
204 body = make_body(None, None, position)
205 self.shape = pymunk.Circle(body, 30)
206 self.shape.sensor = True
207 super(Note, self).__init__(
208 SingleShapePhysicser(space, self.shape),
[207]209 render.ImageRenderer(resources.get_image('objects', 'note.png')),
[201]210 puzzle.CollidePuzzler(),
[222]211 render.TextOverlay(message),
[191]212 )
213
[235]214 @classmethod
215 def requires(cls):
216 return [("name", "string"), ("position", "coordinates"),
217 ("message", "text")]
218
[191]219
[106]220class FloorLight(GameObject):
[162]221 zorder = ZORDER_FLOOR
222
[106]223 def __init__(self, space, position, state_source):
[145]224 body = make_body(None, None, position)
[106]225 self.shape = pymunk.Circle(body, 10)
226 self.shape.collision_type = COLLISION_TYPE_SWITCH
227 self.shape.sensor = True
228 super(FloorLight, self).__init__(
229 SingleShapePhysicser(space, self.shape),
[207]230 render.ImageStateRenderer({
[162]231 True: resources.get_image('objects', 'light_on.png'),
232 False: resources.get_image('objects', 'light_off.png'),
233 }),
[201]234 puzzle.StateProxyPuzzler(state_source),
[106]235 )
[133]236
[235]237 @classmethod
238 def requires(cls):
239 return [("name", "string"), ("position", "coordinates"),
240 ("state_source", "puzzler")]
241
[133]242
243class Box(GameObject):
244 def __init__(self, space, position):
245 body = make_body(10, 10000, position, damping=0.5)
246 self.shape = pymunk.Poly(
247 body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
[208]248 self.shape.friction = 0.5
[318]249 self.shape.collision_type = COLLISION_TYPE_FURNITURE
[133]250 super(Box, self).__init__(
251 SingleShapePhysicser(space, self.shape),
[207]252 render.ImageRenderer(resources.get_image('objects', 'crate.png')),
[133]253 )
[176]254
[235]255 @classmethod
256 def requires(cls):
257 return [("name", "string"), ("position", "coordinates"),
258 ("state_source", "puzzler")]
259
[176]260
261class Door(GameObject):
262 zorder = ZORDER_FLOOR
263
[263]264 def __init__(self, space, position, destination, dest_pos, angle,
265 key_state=None):
[176]266 body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
[281]267 self.shape = pymunk.Circle(body, 30)
[176]268 self.shape.collision_type = COLLISION_TYPE_DOOR
[264]269 self.shape.body.angle = float(angle) / 180 * math.pi
[176]270 self.shape.sensor = True
271 self.destination = destination
272 self.dest_pos = tuple(dest_pos)
[281]273 puzzler = None
274 action = environment.Action(self._post_door_event)
275 if key_state is not None:
[201]276 puzzler = puzzle.StateProxyPuzzler(key_state)
[281]277 action.condition = environment.PuzzleStateCondition(puzzler)
[176]278 super(Door, self).__init__(
279 SingleShapePhysicser(space, self.shape),
[207]280 render.ImageRenderer(resources.get_image('objects', 'door.png')),
[186]281 puzzler,
[281]282 interactible=environment.Interactible(action),
[176]283 )
284
[281]285 def _post_door_event(self, protagonist):
286 DoorEvent.post(self.destination, self.dest_pos)
[224]287
[235]288 @classmethod
289 def requires(cls):
290 return [("name", "string"), ("position", "coordinates"),
291 ("destination", "level name"), ("dest_pos", "coordinate"),
[263]292 ("angle", "degrees"),
293 ("key_state", "puzzler (optional)")]
[235]294
[224]295
296class Bulkhead(GameObject):
297 zorder = ZORDER_FLOOR
298
299 def __init__(self, space, end1, end2, key_state=None):
300 body = make_body(None, None, (0, 0))
301 self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 3)
302 self.shape.collision_type = COLLISION_TYPE_DOOR
303 if key_state is None:
304 puzzler = puzzle.YesPuzzler()
305 else:
306 puzzler = puzzle.StateProxyPuzzler(key_state)
307 super(Bulkhead, self).__init__(
308 SingleShapePhysicser(space, self.shape),
309 render.ShapeStateRenderer(),
310 puzzler,
311 )
312
[256]313 def collide_with_protagonist(self, protagonist):
[224]314 if self.puzzler.get_state():
315 # Reject the collision, we can walk through.
316 return False
317 return True
[235]318
[319]319 collide_with_furniture = collide_with_protagonist
320
[235]321 @classmethod
322 def requires(cls):
323 return [("name", "string"), ("end1", "coordinates"),
324 ("end2", "coordinates"), ("key_state", "puzzler")]
[261]325
326
[282]327class ToggleSwitch(GameObject):
328 zorder = ZORDER_LOW
329
330 def __init__(self, space, position):
331 body = make_body(None, None, position)
332 self.shape = pymunk.Circle(body, 20)
333 self.shape.sensor = True
334 self.toggle_on = False
335 super(ToggleSwitch, self).__init__(
336 SingleShapePhysicser(space, self.shape),
[296]337 render.ImageStateRenderer({
338 True: resources.get_image('objects', 'lever.png'),
339 False: resources.get_image(
340 'objects', 'lever.png', transforms=(FLIP_H,)),
341 }),
[282]342 puzzle.ParentAttrPuzzler('toggle_on'),
343 interactible=environment.Interactible(
344 environment.Action(self._toggle)),
345 )
346
347 def _toggle(self, protagonist):
348 self.toggle_on = not self.toggle_on
349
350 @classmethod
351 def requires(cls):
352 return [("name", "string"), ("position", "coordinates")]
353
354
[261]355class Bullet(GameObject):
[305]356 def __init__(self, space, position, impulse, damage,
357 source_collision_type):
[261]358 body = make_body(1, pymunk.inf, position)
[293]359 self.last_position = position
[286]360 self.shape = pymunk.Circle(body, 2)
[293]361 self.shape.sensor = True
[261]362 self.shape.collision_type = COLLISION_TYPE_PROJECTILE
[305]363 self.damage = damage
[293]364 self.source_collision_type = source_collision_type
[261]365 super(Bullet, self).__init__(
366 SingleShapePhysicser(space, self.shape),
367 render.ImageRenderer(resources.get_image('objects', 'bullet.png')),
368 )
369 self.physicser.apply_impulse(impulse)
[286]370
[333]371 def update(self, dt):
372 super(Bullet, self).update(dt)
[297]373 position = (self.physicser.position.x, self.physicser.position.y)
[293]374 r = self.get_space().segment_query(self.last_position, position)
375 self.last_position = position
376 for collision in r:
[302]377 shape = collision.shape
378 if (shape.collision_type == self.source_collision_type
379 or shape == self.physicser.get_shape()
380 or shape.sensor):
[293]381 continue
[302]382 if hasattr(shape, 'physicser'):
383 shape.physicser.game_object.hit(self)
[293]384 self.physicser.remove_from_space()
385 self.remove = True
386 break
387
[286]388
389class CollectibleGameObject(GameObject):
390 zorder = ZORDER_LOW
391
392 def __init__(self, space, name, shape, renderer):
393 self._name = name
394 shape.sensor = True
395 super(CollectibleGameObject, self).__init__(
396 SingleShapePhysicser(space, shape),
397 renderer,
398 interactible=environment.Interactible(
399 environment.Action(
400 self._collect, environment.HumanFormCondition())),
401 )
402
403 def _collect(self, protagonist):
404 protagonist.inventory[self._name] = self
405 # TODO: Make this less hacky.
406 self.physicser.remove_from_space()
407 self.renderer = render.NullRenderer()
408
409
410class Gun(CollectibleGameObject):
411 def __init__(self, space, position):
412 body = make_body(None, None, position)
413 self.shape = pymunk.Circle(body, 20)
414 super(Gun, self).__init__(
415 space, 'gun', self.shape,
[332]416 render.ImageRenderer(resources.get_image('objects', 'gun.png')),
[286]417 )
[312]418
419
420class ClawAttack(GameObject):
[333]421 def __init__(self, space, position, vector, damage):
[335]422 body = make_body(1, pymunk.inf, position)
[333]423 body.angle = vector.angle
[312]424 self.shape = pymunk.Circle(body, 30)
425 self.shape.sensor = True
426 self.shape.collision_type = COLLISION_TYPE_WEREWOLF_ATTACK
[333]427 self.damage = damage
[312]428 super(ClawAttack, self).__init__(
429 SingleShapePhysicser(space, self.shape),
[333]430 render.ImageRenderer(resources.get_image(
431 'objects', 'werewolf_SW_claw_attack.png',
432 transforms=(FLIP_H,))),
[312]433 )
[335]434 self.physicser.apply_impulse(vector)
[312]435
[333]436 def update(self, dt):
437 super(ClawAttack, self).update(dt)
[335]438 if self.lifetime > 0.1:
[312]439 self.physicser.remove_from_space()
440 self.remove = True
Note: See TracBrowser for help on using the repository browser.