Ignore:
Files:
28 added
16 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • TODO.txt

    r240 r242  
    33TODO
    44====
    5 
    6 * Changing areas
    75
    86* Items
     
    119
    1210* Puzzle objects
     11  - We need more
     12  - We need to make puzzles out of them
    1313  - How much state do we save to avoid breaking the game?
     14  - Toggle switch?
     15  - Laser emitters and detectors? (Maybe later -- seems like a lot of work)
    1416
    1517* Game saving
     
    1719
    1820* Enemies
    19   - Are desirable
     21  - One isn't enough
    2022
    2123* Shooting
     
    2931        * bullets (alien acid spit and human bullets)
    3032        * Weapons which can be overlaid with the protag's sprite in different positions?
    31         * Protagonist facing N, S and SW (already have W and NW). Rename to cardinal directions.
     33        * Last few protagonist directional images
    3234        * Two of each of these (human and alien design)
    3335                * floor tile
     
    3739                * pushable obstacle (1 done)
    3840                * smashable obstacle?
    39                 * tiles for different surfaces? e.g. damaging (pool of acid), slippery (ice), sticky (alien goop), etc..
     41                * tiles for different surfaces? e.g. damaging (pool of acid), slippery (ice), sticky (alien goop), etc.
    4042
    4143* Sounds required:
  • data/levels/level1

    r209 r224  
    5151  - Run around, press some buttons, have fun!
    5252  classname: Note
     53- args:
     54  - [800, 680]
     55  - [900, 680]
     56  - door_switch
     57  classname: Bulkhead
     58  name: switch_bulkhead
    5359lines:
    5460- - [750, 680]
     61  - [800, 680]
     62- - [900, 680]
    5563  - [950, 680]
    5664- - [750, 480]
  • nagslang/enemies.py

    r208 r229  
     1import math
     2
    13import pymunk
    24import pymunk.pygame_util
     
    3436
    3537class PatrollingAlien(Enemy):
     38    is_moving = True  # Always walking.
    3639
    3740    def __init__(self, space, position, end_position):
     
    5558
    5659    def _setup_renderer(self):
    57         self.renderer = render.AnimatedFacingImageRenderer(
    58             (self._get_image('alien_A_1.png'),
    59              self._get_image('alien_A_1.png'),
    60              self._get_image('alien_A_1.png'),
    61              self._get_image('alien_A_1.png'),
    62              self._get_image('alien_A_1.png'),
    63              self._get_image('alien_A_1.png'),
    64              self._get_image('alien_A_2.png'),
    65              self._get_image('alien_A_2.png'),
    66              self._get_image('alien_A_2.png')),
    67             (self._get_image('alien_A_1.png', FLIP_H),
    68              self._get_image('alien_A_1.png', FLIP_H),
    69              self._get_image('alien_A_1.png', FLIP_H),
    70              self._get_image('alien_A_1.png', FLIP_H),
    71              self._get_image('alien_A_1.png', FLIP_H),
    72              self._get_image('alien_A_1.png', FLIP_H),
    73              self._get_image('alien_A_2.png', FLIP_H),
    74              self._get_image('alien_A_2.png', FLIP_H),
    75              self._get_image('alien_A_2.png', FLIP_H)))
    76         # We're always animated
    77         self.renderer.start()
     60        self.renderer = render.FacingSelectionRenderer({
     61            'left': render.TimedAnimatedRenderer(
     62                [self._get_image('alien_A_1.png'),
     63                 self._get_image('alien_A_2.png')], 3),
     64            'right': render.TimedAnimatedRenderer(
     65                [self._get_image('alien_A_1.png', FLIP_H),
     66                 self._get_image('alien_A_2.png', FLIP_H)], 3),
     67        })
    7868
    7969    def get_render_angle(self):
    80         return self.angle
     70        # No image rotation when rendering, please.
     71        return 0
     72
     73    def get_facing_direction(self):
     74        # Enemies can face left or right.
     75        if - math.pi / 2 < self.angle <= math.pi / 2:
     76            return 'right'
     77        else:
     78            return 'left'
    8179
    8280    def _switch_direction(self):
  • nagslang/game_object.py

    r211 r229  
    99from nagslang.resources import resources
    1010from nagslang.events import DoorEvent
    11 from nagslang.widgets.text import LabelWidget
    1211
    1312
     
    2625
    2726    def add_to_space(self):
    28         raise NotImplementedError()
     27        shape = self.get_shape()
     28        self.get_space().add(shape)
     29        if not shape.body.is_static:
     30            self.get_space().add(shape.body)
    2931
    3032    def remove_from_space(self):
    31         raise NotImplementedError()
     33        shape = self.get_shape()
     34        self.get_space().remove(shape)
     35        if not shape.body.is_static:
     36            self.get_space().remove(shape.body)
    3237
    3338    def get_render_position(self, surface):
    34         raise NotImplementedError()
     39        pos = self.get_shape().body.position
     40        return pymunk.pygame_util.to_pygame(pos, surface)
    3541
    3642    def get_angle(self):
    37         raise NotImplementedError()
     43        return self.get_shape().body.angle
     44
     45    def get_velocity(self):
     46        return self.get_shape().body.velocity
     47
     48    def _get_position(self):
     49        return self.get_shape().body.position
     50
     51    def _set_position(self, position):
     52        self.get_shape().body.position = position
     53
     54    position = property(_get_position, _set_position)
    3855
    3956    def apply_impulse(self, j, r=(0, 0)):
    40         raise NotImplementedError()
     57        return self.get_shape().body.apply_impulse(j, r)
    4158
    4259
     
    4966    def get_shape(self):
    5067        return self._shape
    51 
    52     def add_to_space(self):
    53         self.get_space().add(self._shape)
    54         if not self._shape.body.is_static:
    55             self.get_space().add(self._shape.body)
    56 
    57     def remove_from_space(self):
    58         self.get_space().remove(self._shape)
    59         if not self._shape.body.is_static:
    60             self.get_space().remove(self._shape.body)
    61 
    62     def get_render_position(self, surface):
    63         pos = self._shape.body.position
    64         return pymunk.pygame_util.to_pygame(pos, surface)
    65 
    66     def get_angle(self):
    67         return self._shape.body.angle
    68 
    69     def apply_impulse(self, j, r=(0, 0)):
    70         return self._shape.body.apply_impulse(j, r)
    7168
    7269
     
    8784
    8885
    89 class Overlay(object):
    90     def set_game_object(self, game_object):
    91         self.game_object = game_object
    92 
    93     def render(self, surface, display_offset):
    94         pass
    95 
    96     def is_visible(self):
    97         return self.game_object.puzzler.get_state()
    98 
    99 
    100 class TextOverlay(Overlay):
    101     def __init__(self, text):
    102         self.text = text
    103         self.widget = LabelWidget((20, 20), self.text)
    104 
    105     def render(self, surface, display_offset):
    106         x, y = 20, 20
    107         if display_offset[0] < 0:
    108             x += abs(display_offset[0])
    109         if display_offset[1] < 0:
    110             y += abs(display_offset[1])
    111         self.widget.rect.topleft = (x, y)
    112         self.widget.draw(surface)
    113 
    114 
    11586class GameObject(object):
    11687    """A representation of a thing in the game world.
     
    12091
    12192    zorder = ZORDER_LOW
     93    is_moving = False  # `True` if a movement animation should play.
    12294
    12395    def __init__(self, physicser, renderer, puzzler=None, overlay=None):
     
    146118        return self.physicser.get_angle()
    147119
     120    def get_facing_direction(self):
     121        """Used by rendererd that care what direction an object is facing.
     122        """
     123        return None
     124
    148125    def render(self, surface):
    149126        return self.renderer.render(surface)
     
    190167            render.ImageRenderer(resources.get_image('objects', 'note.png')),
    191168            puzzle.CollidePuzzler(),
    192             TextOverlay(message),
     169            render.TextOverlay(message),
    193170        )
    194171
     
    249226        if self.puzzler.get_state():
    250227            DoorEvent.post(self.destination, self.dest_pos)
     228
     229
     230class Bulkhead(GameObject):
     231    zorder = ZORDER_FLOOR
     232
     233    def __init__(self, space, end1, end2, key_state=None):
     234        body = make_body(None, None, (0, 0))
     235        self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 3)
     236        self.shape.collision_type = COLLISION_TYPE_DOOR
     237        if key_state is None:
     238            puzzler = puzzle.YesPuzzler()
     239        else:
     240            puzzler = puzzle.StateProxyPuzzler(key_state)
     241        super(Bulkhead, self).__init__(
     242            SingleShapePhysicser(space, self.shape),
     243            render.ShapeStateRenderer(),
     244            puzzler,
     245        )
     246
     247    def collide_with_protagonist(self):
     248        if self.puzzler.get_state():
     249            # Reject the collision, we can walk through.
     250            return False
     251        return True
  • nagslang/protagonist.py

    r208 r230  
    11import pymunk
    22import pymunk.pygame_util
    3 
    4 import math
     3from pymunk.vec2d import Vec2d
    54
    65from nagslang import render
    76from nagslang.constants import COLLISION_TYPE_PLAYER, ZORDER_MID
    8 from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
     7from nagslang.game_object import GameObject, Physicser, make_body
    98from nagslang.mutators import FLIP_H
    109from nagslang.resources import resources
    1110
    1211
     12class ProtagonistPhysicser(Physicser):
     13    def __init__(self, space, form_shapes):
     14        self._space = space
     15        self._form_shapes = form_shapes
     16
     17    def switch_form(self, old_form, new_form):
     18        self._space.remove(self._form_shapes[old_form])
     19        shape = self._form_shapes[new_form]
     20        self._space.add(shape)
     21        for attr, value in shape.protagonist_body_props.iteritems():
     22            setattr(shape.body, attr, value)
     23
     24    def get_shape(self):
     25        return self._form_shapes[self.game_object.form]
     26
     27
     28class ProtagonistFormSelectionRenderer(render.RendererSelectionRenderer):
     29    def select_renderer(self):
     30        return self.game_object.form
     31
     32
    1333class Protagonist(GameObject):
    1434    """Representation of our fearless protagonist.
     
    1838
    1939    HUMAN_FORM = 'human'
    20     HUMAN_FORM_BACK = 'human_back'
    2140    WOLF_FORM = 'wolf'
    22     WOLF_FORM_BACK = 'wolf_back'
    2341
    2442    def __init__(self, space, position):
    25         self._setup_physics(space, position)
    26         self._setup_renderers()
     43        physicser = self._make_physics(space, position)
     44        renderer = self._make_renderer()
    2745        self.inventory = {}
    2846        self.form = self.HUMAN_FORM
    29         self.render_form = self.HUMAN_FORM
    30 
    31         super(Protagonist, self).__init__(
    32             self._physicsers[self.form], self._renderers[self.form])
     47        self.angle = 0
     48        self.is_moving = False
     49
     50        super(Protagonist, self).__init__(physicser, renderer)
    3351        self.zorder = ZORDER_MID
    3452
    3553        self.go_human()
    3654
    37     def _setup_physics(self, space, position):
    38         self._body = make_body(10, pymunk.inf, position, 0.8)
    39 
    40         self._shapes = {
    41             self.HUMAN_FORM: pymunk.Poly(
    42                 self._body, [(-15, -30), (15, -30), (15, 30), (-15, 30)]),
    43             self.WOLF_FORM: pymunk.Circle(self._body, 30),
     55    def _make_physics(self, space, position):
     56        body = make_body(10, pymunk.inf, position, 0.8)
     57        body.velocity_limit = 1000
     58
     59        human = pymunk.Poly(body, [(-15, -30), (15, -30), (15, 30), (-15, 30)])
     60        human.elasticity = 1.0
     61        human.collision_type = COLLISION_TYPE_PLAYER
     62        human.protagonist_body_props = {
     63            'mass': 10,
     64            'damping': 0.8,
    4465        }
    45         self._shapes[self.HUMAN_FORM].friction = 1.0
    46         self._shapes[self.WOLF_FORM].friction = 0.05
    47         self._physicsers = {}
    48         for form, shape in self._shapes.iteritems():
    49             shape.elasticity = 1.0
    50             shape.collision_type = COLLISION_TYPE_PLAYER
    51             self._physicsers[form] = SingleShapePhysicser(space, shape)
    52         self.angle = 0
     66
     67        wolf = pymunk.Circle(body, 30)
     68        wolf.elasticity = 1.0
     69        wolf.collision_type = COLLISION_TYPE_PLAYER
     70        wolf.protagonist_body_props = {
     71            'mass': 100,
     72            'damping': 0.9,
     73        }
     74
     75        return ProtagonistPhysicser(space, {
     76            self.HUMAN_FORM: human,
     77            self.WOLF_FORM: wolf,
     78        })
    5379
    5480    def _get_image(self, name, *transforms):
    5581        return resources.get_image('creatures', name, transforms=transforms)
    5682
    57     def _setup_renderers(self):
    58         self._renderers = {
    59             self.HUMAN_FORM: render.AnimatedFacingImageRenderer(
    60                 (self._get_image('human_1.png'),
    61                  self._get_image('human_1.png'),
    62                  self._get_image('human_1.png'),
    63                  self._get_image('human_2.png'),
    64                  self._get_image('human_2.png'),
    65                  self._get_image('human_2.png')),
    66                 (self._get_image('human_1.png', FLIP_H),
    67                  self._get_image('human_1.png', FLIP_H),
    68                  self._get_image('human_1.png', FLIP_H),
    69                  self._get_image('human_2.png', FLIP_H),
    70                  self._get_image('human_2.png', FLIP_H),
    71                  self._get_image('human_2.png', FLIP_H))),
    72             self.HUMAN_FORM_BACK: render.AnimatedFacingImageRenderer(
    73                 (self._get_image('human_back_1.png'),
    74                  self._get_image('human_back_1.png'),
    75                  self._get_image('human_back_1.png'),
    76                  self._get_image('human_back_2.png'),
    77                  self._get_image('human_back_2.png'),
    78                  self._get_image('human_back_2.png')),
    79                 (self._get_image('human_back_1.png', FLIP_H),
    80                  self._get_image('human_back_1.png', FLIP_H),
    81                  self._get_image('human_back_1.png', FLIP_H),
    82                  self._get_image('human_back_2.png', FLIP_H),
    83                  self._get_image('human_back_2.png', FLIP_H),
    84                  self._get_image('human_back_2.png', FLIP_H))),
    85             self.WOLF_FORM: render.AnimatedFacingImageRenderer(
    86                 (self._get_image('werewolf_1.png'),
    87                  self._get_image('werewolf_1.png'),
    88                  self._get_image('werewolf_1.png'),
    89                  self._get_image('werewolf_2.png'),
    90                  self._get_image('werewolf_2.png'),
    91                  self._get_image('werewolf_2.png')),
    92                 (self._get_image('werewolf_1.png', FLIP_H),
    93                  self._get_image('werewolf_1.png', FLIP_H),
    94                  self._get_image('werewolf_1.png', FLIP_H),
    95                  self._get_image('werewolf_2.png', FLIP_H),
    96                  self._get_image('werewolf_2.png', FLIP_H),
    97                  self._get_image('werewolf_2.png', FLIP_H))),
    98             self.WOLF_FORM_BACK: render.AnimatedFacingImageRenderer(
    99                 (self._get_image('werewolf_back_1.png'),
    100                  self._get_image('werewolf_back_1.png'),
    101                  self._get_image('werewolf_back_1.png'),
    102                  self._get_image('werewolf_back_2.png'),
    103                  self._get_image('werewolf_back_2.png'),
    104                  self._get_image('werewolf_back_2.png')),
    105                 (self._get_image('werewolf_back_1.png', FLIP_H),
    106                  self._get_image('werewolf_back_1.png', FLIP_H),
    107                  self._get_image('werewolf_back_1.png', FLIP_H),
    108                  self._get_image('werewolf_back_2.png', FLIP_H),
    109                  self._get_image('werewolf_back_2.png', FLIP_H),
    110                  self._get_image('werewolf_back_2.png', FLIP_H))),
    111         }
    112         for renderer in self._renderers.values():
    113             renderer.set_game_object(self)
     83    def _make_renderer(self):
     84        return ProtagonistFormSelectionRenderer({
     85            self.HUMAN_FORM: render.FacingSelectionRenderer(
     86                {
     87                    'N': render.MovementAnimatedRenderer(
     88                        [self._get_image('human_N_1.png'),
     89                         self._get_image('human_N_2.png')], 3),
     90                    'S': render.MovementAnimatedRenderer(
     91                        [self._get_image('human_S_1.png'),
     92                         self._get_image('human_S_2.png')], 3),
     93                    'W': render.MovementAnimatedRenderer(
     94                        [self._get_image('human_W_1.png'),
     95                         self._get_image('human_W_2.png')], 3),
     96                    'E': render.MovementAnimatedRenderer(
     97                        [self._get_image('human_W_1.png', FLIP_H),
     98                         self._get_image('human_W_2.png', FLIP_H)], 3),
     99                    'NW': render.MovementAnimatedRenderer(
     100                        [self._get_image('human_NW_1.png'),
     101                         self._get_image('human_NW_2.png')], 3),
     102                    'NE': render.MovementAnimatedRenderer(
     103                        [self._get_image('human_NW_1.png', FLIP_H),
     104                         self._get_image('human_NW_2.png', FLIP_H)], 3),
     105                    'SW': render.MovementAnimatedRenderer(
     106                        [self._get_image('human_SW_1.png'),
     107                         self._get_image('human_SW_2.png')], 3),
     108                    'SE': render.MovementAnimatedRenderer(
     109                        [self._get_image('human_SW_1.png', FLIP_H),
     110                         self._get_image('human_SW_2.png', FLIP_H)], 3),
     111                }),
     112            self.WOLF_FORM: render.FacingSelectionRenderer(
     113                {
     114                    'N': render.MovementAnimatedRenderer(
     115                        [self._get_image('werewolf_N_1.png'),
     116                         self._get_image('werewolf_N_2.png')], 3),
     117                    'S': render.MovementAnimatedRenderer(
     118                        [self._get_image('werewolf_S_1.png'),
     119                         self._get_image('werewolf_S_2.png')], 3),
     120                    'W': render.MovementAnimatedRenderer(
     121                        [self._get_image('werewolf_W_1.png'),
     122                         self._get_image('werewolf_W_2.png')], 3),
     123                    'E': render.MovementAnimatedRenderer(
     124                        [self._get_image('werewolf_W_1.png', FLIP_H),
     125                         self._get_image('werewolf_W_2.png', FLIP_H)], 3),
     126                    'NW': render.MovementAnimatedRenderer(
     127                        [self._get_image('werewolf_NW_1.png'),
     128                         self._get_image('werewolf_NW_2.png')], 3),
     129                    'NE': render.MovementAnimatedRenderer(
     130                        [self._get_image('werewolf_NW_1.png', FLIP_H),
     131                         self._get_image('werewolf_NW_2.png', FLIP_H)], 3),
     132                    'SW': render.MovementAnimatedRenderer(
     133                        [self._get_image('werewolf_SW_1.png'),
     134                         self._get_image('werewolf_SW_2.png')], 3),
     135                    'SE': render.MovementAnimatedRenderer(
     136                        [self._get_image('werewolf_SW_1.png', FLIP_H),
     137                         self._get_image('werewolf_SW_2.png', FLIP_H)], 3),
     138                }),
     139        })
    114140
    115141    @classmethod
     
    122148
    123149    def get_render_angle(self):
    124         return self.angle
     150        # No image rotation when rendering, please.
     151        return 0
     152
     153    def get_facing_direction(self):
     154        # It's easier to work with a vector than an angle here.
     155        vec = Vec2d.unit()
     156        vec.angle = self.angle
     157        # We probably don't have exactly -1, 0, or 1 here.
     158        x = int(round(vec.x))
     159        y = int(round(vec.y))
     160
     161        return {
     162            (0, 1): 'N',
     163            (0, -1): 'S',
     164            (-1, 0): 'W',
     165            (1, 0): 'E',
     166            (1, 1): 'NE',
     167            (1, -1): 'SE',
     168            (-1, 1): 'NW',
     169            (-1, -1): 'SW',
     170        }[(x, y)]
    125171
    126172    def go_werewolf(self):
    127         self._physicsers[self.form].remove_from_space()
     173        self.physicser.switch_form(self.form, self.WOLF_FORM)
    128174        self.form = self.WOLF_FORM
    129         self._physicsers[self.form].add_to_space()
    130         self.physicser = self._physicsers[self.form]
    131         self._body.mass = 100
    132         self._body.velocity_limit = 1000
    133175        self.impulse_factor = 4000
    134         self._body.damping = 0.9
    135         if self.render_form == self.HUMAN_FORM:
    136             self.render_form = self.WOLF_FORM
    137         elif self.render_form == self.HUMAN_FORM_BACK:
    138             self.render_form = self.WOLF_FORM_BACK
    139         else:
    140             self.render_form = self.WOLF_FORM
    141         self.renderer = self._renderers[self.render_form]
    142176
    143177    def go_human(self):
    144         self._physicsers[self.form].remove_from_space()
     178        self.physicser.switch_form(self.form, self.HUMAN_FORM)
    145179        self.form = self.HUMAN_FORM
    146         self._physicsers[self.form].add_to_space()
    147         self.physicser = self._physicsers[self.form]
    148         self._body.mass = 10
    149         self._body.velocity_limit = 1000
    150180        self.impulse_factor = 500
    151         self._body.damping = 0.8
    152         if self.render_form == self.WOLF_FORM:
    153             self.render_form = self.HUMAN_FORM
    154         elif self.render_form == self.WOLF_FORM_BACK:
    155             self.render_form = self.HUMAN_FORM_BACK
    156         else:
    157             self.render_form = self.HUMAN_FORM
    158         self.renderer = self._renderers[self.render_form]
    159 
    160     def _switch_to_back(self):
    161         if self.render_form == self.HUMAN_FORM:
    162             self.render_form = self.HUMAN_FORM_BACK
    163         elif self.render_form == self.WOLF_FORM:
    164             self.render_form = self.WOLF_FORM_BACK
    165         self.renderer = self._renderers[self.render_form]
    166 
    167     def _switch_to_front(self):
    168         if self.render_form == self.HUMAN_FORM_BACK:
    169             self.render_form = self.HUMAN_FORM
    170         elif self.render_form == self.WOLF_FORM_BACK:
    171             self.render_form = self.WOLF_FORM
    172         self.renderer = self._renderers[self.render_form]
    173181
    174182    def set_direction(self, dx, dy):
    175183        if (dx, dy) == (0, 0):
    176             self.renderer.stop()
     184            self.is_moving = False
    177185            return
    178         old_angle = self.angle
     186        self.is_moving = True
    179187        self.angle = pymunk.Vec2d((dx, dy)).angle
    180         # If we've gone from quadrants 2 & 3 to 1 & 4 (or vice versa)
    181         # switch between front & back views
    182         if self.angle != math.pi:
    183             # == math.pi is going straight left, which can't
    184             # trigger a front/back swap and simplifies these checks
    185             if self.angle > 0 and old_angle != self.angle:
    186                 self._switch_to_back()
    187             elif self.angle < 0 and old_angle != self.angle:
    188                 self._switch_to_front()
    189         self._body.apply_impulse(
     188        self.physicser.apply_impulse(
    190189            (dx * self.impulse_factor, dy * self.impulse_factor))
    191         self.renderer.start()
    192190
    193191    def set_position(self, position):
    194         self._body.position = position
     192        self.physicser.position = position
    195193
    196194    def copy_state(self, old_protagonist):
    197         self._physicsers[self.form].remove_from_space()
    198         self._body.position = old_protagonist._body.position
     195        self.physicser.position = old_protagonist.physicser.position
     196        self.physicser.switch_form(self.form, old_protagonist.form)
     197        self.impulse_factor = old_protagonist.impulse_factor
    199198        self.form = old_protagonist.form
    200199        self.angle = old_protagonist.angle
    201         self.render_form = old_protagonist.render_form
    202200        self.inventory = old_protagonist.inventory
    203         self.renderer = self._renderers[self.render_form]
    204         self._physicsers[self.form].add_to_space()
    205         self.physicser = self._physicsers[self.form]
    206201
    207202    def toggle_form(self):
     
    234229        if (dx, dy) == (0, 0):
    235230            return
    236         self._body.apply_impulse((dx, dy))
     231        self.physicser.apply_impulse((dx, dy))
  • nagslang/render.py

    r207 r230  
    55
    66from nagslang.options import options
     7from nagslang.widgets.text import LabelWidget
    78
    89
     
    7273
    7374
    74 class FacingImageRenderer(ImageRenderer):
    75     def __init__(self, left_image, right_image):
    76         self._images = {
    77             'left': left_image,
    78             'right': right_image,
    79         }
    80         self._face = 'left'
     75class TimedAnimatedRenderer(ImageRenderer):
     76    def __init__(self, images, frame_ticks=1):
     77        self._images = images
     78        self._frame_ticks = frame_ticks
     79        self._frame_tick = 0
     80        self._frame = 0
    8181
    82     def _update_facing(self, angle):
    83         if abs(angle) < math.pi / 2:
    84             self._face = 'right'
    85         elif abs(angle) > math.pi / 2:
    86             self._face = 'left'
     82    def advance_tick(self):
     83        self._frame_tick += 1
     84        if self._frame_tick > self._frame_ticks:
     85            self._frame_tick = 0
     86            self._frame += 1
     87        if self._frame >= len(self._images):
     88            self._frame = 0
    8789
    88     def rotate_image(self, image):
    89         # Facing images don't get rotated.
    90         return image
    91 
    92     def get_facing_image(self):
    93         return self._images[self._face]
     90    def reset(self):
     91        self._frame_tick = 0
     92        self._frame = 0
    9493
    9594    def get_image(self):
    96         angle = self.game_object.get_render_angle()
    97         self._update_facing(angle)
    98         return self.get_facing_image()
    99 
    100 
    101 class AnimatedFacingImageRenderer(FacingImageRenderer):
    102     def __init__(self, left_images, right_images):
    103         self._images = {
    104             'left': left_images,
    105             'right': right_images,
    106         }
    107         self._frame = 0
    108         self._moving = False
    109         self._face = 'left'
    110 
    111     def get_facing_image(self):
    112         if self._frame >= len(self._images[self._face]):
    113             self._frame = 0
    114         return self._images[self._face][self._frame]
    115 
    116     def animate(self):
    117         if self._moving:
    118             self._frame += 1
    119         else:
    120             self._frame = 0
    121 
    122     def start(self):
    123         self._moving = True
    124 
    125     def stop(self):
    126         self._moving = False
    127 
    128 
    129 class TimedAnimatedRenderer(ImageRenderer):
    130 
    131     def __init__(self, images):
    132         self._images = images
    133         self._frame = 0
    134         self._image = None
    135 
    136     def get_image(self):
    137         if self._frame > len(self._imaages):
    138             self._frame = 0
    13995        return self._images[self._frame]
    14096
    14197    def animate(self):
    142         self._frame += 1
     98        self.advance_tick()
     99
     100
     101class MovementAnimatedRenderer(TimedAnimatedRenderer):
     102    def animate(self):
     103        if self.game_object.is_moving:
     104            self.advance_tick()
     105        else:
     106            self.reset()
     107
     108
     109class RendererSelectionRenderer(Renderer):
     110    def __init__(self, renderers):
     111        self._renderers = renderers
     112
     113    def set_game_object(self, game_object):
     114        self.game_object = game_object
     115        for renderer in self._renderers.values():
     116            renderer.set_game_object(game_object)
     117
     118    @property
     119    def renderer(self):
     120        return self._renderers[self.select_renderer()]
     121
     122    def render(self, surface):
     123        return self.renderer.render(surface)
     124
     125    def animate(self):
     126        return self.renderer.animate()
     127
     128    def select_renderer(self):
     129        raise NotImplementedError()
     130
     131
     132class FacingSelectionRenderer(RendererSelectionRenderer):
     133    def select_renderer(self):
     134        return self.game_object.get_facing_direction()
    143135
    144136
     
    162154        self.game_object.get_shape().color = color
    163155        super(ShapeStateRenderer, self).render(surface)
     156
     157
     158class Overlay(object):
     159    def set_game_object(self, game_object):
     160        self.game_object = game_object
     161
     162    def render(self, surface, display_offset):
     163        pass
     164
     165    def is_visible(self):
     166        return self.game_object.puzzler.get_state()
     167
     168
     169class TextOverlay(Overlay):
     170    def __init__(self, text):
     171        self.text = text
     172        self.widget = LabelWidget((20, 20), self.text)
     173
     174    def render(self, surface, display_offset):
     175        x, y = 20, 20
     176        if display_offset[0] < 0:
     177            x += abs(display_offset[0])
     178        if display_offset[1] < 0:
     179            y += abs(display_offset[1])
     180        self.widget.rect.topleft = (x, y)
     181        self.widget.draw(surface)
  • nagslang/screens/menu.py

    r180 r226  
    99
    1010class MenuScreen(Screen):
     11    def setup(self):
     12        # Position is hacked later
     13        self.cursor = TextWidget((0, 0), u'\N{Rightwards Arrow}',
     14                                 colour='red')
     15        self.cursor_pos = 0
     16        self.options = [
     17            self.new_game,
     18            self.load_game,
     19            self.quit,
     20        ]
     21        self.widgets = [
     22            TextWidget((10, 10), 'Menu', fontsize=20),
     23            TextWidget((40, 50), 'Start new game'),
     24            TextWidget((40, 70), 'Restore saved game'),
     25            TextWidget((40, 90), 'Quit'),
     26            self.cursor,
     27        ]
    1128
    1229    def handle_event(self, ev):
    1330        if ev.type == pygame.locals.KEYDOWN:
    1431            if ev.key == pygame.locals.K_ESCAPE:
    15                 QuitEvent.post()
    16             elif ev.key == pygame.locals.K_1:
    17                 ScreenChange.post('level1', None)
     32                self.quit()
     33            elif ev.key == pygame.locals.K_DOWN:
     34                self.cursor_pos = (self.cursor_pos + 1) % len(self.options)
     35            elif ev.key == pygame.locals.K_UP:
     36                self.cursor_pos = (self.cursor_pos - 1) % len(self.options)
     37            elif ev.key == pygame.locals.K_RETURN:
     38                self.options[self.cursor_pos]()
    1839
    1940    def render(self, surface):
    2041        surface.fill(pygame.color.Color(255, 255, 255))
    21         TextWidget((10, 10), 'Menu').draw(surface)
    22         TextWidget((10, 30), 'Press 1 to start').draw(surface)
     42        self.cursor.pos = (10, 50 + 20 * self.cursor_pos)
     43        self.cursor.rect.top = self.cursor.pos[1]
     44        for widget in self.widgets:
     45            widget.draw(surface)
     46
     47    def new_game(self):
     48        ScreenChange.post('level1', None)
     49
     50    def load_game(self):
     51        raise NotImplementedError()
     52
     53    def quit(self):
     54        QuitEvent.post()
  • source/Makefile

    r48 r231  
    11SOURCES = $(shell find images -name '*.svg')
    2 TARGETS = $(patsubst %.svg,%.png,$(SOURCES))
     2TARGETS = $(patsubst %.svg,../data/%.png,$(SOURCES))
    33OPTIMIZE = 1
    44
    55all: $(TARGETS)
    66
    7 install: $(TARGETS)
    8         set -ex; \
    9         for fn in $(TARGETS); do \
    10                 mkdir -p ../data/"$$(dirname $$fn)"; \
    11                 cp $$fn ../data/$$fn; \
    12         done
    13 
    147clean:
    158        rm -f $(TARGETS)
    169
    17 %.png: %.svg
     10../data/%.png: %.svg
     11        mkdir -p $(dir $@)
    1812        inkscape --export-png $@ --export-dpi 9 $<
    1913ifeq ($(OPTIMIZE),1)
  • tools/area_editor.py

    r206 r225  
    2525from albow.widget import Widget
    2626from albow.controls import Button, Label, CheckBox
    27 from albow.dialogs import alert
     27from albow.dialogs import alert, Dialog
     28from albow.layout import Row
     29from albow.table_view import TableView, TableColumn
    2830
    2931from nagslang.options import parse_args
     
    3941MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
    4042MENU_WIDTH = 200 - MENU_PAD
     43
     44BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
     45CHECK_RECT = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
     46                              MENU_BUTTON_HEIGHT // 2)
    4147
    4248
     
    166172
    167173
     174class ObjectTable(TableView):
     175
     176    columns = [TableColumn("Object", 690, 'l', '%r')]
     177
     178    def __init__(self, data):
     179        super(ObjectTable, self).__init__(height=450)
     180        self.data = data
     181        self.selected_row = -1
     182
     183    def num_rows(self):
     184        return len(self.data)
     185
     186    def row_data(self, i):
     187        data = self.data[i]
     188        if 'name' in data:
     189            return ('%s (%s)' % (data['classname'], data['name']), )
     190        return (data['classname'], )
     191
     192    def row_is_selected(self, i):
     193        return self.selected_row == i
     194
     195    def click_row(self, i, ev):
     196        self.selected_row = i
     197
     198    def get_selection(self):
     199        if self.selected_row >= 0:
     200            return self.data[self.selected_row]
     201        return None
     202
     203
    168204class LevelWidget(Widget):
    169205
     
    329365            alert("Failed to close the polygon")
    330366
     367    def _make_edit_dialog(self, entries):
     368        # Dialog to hold the editor
     369        edit_box = Dialog()
     370        edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
     371        table = ObjectTable(entries)
     372        edit_box.add(table)
     373        buttons = []
     374        for text in ['OK', 'Delete', 'Cancel']:
     375            but = Button(text, action=lambda x=text: edit_box.dismiss(x))
     376            buttons.append(but)
     377        row = Row(buttons)
     378        row.rect = pygame.rect.Rect(0, 450, 700, 50)
     379        edit_box.add(row)
     380        return edit_box
     381
     382    def edit_objects(self):
     383        edit_box = self._make_edit_dialog(self.level._game_objects)
     384        res = edit_box.present()
     385        if res == 'OK':
     386            # Edit object stuff goes here
     387            pass
     388        elif res == 'Delete':
     389            pass
     390
     391    def edit_enemies(self):
     392        edit_box = self._make_edit_dialog(self.level._enemies)
     393        res = edit_box.present()
     394        if res == 'OK':
     395            # Edit object stuff goes here
     396            pass
     397        elif res == 'Delete':
     398            pass
     399
    331400
    332401class PolyButton(Button):
     
    353422        self.level_widget = LevelWidget(self.level)
    354423        self.add(self.level_widget)
     424
     425        self._dMenus = {}
     426
     427        self._make_draw_menu()
     428        self._make_objects_menu()
     429
     430        self._menu_mode = 'drawing'
     431        self._populate_menu()
     432
     433    def _make_draw_menu(self):
     434        widgets = []
    355435
    356436        # Add poly buttons
     
    366446                                 y)
    367447                y += MENU_BUTTON_HEIGHT + MENU_PAD
    368             self.add(but)
    369 
    370         button_rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
    371 
    372         check_rect = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
    373                                       MENU_BUTTON_HEIGHT // 2)
     448            widgets.append(but)
    374449
    375450        end_poly_but = PolyButton(None, self.level_widget)
    376         end_poly_but.rect = button_rect.copy()
     451        end_poly_but.rect = BUTTON_RECT.copy()
    377452        end_poly_but.rect.move_ip(MENU_LEFT, y)
    378         self.add(end_poly_but)
     453        widgets.append(end_poly_but)
    379454        y += MENU_BUTTON_HEIGHT + MENU_PAD
    380455
    381456        draw_line = Button("Draw interior wall", self.level_widget.line_mode)
    382         draw_line.rect = button_rect.copy()
     457        draw_line.rect = BUTTON_RECT.copy()
    383458        draw_line.rect.move_ip(MENU_LEFT, y)
    384         self.add(draw_line)
     459        widgets.append(draw_line)
    385460        y += MENU_BUTTON_HEIGHT + MENU_PAD
    386461
    387462        fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
    388         fill_but.rect = button_rect.copy()
     463        fill_but.rect = BUTTON_RECT.copy()
    389464        fill_but.rect.move_ip(MENU_LEFT, y)
    390         self.add(fill_but)
     465        widgets.append(fill_but)
    391466        y += MENU_BUTTON_HEIGHT + MENU_PAD
    392467
    393468        save_but = Button('Save Level', action=self.save)
    394         save_but.rect = button_rect.copy()
     469        save_but.rect = BUTTON_RECT.copy()
    395470        save_but.rect.move_ip(MENU_LEFT, y)
    396         self.add(save_but)
     471        widgets.append(save_but)
    397472        y += MENU_BUTTON_HEIGHT + MENU_PAD
    398473
    399474        close_poly_but = Button('Close Polygon',
    400475                                action=self.level_widget.close_poly)
    401         close_poly_but.rect = button_rect.copy()
     476        close_poly_but.rect = BUTTON_RECT.copy()
    402477        close_poly_but.rect.move_ip(MENU_LEFT, y)
    403         self.add(close_poly_but)
     478        widgets.append(close_poly_but)
    404479        y += MENU_BUTTON_HEIGHT + MENU_PAD
    405480
    406481        white = pygame.color.Color("white")
    407482        self.show_objs = CheckBox(fg_color=white)
    408         self.show_objs.rect = check_rect.copy()
     483        self.show_objs.rect = CHECK_RECT.copy()
    409484        self.show_objs.rect.move_ip(MENU_LEFT, y)
    410485        label = Label("Show Objects", fg_color=white)
    411486        label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
    412         self.add(self.show_objs)
    413         self.add(label)
     487        widgets.append(self.show_objs)
     488        widgets.append(label)
    414489        y += label.rect.height + MENU_PAD
    415490
    416491        self.show_enemies = CheckBox(fg_color=white)
    417         self.show_enemies.rect = check_rect.copy()
     492        self.show_enemies.rect = CHECK_RECT.copy()
    418493        self.show_enemies.rect.move_ip(MENU_LEFT, y)
    419494        label = Label("Show enemy start pos", fg_color=white)
    420495        label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
    421         self.add(self.show_enemies)
    422         self.add(label)
     496        widgets.append(self.show_enemies)
     497        widgets.append(label)
    423498        y += label.rect.height + MENU_PAD
    424499
     500        switch_but = Button('Switch to Objects', action=self.switch_to_objects)
     501        switch_but.rect = BUTTON_RECT.copy()
     502        switch_but.rect.move_ip(MENU_LEFT, y)
     503        widgets.append(switch_but)
     504        y += switch_but.rect.height + MENU_PAD
     505
    425506        quit_but = Button('Quit', action=self.quit)
    426         quit_but.rect = button_rect.copy()
     507        quit_but.rect = BUTTON_RECT.copy()
    427508        quit_but.rect.move_ip(MENU_LEFT, y)
    428         self.add(quit_but)
     509        widgets.append(quit_but)
     510
     511        self._dMenus['drawing'] = widgets
     512
     513    def _make_objects_menu(self):
     514        widgets = []
     515
     516        # Add poly buttons
     517        y = 15
     518
     519        edit_objs_but = Button('Edit Objects',
     520                               action=self.level_widget.edit_objects)
     521        edit_objs_but.rect = BUTTON_RECT.copy()
     522        edit_objs_but.rect.move_ip(MENU_LEFT, y)
     523        widgets.append(edit_objs_but)
     524        y += MENU_BUTTON_HEIGHT + MENU_PAD
     525
     526        edir_enemies_but = Button('Edit Enemies',
     527                                  action=self.level_widget.edit_enemies)
     528        edir_enemies_but.rect = BUTTON_RECT.copy()
     529        edir_enemies_but.rect.move_ip(MENU_LEFT, y)
     530        widgets.append(edir_enemies_but)
     531        y += MENU_BUTTON_HEIGHT + MENU_PAD
     532
     533        save_but = Button('Save Level', action=self.save)
     534        save_but.rect = BUTTON_RECT.copy()
     535        save_but.rect.move_ip(MENU_LEFT, y)
     536        widgets.append(save_but)
     537        y += MENU_BUTTON_HEIGHT + MENU_PAD
     538
     539        switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
     540        switch_but.rect = BUTTON_RECT.copy()
     541        switch_but.rect.move_ip(MENU_LEFT, y)
     542        widgets.append(switch_but)
     543        y += switch_but.rect.height + MENU_PAD
     544
     545        quit_but = Button('Quit', action=self.quit)
     546        quit_but.rect = BUTTON_RECT.copy()
     547        quit_but.rect.move_ip(MENU_LEFT, y)
     548        widgets.append(quit_but)
     549
     550        self._dMenus['objects'] = widgets
    429551
    430552    def key_down(self, ev):
     
    446568            alert("Failed to save level.\n\n%s" % '\n'.join(messages))
    447569
     570    def switch_to_draw(self):
     571        if self._menu_mode != 'drawing':
     572            self._clear_menu()
     573            self._menu_mode = 'drawing'
     574            self._populate_menu()
     575
     576    def switch_to_objects(self):
     577        if self._menu_mode != 'objects':
     578            self._clear_menu()
     579            self._menu_mode = 'objects'
     580            self._populate_menu()
     581
     582    def _clear_menu(self):
     583        for widget in self._dMenus[self._menu_mode]:
     584            self.remove(widget)
     585
     586    def _populate_menu(self):
     587        self.level_widget.change_poly(None)
     588        for widget in self._dMenus[self._menu_mode]:
     589            self.add(widget)
     590        self.invalidate()
     591
    448592    def mouse_move(self, ev):
    449593        self.level_widget.mouse_move(ev)
     
    451595    def draw(self, surface):
    452596        # Update checkbox state
    453         self.level_widget.set_objects(self.show_objs.value)
    454         self.level_widget.set_enemies(self.show_enemies.value)
     597        if self._menu_mode == 'drawing':
     598            self.level_widget.set_objects(self.show_objs.value)
     599            self.level_widget.set_enemies(self.show_enemies.value)
     600        else:
     601            self.level_widget.set_objects(True)
     602            self.level_widget.set_enemies(True)
    455603        super(EditorApp, self).draw(surface)
    456604
Note: See TracChangeset for help on using the changeset viewer.