changeset 168:ce8d4fc3baf4

A patrolling alien
author Neil Muller <drnlmuller@gmail.com>
date Tue, 03 Sep 2013 14:39:38 +0200
parents bb297f3f99f4
children 8c3b88b34a9c
files nagslang/constants.py nagslang/enemies.py nagslang/level.py
diffstat 3 files changed, 135 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/nagslang/constants.py	Tue Sep 03 11:21:43 2013 +0200
+++ b/nagslang/constants.py	Tue Sep 03 14:39:38 2013 +0200
@@ -13,6 +13,7 @@
 COLLISION_TYPE_WALL = 2
 COLLISION_TYPE_SWITCH = 3
 COLLISION_TYPE_BOX = 4
+COLLISION_TYPE_ENEMY = 5
 
 SWITCH_PUSHERS = [COLLISION_TYPE_PLAYER, COLLISION_TYPE_BOX]
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/enemies.py	Tue Sep 03 14:39:38 2013 +0200
@@ -0,0 +1,116 @@
+import pymunk
+import pymunk.pygame_util
+
+from nagslang.constants import COLLISION_TYPE_ENEMY, ZORDER_MID
+from nagslang.game_object import (
+    GameObject, SingleShapePhysicser, AnimatedFacingImageRenderer, make_body)
+from nagslang.mutators import FLIP_H
+from nagslang.resources import resources
+
+
+class Enemy(GameObject):
+    """A base class for mobile enemies"""
+
+    def __init__(self, space, position):
+        self._setup_physics(space, position)
+        self._setup_renderer()
+
+        super(Enemy, self).__init__(
+            self._physicser, self.renderer)
+        self.zorder = ZORDER_MID
+
+    def _get_image(self, name, *transforms):
+        return resources.get_image('creatures', name, transforms=transforms)
+
+    def _setup_physics(self, space, position):
+        raise NotImplementedError
+
+    def _setup_renderer(self):
+        raise NotImplementedError
+
+    def attack(self):
+        raise NotImplementedError
+
+
+class PatrollingAlien(Enemy):
+
+    def __init__(self, space, position, end_position):
+        # An enemy that patrols between the two points
+        super(PatrollingAlien, self).__init__(space, position)
+        self._start_pos = position
+        self._end_pos = end_position
+        self._direction = 'away'
+
+    def _setup_physics(self, space, position):
+        self._body = make_body(5, pymunk.inf, position, 0.8)
+
+        self._shape = pymunk.Circle(self._body, 30)
+
+        self._shape.elasticity = 1.0
+        self._shape.friction = 10.0
+        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 = AnimatedFacingImageRenderer(
+            (self._get_image('alien_A_1.png'),
+             self._get_image('alien_A_1.png'),
+             self._get_image('alien_A_1.png'),
+             self._get_image('alien_A_1.png'),
+             self._get_image('alien_A_1.png'),
+             self._get_image('alien_A_1.png'),
+             self._get_image('alien_A_2.png'),
+             self._get_image('alien_A_2.png'),
+             self._get_image('alien_A_2.png')),
+            (self._get_image('alien_A_1.png', FLIP_H),
+             self._get_image('alien_A_1.png', FLIP_H),
+             self._get_image('alien_A_1.png', FLIP_H),
+             self._get_image('alien_A_1.png', FLIP_H),
+             self._get_image('alien_A_1.png', FLIP_H),
+             self._get_image('alien_A_1.png', FLIP_H),
+             self._get_image('alien_A_2.png', FLIP_H),
+             self._get_image('alien_A_2.png', FLIP_H),
+             self._get_image('alien_A_2.png', FLIP_H)))
+        # We're always animated
+        self.renderer.start()
+
+    def get_render_angle(self):
+        return self.angle
+
+    def _switch_direction(self):
+        if self._direction == 'away':
+            self._direction = 'towards'
+        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 animate(self):
+        # Calculate the step every frame
+        if self._direction == 'away':
+            target = self._end_pos
+        else:
+            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 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 abs(y_step) < 0.5:
+            y_step = 0
+        if abs(x_step) < 1 and abs(y_step) < 1:
+            self._switch_direction()
+        self.set_direction(x_step, y_step)
+        super(PatrollingAlien, self).animate()
--- a/nagslang/level.py	Tue Sep 03 11:21:43 2013 +0200
+++ b/nagslang/level.py	Tue Sep 03 14:39:38 2013 +0200
@@ -2,6 +2,7 @@
 import pygame.locals as pgl
 
 from nagslang import game_object as go
+from nagslang import enemies
 from nagslang.resources import resources
 from nagslang.yamlish import load, dump
 
@@ -30,6 +31,7 @@
         self._glue = go.PuzzleGlue()
         self._drawables = []
         self._game_objects = []
+        self._enemies = []
 
     def _get_data(self):
         # For overriding in tests.
@@ -43,6 +45,7 @@
             'base_tile': self.basetile,
             'polygons': self.polygons,
             'game_objects': self._game_objects,
+            'enemies': self._enemies,
         }, f)
 
     def load(self, space):
@@ -56,6 +59,9 @@
         self._game_objects = data.get('game_objects', [])
         for game_object_dict in self._game_objects:
             self._create_game_object(space, **game_object_dict)
+        self._enemies = data.get('enemies', [])
+        for enemy_dict in self._enemies:
+            self._create_enemy(space, **enemy_dict)
 
     def _create_game_object(self, space, classname, args, name=None):
         # We should probably build a registry of game objects or something.
@@ -73,6 +79,18 @@
         if name is not None:
             self._glue.add_component(name, gobj)
 
+    def _create_enemy(self, space, classname, args, name=None):
+        cls = getattr(enemies, classname)
+        if issubclass(cls, go.GameObject):
+            gobj = cls(space, *args)
+            self._drawables.append(gobj)
+        else:
+            raise TypeError(
+                "Expected a subclass of GameObject, got %s" % (
+                    classname))
+        if name is not None:
+            self._glue.add_component(name, gobj)
+
     def all_closed(self):
         """Check if all the polygons are closed"""
         closed = True