sword attack and take damage animations
This commit is contained in:
parent
5cf548fc2e
commit
ad0c68aedb
297
NS.py
297
NS.py
|
@ -35,8 +35,7 @@ from lib.pgfw.pgfw.GameChild import GameChild
|
|||
from lib.pgfw.pgfw.Sprite import Sprite, RainbowSprite
|
||||
from lib.pgfw.pgfw.Animation import Animation
|
||||
from lib.pgfw.pgfw.extension import (
|
||||
get_step, get_step_relative, get_delta, reflect_angle,
|
||||
render_box, get_hsla_color, get_hue_shifted_surface,
|
||||
get_step, get_step_relative, get_delta, reflect_angle, get_distance, render_box, get_hsla_color, get_hue_shifted_surface,
|
||||
get_color_swapped_surface, load_frames, fill_colorkey
|
||||
)
|
||||
from lib.pgfw.pgfw.gfx_extension import aa_filled_polygon
|
||||
|
@ -1296,8 +1295,6 @@ class Platform(GameChild):
|
|||
for light in self.lights:
|
||||
if light.glowing():
|
||||
self.get_display_surface().blit(self.glow_masks[light.position][light.glow_index], self.view.location, None, BLEND_RGBA_ADD)
|
||||
# for light in self.lights:
|
||||
# light.draw_glow()
|
||||
|
||||
|
||||
class Light(Animation):
|
||||
|
@ -1580,8 +1577,14 @@ class Timer(Meter):
|
|||
|
||||
|
||||
class Life(Meter):
|
||||
"""
|
||||
This class stores the state of the player's HP
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Initialize the Meter super class and graphics
|
||||
"""
|
||||
Meter.__init__(self, parent)
|
||||
dsr = self.get_display_surface().get_rect()
|
||||
background = load(self.get_resource("HUD_health.png")).convert()
|
||||
|
@ -1590,8 +1593,13 @@ class Life(Meter):
|
|||
self.setup(background, rect, 70, (255, 0, 0), 3, "scrapeIcons/scrapeIcons_03.png")
|
||||
|
||||
def decrease(self):
|
||||
"""
|
||||
Remove one health point. Set the current sword to attacking the chameleon. If this meter is depleted, remove a life
|
||||
and trigger the boss's battle finish method.
|
||||
"""
|
||||
self.get_audio().play_sfx("hurt")
|
||||
self.change(-1)
|
||||
self.get_game().boss.sword.attack(player=True)
|
||||
if self.amount <= 0:
|
||||
self.amount = 0
|
||||
self.parent.boys.change(-1)
|
||||
|
@ -1631,26 +1639,42 @@ class Boss(Animation):
|
|||
self.kool_man.load_from_path("Kool_man_waah.png", True)
|
||||
self.spoopy = Sprite(self)
|
||||
self.spoopy.load_from_path("Spoopy.png", True)
|
||||
# Set up alien sprite with boil and hurt animations
|
||||
self.visitor = Sprite(self, 42)
|
||||
self.visitor.add_frameset(name="hurt", switch=True)
|
||||
self.visitor.load_from_path("alienAnimations/alienHit", True)
|
||||
self.visitor.add_frameset(name="normal", switch=True)
|
||||
self.visitor.load_from_path("alienAnimations/alienBoil", True)
|
||||
for sprite in self.kool_man, self.visitor, self.spoopy:
|
||||
sprite.location.topleft = 207, 10
|
||||
self.health = Health(self)
|
||||
self.sword = Sword(self)
|
||||
self.register(self.brandish, self.cancel_flash, self.show_introduction_dialogue,
|
||||
self.show_end_dialogue, self.end_dialogue)
|
||||
self.register(self.brandish, self.cancel_flash, self.show_introduction_dialogue, self.show_end_dialogue, self.end_dialogue,
|
||||
self.end_player_damage, self.end_hit_animation)
|
||||
self.register(self.flash_player_damage, interval=100)
|
||||
self.kool_man.add_frameset([0], name="normal", switch=True)
|
||||
# Set alien's normal frameset to an idle animation
|
||||
self.visitor.add_frameset(list(range(0, len(self.visitor.frames))), name="normal", switch=True)
|
||||
self.spoopy.add_frameset([0], name="normal", switch=True)
|
||||
self.kool_man_avatar = load(self.get_resource("Kool_man_avatar.png")).convert()
|
||||
self.visitor_avatar = load(self.get_resource("Visitor_avatar.png")).convert()
|
||||
self.spoopy_avatar = load(self.get_resource("Spoopy_avatar.png")).convert()
|
||||
self.advance_prompt = AdvancePrompt(self)
|
||||
self.backgrounds = [Sprite(self), Sprite(self), Sprite(self)]
|
||||
self.backgrounds[0].load_from_path(self.get_resource("bg/bg001.png"))
|
||||
self.backgrounds[1].load_from_path(self.get_resource("bg/bg002.png"))
|
||||
self.backgrounds[2].load_from_path(self.get_resource("bg/bg003.png"))
|
||||
# Add background graphics and generate screen effects
|
||||
for ii, background in enumerate(self.backgrounds):
|
||||
background.add_frameset(name="normal", switch=True)
|
||||
background.load_from_path(f"bg/bg00{ii + 1}.png")
|
||||
background.add_frameset(name="inverted", switch=True)
|
||||
frame = pygame.Surface(background.frames[0].get_size())
|
||||
frame.fill((255, 255, 255))
|
||||
frame.blit(background.frames[0], (0, 0), None, pygame.BLEND_RGB_SUB)
|
||||
background.add_frame(frame)
|
||||
background.add_frameset(name="charging", switch=True)
|
||||
frame = background.frames[0].copy()
|
||||
mask = frame.copy()
|
||||
mask.fill((80, 80, 80))
|
||||
frame.blit(mask, (0, 0), None, pygame.BLEND_RGB_SUB)
|
||||
background.add_frame(frame)
|
||||
background.set_frameset("normal")
|
||||
self.countdown = Countdown(self)
|
||||
# Set the alien's arm to its own sprite
|
||||
self.alien_arm = Sprite(self, 42)
|
||||
|
@ -1710,8 +1734,8 @@ class Boss(Animation):
|
|||
def cancel_flash(self):
|
||||
if self.level_index == 0:
|
||||
self.kool_man.set_frameset("normal")
|
||||
elif self.level_index == 1:
|
||||
self.visitor.set_frameset("normal")
|
||||
# elif self.level_index == 1:
|
||||
# self.visitor.set_frameset("normal")
|
||||
elif self.level_index == 2:
|
||||
self.spoopy.set_frameset("normal")
|
||||
|
||||
|
@ -1775,6 +1799,11 @@ class Boss(Animation):
|
|||
self.brandish_complete = True
|
||||
self.countdown.reset()
|
||||
self.halt(self.end_dialogue)
|
||||
self.halt(self.flash_player_damage)
|
||||
self.halt(self.end_player_damage)
|
||||
for background in self.backgrounds:
|
||||
background.set_frameset("normal")
|
||||
self.halt(self.end_hit_animation)
|
||||
|
||||
def deactivate(self):
|
||||
self.active = False
|
||||
|
@ -1953,6 +1982,10 @@ class Boss(Animation):
|
|||
self.sword.reset()
|
||||
self.sword.play(self.sword.brandish, play_once=True)
|
||||
self.get_game().chemtrails.challenge()
|
||||
self.backgrounds[self.level_index].set_frameset("charging")
|
||||
# Set each boss to its normal frameset
|
||||
# for boss in (self.kool_man, self.visitor, self.spoopy):
|
||||
# boss.set_frameset("normal")
|
||||
|
||||
def choose_new_edge(self, edges):
|
||||
while True:
|
||||
|
@ -2026,8 +2059,8 @@ class Boss(Animation):
|
|||
def damage(self):
|
||||
if self.level_index == 0:
|
||||
self.kool_man.set_frameset(0)
|
||||
elif self.level_index == 1:
|
||||
self.visitor.set_frameset(0)
|
||||
# elif self.level_index == 1:
|
||||
# self.visitor.set_frameset("normal")
|
||||
elif self.level_index == 2:
|
||||
self.spoopy.set_frameset(0)
|
||||
|
||||
|
@ -2038,6 +2071,37 @@ class Boss(Animation):
|
|||
else:
|
||||
self.get_game().wipe.start(self.transition_to_battle)
|
||||
|
||||
def start_player_damage(self):
|
||||
"""
|
||||
Launch the flash player damage effect and queue it to end after a certain amount of time
|
||||
"""
|
||||
self.play(self.flash_player_damage)
|
||||
self.play(self.end_player_damage, play_once=True, delay=1500)
|
||||
|
||||
def flash_player_damage(self):
|
||||
"""
|
||||
Invert the screen to indicate player has taken damage.
|
||||
"""
|
||||
background = self.backgrounds[self.level_index]
|
||||
if background.get_current_frameset().name == "normal":
|
||||
background.set_frameset("inverted")
|
||||
else:
|
||||
background.set_frameset("normal")
|
||||
|
||||
def end_player_damage(self):
|
||||
"""
|
||||
Halt the flash player damage animation and return the background to normal
|
||||
"""
|
||||
self.halt(self.flash_player_damage)
|
||||
self.backgrounds[self.level_index].set_frameset("normal")
|
||||
|
||||
def end_hit_animation(self):
|
||||
"""
|
||||
Return boss's animation to normal
|
||||
"""
|
||||
for boss in (self.kool_man, self.visitor, self.spoopy):
|
||||
boss.set_frameset("normal")
|
||||
|
||||
def update(self):
|
||||
if self.active:
|
||||
self.backgrounds[self.level_index].update()
|
||||
|
@ -2144,92 +2208,150 @@ class Countdown(GameChild):
|
|||
|
||||
|
||||
class Sword(Animation):
|
||||
"""
|
||||
This class stores the graphics for the swords that appear as hovering sprites when the boss is attacking.
|
||||
"""
|
||||
|
||||
SHIFT = 15
|
||||
SPRITE_COUNT = 6
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Allocate and populate lists of sword animation frames. For each boss, create sword and spinning sword animation frames. For each
|
||||
animation, create six color versions, one for each orientation on the platform.
|
||||
"""
|
||||
Animation.__init__(self, parent)
|
||||
swords = self.swords = []
|
||||
#for root in "Sword_kool_man/", "Sword_visitor/", "Sword_spoopy/":
|
||||
# These will be three dimensional lists: swords[boss][position][frame]
|
||||
self.swords = []
|
||||
self.spinning_swords = []
|
||||
def rotate_sword(base, position):
|
||||
"""
|
||||
Rotate the sword based on the orientation of the board in this position
|
||||
"""
|
||||
if position == NS.N or position == NS.S:
|
||||
rotated = rotate(base, 270)
|
||||
elif position == NS.NW:
|
||||
rotated = rotate(base, 45)
|
||||
elif position == NS.NE:
|
||||
rotated = rotate(base, 310)
|
||||
else:
|
||||
rotated = base
|
||||
return rotated
|
||||
def fill_sword(surface, position, colors):
|
||||
"""
|
||||
Blend the platform colors into the grayscale base image
|
||||
"""
|
||||
rect = surface.get_rect()
|
||||
if position == NS.N or position == NS.S:
|
||||
surface.fill(colors[0], (0, 0, rect.w / 2, rect.h), BLEND_RGBA_MIN)
|
||||
surface.fill(colors[1], (rect.centerx, 0, rect.w / 2, rect.h), BLEND_RGBA_MIN)
|
||||
else:
|
||||
surface.fill(colors[0], (0, 0, rect.w, rect.h / 2), BLEND_RGBA_MIN)
|
||||
surface.fill(colors[1], (0, rect.centery, rect.w, rect.h / 2), BLEND_RGBA_MIN)
|
||||
# Open a folder of sword frames for each boss
|
||||
for root in "Sword_kool_man/", "local/test/", "Sword_spoopy/":
|
||||
swords.append([[], [], [], [], [], []])
|
||||
# Create a list of lists of sword frames, each list of sword frames corresponds to an orientation on the platform
|
||||
self.swords.append([[], [], [], [], [], []])
|
||||
self.spinning_swords.append([[], [], [], [], [], []])
|
||||
base_image_paths = sorted(iglob(join(self.get_resource(root), "*.png")))
|
||||
# If effects are turned off, use a single frame
|
||||
if not self.get_configuration("display", "effects"):
|
||||
base_image_paths = [base_image_paths[0]]
|
||||
# Create a square surface that can be used to blit the rotated sword centered so each frame is the same size
|
||||
size = max(*load(self.get_resource(base_image_paths[0])).get_size())
|
||||
background = pygame.Surface((size, size), pygame.SRCALPHA)
|
||||
# Create spinning sword effect by rotating the first base frame image, one for each platform position
|
||||
for position in range(6):
|
||||
base = rotate_sword(load(self.get_resource(base_image_paths[0])).convert_alpha(), position)
|
||||
# Blend the appropriate colors into the base image
|
||||
fill_sword(base, position, self.get_game().platform.get_color_pair_from_edge(position))
|
||||
# Create a frame for each angle and store it in the list
|
||||
for angle in range(0, 360, 36):
|
||||
rotated = rotate(base, angle)
|
||||
frame = background.copy()
|
||||
rect = rotated.get_rect()
|
||||
rect.center = frame.get_rect().center
|
||||
frame.blit(rotated, rect)
|
||||
self.spinning_swords[-1][position].append(frame)
|
||||
# Create frames for each platform orientation by rotating the base frame images and blending colors over them
|
||||
for frame_index, path in enumerate(base_image_paths):
|
||||
base = load(self.get_resource(path)).convert_alpha()
|
||||
# Iterate over all six orientation possibilities
|
||||
for position in range(6):
|
||||
if position == NS.N or position == NS.S:
|
||||
rotated = rotate(base, 270)
|
||||
elif position == NS.NW:
|
||||
rotated = rotate(base, 45)
|
||||
elif position == NS.NE:
|
||||
rotated = rotate(base, 310)
|
||||
else:
|
||||
rotated = base
|
||||
rotated = rotate_sword(base, position)
|
||||
surface = rotated.copy()
|
||||
colors = self.get_game().platform.get_color_pair_from_edge(position)
|
||||
color_a = Color(colors[0].r, colors[0].g, colors[0].b, 255)
|
||||
color_b = Color(colors[1].r, colors[1].g, colors[1].b, 255)
|
||||
# edit lightness to create glowing effect
|
||||
# Edit lightness to create glowing effect
|
||||
for color in (color_a, color_b):
|
||||
h, s, l, a = color.hsla
|
||||
l = 30 + int(abs((frame_index % 10) - 5) / 5 * 60)
|
||||
color.hsla = h, s, l, a
|
||||
rect = surface.get_rect()
|
||||
if position == NS.N or position == NS.S:
|
||||
surface.fill(color_a, (0, 0, rect.w / 2, rect.h), BLEND_RGBA_MIN)
|
||||
surface.fill(color_b, (rect.centerx, 0, rect.w / 2, rect.h), BLEND_RGBA_MIN)
|
||||
else:
|
||||
surface.fill(color_a, (0, 0, rect.w, rect.h / 2), BLEND_RGBA_MIN)
|
||||
surface.fill(color_b, (0, rect.centery, rect.w, rect.h / 2), BLEND_RGBA_MIN)
|
||||
swords[-1][position].append(surface)
|
||||
masks = self.masks = []
|
||||
for alpha in range(16, 255, 16):
|
||||
surface = Surface((300, 300), SRCALPHA)
|
||||
surface.fill((255, 255, 255, alpha))
|
||||
masks.append(surface)
|
||||
fill_sword(surface, position, (color_a, color_b))
|
||||
self.swords[-1][position].append(surface)
|
||||
self.register(self.brandish, self.lower, self.swab)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Halt animations, clear sprites
|
||||
"""
|
||||
self.halt(self.brandish)
|
||||
self.halt(self.lower)
|
||||
self.halt(self.swab)
|
||||
self.next_index = 0
|
||||
self.sprites = []
|
||||
self.active_sprite_index = 0
|
||||
self.attacking_player = False
|
||||
|
||||
def brandish(self):
|
||||
"""
|
||||
Get the next sword position to brandish from `self.parent`, create a sprite with a regular rotating frameset and spinning attack
|
||||
frameset, place it around a rectangle in the center of the screen, and store it in a list.
|
||||
"""
|
||||
level_index = self.parent.level_index
|
||||
position = self.parent.unbrandished.pop(0)
|
||||
offset = -self.SHIFT
|
||||
for ii, queued in enumerate(self.parent.queue):
|
||||
offset += self.SHIFT * (queued == position)
|
||||
if len(self.parent.unbrandished) == len(self.parent.queue) - ii - 1:
|
||||
break
|
||||
dsr = self.get_display_surface().get_rect()
|
||||
sprite = Sprite(self)
|
||||
for frame in self.swords[level_index][position]:
|
||||
# Add frames from storage for regular and attacking animations
|
||||
for frame in self.swords[level_index][position] + self.spinning_swords[level_index][position]:
|
||||
sprite.add_frame(frame)
|
||||
sprite.add_frameset(list(range(len(self.swords[level_index][position]), len(sprite.frames))), name="attack")
|
||||
# Add an explosion effect frameset
|
||||
sprite.add_frameset(name="explode", switch=True, framerate=70)
|
||||
surface = pygame.Surface((200, 200), pygame.SRCALPHA)
|
||||
thickness = 6
|
||||
color = pygame.Color(0, 0, 0)
|
||||
for radius in range(6, 100, 4):
|
||||
frame = surface.copy()
|
||||
ratio = float(radius - 6) / (100 - 6)
|
||||
color.hsla = 60 * (1 - ratio), 100, 50, 100
|
||||
pygame.draw.circle(frame, color, (100, 100), radius, max(1, int(thickness)))
|
||||
thickness -= .2
|
||||
sprite.add_frame(frame)
|
||||
sprite.add_frameset(list(range(0, len(self.swords[level_index][position]))), name="normal", switch=True)
|
||||
# Place on screen around an invisible rectangle in the center
|
||||
if position in (NS.W, NS.E):
|
||||
sprite.location.centery = dsr.centery - 80 + offset
|
||||
sprite.location.centery = dsr.centery - 80
|
||||
if position == NS.W:
|
||||
sprite.location.centerx = dsr.centerx - 100 - offset
|
||||
sprite.location.centerx = dsr.centerx - 100
|
||||
else:
|
||||
sprite.location.centerx = dsr.centerx + 100 - offset
|
||||
sprite.location.centerx = dsr.centerx + 100
|
||||
elif position in (NS.N, NS.S):
|
||||
sprite.location.centerx = dsr.centerx - offset
|
||||
sprite.location.centerx = dsr.centerx
|
||||
if position == NS.N:
|
||||
sprite.location.centery = dsr.centery - 150 + offset
|
||||
sprite.location.centery = dsr.centery - 150
|
||||
else:
|
||||
sprite.location.centery = dsr.centery + 20 + offset
|
||||
sprite.location.centery = dsr.centery + 20
|
||||
else:
|
||||
sprite.location.center = dsr.centerx - offset, dsr.centery - 80
|
||||
sprite.location.center = dsr.centerx, dsr.centery - 80
|
||||
self.sprites.append(sprite)
|
||||
self.get_audio().play_sfx("brandish")
|
||||
# Set brandish to complete on a delay
|
||||
self.play(self.lower, delay=400, play_once=True)
|
||||
# Brandish more swords
|
||||
if len(self.parent.unbrandished) > 0:
|
||||
self.play(self.brandish, delay=self.get_configuration("time", "sword-delay"), play_once=True)
|
||||
# Trigger boss's sword swab animation on a delay
|
||||
if level_index == 1:
|
||||
self.parent.alien_arm.unhide()
|
||||
self.parent.alien_arm.set_frameset(str(position))
|
||||
|
@ -2237,24 +2359,83 @@ class Sword(Animation):
|
|||
self.play(self.swab, delay=self.get_configuration("time", "sword-delay") - 42 * 4, play_once=True, position=position)
|
||||
|
||||
def swab(self, position):
|
||||
"""
|
||||
Activate boss's sword swab animation
|
||||
"""
|
||||
if self.parent.level_index == 1:
|
||||
self.parent.alien_arm.set_frameset(f"{position}_{self.parent.unbrandished[0]}")
|
||||
|
||||
def lower(self):
|
||||
"""
|
||||
Set brandish to complete.
|
||||
"""
|
||||
if len(self.parent.unbrandished) == 0:
|
||||
self.parent.brandish_complete = True
|
||||
self.parent.backgrounds[self.parent.level_index].set_frameset("normal")
|
||||
if self.parent.level_index == 1:
|
||||
self.parent.alien_arm.hide()
|
||||
|
||||
def block(self):
|
||||
if len(self.sprites):
|
||||
self.sprites.pop(0)
|
||||
"""
|
||||
Successfully block a sword move, setting the sprite to attacking and moving the active index.
|
||||
"""
|
||||
if self.sprites:
|
||||
self.attack(player=False)
|
||||
self.active_sprite_index += 1
|
||||
|
||||
def attack(self, player):
|
||||
"""
|
||||
Set the currently active sprite to its attacking animation.
|
||||
|
||||
@param player boolean that sets whether the attack is toward the player or boss
|
||||
"""
|
||||
center_save = self.sprites[self.active_sprite_index].location.center
|
||||
self.sprites[self.active_sprite_index].set_frameset("attack")
|
||||
self.sprites[self.active_sprite_index].location.center = center_save
|
||||
self.attacking_player = player
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Draw previously blocked swords and the boss's current move sword.
|
||||
"""
|
||||
Animation.update(self)
|
||||
# only draw the current sword in the queue
|
||||
if self.sprites:
|
||||
self.sprites[0].update()
|
||||
for ii, sprite in enumerate(self.sprites[:self.active_sprite_index + 1]):
|
||||
if sprite.get_current_frameset().name == "attack" and not sprite.is_hidden():
|
||||
if self.attacking_player and ii == self.active_sprite_index:
|
||||
scale = 1.1
|
||||
end = self.get_game().platform.view.location.center
|
||||
else:
|
||||
scale = 0.95
|
||||
end = self.get_game().boss.visitor.location.center
|
||||
for frame_index in sprite.get_current_frameset().order:
|
||||
width, height = sprite.frames[frame_index].get_size()
|
||||
if width < 800 and height < 800:
|
||||
scaled_width, scaled_height = int(width * scale), int(height * scale)
|
||||
sprite.frames[frame_index] = pygame.transform.scale(sprite.frames[frame_index], (scaled_width, scaled_height))
|
||||
if width >= 800 or width < 75 or height >= 800 or height < 75:
|
||||
if self.attacking_player and ii == self.active_sprite_index:
|
||||
sprite.hide()
|
||||
self.get_game().boss.start_player_damage()
|
||||
else:
|
||||
center_save = sprite.location.center
|
||||
sprite.set_frameset("explode")
|
||||
sprite.location.center = center_save
|
||||
if self.parent.level_index == 1:
|
||||
if self.parent.is_playing(self.parent.end_hit_animation, include_delay=True):
|
||||
self.parent.halt(self.parent.end_hit_animation)
|
||||
self.parent.visitor.set_frameset("hurt")
|
||||
self.parent.play(self.parent.end_hit_animation, play_once=True, delay=500)
|
||||
else:
|
||||
center_save = sprite.location.center
|
||||
sprite.get_current_frameset().measure_rect()
|
||||
sprite.update_location_size()
|
||||
sprite.location.center = center_save
|
||||
# sprite.move(*get_step(sprite.location.center, end, 4))
|
||||
elif sprite.get_current_frameset().name == "explode" and not sprite.is_hidden():
|
||||
if sprite.get_current_frameset().get_current_id() == sprite.get_current_frameset().order[-1]:
|
||||
sprite.hide()
|
||||
sprite.update()
|
||||
|
||||
|
||||
class Health(Meter):
|
||||
|
|
2
lib/pgfw
2
lib/pgfw
|
@ -1 +1 @@
|
|||
Subproject commit 6693ca45140aa67df7a6b0622a98d8d5b59079a3
|
||||
Subproject commit f5de024ef3627a20191b0a36308ac8ad8a4e6e6c
|
Loading…
Reference in New Issue