source: nagslang/enemies.py@ 281:9b56e954c674

Last change on this file since 281:9b56e954c674 was 278:e72025e9aa07, checked in by Neil Muller <drnlmuller@…>, 8 years ago

simple slow charging alien

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