Animation Roblox, sans interface
Première fois sur cette machine ? Lisez
LOCAL.mddans ce dossier. S'il n'existe pas, exécutez le skillroblox-skills-setup(ou copiezLOCAL.template.mdversLOCAL.mdet remplissez-le).LOCAL.mdcontient vos chemins et emplacements d'outils ; il vous appartient et n'est jamais partagé.
Autorisez l'animation en tant que données (une spécification JSON de poses dans le temps), cuisez-la en une véritable Action Blender, rendez-la, et regardez le résultat avant d'y croire.
La boucle est : auteur → rendre chaque image → tuiler en une planche contact → regarder → corriger → répéter. Tout existe ici parce qu'une étape de cette boucle a capturé un échec que tous les autres contrôles ont manqué.
Lisez d'abord ceci : le piège qui fait perdre le plus de temps
La plupart des rigs Roblox fournissent une Action Blender qui contient leur pose de repos. Blender réévalue cette Action pendant le rendu. Donc, si vous définissez des valeurs de pose puis rendez :
- chaque sonde numérique signale que votre pose s'est parfaitement appliquée
- chaque image rendue montre la pose par défaut du rig
- les données et l'image ne se réconcilient jamais, et il n'y a pas d'erreur
Effacer l'Action non plus ne sert à rien, parce que cette Action EST la pose de repos — effacez-la et les membres s'effondrent dans le torse.
La solution : insérez des IMAGES CLÉS dans cette même Action, puis utilisez frame_set() par image et rendez. L'animation est alors l'Action, rien ne peut l'écraser, les os non touchés conservent leurs valeurs de repos par extrapolation constante, et vous obtenez une véritable Action — ce qui est de toute façon nécessaire pour l'exportation Roblox. scripts/anim_render.py fait déjà cela.
Liste complète dans references/traps.md — 28 d'entre eux. Lisez-la avant de déboguer quoi que ce soit.
Animer en FK. Pas en IK.
Une KeyframeSequence Roblox est un arbre de rotations par joint, une par Motor6D. C'est du FK. Chaque rig R15 fournit un os de contrôle par joint pour correspondre. Pilotez le personnage avec des cibles IK et vous faites un aller-retour via un solveur que Roblox n'a jamais demandé, et vous payez le prix :
- les cibles IK de position seule laissent le ROULIS des membres libre, donc les pièces Roblox cubiques présentent la face que le solveur choisit et sautent entre les images. Sur un avatar texturé, cela se lit comme des couleurs qui changent instantanément — le symptôme qui rend une animation buguée
- le solveur saute entre des branches également bonnes d'image en image, donc les membres claquent alors que chaque image se vérifie parfaitement
- c'est lent
Le FK n'a pas de branches, pas de singularités, pas d'ambiguïté de rouli, est exactement 1:1 en degrés, et s'exporte directement vers Roblox.
La plupart des rigs sont livrés en mode IK, ce qui rend chaque os FK_* inerte. Basculez l'interrupteur — ne concluez pas que le FK est mort :
"properties": {"ARM_IK_FK.L": 0.0, "ARM_IK_FK.R": 0.0,
"LEG_IK_FK.L": 0.0, "LEG_IK_FK.R": 0.0}
Mesuré sur un rig R15 communautaire gratuit, et agréablement uniforme : chaque membre repose pointant droit vers le bas ; sur chaque os FK_* X négatif balance vers l'avant, Z positif balance vers la droite du personnage, 1:1 en degrés, identique des deux côtés — pas d'inversion miroir. Les angles enfants sont relatifs au parent.
La leçon générale, quel que soit le rig utilisé : si un os de contrôle est inerte, cherchez un interrupteur qui le désactive avant de l'abandonner. Les rigs de style Rigify R15 portent des propriétés personnalisées ARM_IK_FK / LEG_IK_FK qui par défaut sont à 1.0 (IK). Mettez-les à 0.0, clavetez-les (pour que frame_set ne puisse pas les remettre), et animez en FK. Les conventions de direction ci-dessus ont été mesurées sur un rig ; calibrate_rig.py et sweep_control.py les remesurent sur le vôtre, donc exécutez-les sur tout rig que vous n'avez pas utilisé. Le chemin .blend de votre rig va dans LOCAL.md.
N'utilisez IK que lorsqu'un membre doit atteindre un point mondial exact. Lisez ensuite les pièges 12–15.
Faites-le ressembler à Roblox, pas à la référence
- 30 FPS, et un mouvement de combat dure 0,3–0,7s. Le réglage par défaut de l'éditeur d'animations est 30fps. Le timing réel est bien trop lent pour un jeu. Prenez la mécanique du mouvement réel ; prenez le timing de Roblox.
- Cinq poses : inactif, anticipation, contact, suivi, récupération.
- Interpolation par segment —
"ease": "in"pour accélérer dans le coup,"out"pour se stabiliser. Un smoothstep fixe décélère dans chaque image clé, donc le coup ralentit jusqu'à s'arrêter à son propre cadre de contact et l'ensemble semble mou. - Exagérez. L'animation de combat réelle de Roblox va bien au-delà de la plage humaine et se lit sur la silhouette.
- Rendez en gris uni (
"plain": true). Une texture d'avatar chargée transforme un rig cubique en un fouillis illisible et vous finissez par déboguer le costume au lieu de la pose. Les animations Roblox réelles sont présentées sur un mannequin uni exactement pour cette raison.
references/pose-recipes.md contient les chiffres réels — des tables d'angle mesurées pour la garde, l'anticipation, la chambre, le contact et le suivi, le squelette de timing, et les deux règles qui prennent les gens au dépourvu (l'épaule + le coude doivent rester sous ~120 total ; un high kick a besoin de UpperLeg X au-delà de −120 ou la jambe fusionne avec le torse). Commencez par là et ajustez par rapport à l'aperçu. Ne les redérivez pas.
Soyez rapide — mesuré, pas deviné
La boucle entière se fait en secondes. Si une passe prend des minutes, quelque chose ne va pas. Mesuré sur un PC milieu de gamme, une animation de 24–36 images :
| étape | temps | notes |
|---|---|---|
| Blender charge le rig | 0,7s | le démarrage n'est jamais le problème |
| sonde numérique (carte de contrôle FK complète) | 0,1s | posez des questions au rig avec des chiffres |
rendu d'aperçu ("preview": true) | 4–6.5s | 1 vue, 320x240, 1 échantillon |
| rendu complet, 2 vues @ 640x560 | 9–17s | faites-le une fois, à la fin |
| encodage GIF | 0,3s | |
to_roblox.py → Luau | 0,1s | |
| construction + vérification dans Studio | un appel execute_luau |
Temps machine pour une animation terminée dans Studio : bien moins d'une minute. Ce qui coûte du temps, ce sont les itérations d'écriture, donc investissez-les dans l'aperçu et jamais dans le rendu complet. En partant de references/pose-recipes.md, un second mouvement devrait atterrir en 1–3 aperçus — le poing et la vague dans references/ étaient tous les deux bons du premier coup.
"report": true imprime la position de chaque joint à chaque battement : quand une pose semble fausse, il nomme le joint en une seule exécution au lieu de cinq passes de rendu à deviner.
Ne rendez pas pour répondre à une question à laquelle une sonde peut répondre.
Non-négociables
Chacun est ici parce que l'avoir sauté a livré quelque chose de cassé qui semblait bien :
- Ne faites jamais confiance à une exécution qui s'est terminée par 0.
anim_render.pyvérifie l'intégrité du rig par image, vérifie que les images ont été écrites, et se termine avec un code non nul sinon. - Regardez les images. Chaque passe.
- Avec une référence, construisez le côte à côte et jugez sur ça — jamais sur le rendu seul.
- Inspectez le GIF final lui-même, pas les PNG à partir desquels vous l'avez encodé.
- Ne relâchez jamais une tolérance pour passer un feu rouge au vert. Corrigez la spécification.
Flux de travail
1. Calibrer le rig (tout rig que vous n'avez pas utilisé)
blender --background <rig>.blend --enable-autoexec --python scripts/calibrate_rig.py
blender --background <rig>.blend --enable-autoexec --python scripts/sweep_control.py -- FK_UpperLeg.R:rot TORSO:rot
calibrate_rig.py signale l'armature de déformation vs contrôle, le piège de l'Action, l'état de l'interrupteur IK/FK, et quels contrôles bougent réellement quelque chose.
sweep_control.py donne la direction de chaque axe, le signe et la plage utilisable — il mesure la direction de l'os ainsi que la position, donc les contrôles de rotation seule ne sont pas manqués.
Si les os FK_* semblent inertes, c'est l'interrupteur IK/FK, pas un rig mort.
2. Écrire la spécification
fps, duration, w, h, margin, views, properties, plain, preview, report, et keyframes[] de {t, ease, bones: {Bone: {rot|loc|target|aim}}}.
rot est ce que vous voulez presque toujours. loc pour les contrôles sans effecteur final (ROOT). target / aim sont la porte de sortie IK — voir pièges 12–15.
Trois choses qui tournent mal à chaque fois si vous les oubliez :
- Les angles enfants FK se composent avec le parent. Épaule 50° + coude 70° place l'avant-bras à 120° de la verticale, pointant au-dessus de l'épaule. Gardez épaule + coude sous ~110° pour une garde.
- L'inclinaison et la torsion vont sur
UpTorso, qui pilote uniquement le haut du corps.TORSOpilote aussi les jambes, donc l'incliner fait basculer la figure et glisse le pied planté. Le rig amplifie aussi :X = +15fait basculer la tête de 23°. - Contre-rotationnez la cheville d'appui,
FK_Foot.X ≈ -(FK_UpperLeg.X + FK_LowerLeg.X), ou le pied s'incline avec la jambe et le personnage a l'air de se tenir sur une pente.
Voir references/example-fk-roundhouse.json (coup de pied de 0,8s) et references/example-fk-punch.json (cross de 0,5s), plus references/pose-recipes.md.
2a. SI UN ACCESSOIRE EST DANS LA MAIN, RECHERCHEZ COMMENT IL EST VRAIMENT TENU. D'ABORD.
Cette étape se produit AVANT d'écrire une image clé, et elle n'est pas optionnelle. Dès qu'un clip contient une arme ou un outil, « la pose se lit-elle » cesse d'être une question sur le rig et devient une question sur cet objet, qui est un fait du monde et pas quelque chose à déduire d'une matrice.
L'échec que cela existe pour empêcher, d'une construction réelle : une glaive a été créée avec sa tête VERS LE BAS à la hanche du porteur. Chaque étape en aval a alors fait son travail parfaitement — le moteur a reproduit la prise créée, la bande de pose correspondait à la planche contact image par image, erreur de prise 0,000, zéro échantillon de l'arme à l'intérieur du corps — et le jeu a expédié une arme d'hast portée à l'envers. Une pose erronée créée ici est invisible pour tous les contrôles après elle, parce que tous les contrôles après mesurent l'accord avec CELLE-CI. Quand un humain a enfin regardé, le verdict a été qu'elle était à l'envers. Ils avaient raison, et le coût a été une session entière en aval d'une décision prise en dix secondes ici.
Avant d'écrire :
- Recherchez comment la classe d'arme est portée et utilisée. Des photos de pratiquants, pas de concept art, pas d'autres jeux. Dix secondes de lecture battent quatre tours de devinettes sur des chiffres.
- Écrivez la réponse dans la spécification en tant que
_intent, comme une phrase sur l'arme : « tête en haut, hampe croisant l'épaule avant », « lame inclinée vers le haut et vers l'avant depuis une main abaissée ». Cette phrase est ce avec quoi la planche contact sera jugée — voir étape 4. - Vérifiez les plus évidentes de la liste ci-dessous avant de chercher une recherche.
| classe | au repos | en balancement |
|---|---|---|
| arme d'hast, glaive, naginata, lance | tête EN HAUT — portée sur l'épaule, plantée crosse au sol, ou en garde avec la lame à hauteur de poitrine ou plus. Jamais pendante aux pieds | la tête mène l'arc ; le hampe croise devant le corps, jamais dans le même plan vertical que le bras |
| épée | inclinée vers le haut et vers l'avant depuis une main abaissée, environ +35 degrés. Droite vers le bas se lit comme tombée | le tranchant mène ; le poignet n'est pas une charnière, tout le bras porte l'arc |
| hache, marteau | tête en haut ou sur l'épaule, le poids porté haut | la tête est la masse — elle arrive en dernier et elle arrive le plus fort |
| arme de poing, gantelet | porté, pas saisi ; une garde de boxeur, les deux poings en l'air | les hanches le poussent, pas l'épaule |
Tenez l'accessoire dans une pose que le propre corps du porteur pourrait adopter. Un transport à une main de quoi que ce soit de plus long que le personnage est grand ne se trouve pas dans le même plan que le bras, sinon ça clippe.
3. Rendre et vérifier
blender --background <rig>.blend --enable-autoexec --python scripts/anim_render.py -- spec.json out/
ffmpeg -start_number 0 -i "out/side_%03d.png" -vf "scale=240:-1,tile=6x6" sheet.png
La caméra se cadre sur les limites de l'animation entière et résout sa distance par rapport au champ de vision, donc rien ne sort du cadre ; margin (par défaut 1,10) est le seul réglage. Rendez au moins deux angles — un coup de pied droit est invisible depuis la gauche.
Ensuite, jugez la planche par rapport à la RÉFÉRENCE, pas par rapport à elle-même. Ouvrez la phrase _intent de l'étape 3a et vérifiez la planche image par image par rapport à elle : la tête est-elle en haut, le tranchant mène-t-il, le hampe croise-t-elle devant le corps. Une planche contact ne prouve jamais que le rendu correspond à la spécification. Elle ne peut pas vous dire que la spécification était correcte, et chaque étape en aval la traitera comme vérité absolue — c'est donc le dernier point dans le pipeline où une pose erronée est bon marché à corriger. Si la planche et la référence sont en désaccord, la référence gagne et la spécification change.
4. Livrez un GIF, puis vérifiez le GIF
ffmpeg -framerate 30 -start_number 0 -i "out/side_%03d.png" -framerate 30 -start_number 0 -i "out/tq_%03d.png" -filter_complex "[0]scale=420:-1[a];[1]scale=420:-1[b];[a][b]hstack=2,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3" -loop 0 anim.gif
Lisez la planche UNE FOIS, à la taille nécessaire et pas plus grande. Une planche contact est l'objet le plus cher de ce pipeline : une image est renvoyée à chaque appel d'outil ultérieur dans le même contexte, donc une lecture de planche en pleine résolution tôt coûte des centaines de milliers de jetons d'ici la fin d'une exécution. Mettez-la en tuiles réduites, recadrez sur le membre en question quand c'est la question, et ne la rouvrez pas pour revérifier quelque chose que vous auriez pu mesurer. Quand un contrôle peut être un NOMBRE — l'arme quitte-t-elle la main, traverse-t-elle un membre, à quel angle se trouve-t-elle — faites-en un nombre et lisez une ligne de texte à la place.
Ensuite, extrayez des images du GIF fini et regardez-les — le mouvement se lit différemment des images fixes, et les paramètres d'encodage échouent silencieusement :
ffmpeg -i anim.gif -vf "select='not(mod(n,2))',tile=4x3" -vsync 0 verify.png
Un clip créé casse ceux que vous n'avez pas créés
Le script Animate par défaut de Roblox joue inactif, marche, course, saut, chute et escalade en priorité Core. Tout ce que vous créez et jouez à Idle ou au-dessus les masque sur chaque joint qu'il pose, pendant toute sa durée.
Donc, une boucle d'inactivité de tout le corps — la correction correcte pour un personnage se tenant debout avec une arme — empêche les jambes de bouger quand le joueur marche, parce que votre clip pose les jambes à une priorité plus élevée que le cycle de marche. Cela ressemble exactement à « l'animation de marche est manquante », et cela apparaît uniquement sur le personnage pour lequel vous avez créé l'inactivité, ce qui donne l'impression que
…
5. Forcer un masque ne suffit pas
Un masque de poids sur les jambes ne résout pas : votre clip pèse encore sur les jambes, et bien que le cycle de marche puisse les faire bouger, la priorité de votre clip gagne. Donc les jambes oscillent au-dessus de l'animation de marche, produisant un mouvement saccadé et déformé.
La manière la plus fiable d'éviter cela : ne posez pas les jambes dans une animation de haut du corps. Utilisez le squelette squeletté (SplitTrack ou une planification explicite des images clés) pour garantir que les jambes sont laissées au repos, et que le script Animate par défaut de Roblox peut les gérer normalement. Si l'animation doit contrôler tout le corps (par exemple, un saut, une glissade, un coup de pied), alors la priorité du clip doit être élevée (Action ou Movement), et vous devez explicitement gérer la transition en sortie du clip (par exemple, blend out vers inactif).