source: nagslang/enemies.py@ 307:c2bbb1e70d6f

Last change on this file since 307:c2bbb1e70d6f was 307:c2bbb1e70d6f, checked in by Neil Muller <drnlmuller@…>, 9 years ago

Rename animate to update and pass seconds, for future fun

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