source: nagslang/enemies.py@ 308:3dee86b6c216

Last change on this file since 308:3dee86b6c216 was 308:3dee86b6c216, checked in by Stefano Rivera <stefano@…>, 9 years ago

Enemies leave corpses

File size: 8.2 KB
Line 
1import math
2
3import pymunk
4import pymunk.pygame_util
5
6from nagslang import render
7from nagslang.constants import (COLLISION_TYPE_ENEMY, COLLISION_TYPE_BOX,
8 ZORDER_MID)
9from nagslang.events import EnemyDeathEvent
10from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
11from nagslang.mutators import FLIP_H
12from nagslang.resources import resources
13
14
15def get_editable_enemies():
16 classes = []
17 for cls_name, cls in globals().iteritems():
18 if isinstance(cls, type) and issubclass(cls, Enemy):
19 if hasattr(cls, 'requires'):
20 classes.append((cls_name, cls))
21 return classes
22
23
24class Enemy(GameObject):
25 """A base class for mobile enemies"""
26 enemy_type = None # Image to use for dead Enemy
27
28 def __init__(self, space, world, position):
29 self._setup_physics(space, position)
30 self._setup_renderer()
31 self.health = 42
32
33 super(Enemy, self).__init__(
34 self._physicser, self.renderer)
35 self.zorder = ZORDER_MID
36 self.world = world
37
38 def _get_image(self, name, *transforms):
39 return resources.get_image('creatures', name, transforms=transforms)
40
41 def _setup_physics(self, space, position):
42 raise NotImplementedError
43
44 def _setup_renderer(self):
45 raise NotImplementedError
46
47 def attack(self):
48 raise NotImplementedError
49
50 @classmethod
51 def requires(cls):
52 return [("name", "string"), ("position", "coordinates")]
53
54 def lose_health(self, amount):
55 self.health -= amount
56 if self.health < 0:
57 self.physicser.remove_from_space()
58 self.remove = True
59 EnemyDeathEvent.post(self.physicser.position, 'A')
60
61
62class DeadEnemy(GameObject):
63 def __init__(self, space, world, position, enemy_type='A'):
64 body = make_body(10, 10000, position, damping=0.5)
65 self.shape = pymunk.Poly(
66 body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
67 self.shape.friction = 0.5
68 self.shape.collision_type = COLLISION_TYPE_BOX
69 super(DeadEnemy, self).__init__(
70 SingleShapePhysicser(space, self.shape),
71 render.ImageRenderer(resources.get_image(
72 'creatures', 'alien_%s_dead.png' % enemy_type)),
73 )
74
75 @classmethod
76 def requires(cls):
77 return [("name", "string"), ("position", "coordinates")]
78
79
80class PatrollingAlien(Enemy):
81 is_moving = True # Always walking.
82 enemy_type = 'A'
83
84 def __init__(self, space, world, position, end_position):
85 # An enemy that patrols between the two points
86 super(PatrollingAlien, self).__init__(space, world, position)
87 self._start_pos = position
88 self._end_pos = end_position
89 self._direction = 'away'
90
91 def _setup_physics(self, space, position):
92 self._body = make_body(10, pymunk.inf, position, 0.8)
93
94 self._shape = pymunk.Circle(self._body, 30)
95
96 self._shape.elasticity = 1.0
97 self._shape.friction = 0.05
98 self._shape.collision_type = COLLISION_TYPE_ENEMY
99 self._physicser = SingleShapePhysicser(space, self._shape)
100 self.impulse_factor = 50
101 self.angle = 0
102
103 def _setup_renderer(self):
104 self.renderer = render.FacingSelectionRenderer({
105 'left': render.TimedAnimatedRenderer(
106 [self._get_image('alien_A_1.png'),
107 self._get_image('alien_A_2.png')], 3),
108 'right': render.TimedAnimatedRenderer(
109 [self._get_image('alien_A_1.png', FLIP_H),
110 self._get_image('alien_A_2.png', FLIP_H)], 3),
111 })
112
113 def get_render_angle(self):
114 # No image rotation when rendering, please.
115 return 0
116
117 def get_facing_direction(self):
118 # Enemies can face left or right.
119 if - math.pi / 2 < self.angle <= math.pi / 2:
120 return 'right'
121 else:
122 return 'left'
123
124 def _switch_direction(self):
125 if self._direction == 'away':
126 self._direction = 'towards'
127 else:
128 self._direction = 'away'
129
130 def set_direction(self, dx, dy):
131 self.angle = pymunk.Vec2d((dx, dy)).angle
132 self._body.apply_impulse(
133 (dx * self.impulse_factor, dy * self.impulse_factor))
134
135 def update(self, seconds):
136 # Calculate the step every frame
137 if self._direction == 'away':
138 target = self._end_pos
139 else:
140 target = self._start_pos
141 x_step = 0
142 y_step = 0
143 if (target[0] < self._body.position[0]):
144 x_step = max(-1, target[0] - self._body.position[0])
145 elif (target[0] > self._body.position[0]):
146 x_step = min(1, target[0] - self._body.position[0])
147 if abs(x_step) < 0.5:
148 x_step = 0
149 if (target[1] < self._body.position[1]):
150 y_step = max(-1, target[1] - self._body.position[1])
151 elif (target[1] > self._body.position[1]):
152 y_step = min(1, target[1] - self._body.position[1])
153 if abs(y_step) < 0.5:
154 y_step = 0
155 if abs(x_step) < 1 and abs(y_step) < 1:
156 self._switch_direction()
157 self.set_direction(x_step, y_step)
158 super(PatrollingAlien, self).update(seconds)
159
160 def collide_with_protagonist(self, protagonist):
161 protagonist.lose_health(15)
162
163 def hit(self, weapon):
164 self.lose_health(weapon.damage)
165
166 @classmethod
167 def requires(cls):
168 return [("name", "string"), ("position", "coordinates"),
169 ("end_position", "coordinates")]
170
171
172class ChargingAlien(Enemy):
173 # Simplistic charging of the protagonist
174 is_moving = False
175 enemy_type = 'B'
176
177 def __init__(self, space, world, position, attack_range=100):
178 super(ChargingAlien, self).__init__(space, world, position)
179 self._range = attack_range
180
181 def _setup_physics(self, space, position):
182 self._body = make_body(100, pymunk.inf, position, 0.8)
183
184 self._shape = pymunk.Circle(self._body, 30)
185
186 self._shape.elasticity = 1.0
187 self._shape.friction = 0.05
188 self._shape.collision_type = COLLISION_TYPE_ENEMY
189 self._physicser = SingleShapePhysicser(space, self._shape)
190 self.impulse_factor = 300
191 self.angle = 0
192
193 def _setup_renderer(self):
194 self.renderer = render.FacingSelectionRenderer({
195 'left': render.TimedAnimatedRenderer(
196 [self._get_image('alien_B_1.png'),
197 self._get_image('alien_B_2.png')], 3),
198 'right': render.TimedAnimatedRenderer(
199 [self._get_image('alien_B_1.png', FLIP_H),
200 self._get_image('alien_B_2.png', FLIP_H)], 3),
201 })
202
203 def get_render_angle(self):
204 # No image rotation when rendering, please.
205 return 0
206
207 def get_facing_direction(self):
208 # Enemies can face left or right.
209 if - math.pi / 2 < self.angle <= math.pi / 2:
210 return 'right'
211 else:
212 return 'left'
213
214 def set_direction(self, dx, dy):
215 self.angle = pymunk.Vec2d((dx, dy)).angle
216 self._body.apply_impulse(
217 (dx * self.impulse_factor, dy * self.impulse_factor))
218
219 def update(self, seconds):
220 # Calculate the step every frame
221 # Distance to the protagonist
222 pos = self._body.position
223 target = self.world.protagonist.get_shape().body.position
224 if pos.get_distance(target) > self._range:
225 # stop
226 self.is_moving = False
227 return
228 self.is_moving = True
229 x_step = 0
230 y_step = 0
231 if (target[0] < pos[0]):
232 x_step = max(-1, target[0] - pos[0])
233 elif (target[0] > pos[0]):
234 x_step = min(1, target[0] - pos[0])
235 if abs(x_step) < 0.5:
236 x_step = 0
237 if (target[1] < pos[1]):
238 y_step = max(-1, target[1] - pos[1])
239 elif (target[1] > pos[1]):
240 y_step = min(1, target[1] - pos[1])
241 if abs(y_step) < 0.5:
242 y_step = 0
243 self.set_direction(x_step, y_step)
244 super(ChargingAlien, self).update(seconds)
245
246 def collide_with_protagonist(self, protagonist):
247 protagonist.lose_health(20)
248
249 @classmethod
250 def requires(cls):
251 return [("name", "string"), ("position", "coordinates"),
252 ("attack_range", "distance")]
Note: See TracBrowser for help on using the repository browser.