source: nagslang/screens/area.py @ 269:9fcdb106424a

Last change on this file since 269:9fcdb106424a was 269:9fcdb106424a, checked in by Simon Cross <hodgestar@…>, 7 years ago

Add first draft of sound support.

File size: 7.5 KB
Line 
1"""Display a game area."""
2
3import pygame
4import pymunk
5import pymunk.pygame_util
6
7from nagslang.constants import COLLISION_TYPE_PLAYER, CALLBACK_COLLIDERS
8from nagslang.events import ScreenChange, DoorEvent, FireEvent
9from nagslang.level import Level
10from nagslang.protagonist import Protagonist
11from nagslang.screens.base import Screen
12from nagslang.game_object import Bullet
13from nagslang.sound import sound
14
15
16class ControlKeys(object):
17    direction_keys = {
18        (0, 1): set([pygame.locals.K_UP, pygame.locals.K_w]),
19        (0, -1): set([pygame.locals.K_DOWN, pygame.locals.K_s]),
20        (-1, 0): set([pygame.locals.K_LEFT, pygame.locals.K_a]),
21        (1, 0): set([pygame.locals.K_RIGHT, pygame.locals.K_d]),
22    }
23
24    def __init__(self):
25        self.keys_down = set()
26
27    def key_down(self, key):
28        self.keys_down.add(key)
29
30    def key_up(self, key):
31        self.keys_down.discard(key)
32
33    def handle_event(self, ev):
34        if ev.type == pygame.locals.KEYDOWN:
35            self.key_down(ev.key)
36        elif ev.type == pygame.locals.KEYUP:
37            self.key_up(ev.key)
38
39    def get_direction(self):
40        dx, dy = 0, 0
41        for (tx, ty), keys in self.direction_keys.iteritems():
42            if self.keys_down & keys:
43                dx += tx
44                dy += ty
45        return (dx, dy)
46
47
48class Drawables(object):
49    def __init__(self):
50        self._drawables = {}
51
52    def add(self, drawable):
53        self._drawables.setdefault(drawable.zorder, []).append(drawable)
54
55    def remove(self, drawable):
56        self._drawables[drawable.zorder].remove(drawable)
57
58    def get_drawables(self):
59        for zorder in sorted(self._drawables):
60            for drawable in self._drawables[zorder]:
61                yield drawable
62
63    __iter__ = get_drawables
64
65
66class AreaScreen(Screen):
67
68    def setup(self):
69        self._disable_render = False  # Avoid redrawing on scene changes
70        self.keys = ControlKeys()
71        self._level = Level(self.name)
72        self._level.load(self.space)
73        self._drawables = Drawables()
74        self.add_walls()
75        self._add_collision_handlers()
76        if self.protagonist is not None:
77            # We do things this way to avoid extra pymunk
78            # juggling to move objects between spaces
79            old_protagonist = self.protagonist
80            self.add_protagonist()
81            self.protagonist.copy_state(old_protagonist)
82        else:
83            self.add_protagonist()
84        self.add_game_objects()
85        sound.play_music("POL-cyber-factory-short.ogg")
86
87    def _collision_pre_solve_handler(self, space, arbiter):
88        gobj = arbiter.shapes[1].physicser.game_object
89        result = gobj.collide_with_protagonist(self.protagonist)
90        # The collision handler must return `True` or `False`. We don't want to
91        # accidentally reject collisions from handlers that return `None`, so
92        # we explicitly check for `False` and treate everything else as `True`.
93        if result is False:
94            return False
95        return True
96
97    def _add_collision_handlers(self):
98        for collision_type in CALLBACK_COLLIDERS:
99            self.space.add_collision_handler(
100                COLLISION_TYPE_PLAYER, collision_type,
101                pre_solve=self._collision_pre_solve_handler)
102
103    def add_walls(self):
104        self.walls = []
105        body = pymunk.Body()
106        body.position = (0, 0)
107        walls = self._level.get_walls()
108        for wall in walls:
109            corners = wall
110            corner = corners[-1]
111            for next_corner in corners:
112                wall = pymunk.Segment(body, corner, next_corner, 5)
113                wall.elasticity = 1.0
114                self.walls.append(wall)
115                corner = next_corner
116        self.space.add(*self.walls)
117
118    def add_game_objects(self):
119        for drawable in self._level.drawables:
120            self._drawables.add(drawable)
121
122    def add_protagonist(self):
123        self.protagonist = Protagonist(self.space, (350, 300))
124        self._drawables.add(self.protagonist)
125
126    def handle_event(self, ev):
127        if ev.type == pygame.locals.KEYDOWN:
128            if ev.key == pygame.locals.K_ESCAPE:
129                ScreenChange.post('menu')
130            if ev.key == pygame.locals.K_c:
131                self.protagonist.toggle_form()
132                self.world.transformations += 1
133            if ev.key == pygame.locals.K_SPACE:
134                self.world.attacks += 1
135                self.protagonist.attack()
136        elif DoorEvent.matches(ev):
137            self.protagonist.set_position(ev.dest_pos)
138            if ev.destination != self.name:
139                # Go to anther screen
140                self._disable_render = True
141                self.world.rooms += 1
142                ScreenChange.post(ev.destination, self.protagonist)
143                return
144            # else we're teleporting within the screen, and just the
145            # position change is enough
146        elif FireEvent.matches(ev):
147            bullet = Bullet(self.space, ev.source, ev.impulse)
148            self._drawables.add(bullet)
149        self.keys.handle_event(ev)
150
151    def _calc_viewport(self, level_surface, display_surface):
152        level_size = level_surface.get_size()
153        display_size = display_surface.get_size()
154        protagnist_pos = self.protagonist.physicser.get_render_position(
155            level_surface)
156        x_wide = display_size[0] // 2
157        y_wide = display_size[1] // 2
158        if display_size[0] > level_size[0]:
159            x = -(display_size[0] - level_size[0]) // 2
160        elif protagnist_pos[0] < x_wide:
161            x = 0
162        elif protagnist_pos[0] > level_size[0] - x_wide:
163            x = level_size[0] - display_size[0]
164        else:
165            x = protagnist_pos[0] - x_wide
166        if display_size[1] > level_size[1]:
167            y = -(display_size[1] - level_size[1]) // 2
168        elif protagnist_pos[1] < y_wide:
169            y = 0
170        elif protagnist_pos[1] > level_size[1] - y_wide:
171            y = level_size[1] - display_size[1]
172        else:
173            y = protagnist_pos[1] - y_wide
174        return pygame.rect.Rect(x, y, display_size[0], display_size[1])
175
176    def render(self, surface):
177        if self._disable_render:
178            return
179        background = self._level.get_background()
180        mysurface = background.copy()
181        for drawable in self._drawables:
182            drawable.render(mysurface)
183        render_rect = self._calc_viewport(mysurface, surface)
184        surface.blit(mysurface, (0, 0), render_rect)
185        for overlay in self._level.overlay_drawables:
186            if overlay.is_visible():
187                overlay.render(surface, render_rect.topleft)
188        self.render_health_bar(surface)
189
190    def tick_protagonist(self):
191        dx, dy = self.keys.get_direction()
192        self.protagonist.set_direction(dx, dy)
193
194    def tick(self, seconds):
195        self.tick_protagonist()
196        for drawable in self._drawables:
197            drawable.animate()
198
199        super(AreaScreen, self).tick(seconds)
200
201    def render_health_bar(self, surface, damage_experienced=None):
202        rect = pygame.Rect(50, 500, 110, 50)
203        if damage_experienced:
204            health_box_colour = pygame.color.THECOLORS['red']
205        else:
206            health_box_colour = pygame.color.THECOLORS['white']
207        pygame.draw.rect(surface,  health_box_colour, rect, 0)
208        if self.protagonist.in_human_form():
209            health_colour = pygame.color.THECOLORS['red']
210        else:
211            health_colour = pygame.color.THECOLORS['purple']
212        rect = pygame.Rect(55, 505, self.protagonist.get_health_level(), 40)
213        pygame.draw.rect(surface,  health_colour,
214                         rect, 0)
Note: See TracBrowser for help on using the repository browser.