- Animazioni in XNA -
|
|||
COSA SERVE PER QUESTO TUTORIAL | |||
Download | Chiedi sul FORUM | Glossario | cognizioni basiche di C# | ||
Come creare un personaggio animato | |||
LA CLASSE ANIMATION Una classe contenente tutte le informazioni che riguardano l'animazione.
Nel precedente tutorial abbiamo visto come è strutturato un gioco di
XNA, come gestire la logica di gioco e le funzionalità basiche per il
disegno; oggi ci concentreremo principalmente su come creare uno
"personaggio" animato e come dargli altre funzionalità basiche come la
possibilità di ruotare o di ridimensionarsi. In concreto creeremo una
stellina animata e rotante nell'area di gioco alla pressione del tasto
sinistro del mouse. Per fare questo dovremo
creare due classi: la classe Character che conterrà la lista delle
animazioni disponibili per il personaggio, la sua posizione e i metodi
Update e Draw, e la classe Animation che servirà per mantenere ed
elaborare informazioni sull'animazione come frame corrente, angolo di
rotazione corrente, numero di volte che l'animazione è stata riprodotta
e così via.
class Animation: ICloneable { // Numero di volte che l'animazione è stata ripetuta private int repeated; public int Repeated { get { return repeated; } } // Tempo in cui l'animazione è iniziata private TimeSpan startTime; // Numero massimo di ripetizioni dell'animazioni // dopo le quali Update restituirà false private int maxRepeat = int.MaxValue; // Angolo di rotazione corrente in radianti private double currentRotation = 0; public double CurrentRotation { get { return currentRotation; } } // Indice del frame corrente private int currentFrame = -1; public int CurrentFrame { get { return currentFrame; } } // Proprietà per la velocità con cui si devono // succedere i frame public float fps { get; set; } // Immagine contenente tutti i frame public Texture2D Frames { get; set; } // Larghezza del singolo frame public int FrameWidth { get; set; } // Se true indica che l'animazione deve essere // riprodotta in senso inverso public bool Backward { get; set; } // Velocità angolare con cui deve ruotare l'animazione public double AngularVelocity { get; set; } // Angolo iniziale di rotazione public double InitalAngle { get; set; } // Punto rispetto al quale effettuare la rotazione public Vector2 RotationPoint { get; set; } // Il fattore di ingrandimento dell'animazione public float Scale { get; set; } // [...] Costruttori tralasciati // Proprietà che restituisce il rettangolo relativo // all'immagine contenente i frame da dissgnare public Rectangle CurrentRectangle { get { return new Rectangle(this.FrameWidth * this.currentFrame, 0, this.FrameWidth, this.Frames.Height); } } // Proprietà per conoscere il numero totale di frame public int TotalFrames { get { return (this.Frames.Width / this.FrameWidth); } } // [...] } Vi è poi il metodo Start che null'altro fa che impostare il tempo di avvio dell'animazione a quello passatogli e quindi azzerare tutte le variabili interne: // Avvia l'animazione, vengono azzerati angolo di // rotazione, numero di ripetizioni, frame corrente // e tempo di avvio public void Start(GameTime now) { this.repeated = 0; this.startTime = now.TotalGameTime; this.currentFrame = 0; this.currentRotation = 0; } Infine abbiamo il metodo Update che si occupa di aggiornare tutte le variabili interne a seconda del tempo trascorso dall'inizio dell'animazione: // Aggiorna le variabili riguardanti l'animazione // a seconda del tempo trascorso public bool Update(GameTime now) { // Se non è ancora stato chiamato il metodo Start // lo chiamiamo ora if (this.currentFrame == -1) this.Start(now); // Calcoliamo il numero di secondi trascorsi // dall'avvio dell'animazione double totalSeconds = (now.TotalGameTime - startTime).TotalSeconds; // Calcoliamo il numero totale di frame (teoricamente) // disegnati finora double frameDrawn = (totalSeconds * fps); // Calcoliamo il numero di volte che l'animazione è // stata riprodotta repeated = (int)Math.Truncate(frameDrawn / this.TotalFrames); // Calcoliamo il frame da disegnare prendendo il resto // della divisione del totale dei frame disegnati // per il numero di frame currentFrame = (int)Math.Truncate(frameDrawn % this.TotalFrames); // Calcoliamo l'angolo di rotazione corrente sommando // all'angolo iniziale il prodotto del tempo trascorso // per la velocità angolare currentRotation = InitalAngle + totalSeconds * AngularVelocity; // Per far scorrere l'animazione all'incontrario // semplicemente prendiamo il corrisponde opposto if (this.Backward) currentFrame = this.TotalFrames - currentFrame - 1; // Restituisce true se non è stato superato il numero // di ripetizioni massimo return (Repeated < maxRepeat); }
Per calcolare il numero di volte che l'animazione è stata riprodotta
semplicemente dividiamo il numero totale di frame disegnati per i frame
totali dell'animazione, per stabilire quale sia il frame corrente
prendiamo il resto della
divisione tra il numero totale di frame disegnati e il totale dei frame. Il numero totale di frame si ottiene
semplicemente moltiplicando il tempo trascorso dall'inizio
dell'animazione per il numero di frame al secondo. L'angolo di rotazione
si ottiene in maniera simile: si somma all'angolo iniziale il prodotto della velocità angolare per il tempo trascorso. Una classe contenente tutte le informazioni che riguardano l'animazione. Iniziamo dunque dalla classe Character: class Character : DrawableGameComponent { private SpriteBatch spriteBatch; // Indice dell'animazione corrente private int currentAnimationIndex = 0; // Array contenente tutte le animazioni del Character public Animation[] Animations { get; set; } // Posizione corrente del Character public Vector2 Position { get; set; } // Costruttori public Character(Game game, Vector2 InitialPosition) : base(game) { this.Position = InitialPosition; } public Character(Game game) : this(game, new Vector2(0, 0)) { } protected override void LoadContent() { base.LoadContent(); // Otteniamo lo spriteBatch del gioco spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch)); } // [...] public Animation CurrentAnimation { get { return Animations[currentAnimationIndex]; } set { currentAnimationIndex = Array.IndexOf(Animations, value); if (currentAnimationIndex == -1) throw new IndexOutOfRangeException(); } } } Per prima cosa notiamo che essa eredita da DrawableGameComponent, il che significa che è un componente del gioco (vedremo in seguito come aggiungerlo) e che dispone quindi di tutti i metodi che abbiamo visto essere propri di Game, come Draw, Update, Initialize, LoadContent e così via. Tra la sue proprietà principali vi sono Animations che imposta o restituisce la lista delle animazioni disponibili (memorizzate sotto forma di un semplice array) e CurrentAnimation che restituisce quella che tra queste è correntemente in esecuzione, indicata dal campo currentAnimationIndex. L'ultima proprietà disponibile è Position che indica semplicemente la posizione in cui si trova l'oggetto da disegnare. Vediamo i metodi principali come sono implementati a partire dal metodo Update: public override void Update(GameTime gameTime) { base.Update(gameTime); // Aggiorna le variabili dell'animazione while (!this.CurrentAnimation.Update(gameTime)) { // L'animazione è finita, passiamo alla successiva Animation oldAnimation = CurrentAnimation; CurrentAnimation = Animations[(currentAnimationIndex + 1) % Animations.Length]; CurrentAnimation.Start(gameTime); CurrentAnimation.InitalAngle = oldAnimation.CurrentRotation; } } Ricordiamo che il metodo Update serve per gestire la logica di gioco, al contrario di Draw che si occupa di creare l'output grafico. Sostanzialmente questa funzione non fa altro che chiamare il metodo Update dell'oggetto Animation corrente (che abbiamo visto poco fa), comunicandogli le informazioni sul tempo di gioco cosicché possa stabilire quale frame mostrare. Questa chiamata è posta come condizione di un ciclo while poiché Update restituisce false se ha terminato il numero di esecuzioni massimo impostato, e in questo specifico caso abbiamo deciso di adottare la politica di passare all'animazione successiva se si verifica la condizione. public override void Draw(GameTime gameTime) { base.Draw(gameTime); // Disegnamo il character con tutte le informazioni // che ci vengono da this.CurrentAnimation, eccetto // la posizione corrente spriteBatch.Draw(this.CurrentAnimation.Frames, this.Position, this.CurrentAnimation.CurrentRectangle, Color.White, (float)this.CurrentAnimation.CurrentRotation, this.CurrentAnimation.RotationPoint, this.CurrentAnimation.Scale, SpriteEffects.None, 0); } Il metodo Draw è relativamente semplice, non fa altro che utilizzare tutte le funzionalità (o quasi) del metodo SpriteBatch.Draw: il primo parametro indica l'immagine da cui prendere l'area da disegnare, il secondo la posizione dove disegnare, il terzo l'area dell'immagine da disegnare, il quarto un filtro da utilizzare sul colore (Color.White non applica alcuna trasformazione), il quinto l'angolo di rotazione (in radianti), il sesto il punto rispetto al quale effettuare la rotazione, l'ottavo il fattore di ingrandimento e gli ultimi due non hanno alcun effetto in questo caso. LA CLASSE GAMELa classe principale del gioco che lo descrive al livello di astrazione maggiore. La classe Game, quella principale del gioco e che lo gestisce, non ha grandi particolarità rispetto al precedente tutorial. Al di fuori del metodo Update, l'unica novità rilevante è aver impostato il mouse visibile nel costruttore impostando la proprietà IsMouseVisible su true. Passiamo subito al cuore della classe: public class CreaStellineGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; // Immagine contenente i frame dell'animazione Texture2D framesStellina; // Stato del tasto sinistro del mouse dell'ultimo // Update ButtonState lastMouseState = ButtonState.Released; // Generatore di numeri casuali usato durante tutto // il gioco Random gameRandom = new Random(); // [...] // Gestiamo la logica di gioco protected override void Update(GameTime gameTime) { if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape)) this.Exit(); // Prendiamo lo stato corrente del mouse MouseState mouse = Mouse.GetState(); // Se è stato rilasciato il mouse if (mouse.LeftButton == ButtonState.Released && lastMouseState == ButtonState.Pressed) { // Creiamo l'animazione della stellina che si // ingrandisce Animation cresce = new Animation(framesStellina, 190, 25, 1); // Impostiamo una velocità angolare casuale cresce.AngularVelocity = (float) gameRandom.NextDouble() * 4 - 2; // Impostiamo una dimensione casuale tra le dimensioni // originali dell'immagine e la loro metà cresce.Scale = (float)gameRandom.NextDouble() * 0.5f + 0.5f; // Cloniamo l'animazione appena creata e impostiamo // che vada al contrario Animation decresce = (Animation) ((ICloneable) cresce).Clone(); decresce.Backward = true; // Inizializziamo la stellina Character stellina = new Character(this, new Vector2(mouse.X, mouse.Y)); // Impostiamo le animazioni della stellina stellina.Animations = new Animation[] { cresce, decresce }; // Aggiungiamo la stellina appena creata ai // componenti del gioco this.Components.Add(stellina); } // Aggiorniamo qual è lo stato del tasto sinistro lastMouseState = mouse.LeftButton; base.Update(gameTime); } // [...] }
Il metodo Update entra in azione quando viene rilasciato il tasto
sinistro del mouse, ovvero quando si passa dallo stato di pressione a
quello di rilascio tra una chiamata di Update e la successiva.
Per creare una nuova stellina cominciamo a chiamare il costruttore di
Character passandogli l'immagine contenente i frame
precedentemente caricata (framesStellina) nel metodo
LoadContent, la larghezza del singolo frame, i frame da
mostrare al secondo e il numero massimo di ripetizioni. Impostando il
numero massimo di ripetizioni faremo in modo che ogni volta che è stata
completata un'animazione si passerà alla successiva. Impostiamo poi su
valori casuali la velocità angolare e il fattore di ridimensionamento. A
questo punto cloniamo l'animazione impostando però la proprietà
Backward su true, in questo modo l'animazione
cresce procederà normalmente, mentre decresce
sarà del tutto equivalente eccetto per il fatto che procede a ritroso.
|
|||
<< INDIETRO | by VeNoM00 |