Mercurial > nagslang
view nagslang/game_object.py @ 302:a0a471ad2ee8
Pass bullet hits through to the target
author | Stefano Rivera <stefano@rivera.za.net> |
---|---|
date | Thu, 05 Sep 2013 23:57:50 +0200 |
parents | b00ed05f7364 |
children | ce11e1cae0ed |
line wrap: on
line source
import pymunk import pymunk.pygame_util import math from nagslang import environment from nagslang import puzzle from nagslang import render from nagslang.mutators import FLIP_H from nagslang.constants import ( SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW, ZORDER_FLOOR, COLLISION_TYPE_DOOR, COLLISION_TYPE_PROJECTILE) from nagslang.resources import resources from nagslang.events import DoorEvent def get_editable_game_objects(): classes = [] for cls_name, cls in globals().iteritems(): if isinstance(cls, type) and hasattr(cls, 'requires'): classes.append((cls_name, cls)) return classes class Physicser(object): def __init__(self, space): self._space = space def get_space(self): return self._space def set_space(self, new_space): self._space = new_space def set_game_object(self, game_object): self.game_object = game_object def get_shape(self): raise NotImplementedError() def add_to_space(self): shape = self.get_shape() self.get_space().add(shape) if not shape.body.is_static: self.get_space().add(shape.body) def remove_from_space(self): shape = self.get_shape() self.get_space().remove(shape) if not shape.body.is_static: self.get_space().remove(shape.body) def get_render_position(self, surface): pos = self.get_shape().body.position return pymunk.pygame_util.to_pygame(pos, surface) def get_angle(self): return self.get_shape().body.angle def get_velocity(self): return self.get_shape().body.velocity def _get_position(self): return self.get_shape().body.position def _set_position(self, position): self.get_shape().body.position = position position = property(_get_position, _set_position) def apply_impulse(self, j, r=(0, 0)): return self.get_shape().body.apply_impulse(j, r) class SingleShapePhysicser(Physicser): def __init__(self, space, shape): super(SingleShapePhysicser, self).__init__(space) self._shape = shape shape.physicser = self def get_shape(self): return self._shape def damping_velocity_func(body, gravity, damping, dt): """Apply custom damping to this body's velocity. """ damping = getattr(body, 'damping', damping) return pymunk.Body.update_velocity(body, gravity, damping, dt) def make_body(mass, moment, position, damping=None): body = pymunk.Body(mass, moment) body.position = tuple(position) if damping is not None: body.damping = damping body.velocity_func = damping_velocity_func return body class GameObject(object): """A representation of a thing in the game world. This has a rendery thing, physicsy things and maybe some other things. """ zorder = ZORDER_LOW is_moving = False # `True` if a movement animation should play. def __init__(self, physicser, renderer, puzzler=None, overlay=None, interactible=None): self.physicser = physicser physicser.set_game_object(self) self.physicser.add_to_space() self.renderer = renderer renderer.set_game_object(self) self.puzzler = puzzler if puzzler is not None: puzzler.set_game_object(self) self.overlay = overlay if overlay is not None: self.overlay.set_game_object(self) self.interactible = interactible if interactible is not None: self.interactible.set_game_object(self) self.remove = False # If true, will be removed from drawables def get_space(self): return self.physicser.get_space() def get_shape(self): return self.physicser.get_shape() def get_render_position(self, surface): return self.physicser.get_render_position(surface) def get_render_angle(self): return self.physicser.get_angle() def get_facing_direction(self): """Used by rendererd that care what direction an object is facing. """ return None def render(self, surface): return self.renderer.render(surface) def animate(self): self.renderer.animate() def hit(self, weapon): '''Was hit with a weapon (such as a bullet)''' pass def collide_with_protagonist(self, protagonist): """Called as a `pre_solve` collision callback with the protagonist. You can return `False` to ignore the collision, anything else (including `None`) to process the collision as normal. """ return True @classmethod def requires(cls): """Hints for the level editor""" return [("name", "string")] class FloorSwitch(GameObject): zorder = ZORDER_FLOOR def __init__(self, space, position): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 30) self.shape.collision_type = COLLISION_TYPE_SWITCH self.shape.sensor = True super(FloorSwitch, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageStateRenderer({ True: resources.get_image('objects', 'sensor_on.png'), False: resources.get_image('objects', 'sensor_off.png'), }), puzzle.CollidePuzzler(*SWITCH_PUSHERS), ) @classmethod def requires(cls): return [("name", "string"), ("position", "coordinates")] class Note(GameObject): zorder = ZORDER_FLOOR def __init__(self, space, position, message): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 30) self.shape.sensor = True super(Note, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageRenderer(resources.get_image('objects', 'note.png')), puzzle.CollidePuzzler(), render.TextOverlay(message), ) @classmethod def requires(cls): return [("name", "string"), ("position", "coordinates"), ("message", "text")] class FloorLight(GameObject): zorder = ZORDER_FLOOR def __init__(self, space, position, state_source): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 10) self.shape.collision_type = COLLISION_TYPE_SWITCH self.shape.sensor = True super(FloorLight, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageStateRenderer({ True: resources.get_image('objects', 'light_on.png'), False: resources.get_image('objects', 'light_off.png'), }), puzzle.StateProxyPuzzler(state_source), ) @classmethod def requires(cls): return [("name", "string"), ("position", "coordinates"), ("state_source", "puzzler")] class Box(GameObject): def __init__(self, space, position): body = make_body(10, 10000, position, damping=0.5) self.shape = pymunk.Poly( body, [(-20, -20), (20, -20), (20, 20), (-20, 20)]) self.shape.friction = 0.5 self.shape.collision_type = COLLISION_TYPE_BOX super(Box, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageRenderer(resources.get_image('objects', 'crate.png')), ) @classmethod def requires(cls): return [("name", "string"), ("position", "coordinates"), ("state_source", "puzzler")] class Door(GameObject): zorder = ZORDER_FLOOR def __init__(self, space, position, destination, dest_pos, angle, key_state=None): body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5) self.shape = pymunk.Circle(body, 30) self.shape.collision_type = COLLISION_TYPE_DOOR self.shape.body.angle = float(angle) / 180 * math.pi self.shape.sensor = True self.destination = destination self.dest_pos = tuple(dest_pos) puzzler = None action = environment.Action(self._post_door_event) if key_state is not None: puzzler = puzzle.StateProxyPuzzler(key_state) action.condition = environment.PuzzleStateCondition(puzzler) super(Door, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageRenderer(resources.get_image('objects', 'door.png')), puzzler, interactible=environment.Interactible(action), ) def _post_door_event(self, protagonist): DoorEvent.post(self.destination, self.dest_pos) @classmethod def requires(cls): return [("name", "string"), ("position", "coordinates"), ("destination", "level name"), ("dest_pos", "coordinate"), ("angle", "degrees"), ("key_state", "puzzler (optional)")] class Bulkhead(GameObject): zorder = ZORDER_FLOOR def __init__(self, space, end1, end2, key_state=None): body = make_body(None, None, (0, 0)) self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 3) self.shape.collision_type = COLLISION_TYPE_DOOR if key_state is None: puzzler = puzzle.YesPuzzler() else: puzzler = puzzle.StateProxyPuzzler(key_state) super(Bulkhead, self).__init__( SingleShapePhysicser(space, self.shape), render.ShapeStateRenderer(), puzzler, ) def collide_with_protagonist(self, protagonist): if self.puzzler.get_state(): # Reject the collision, we can walk through. return False return True @classmethod def requires(cls): return [("name", "string"), ("end1", "coordinates"), ("end2", "coordinates"), ("key_state", "puzzler")] class ToggleSwitch(GameObject): zorder = ZORDER_LOW def __init__(self, space, position): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 20) self.shape.sensor = True self.toggle_on = False super(ToggleSwitch, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageStateRenderer({ True: resources.get_image('objects', 'lever.png'), False: resources.get_image( 'objects', 'lever.png', transforms=(FLIP_H,)), }), puzzle.ParentAttrPuzzler('toggle_on'), interactible=environment.Interactible( environment.Action(self._toggle)), ) def _toggle(self, protagonist): self.toggle_on = not self.toggle_on @classmethod def requires(cls): return [("name", "string"), ("position", "coordinates")] class Bullet(GameObject): def __init__(self, space, position, impulse, source_collision_type): body = make_body(1, pymunk.inf, position) self.last_position = position self.shape = pymunk.Circle(body, 2) self.shape.sensor = True self.shape.collision_type = COLLISION_TYPE_PROJECTILE self.source_collision_type = source_collision_type super(Bullet, self).__init__( SingleShapePhysicser(space, self.shape), render.ImageRenderer(resources.get_image('objects', 'bullet.png')), ) self.physicser.apply_impulse(impulse) def animate(self): super(Bullet, self).animate() position = (self.physicser.position.x, self.physicser.position.y) r = self.get_space().segment_query(self.last_position, position) self.last_position = position for collision in r: shape = collision.shape if (shape.collision_type == self.source_collision_type or shape == self.physicser.get_shape() or shape.sensor): continue if hasattr(shape, 'physicser'): shape.physicser.game_object.hit(self) self.physicser.remove_from_space() self.remove = True break class CollectibleGameObject(GameObject): zorder = ZORDER_LOW def __init__(self, space, name, shape, renderer): self._name = name shape.sensor = True super(CollectibleGameObject, self).__init__( SingleShapePhysicser(space, shape), renderer, interactible=environment.Interactible( environment.Action( self._collect, environment.HumanFormCondition())), ) def _collect(self, protagonist): protagonist.inventory[self._name] = self # TODO: Make this less hacky. self.physicser.remove_from_space() self.renderer = render.NullRenderer() class Gun(CollectibleGameObject): def __init__(self, space, position): body = make_body(None, None, position) self.shape = pymunk.Circle(body, 20) super(Gun, self).__init__( space, 'gun', self.shape, render.ImageRenderer(resources.get_image('objects', 'bullet.png')), )