source: nagslang/screens/area.py @ 244:93a20b51963f

Last change on this file since 244:93a20b51963f was 244:93a20b51963f, checked in by David Sharpe, 7 years ago

Merge

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