Mercurial > skaapsteker
view skaapsteker/sprites/base.py @ 374:9530b8dbda5f
0 health is DEAD
author | Neil Muller <drnlmuller@gmail.com> |
---|---|
date | Sat, 09 Apr 2011 15:02:44 +0200 |
parents | a2efe5470b79 |
children | 4d6198b68cb9 |
line wrap: on
line source
"""Basic sprite classes.""" import re import time from pygame import Rect import pygame.transform from ..physics import Sprite from ..constants import Layers from ..engine import OpenDialog from .. import data from .. import dialogue TILE_SIZE = (64, 64) # Collision Layers (values are ids not numbers) PC_LAYER = 0 MONSTER_LAYER = 1 NPC_LAYER = 2 PROJECTILE_LAYER = 3 class GameSprite(Sprite): image_dir = 'sprites/' image_file = None def __init__(self, pos, **opts): Sprite.__init__(self) self._starting_tile_pos = pos self.setup(**opts) self.setup_image_data(pos) def setup(self): pass def setup_image_data(self, pos): self.image = data.load_image(self.image_dir + self.image_file) self.rect = self.image.get_rect(midbottom=(pos[0]*TILE_SIZE[0]+TILE_SIZE[0]/2, (pos[1]+1)*TILE_SIZE[1])) self.collide_rect = self.rect.move(0, 0) class AnimatedGameSprite(Sprite): # folder for animation files, e.g. sprites/foo image_dir = None # first item is the starting animation animation_regexes = [ # TODO: swap back once we know how to swap ("running", r"^.*_\d+.png$"), ("standing", r"^.*_standing.png$"), ("attacking", r"^.*_attacking.png$"), ] wants_updates = True frame_pause = 0.1 # default time between animation frames facings = {} def __init__(self, pos, **opts): Sprite.__init__(self) self._animations = dict((k, []) for k, r in self.animation_regexes) self._frame = 0 self._last_time = 0 self._animation = self.animation_regexes[0][0] if self.facings and self._animation in self.facings: self.facing = self.facings[self._animation][0][0] else: self.facing = None for image in data.get_files(self.image_dir): for name, pattern in self.animation_regexes: if re.match(pattern, image): img = data.load_image("%s/%s" % (self.image_dir, image)) if self.facings and name in self.facings: if not self._animations[name]: self._animations[name] = dict((k, []) for k, t in self.facings[name]) for facing, transform in self.facings[name]: if transform: mod_img = transform(img) else: mod_img = img collide_rect = mod_img.get_bounding_rect(1).inflate(-2,-2) self._animations[name][facing].append((mod_img, collide_rect)) else: collide_rect = img.get_bounding_rect(1).inflate(-2,-2) self._animations[name].append((img, collide_rect)) self.collide_rect = Rect((0, 0), (2, 2)) if isinstance(pos, pygame.Rect): self.collide_rect.midbottom = pos.midbottom else: self.collide_rect.midbottom = (pos[0]*TILE_SIZE[0]+TILE_SIZE[0]/2, (pos[1]+1)*TILE_SIZE[1]) self._update_image() self.setup(**opts) def setup(self): pass def _update_image(self, force=False): if self.facing: images = self._animations[self._animation][self.facing] else: images = self._animations[self._animation] if self._frame >= len(images): self._frame = 0 cand_image, cand_collide_rect = images[self._frame] cand_collide_rect = cand_collide_rect.move(0, 0) # copy collide rect before we move it cur_pos = self.collide_rect.midbottom cand_rect = cand_image.get_rect() cand_rect_offset = cand_rect.centerx - cand_collide_rect.centerx, cand_rect.bottom - cand_collide_rect.bottom cand_rect.midbottom = cur_pos[0] + cand_rect_offset[0], cur_pos[1] + cand_rect_offset[1] cand_collide_rect.midbottom = cur_pos if not self.check_collide_rect(cand_collide_rect, cand_rect, cand_image) and not force: return self.image = cand_image self.collide_rect = cand_collide_rect self.rect = cand_rect self.rect_offset = cand_rect_offset self.init_pos() def update(self): if self._last_time is not None: if time.time() - self._last_time > self.frame_pause: self._frame += 1 self._last_time = time.time() force = False if self._animation == 'attacking': force = True self._update_image(force) else: self._last_time = time.time() class Monster(AnimatedGameSprite): collision_layer = MONSTER_LAYER collides_with = set([PC_LAYER, PROJECTILE_LAYER]) debug_color = (240, 120, 120) block = True attack_frame = None # Mark a spefici frame in the animatio n as when the attack lands attack_damage = 1 def __init__(self, pos, **opts): AnimatedGameSprite.__init__(self, pos, **opts) self.floor_rect = Rect(self.collide_rect.topleft, (self.collide_rect.width, 2)) self._layer = Layers.PLAYER self.health = 10 self._done_attack = False self.setup(**opts) def collided_player(self, player): print "%s collided with player" % self self.start_attack(player) def update(self): AnimatedGameSprite.update(self) if self._animation == 'attacking': if self._frame == 0 and self._done_attack: # We've just looped through the animation sequence self._animation = self._old_state self.facing = self._old_facing self._update_image(True) elif self._frame == self.attack_frame and not self._done_attack: # Attack the player self.do_attack() def do_attack(self): """Overriden by monster classes""" if self.check_collides(self._target): self._target.damage(self.attack_damage) self._done_attack = True def start_attack(self, player): if self._animation == 'attacking': return # We're already attacking elif self.attack_frame is not None: self._done_attack = False self._target = player self._old_state = self._animation self._old_facing = self.facing self._animation = 'attacking' self._frame = 0 # Start the attack from the beginning self._update_image(True) self._last_time = self._start_attack_time = time.time() else: player.damage(1) # collision damage def damage(self, damage): print 'Damaged by ', damage self.health -= damage print 'Monster health', self.health if self.health <= 0: self.kill() class PatrollingMonster(Monster): """Monster that collides with horizontal geography""" debug_color = (120, 240, 120) patrol_speed = (200, 0) def update(self): Monster.update(self) if self._animation == 'running': if self.facing == 'left': self.velocity = (-self.patrol_speed[0], 0) elif self.facing == 'right': self.velocity = self.patrol_speed def collided(self, other): Monster.collided(self, other) # Check if the object we've collided with is the same height our higher than us if other.block and other.collide_rect.bottom <= self.collide_rect.bottom: # Change direction if self.facing == 'left': self.facing = 'right' else: self.facing = 'left' class NPC(AnimatedGameSprite): collision_layer = NPC_LAYER collides_with = set([]) debug_color = (240, 240, 240) bounce_factor = (0, 0) # NPC's don't bounce by default block = False actionable = True def __init__(self, pos, **opts): AnimatedGameSprite.__init__(self, pos, **opts) self._layer = Layers.PLAYER def setup(self, name, world, dsm, state): self.name = name self.dsm = dialogue.DSM(name, world, dsm, state) def player_action(self, player): OpenDialog.post(self) class Projectile(AnimatedGameSprite): collision_layer = PROJECTILE_LAYER collides_with = set() gravitates = False DAMAGE = 10 PROJECTILE_SIZE = (0, 0) # pixels VELOCITY = (10, 10) # pixels/s def setup(self, direction, hits, **opts): super(Projectile, self).setup(**opts) self.facing = direction if isinstance(hits, tuple): self.hits = hits + (Geography,) else: self.hits = (hits, Geography) if self.facing == "left": shift = (-self.PROJECTILE_SIZE[0] / 2, self.PROJECTILE_SIZE[1]) dv = (-self.VELOCITY[0], self.VELOCITY[1]) else: shift = (self.PROJECTILE_SIZE[0] / 2, self.PROJECTILE_SIZE[1]) dv = (self.VELOCITY[0], self.VELOCITY[1]) self.rect.move_ip(shift) self.collide_rect.move_ip(shift) self.deltav(dv) def explode(self): self.kill() def collided(self, other): if not isinstance(other, self.hits): return if hasattr(other, 'damage'): other.damage(self.DAMAGE) self.explode() class Item(GameSprite): mobile = False gravitates = False actionable = True collision_layer = NPC_LAYER debug_color = (240, 0, 240) def __init__(self, pos, **opts): GameSprite.__init__(self, pos, **opts) self._layer = Layers.PLAYER def setup(self, name, world): self.name = name self.world = world self._me = getattr(self.world.items, self.name) def player_action(self, player): print "Player touched %s" % self player.take_item(self) def remove(self): self._me.level = '_limbo' self.kill() class Geography(Sprite): mobile = False gravitates = False collides_with = set([PC_LAYER, MONSTER_LAYER, NPC_LAYER, PROJECTILE_LAYER]) is_ground = True actionable = False bounce_factor = (0.0, 0.0) def __init__(self, pos, image): Sprite.__init__(self) self.tile_pos = pos self.image = image self.collide_rect = self.image.get_bounding_rect(1) self.floor_rect = Rect(self.collide_rect.topleft, (self.collide_rect.width, 2)) self.rect = self.image.get_rect() self.rect_offset = self.collide_rect.left - self.rect.left, self.rect.top - self.rect.top self.collide_rect.topleft = pos[0] * TILE_SIZE[0] + self.rect_offset[0], pos[1] * TILE_SIZE[1] + self.rect_offset[1] self.floor_rect.topleft = pos[0] * TILE_SIZE[0] + self.rect_offset[0], pos[1] * TILE_SIZE[1] + self.rect_offset[1] self.rect.topleft = pos[0] * TILE_SIZE[0], pos[1] * TILE_SIZE[1] def get_debug_color(self): if self.floor or self.block: return (240, 240, 0) return (0, 240, 0) class Doorway(GameSprite): mobile = False gravitates = False blocks = False actionable = True image_file = 'torii.png' def setup_image_data(self, pos): super(Doorway, self).setup_image_data(pos) self.image = pygame.transform.scale(self.image, self.image.get_rect().center) self.rect = self.image.get_rect(midbottom=self.rect.midbottom) self.collide_rect = self.rect def setup(self, facing, leadsto): self.facing = facing self.leadsto = leadsto print leadsto def player_action(self, player): print "Player touched %s" % self from .. import engine, levelscene engine.ChangeScene.post((levelscene.LevelScene, self.leadsto)) class StartingDoorway(Doorway): actionable = False def setup_image_data(self, pos): self.image = pygame.Surface((0, 0)) self.rect = self.image.get_rect(midbottom=(pos[0]*TILE_SIZE[0]+TILE_SIZE[0]/2, (pos[1]+1)*TILE_SIZE[1])) self.collide_rect = self.rect.move(0, 0) def setup(self, facing): Doorway.setup(self, facing, None) def find_sprite(descr, mod_name=None): """Create a sprite object from a dictionary describing it.""" descr = dict((str(k), v) for k, v in descr.items()) # convert unicode keys cls_name = descr.pop("type") if mod_name is None: mod_name, cls_name = cls_name.rsplit(".", 1) mod_name = ".".join(["skaapsteker.sprites", mod_name]) mod = __import__(mod_name, fromlist=[cls_name]) cls = getattr(mod, cls_name) return cls(**descr)