lordmauve / pgzero

A zero-boilerplate games programming framework for Python 3, based on Pygame.
https://pygame-zero.readthedocs.io/
GNU Lesser General Public License v3.0
527 stars 191 forks source link

add subrect property to Actor and 3 new class to do frame animation #278

Open yansnow78 opened 2 years ago

yansnow78 commented 2 years ago

add subrect property to Actor.

the goal of subrect is to be able to load a sprite from a sprite-sheet

this property can be a ZRect or pygame.Rect or an objet accepted by pygame.Rect constructor this property can be pass as parameter to the constructor or changed directly

it uses 2 caches for better perfomance: the images loader keep in cache all the subsurface accessed the _surface_cache in Actor keep in cache all the subsurface accessed with their transformed surface (opacted, rotated, ...)

alien_walk2 = (69, 193, 68, 93)
alien = Actor('alienspritesheet', anchor=("center", "center"), subrect=alien_walk2 )
alien_walk2 = (69, 193, 68, 93)
alien = Actor('alienspritesheet', anchor=("center", "center"))
alien.subrect=alien_walk2 

add 3 new classes to do frame animation

add 3 new classes FramesList, FrameBasicAnimation, FrameBasicAnimation feel free to change the names

class FramesList a class use to store frames used by a specific animation it has 3 functions to add frames, all those can be combined

_addFromSheet(self, sheet_name: str, cols: int, rows: int, cnt: int = 0, subrect: CanBeRect = None) to load some frames from a spritesheet

balleRotationFrames = FramesList()
balleRotationFrames.addFromSheet('ball_anim', 8, 4)

_addFromList(self, frameimages: Sequence[str]) to load some frames from a sequence of image names

walkingFrames = FramesList()
walkingFrames.addFromList(('alien_walk1', 'alien_walk2', 'alien_walk3'))

_add(self, image: str, subrect: CanBeRect = None): to load one frame from an image or a spritesheet

walkingFrames = FramesList()
walkingFrames.add('alien')
walkingFrames.add('alien_walk_sprite', (0, 0, 66, 92))
walkingFrames.add('alien_walk_sprite', (66, 0, 66, 92))
walkingFrames.add('alien_walk_sprite', (128, 0, 66, 92))

**class FrameBasicAnimation*** a class used to apply frame to an actor

class FrameBasicAnimation:
    def __init__(self, actor: Actor, frames: FramesList): ...
    def store_actor_image(self): ...
    def restore_actor_image(self): ...
    def sel_frame(self, idx): ...
    def next_frame(self) -> int: ...
    def prev_frame(self) -> int: ...

usage

walkingFrames = FramesList()
walkingFrames .addFromList(('alien_walk1', 'alien_walk2', 'alien_walk3'))
walkingAnim = FrameBasicAnimation(alien, walkingFrames)
...
walkingAnim.store_actor_image()
...
walkingAnim.next_frame()
...
walkingAnim.next_frame()
...
walkingAnim.restore_actor_image()

**class FrameAnimation*** a class used to apply frame to an actor base on fps

class FrameAnimation(FrameBasicAnimation):
    def __init__(self, actor: Actor, frames: FramesList, fps: int, restore_image_at_stop: bool = True)
    def animate(self, dt=-1) -> (int, int):
    def play(self, stop_at_loop: int = -1, stop_at_idx: int = -1, duration: float = 0,
             on_finished: Callable = None) -> bool:  ...
    def play_once(self, on_finished: Callable = None): ...
    def play_several(self, nbr_of_loops: int, on_finished: Callable = None): ...
    def play_during(self, duration: int, on_finished: Callable = None): ...
    def play_infinite(self): ...
    def pause(self): ...
    def unpause(self):  ...
    def stop(self, call_on_finished: bool = False): ...

usage: example 1 : # play a ball 3d rotation animation during 5 second

balle = Actor('ball', center=(200, 200))
...
balleRotationFrames = FramesList()
balleRotationFrames.addFromSheet('ball_anim', 8, 4)
...
balle.rotationAnim = FrameAnimation(balle, balleRotationFrames, fps=32)
....
# make the ball rotating during 5 second
balle.rotationAnim.play_during(5)

example 2 : # play 3 complete animation loop

balle.rotationAnim.play_several(3)

example 2 : # play infinite animation loop

balle.rotationAnim.play_infinite()

example 3 : # go to the correct next animation based on delta time

balle.rotationAnim.animate(dt)
yansnow78 commented 2 years ago

my proposal is based on pgzhelper originally there was just some extra function in pgzhelper.Actor but i think it's better to handle that outside Actor classes this is how it looks like in pgzhelper

@property
def images(self):
@images.setter
def images(self, images):
def load_images(self, sheet_name:str, cols:int, rows:int, cnt:int=0, subrect:pygame.Rect=None):
def sel_image(self, newimage:str):
def sel_image_idx(self, newimage_idx:int):
def next_image(self)-> int:
def animate(self):