view nagslang/enemies.py @ 235:831e4f6b3d18

Add hints for the level editor
author Neil Muller <drnlmuller@gmail.com>
date Wed, 04 Sep 2013 21:16:09 +0200
parents 329b3044ddef
children 083053422a84
line wrap: on
line source

import math

import pymunk
import pymunk.pygame_util

from nagslang import render
from nagslang.constants import COLLISION_TYPE_ENEMY, ZORDER_MID
from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
from nagslang.mutators import FLIP_H
from nagslang.resources import resources


def get_editable_enemies():
    classes = []
    for cls_name, cls in globals().iteritems():
        if isinstance(cls, type) and issubclass(cls, Enemy):
            if hasattr(cls, 'requires'):
                classes.append((cls_name, cls))
    return classes


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

    @classmethod
    def requires(cls):
        return [("name", "string"), ("position", "coordinates")]


class PatrollingAlien(Enemy):
    is_moving = True  # Always walking.

    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(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 get_render_angle(self):
        # No image rotation when rendering, please.
        return 0

    def get_facing_direction(self):
        # Enemies can face left or right.
        if - math.pi / 2 < self.angle <= math.pi / 2:
            return 'right'
        else:
            return 'left'

    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()

    @classmethod
    def requires(cls):
        return [("name", "string"), ("position", "coordinates"),
                ("end_position", "coordinates")]