Mercurial > skaapsteker
view skaapsteker/sprites/base.py @ 296:15b2be883a40
Ancient and honorable tea ceremony.
author | Jeremy Thurgood <firxen@gmail.com> |
---|---|
date | Fri, 08 Apr 2011 22:29:49 +0200 |
parents | 95e2ef31e714 |
children | 78220c989e6a |
line wrap: on
line source
"""Basic sprite classes.""" import re 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 class GameSprite(Sprite): image_dir = 'sprites/' image_file = None def __init__(self, pos, **opts): Sprite.__init__(self) self._starting_tile_pos = pos self.setup_image_data(pos) self.setup(**opts) 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 facings = {} def __init__(self, pos, **opts): Sprite.__init__(self) self._animations = dict((k, []) for k, r in self.animation_regexes) self._frame = 0 self._tick = 0 # TODO: hack to show some animation; kill shortly 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)) 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 _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._tick > 10: self._tick = 0 self._frame += 1 force = False if self._animation == 'attacking': force = True self._update_image(force) self._tick += 1 class Monster(AnimatedGameSprite): collision_layer = MONSTER_LAYER collides_with = set([PC_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.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._tick == 1: # FIXME: This will need to change when AnimatedGameSprite changes # 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 self._tick == 5: # 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) def start_attack(self, player): if self._animation == 'attacking': return # We're already attacking elif self.attack_frame is not None: self._target = player self._old_state = self._animation self._old_facing = self.facing self._animation = 'attacking' self._tick = 1 self._frame = 0 # Start the attack from the beginning self._update_image(True) 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 NPC(AnimatedGameSprite): collision_layer = NPC_LAYER 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.dsm = dialogue.DSM(name, world, dsm, state) def player_action(self, player): OpenDialog.post(self) class Projectile(GameSprite): gravitates = False class Item(GameSprite): mobile = False gravitates = False collision_layer = NPC_LAYER portable = True actionable = True 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.item_state = getattr(self.world.items, self.name) def get_debug_color(self): if self.portable: return (240, 0, 240) return (0, 0, 240) def player_action(self, player): print "Player touched %s" % self player.take_item(self) class Geography(Sprite): mobile = False gravitates = False collides_with = set([PC_LAYER, MONSTER_LAYER, NPC_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)