# HG changeset patch # User Jeremy Thurgood # Date 1378460388 -7200 # Node ID a3f1b2f0e3fb8812cb012a26b0526ed1e93b025e # Parent 3dd32686dbc31e34bf8a4ff3f653f6a0d1f397af Physics-related cleanup. diff -r 3dd32686dbc3 -r a3f1b2f0e3fb nagslang/enemies.py --- a/nagslang/enemies.py Fri Sep 06 10:49:19 2013 +0200 +++ b/nagslang/enemies.py Fri Sep 06 11:39:48 2013 +0200 @@ -10,6 +10,7 @@ from nagslang.game_object import GameObject, SingleShapePhysicser, make_body from nagslang.mutators import FLIP_H from nagslang.resources import resources +from nagslang.utils import vec_with_length def get_editable_enemies(): @@ -21,29 +22,37 @@ return classes +def get_alien_image(enemy_type, suffix, *transforms): + image_name = 'alien_%s_%s.png' % (enemy_type, suffix) + return resources.get_image('creatures', image_name, transforms=transforms) + + class Enemy(GameObject): """A base class for mobile enemies""" - enemy_type = None # Image to use for dead Enemy + zorder = ZORDER_MID + enemy_type = None # Which set of images to use enemy_damage = None + health = None + impulse_factor = None def __init__(self, space, world, position): - self._setup_physics(space, position) - self._setup_renderer() - self.health = 42 + super(Enemy, self).__init__( + self.make_physics(space, position), self.make_renderer()) + self.world = world + self.angle = 0 - super(Enemy, self).__init__( - self._physicser, self.renderer) - self.zorder = ZORDER_MID - self.world = world - - def _get_image(self, name, *transforms): - return resources.get_image('creatures', name, transforms=transforms) - - def _setup_physics(self, space, position): + def make_physics(self, space, position): raise NotImplementedError - def _setup_renderer(self): - raise NotImplementedError + def make_renderer(self): + return render.FacingSelectionRenderer({ + 'left': render.TimedAnimatedRenderer( + [get_alien_image(self.enemy_type, '1'), + get_alien_image(self.enemy_type, '2')], 3), + 'right': render.TimedAnimatedRenderer( + [get_alien_image(self.enemy_type, '1', FLIP_H), + get_alien_image(self.enemy_type, '2', FLIP_H)], 3), + }) def attack(self): raise NotImplementedError @@ -62,6 +71,11 @@ self.remove = True EnemyDeathEvent.post(self.physicser.position, self.enemy_type) + def set_direction(self, dx, dy): + vec = vec_with_length((dx, dy), self.impulse_factor) + self.angle = vec.angle + self.physicser.apply_impulse(vec) + def collide_with_protagonist(self, protagonist): if self.enemy_damage is not None: protagonist.lose_health(self.enemy_damage) @@ -91,6 +105,8 @@ class PatrollingAlien(Enemy): is_moving = True # Always walking. enemy_type = 'A' + health = 42 + impulse_factor = 50 def __init__(self, space, world, position, end_position): # An enemy that patrols between the two points @@ -99,27 +115,13 @@ self._end_pos = end_position self._direction = 'away' - def _setup_physics(self, space, position): - self._body = make_body(10, pymunk.inf, position, 0.8) - - self._shape = pymunk.Circle(self._body, 30) - - self._shape.elasticity = 1.0 - self._shape.friction = 0.05 - self._shape.collision_type = COLLISION_TYPE_ENEMY - self._physicser = SingleShapePhysicser(space, self._shape) - self.impulse_factor = 50 - self.angle = 0 - - def _setup_renderer(self): - self.renderer = render.FacingSelectionRenderer({ - 'left': render.TimedAnimatedRenderer( - [self._get_image('alien_A_1.png'), - self._get_image('alien_A_2.png')], 3), - 'right': render.TimedAnimatedRenderer( - [self._get_image('alien_A_1.png', FLIP_H), - self._get_image('alien_A_2.png', FLIP_H)], 3), - }) + def make_physics(self, space, position): + body = make_body(10, pymunk.inf, position, 0.8) + shape = pymunk.Circle(body, 30) + shape.elasticity = 1.0 + shape.friction = 0.05 + shape.collision_type = COLLISION_TYPE_ENEMY + return SingleShapePhysicser(space, shape) def get_render_angle(self): # No image rotation when rendering, please. @@ -138,11 +140,6 @@ else: self._direction = 'away' - def set_direction(self, dx, dy): - self.angle = pymunk.Vec2d((dx, dy)).angle - self._body.apply_impulse( - (dx * self.impulse_factor, dy * self.impulse_factor)) - def update(self, dt): # Calculate the step every frame if self._direction == 'away': @@ -151,16 +148,16 @@ target = self._start_pos x_step = 0 y_step = 0 - if (target[0] < self._body.position[0]): - x_step = max(-1, target[0] - self._body.position[0]) - elif (target[0] > self._body.position[0]): - x_step = min(1, target[0] - self._body.position[0]) + if (target[0] < self.physicser.position[0]): + x_step = max(-1, target[0] - self.physicser.position[0]) + elif (target[0] > self.physicser.position[0]): + x_step = min(1, target[0] - self.physicser.position[0]) if abs(x_step) < 0.5: x_step = 0 - if (target[1] < self._body.position[1]): - y_step = max(-1, target[1] - self._body.position[1]) - elif (target[1] > self._body.position[1]): - y_step = min(1, target[1] - self._body.position[1]) + if (target[1] < self.physicser.position[1]): + y_step = max(-1, target[1] - self.physicser.position[1]) + elif (target[1] > self.physicser.position[1]): + y_step = min(1, target[1] - self.physicser.position[1]) if abs(y_step) < 0.5: y_step = 0 if abs(x_step) < 1 and abs(y_step) < 1: @@ -178,32 +175,20 @@ # Simplistic charging of the protagonist is_moving = False enemy_type = 'B' + health = 42 + impulse_factor = 300 def __init__(self, space, world, position, attack_range=100): super(ChargingAlien, self).__init__(space, world, position) self._range = attack_range - def _setup_physics(self, space, position): - self._body = make_body(100, pymunk.inf, position, 0.8) - - self._shape = pymunk.Circle(self._body, 30) - - self._shape.elasticity = 1.0 - self._shape.friction = 0.05 - self._shape.collision_type = COLLISION_TYPE_ENEMY - self._physicser = SingleShapePhysicser(space, self._shape) - self.impulse_factor = 300 - self.angle = 0 - - def _setup_renderer(self): - self.renderer = render.FacingSelectionRenderer({ - 'left': render.TimedAnimatedRenderer( - [self._get_image('alien_B_1.png'), - self._get_image('alien_B_2.png')], 3), - 'right': render.TimedAnimatedRenderer( - [self._get_image('alien_B_1.png', FLIP_H), - self._get_image('alien_B_2.png', FLIP_H)], 3), - }) + def make_physics(self, space, position): + body = make_body(100, pymunk.inf, position, 0.8) + shape = pymunk.Circle(body, 30) + shape.elasticity = 1.0 + shape.friction = 0.05 + shape.collision_type = COLLISION_TYPE_ENEMY + return SingleShapePhysicser(space, shape) def get_render_angle(self): # No image rotation when rendering, please. @@ -216,15 +201,10 @@ else: return 'left' - def set_direction(self, dx, dy): - self.angle = pymunk.Vec2d((dx, dy)).angle - self._body.apply_impulse( - (dx * self.impulse_factor, dy * self.impulse_factor)) - def update(self, dt): # Calculate the step every frame # Distance to the protagonist - pos = self._body.position + pos = self.physicser.position target = self.world.protagonist.get_shape().body.position if pos.get_distance(target) > self._range: # stop diff -r 3dd32686dbc3 -r a3f1b2f0e3fb nagslang/protagonist.py --- a/nagslang/protagonist.py Fri Sep 06 10:49:19 2013 +0200 +++ b/nagslang/protagonist.py Fri Sep 06 11:39:48 2013 +0200 @@ -1,6 +1,5 @@ import pymunk import pymunk.pygame_util -from pymunk.vec2d import Vec2d from nagslang import render from nagslang.constants import ( @@ -12,6 +11,7 @@ from nagslang.mutators import FLIP_H from nagslang.resources import resources from nagslang.events import ScreenChange +from nagslang.utils import vec_from_angle, vec_with_length class ProtagonistPhysicser(Physicser): @@ -170,10 +170,11 @@ return 0 def get_facing_direction(self): - # It's easier to work with a vector than an angle here. - vec = Vec2d.unit() - vec.angle = self.angle - # We probably don't have exactly -1, 0, or 1 here. + # Our angle is quantised to 45 degree intervals, so possible values for + # x and y in a unit vector are +/-(0, sqrt(2)/2, 1) with some floating + # point imprecision. Rounding will normalise these to (-1.0, 0.0, 1.0) + # which we can safely turn into integers and use as dict keys. + vec = vec_from_angle(self.angle) x = int(round(vec.x)) y = int(round(vec.y)) @@ -203,9 +204,9 @@ self.is_moving = False return self.is_moving = True - self.angle = pymunk.Vec2d((dx, dy)).angle - self.physicser.apply_impulse( - (dx * self.impulse_factor, dy * self.impulse_factor)) + vec = vec_with_length((dx, dy), self.impulse_factor) + self.angle = vec.angle + self.physicser.apply_impulse(vec) def set_position(self, position): self.physicser.position = position @@ -258,16 +259,12 @@ def shoot(self): if not self.has_item('gun'): return - vec = Vec2d.unit() - vec.angle = self.angle - vec.length = 1000 + vec = vec_from_angle(self.angle, 1000) FireEvent.post( self.physicser.position, vec, BULLET_DAMAGE, COLLISION_TYPE_PLAYER) def claw(self): - vec = Vec2d.unit() - vec.angle = self.angle - vec.length = 30 + vec = vec_from_angle(self.angle, 30) ClawEvent.post(self.physicser.position, vec, CLAW_DAMAGE) def in_wolf_form(self): diff -r 3dd32686dbc3 -r a3f1b2f0e3fb nagslang/utils.py --- a/nagslang/utils.py Fri Sep 06 10:49:19 2013 +0200 +++ b/nagslang/utils.py Fri Sep 06 11:39:48 2013 +0200 @@ -1,4 +1,5 @@ import pygame +from pymunk.vec2d import Vec2d def convert_colour(colour): @@ -9,3 +10,15 @@ if isinstance(colour, basestring): return pygame.Color(colour) raise ValueError() + + +def vec_from_angle(angle, length=1): + vec = Vec2d(length, 0) + vec.angle = angle + return vec + + +def vec_with_length(coords, length=1): + vec = Vec2d(coords) + vec.length = length + return vec