Mercurial > touhou
changeset 122:174324a4da51
Add support for launch animations! (Warning: slow :()
author | Thibaut Girka <thib@sitedethib.com> |
---|---|
date | Sat, 10 Sep 2011 01:26:30 +0200 |
parents | 1bc0ad774ed4 |
children | d1c82d43bbf3 |
files | data/ST/etama3.script eclviewer.py pytouhou/game/bullet.py pytouhou/game/bullettype.py pytouhou/game/game.py pytouhou/game/games.py pytouhou/game/sprite.py pytouhou/opengl/gamerenderer.py pytouhou/utils/interpolator.py pytouhou/vm/anmrunner.py |
diffstat | 10 files changed, 163 insertions(+), 98 deletions(-) [+] |
line wrap: on
line diff
--- a/data/ST/etama3.script +++ b/data/ST/etama3.script @@ -180,7 +180,7 @@ Instruction: 0 0 1 88 Instruction: 0 0 13 Instruction: 0 0 15 -# ARDCODED: cancel anim for bullet type 0 +# HARDCODED: cancel anim for bullet type 0 Script: 11 Instruction: 0 0 1 56 Instruction: 0 0 0
--- a/eclviewer.py +++ b/eclviewer.py @@ -19,7 +19,7 @@ import os from pytouhou.resource.loader import Loader from pytouhou.game.background import Background from pytouhou.opengl.gamerenderer import GameRenderer -from pytouhou.game.game import Game +from pytouhou.game.games import EoSDGame from pytouhou.game.player import Player @@ -27,7 +27,7 @@ def main(path, stage_num): resource_loader = Loader() resource_loader.scan_archives(os.path.join(path, name) for name in ('CM.DAT', 'ST.DAT')) - game = Game(resource_loader, [Player()], stage_num, 3, 16) + game = EoSDGame(resource_loader, [Player()], stage_num, 3, 16) # Load stage data stage = resource_loader.get_stage('stage%d.std' % stage_num)
--- a/pytouhou/game/bullet.py +++ b/pytouhou/game/bullet.py @@ -20,19 +20,14 @@ from pytouhou.game.sprite import Sprite class Bullet(object): - __slots__ = ('x', 'y', 'angle', 'speed', 'frame', 'grazed', - 'flags', 'attributes', 'anim_idx', 'sprite_idx_offset', 'player', - 'speed_interpolator', - '_game_state', '_sprite', '_anmrunner', - '_removed', '_launched') - - def __init__(self, pos, anim_idx, sprite_idx_offset, + def __init__(self, pos, type_idx, sprite_idx_offset, angle, speed, attributes, flags, player, game_state): self._game_state = game_state self._sprite = None self._anmrunner = None self._removed = False self._launched = False + self._bullet_type = game_state.bullet_types[type_idx] self.speed_interpolator = None self.frame = 0 @@ -40,101 +35,111 @@ class Bullet(object): self.player = player - self.anim_idx = anim_idx self.sprite_idx_offset = sprite_idx_offset - #TODO - #if flags & (2|4|8): - # if flags & 2: #TODO: Huh?! - # index = 14 - # elif flags & 4: - # index = 15 - # else: - # index = 19 - # self._sprite = Sprite() - # self._anmrunner = ANMRunner(self._game_state.resource_loader.get_anm_wrapper(('etama3.anm',)), #TODO - # index, self._sprite, 0) #TODO: offset - # self._anmrunner.run_frame() - self.flags = flags self.attributes = list(attributes) self.x, self.y = pos self.angle = angle self.speed = speed + dx, dy = cos(angle) * speed, sin(angle) * speed + self.delta = dx, dy - if flags & 1: - self.speed_interpolator = Interpolator((speed + 5.,)) - self.speed_interpolator.set_interpolation_start(0, (speed + 5.,)) - self.speed_interpolator.set_interpolation_end(16, (speed,)) - if flags & 448: - self.speed_interpolator = Interpolator((speed,)) - self.speed_interpolator.set_interpolation_start(0, (speed,)) - self.speed_interpolator.set_interpolation_end(attributes[0] - 1, (0,)) # TODO: really -1? Without, the new speed isn’t set. + #TODO + if flags & 14: + bt = self._bullet_type + if flags & 2: + index = bt.launch_anim2_index + launch_mult = bt.launch_anim_penalties[0] + elif flags & 4: + index = bt.launch_anim4_index + launch_mult = bt.launch_anim_penalties[1] + else: + index = bt.launch_anim8_index + launch_mult = bt.launch_anim_penalties[2] + self.launch_delta = dx * launch_mult, dy * launch_mult + self._sprite = Sprite() + self._anmrunner = ANMRunner(bt.anm_wrapper, + index, self._sprite, + bt.launch_anim_offsets[sprite_idx_offset]) + self._anmrunner.run_frame() + else: + self.launch() - - def get_player_angle(self): - return atan2(self.player.y - self.y, self.player.x - self.x) + self._sprite.angle = angle def is_visible(self, screen_width, screen_height): - if self._sprite: - tx, ty, tw, th = self._sprite.texcoords - if self._sprite.corner_relative_placement: - raise Exception #TODO - else: - tx, ty, tw, th = 0., 0., 0., 0. + tx, ty, tw, th = self._sprite.texcoords + if self._sprite.corner_relative_placement: + raise Exception #TODO max_x = tw / 2. max_y = th / 2. - min_x = -max_x - min_y = -max_y - if any((min_x > screen_width - self.x, + if any((max_x < self.x - screen_width, max_x < -self.x, - min_y > screen_height - self.y, + max_y < self.y - screen_height, max_y < -self.y)): return False return True - def set_anim(self, anim_idx=None, sprite_idx_offset=None): - if anim_idx is not None: - self.anim_idx = anim_idx - + def set_anim(self, sprite_idx_offset=None): if sprite_idx_offset is not None: self.sprite_idx_offset = sprite_idx_offset + bt = self._bullet_type self._sprite = Sprite() - anm_wrapper = self._game_state.resource_loader.get_anm_wrapper(('etama3.anm',)) #TODO - self._anmrunner = ANMRunner(anm_wrapper, self.anim_idx, + self._sprite.angle = self.angle + self._anmrunner = ANMRunner(bt.anm_wrapper, bt.anim_index, self._sprite, self.sprite_idx_offset) + self._anmrunner.run_frame() + + + def launch(self): + self._launched = True + self.update = self.update_full + self.set_anim() + if self.flags & 1: + self.speed_interpolator = Interpolator((self.speed + 5.,), 0, + (self.speed,), 16) def update(self): - if not self._sprite or self._sprite._removed: - self._launched = True - self.set_anim() + dx, dy = self.launch_delta + self.x += dx + self.y += dy + + self.frame += 1 + + if not self._anmrunner.run_frame(): + self.launch() + + def update_simple(self): + dx, dy = self.delta + self.x += dx + self.y += dy + + + def update_full(self): sprite = self._sprite if self._anmrunner is not None and not self._anmrunner.run_frame(): self._anmrunner = None - if sprite.automatic_orientation and sprite.angle != self.angle: - sprite.angle = self.angle - sprite._changed = True #TODO: flags x, y = self.x, self.y + dx, dy = self.delta if self.flags & 16: frame, count = self.attributes[0:2] length, angle = self.attributes[4:6] angle = self.angle if angle < -900.0 else angle #TODO: is that right? - dx = cos(self.angle) * self.speed + cos(angle) * length - dy = sin(self.angle) * self.speed + sin(angle) * length - self.speed = (dx ** 2 + dy ** 2) ** 0.5 - self.angle = atan2(dy, dx) + dx, dy = dx + cos(angle) * length, dy + sin(angle) * length + self.delta = dx, dy if self.frame == frame: #TODO: include last frame, or not? if count > 0: self.attributes[1] -= 1 @@ -147,7 +152,12 @@ class Bullet(object): acceleration, angular_speed = self.attributes[4:6] self.speed += acceleration self.angle += angular_speed - if self.frame != 0 and self.frame % frame == 0: + dx, dy = cos(self.angle) * self.speed, sin(self.angle) * self.speed + self.delta = dx, dy + sprite.angle = self.angle + if sprite.automatic_orientation: + sprite._changed = True + if self.frame % frame == 0: if count > 0: self.attributes[1] -= 1 else: @@ -156,31 +166,38 @@ class Bullet(object): #TODO: check frame, count = self.attributes[0:2] angle, speed = self.attributes[4:6] - if self.frame != 0 and self.frame % frame == 0: + if self.frame % frame == 0: count = count - 1 if self.flags & 64: - self.angle = self.angle + angle + self.angle += angle elif self.flags & 128: - self.angle = self.get_player_angle() + angle + self.angle = atan2(self.player.y - y, self.player.x - x) + angle elif self.flags & 256: self.angle = angle - if count: - self.speed_interpolator.set_interpolation_start(self.frame, (speed,)) - self.speed_interpolator.set_interpolation_end(self.frame + frame - 1, (0,)) # TODO: really -1? Without, the new speed isn’t set. + dx, dy = cos(self.angle) * speed, sin(self.angle) * speed + self.delta = dx, dy + sprite.angle = self.angle + if sprite.automatic_orientation: + sprite._changed = True + + if count > 0: + self.speed_interpolator = Interpolator((speed,), self.frame, + (0.,), self.frame + frame - 1) else: self.flags &= ~448 self.speed = speed self.attributes[1] = count #TODO: other flags + elif not self.speed_interpolator and self._anmrunner is None: + self.update = self.update_simple if self.speed_interpolator: self.speed_interpolator.update(self.frame) self.speed, = self.speed_interpolator.values - dx, dy = cos(self.angle) * self.speed, sin(self.angle) * self.speed self.x, self.y = x + dx, y + dy self.frame += 1
new file mode 100644 --- /dev/null +++ b/pytouhou/game/bullettype.py @@ -0,0 +1,14 @@ +class BulletType(object): + def __init__(self, anm_wrapper, anim_index, cancel_anim_index, + launch_anim2_index, launch_anim4_index, launch_anim8_index, + launch_anim_penalties=(0.5, 0.4, 1./3.), + launch_anim_offsets=(0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0)): + self.anm_wrapper = anm_wrapper + self.anim_index = anim_index + self.cancel_anim_index = cancel_anim_index + self.launch_anim2_index = launch_anim2_index + self.launch_anim4_index = launch_anim4_index + self.launch_anim8_index = launch_anim8_index + self.launch_anim_penalties = launch_anim_penalties + self.launch_anim_offsets = launch_anim_offsets +
--- a/pytouhou/game/game.py +++ b/pytouhou/game/game.py @@ -22,10 +22,12 @@ from pytouhou.game.enemy import Enemy class GameState(object): __slots__ = ('resource_loader', 'bullets', 'players', 'rank', 'difficulty', 'frame', - 'stage', 'boss', 'prng') - def __init__(self, resource_loader, players, stage, rank, difficulty): + 'stage', 'boss', 'prng', 'bullet_types') + def __init__(self, resource_loader, players, stage, rank, difficulty, bullet_types): self.resource_loader = resource_loader + self.bullet_types = bullet_types + self.bullets = [] self.stage = stage @@ -39,8 +41,8 @@ class GameState(object): class Game(object): - def __init__(self, resource_loader, players, stage, rank, difficulty): - self.game_state = GameState(resource_loader, players, stage, rank, difficulty) + def __init__(self, resource_loader, players, stage, rank, difficulty, bullet_types): + self.game_state = GameState(resource_loader, players, stage, rank, difficulty, bullet_types) self.enemies = []
new file mode 100644 --- /dev/null +++ b/pytouhou/game/games.py @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +from pytouhou.game.game import Game +from pytouhou.game.bullettype import BulletType + +class EoSDGame(Game): + def __init__(self, resource_loader, players, stage, rank, difficulty): + etama3 = resource_loader.get_anm_wrapper(('etama3.anm',)) + etama4 = resource_loader.get_anm_wrapper(('etama4.anm',)) + bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16), + BulletType(etama3, 1, 12, 17, 18, 19), + BulletType(etama3, 2, 12, 17, 18, 19), + BulletType(etama3, 3, 12, 17, 18, 19), + BulletType(etama3, 4, 12, 17, 18, 19), + BulletType(etama3, 5, 12, 17, 18, 19), + BulletType(etama3, 6, 13, 20, 20, 20), + BulletType(etama3, 7, 13, 20, 20, 20), + BulletType(etama3, 8, 13, 20, 20, 20), + BulletType(etama4, 0, 1, 2, 2, 2)] + + Game.__init__(self, resource_loader, players, stage, rank, difficulty, bullet_types)
--- a/pytouhou/game/sprite.py +++ b/pytouhou/game/sprite.py @@ -61,23 +61,23 @@ class Sprite(object): def fade(self, duration, alpha, formula): if not self.fade_interpolator: - self.fade_interpolator = Interpolator((self.alpha,), formula) - self.fade_interpolator.set_interpolation_start(self.frame, (self.alpha,)) - self.fade_interpolator.set_interpolation_end(self.frame + duration - 1, (alpha,)) + self.fade_interpolator = Interpolator((self.alpha,), self.frame, + (alpha,), self.frame + duration, + formula) def scale_in(self, duration, sx, sy, formula): if not self.scale_interpolator: - self.scale_interpolator = Interpolator(self.rescale, formula) - self.scale_interpolator.set_interpolation_start(self.frame, self.rescale) - self.scale_interpolator.set_interpolation_end(self.frame + duration - 1, (sx, sy)) + self.scale_interpolator = Interpolator(self.rescale, self.frame, + (sx, sy), self.frame + duration, + formula) def move_in(self, duration, x, y, z, formula): if not self.offset_interpolator: - self.offset_interpolator = Interpolator(self.dest_offset, formula) - self.offset_interpolator.set_interpolation_start(self.frame, self.dest_offset) - self.offset_interpolator.set_interpolation_end(self.frame + duration - 1, (x, y, z)) + self.offset_interpolator = Interpolator(self.dest_offset, self.frame, + (x, y, z), self.frame + duration, + formula) def update_orientation(self, angle_base=0., force_rotation=False):
--- a/pytouhou/opengl/gamerenderer.py +++ b/pytouhou/opengl/gamerenderer.py @@ -55,7 +55,7 @@ class GameRenderer(pyglet.window.Window) glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) - pyglet.clock.schedule_interval(self.update, 1./120) + pyglet.clock.schedule_interval(self.update, 1./60) pyglet.app.run()
--- a/pytouhou/utils/interpolator.py +++ b/pytouhou/utils/interpolator.py @@ -14,12 +14,13 @@ class Interpolator(object): - def __init__(self, values=(), formula=None): + __slots__ = ('values', 'start_values', 'end_values', 'start_frame', 'end_frame', '_frame', '_formula') + def __init__(self, values=(), start_frame=0, end_values=(), end_frame=0, formula=None): self.values = tuple(values) self.start_values = tuple(values) - self.end_values = tuple(values) - self.start_frame = 0 - self.end_frame = 0 + self.end_values = tuple(end_values) + self.start_frame = start_frame + self.end_frame = end_frame self._frame = 0 self._formula = formula or (lambda x: x) @@ -50,8 +51,8 @@ class Interpolator(object): self._frame = frame if frame >= self.end_frame - 1: #XXX: skip the last interpolation step # This bug is replicated from the original game - self.values = tuple(self.end_values) - self.start_values = tuple(self.end_values) + self.values = self.end_values + self.start_values = self.end_values self.start_frame = frame else: coeff = self._formula(float(frame - self.start_frame) / float(self.end_frame - self.start_frame))
--- a/pytouhou/vm/anmrunner.py +++ b/pytouhou/vm/anmrunner.py @@ -41,14 +41,13 @@ class ANMRunner(object): def run_frame(self): - if self._sprite._removed: + if not self._running: return False + sprite = self._sprite + while self._running: - try: - frame, instr_type, args = self.script[self.instruction_pointer] - except IndexError: - return False + frame, opcode, args = self.script[self.instruction_pointer] if frame > self.frame: break @@ -57,16 +56,15 @@ class ANMRunner(object): if frame == self.frame: try: - callback = self._handlers[instr_type] + callback = self._handlers[opcode] except KeyError: logger.warn('unhandled opcode %d (args: %r)', instr_type, args) else: callback(self, *args) - self._sprite._changed = True + sprite._changed = True self.frame += 1 # Update sprite - sprite = self._sprite sprite.frame += 1 if sprite.rotations_speed_3d != (0., 0., 0.): @@ -102,7 +100,7 @@ class ANMRunner(object): @instruction(0) def remove(self): self._sprite._removed = True - self._running = True + self._running = False @instruction(1)