changeset 334:a3f1b2f0e3fb

Physics-related cleanup.
author Jeremy Thurgood <firxen@gmail.com>
date Fri, 06 Sep 2013 11:39:48 +0200
parents 3dd32686dbc3
children 78b805549b4e
files nagslang/enemies.py nagslang/protagonist.py nagslang/utils.py
diffstat 3 files changed, 80 insertions(+), 90 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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):
--- 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