Aller au contenu principal
Voir
Mystery Gift — Etape 10 / 11Créer des animations
1. Préparer les assets et les textes avant de créer une UI
2. Créer une scène UI
3. Créer une Composition
4. Créer un composant SpriteStack
5. Ajouter une couche de données avec PFM
6. Gérer les entrées clavier
7. Gérer la souris
8. Ajouter du texte multilingue
9. Utiliser les dialogues de confirmation
10. Créer des animations
11. Personnaliser GenericBase

Créer des animations

Ce guide s'appuie sur le guide dialogues. Réclamer un cadeau affiche pour l'instant un message simple ; ici, on le remplace par une bannière animée avec Yuki::Animation, et on branche un handler d'animation dans la Composition pour que les entrées soient bloquées pendant qu'elle joue.

Principe

PSDK utilise Yuki::Animation pour toutes les animations UI, présenté en détail sur la page Fondations de l'animation ; ici, on l'applique. L'idiome standard est ya = Yuki::Animation pour raccourcir les appels. Deux modes de composition existent :

  • ya.player(...) enchaîne les étapes les unes après les autres (séquentiel).
  • ya.parallel(...) joue les étapes en même temps (simultané).

Les briques de base :

  • ya.scalar(duration, obj, :setter=, from, to) interpole une valeur numérique.
  • ya.move(duration, sprite, x1, y1, x2, y2) déplace un sprite.
  • ya.send_command_to(obj, :method, *args) appelle une méthode à un point de la séquence.
  • ya.wait(duration) met en pause.

Les animations sont suivies par un Animation::Handler qui expose done?. Le done? de la Composition consulte le handler, donc la scène bloque les entrées pendant qu'une animation joue -- exactement la garde qu'on a branchée dans le clavier et la souris.

Le composant bannière

Chaque composant crée ses propres animations : il possède ses sprites et sait les animer, et renvoie l'animation sans la démarrer pour que la Composition puisse l'enchaîner. Créez scripts/20 MysteryGift/002 UI/001 ReceivedBanner.rb :

module UI
module MysteryGift
# Banner displayed when a gift is received (background + text with fade animation)
class ReceivedBanner < SpriteStack
# Banner dimensions
BANNER_WIDTH = 280
BANNER_HEIGHT = 28

# Create a new ReceivedBanner centered on screen
# @param viewport [Viewport]
def initialize(viewport)
banner_x = (320 - BANNER_WIDTH) / 2
banner_y = FRAME_Y + FRAME_HEIGHT / 2 - BANNER_HEIGHT / 2
super(viewport, banner_x, banner_y, default_cache: :interface)
create_background
create_text
self.visible = false
end

# Return the animation for showing the banner with a gift name
# @param gift_name [String] the text to display
# @return [Yuki::Animation::TimedAnimation]
def create_show_animation(gift_name)
ya = Yuki::Animation
return ya.player(
ya.send_command_to(@text, :text=, gift_name),
ya.send_command_to(self, :visible=, true),
ya.parallel(
ya.scalar(0.3, @background, :opacity=, 0, 255),
ya.scalar(0.3, @text, :opacity=, 0, 255)
),
ya.wait(1.0),
ya.parallel(
ya.scalar(0.3, @background, :opacity=, 255, 0),
ya.scalar(0.3, @text, :opacity=, 255, 0)
),
ya.send_command_to(self, :visible=, false)
)
end

private

# Create the banner background
def create_background
@background = add_background('mystery_gift/received_banner')
@background.z = 10
end

# Create the banner text
def create_text
@text = add_text(0, 6, BANNER_WIDTH, 16, '', 1, nil, color: 10)
@text.z = 11
end
end
end
end
  • Le composant est créé invisible (self.visible = false) ; l'animation contrôle sa visibilité.
  • create_show_animation renvoie l'animation sans la démarrer -- le patron clé. ya.player enchaîne : poser le texte, afficher la bannière, fondu d'entrée (fond et texte en parallel), pause d'une seconde, fondu de sortie, masquer.
  • Elle utilise les constantes FRAME_Y/FRAME_HEIGHT pour se centrer dans le cadre.

Le handler d'animation dans la Composition

Le handler vit dans la Composition, est mis à jour à chaque frame, et pilote done?. Modifiez scripts/20 MysteryGift/002 UI/999 Composition.rb.

Ajoutez le handler et la bannière dans le constructeur (gardez tout le reste) :

def initialize(viewport, mystery_data)
super(viewport, 0, 0, default_cache: :interface)
@mystery_data = mystery_data
@animation_handler = Yuki::Animation::Handler.new
@selected_index = 0
create_header
create_frame
create_no_gifts_text
create_gift_rows
create_received_banner
refresh
end

Changez update et done? pour utiliser le handler (elles étaient un no-op et true) :

# Update the composition each frame
def update
@animation_handler.update
end

# Tell if all animations are done
# @return [Boolean]
def done?
return @animation_handler.done?
end

Ajoutez la création de la bannière (privée) et la méthode publique qui joue l'animation :

# Start the gift received animation
# @param gift_name [String] the text to display
def start_gift_animation(gift_name)
return unless done?

animation = @received_banner.create_show_animation(gift_name)
full_animation = Yuki::Animation.player(animation, Yuki::Animation.send_command_to(self, :refresh))
full_animation.start
@animation_handler[:gift_received] = full_animation
end
# Create the received banner component
def create_received_banner
@received_banner = ReceivedBanner.new(@viewport)
push_sprite(@received_banner)
end
  • @animation_handler.update fait avancer les animations à chaque frame ; done? renvoie false tant qu'une joue, ce qui bloque maintenant les entrées via les gardes ajoutées plus tôt.
  • start_gift_animation demande son animation à la bannière, puis enchaîne un refresh après elle via Yuki::Animation.player(animation, ...send_command_to(self, :refresh)), la démarre, et la stocke sous une clé nommée dans le handler.
  • return unless done? empêche le chevauchement d'animations.

La jouer à la réclamation

Remplacez le message simple et le refresh dans claim_gift par la bannière. Modifiez scripts/20 MysteryGift/003 GamePlay/002 Logic.rb :

# Claim a valid gift after confirmation
# @param code [String] the valid code
# @param mystery_data [PFM::MysteryGift] the data object
def claim_gift(code, mystery_data)
choice = display_message_and_wait(gift_text(TEXT_CONFIRM), 1, gift_text(TEXT_YES), gift_text(TEXT_NO))
return unless choice == 0

gift_name = mystery_data.claim(code)
@composition.start_gift_animation(format(gift_text(TEXT_RECEIVED), gift_name))
end
  • Le dialogue de confirmation reste. Après la réclamation, au lieu d'un message bloquant + refresh manuel, on joue la bannière ; le refresh enchaîné met à jour la liste une fois la bannière terminée.

Autres briques

ya.scalar interpole n'importe quelle valeur numérique via un setter -- la brique la plus polyvalente :

ya = Yuki::Animation
fade_out = ya.scalar(0.5, @sprite, :opacity=, 255, 0)
bar_fill = ya.scalar(0.3, @bar.src_rect, :width=, old_width, new_width)
zoom_in = ya.scalar(0.2, @icon, :zoom_x=, 1.0, 1.2)

Pour une animation d'attente qui tourne en boucle (une flèche qui rebondit, une icône qui pulse), utilisez ya.timed_loop_animation :

ya = Yuki::Animation
loop_animation = ya.timed_loop_animation(0.5, [ya.shift(0.5, arrow, 2, 0, -2, 0)])
loop_animation.start
  • Une animation en boucle doit être démarrée manuellement et mise à jour via le handler ; elle ne bloque pas done?.

Essayez

Ouvrez la scène, appuyez sur A, tapez MASTERBALL, confirmez avec Oui :

GamePlay.open_mystery_gift

La bannière « Vous avez recu Master Ball ! » apparaît en fondu par-dessus le cadre, tient une seconde, disparaît en fondu, et la liste se rafraîchit avec la nouvelle ligne. Les entrées sont ignorées pendant que la bannière joue.

Conclusion

  • Les composants créent et renvoient leurs propres animations sans les démarrer ; la Composition les enchaîne et les exécute via un Animation::Handler.
  • Utilisez ya = Yuki::Animation ; ya.player pour le séquentiel, ya.parallel pour le simultané.
  • ya.scalar pour les nombres, ya.move pour le déplacement, ya.wait pour les pauses, ya.send_command_to pour les callbacks.
  • Stockez les animations avec @animation_handler[:key] = animation ; tant qu'une joue, done? est false et les entrées sont bloquées.
  • Vérifiez toujours done? avant de démarrer une nouvelle animation ; utilisez timed_loop_animation pour les effets d'attente répétés.