Utilizzare modelli animati

Ammenoché non vogliate fare un gioco di simulazione di guida, di volo o di biliardo.. sarà necessario animare i propri modelli.
Per fare questo esistono diversi approcci. Io cercherò di spiegarvi quello che a mio modo di vedere è il più semplice e veloce, ovvero utilizzando una libreria già pronta all'uso: la mitica Animation Library di Bruno Evangelista.
Come avevo accennato nell'introduzione la tutta questa guida si basa su XNA 2.0 proprio perché si può trovare in rete molto più materiale e non c'è nessuna differenza con XNA 3.0 se non nel codice. Infatti anche la libreria di Bruno Evangelista è compatibile solo con XNA2. Di conseguenza, questa parte del tutorial la potrà seguire solo chi ha scelto di usare XNA 2.0. Spero mi abbiate dato ascolto! :)

NOTA: Esiste on-line una versione della libreria di Bruno ricompilata per XNA 3.0. Questa operazione non è stata creata dall'autore e nemmeno dal sottoscritto, dunque non ho la minima idea se funzioni correttamente. Comunque, vista la difficoltà di reperire tale versione, la metto on-line su questo link. Badate bene che non ho potuto testare questa versione non ufficiale.
In alternativa, è possibile scaricare il codice sorgente della libreria e ricompilare facendo le dovute modifiche per renderla funzionante anche su XNA 3.


La libreria Animation Library

All'interno dello zip troveremo due file .dll: (XNAnimation.dll e XNAnimationPipeline.dll), la libreria è tutta quì.
Utilizzare questa libreria è facilissimo, sarà necessario solo importare nella nostra soluzione due .dll, come avevamo fatto per il Wz_BBox nella lezione 26, per poi avere a disposizione le classi che ci permtteranno di gestire tutti i modelli animati.
Come prima cosa dovremo scaricare la libreria, a questo link. Questa versione è sicuramente funzionante in tutte le sue caratteristiche.

animation_lib_soluzione.jpg
Per importare le .dll correttamente seguite le istruzioni che ho descritto nella lezione 26 con la differenza che le due .dll non vanno importate sotto la stessa cartella "Riferimenti". La XNAnimation.dll va importata in "Riferimenti" del progetto, mentre XNAnimationPipeline.dll va importata sotto "Riferimenti" della cartella Content.

 


Adesso nella nostra soluzione appariranno due nuovi riferimenti.
Per usare le classi contenute in questi nuovi namespace, non dovremo far altro che usare la parola using, come abbiamo sempre fatto per le librerie System e di XNA.
Dunque, aggiungiamo le seguenti direttive    (using XNAnimation; using XNAnimation.Effects; using XNAnimation.Controllers;) su tutti i file .cs dove intendiamo usare le nostre nuove classi.

   using System;
   using Microsoft.Xna.Framework;
   using Microsoft.Xna.Framework.Graphics;
   using Microsoft.Xna.Framework.Content;
   using Microsoft.Xna.Framework.Input;

   using XNAnimation;
   using XNAnimation.Effects;
   using XNAnimation.Controllers;


     namespace Mio_namespace
     {

  .....

A questo punto avremo a disposizione le classi contenute nei namespace di XNAnimation, tra cui AnimationController e SkinnedModel che utilizzeremo.
Il primo lo utilizzeremo per gestire le animazioni del nostro modello.
La seconda classe prenderà il posto della classe che abbiamo sempre usato per i modelli :Model.
Infatti, la classe SkinnedModel non è altro che una versione modificata della classe base Model che XNA ci mette a disposizione.
Ora per caricare i modelli useremo una sintassi diversa, non più Model ma SkinnedModel.

Dunque, per dichiarare il modello animato useremo:

SkinnedModel mio_modello;


Per caricare il modello, sempre nel LoadContent():

mio_modello = Game.Content.Load<SkinnedModel>("modello");

Inoltre, dovremo dichiarare anche il nostro AnimationController, semplicemente così:

AnimationController mio_animationController;

Mentre, per inizializzarlo useremo questa sintassi:

mio_animationController = new AnimationController(mio_modello.SkeletonBones);

 

Proprietà
Prima di poter utilizzare i SkinnedModel dobbiamo aprire una parentesi sul processamento dei files che vengono importati nella nostra soluzione.
Dovendo essere "trattati" in modo diverso dai soliti Model dovremmo cambiare il "Content Processor" del file, ovvero il modo in cui viene processato un contenuto.
Per fare questo ci basterà andare sulle Proprietà del file e cambiare il tipo di processamento, da Model - XNA Framework a Model - XNAnimation.

 

Torniamo al nostro codice ed esaminiamo il metodo LoadContent() nel quale, oltre alle due righe che inizializzano il modello e il controller, avremo quattro altre righe che impostano i parametri dell'animazione del modello.

   protected override void LoadContent()
    {

      mio_modello = Game.Content.Load<SkinnedModel>("modello" );
      mio_animationController = new AnimationController(mio_modello.SkeletonBones);

      mio_animationController.StartClip(mio_modello.AnimationClips["nome_animazione"]);
      mio_animationController.TranslationInterpolation = InterpolationMode.None;
      mio_animationController.OrientationInterpolation = InterpolationMode.None;
      mio_animationController.ScaleInterpolation = InterpolationMode.None;

       base.LoadContent();
    }


La prima delle quattro righe imposta lo StarClip, ovvero l'animazione di partenza dove nome_animazione è il nome (in formato stringa) dell'animazione che abbiamo impostato nel momento in cui abbiamo creato il modello.
Le altre righe impostano le interpolazioni cioè il modo in cui si passa da un'animazione all'altra. Impostando tutte le trasformazioni su ".None", quando ci sarà un cambio di animazione (per esempio, un personaggio che da fermo inizia a camminare), non ci sarà nessuna interpolazione, cioè ci sarà il passaggio diretto da un fotogramma all'altro.

Potremmo impostare anche altri parametri, per esempio, con mio_animationController.Speed = 2; imposteremo la velocità dell'animazione in corso ecc.. A voi la soddisfazione di fare i vostri esperimenti.

Andiamo ora nel metodo Update() dove dovremmo impostare l'aggiornamento del nostro controller.
Per fare ciò ci basterà una sola riga, seguita dall'animazione che vorremo mandare in esecuzione:


  mio_animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

  if(premo_un_tasto)
    mio_animationController.PlayClip(mio_modello.AnimationClips["nome_animazione"]);

  if(premo_un_altro_tasto)
    mio_animationController.PlayClip(mio_modello.AnimationClips["nome_animazione2"]);


In questo modo l'animazione verrà aggiornata ad ogni frame. Sappiamo già che gameTime.ElapsedGameTime è il tempo passato dall'ultimo aggiornamento.
Io ho inserito due if per rendere chiaro come passare da un'animazione all'altra, voi potrete usare qualsiasi altro stratagemma.

Spostiamoci adesso nel metodo Draw() per vedere le differenze con un classico Model:

      foreach (ModelMesh modelMesh in mio_modello.Model.Meshes)
      {

        foreach (SkinnedModelBasicEffect effect in modelMesh.Effects)
        {
        
          effect.Bones = PG_animationController.SkinnedBoneTransforms;

          effect.World = Matrix.CreateTranslation(mia_posizione_modello);
          effect.View = mia_ViewMatrix;
          effect.Projection = mia_ProjMatrix;

          // OPZIONALE - Configura il materiale
          effect.Material.DiffuseColor = new Vector3(0.8f, 0.8f, 0.8f);
          effect.Material.SpecularColor = new Vector3(0.8f, 0.8f, 0.8f);
          effect.Material.SpecularPower = 0.25f;
          effect.Material.EmissiveColor = new Vector3(0f, 0f, 0f);

          // OPZIONALE - Configura le luci
          effect.AmbientLightColor = new Vector3(0.8f, 0.8f, 0.8f);
          effect.LightEnabled = true;
          effect.EnabledLights = EnabledLights.Two;

          // OPZIONALE Prima Luce che illumina il modello
          effect.PointLights[0].Color = new Vector3(0.8f, 0.6f, 0.6f);
          effect.PointLights[0].Position = posizione_luce0;
          effect.PointLights[0].Range = 1500;
          effect.PointLights[0].Fallof = 4;

            }

         modelMesh.Draw(SaveStateMode.None);

         }
         


Come si nota subito, le differenze riguardano "mio_modello.Model.Meshes" e SkinnedModelBasicEffect al posto del classico BasicEffect.
La riga effect.Bones = mio_animationController.SkinnedBoneTransforms; aggiornerà le trasformazioni del nostro modello ed è necessaria.
Di seguito vengono inviate le classiche matrici World View Projection e altri parametri inviati all'effetto.
Infatti gli SkinnedModel possiedono anche degli effetti particolari, diversi da quelli standard. Ovviamente, volendo, potremmo anche usare un qualsiasi altro Effect per renderizzare il modello.
A voi la scoperta delle diverse opzioni che riguardano i materiali, le normal maps e le luci. Vi basterà, come sempre, scrivere "effect" seguito dal punto, per vedere il menù a tendina aperto con le decine di opzioni selezionabili.

Se abbiamo fatto tutto come si deve, avremo la possibilità di vedere i nostro modelli animati eseguire le animazioni che abbiamo creato sul nostro modello.