Olá pessoal. Tudo certo!?
Nesse post, continuo falando sobre “animação” em XNA. No post anterior, mostrei como construir animações lineares simples. Agora, vou um pouco além: mostro como criar animações mais complexas usando uma técnica chamada “Keyframed animation”.
Funciona assim: adicionamos “key” frames onde estabelecemos a rotação e a posição que desejamos que o modelo esteja em determinado tempo de execução.
O código-fonte completo dessa série está disponível em https://github.com/ElemarJR/VamosAprenderXNA. No post, apresento apenas os trechos de código mais relevantes.
Representando Key Frames
Como disse, os “key” frames definem uma posição e rotação para um objeto em um determinado instante de animação. Observe:
class ModelAnimationKeyframe
{
public Vector3 Position { get; private set; }
public Vector3 Rotation { get; private set; }
public TimeSpan Time { get; private set; }
public ModelAnimationKeyframe(
Vector3 position,
Vector3 rotation,
TimeSpan time
)
{
this.Position = position;
this.Rotation = rotation;
this.Time = time;
}
}
De forma simples, para definirmos os dados necessários para a animação, bastaria uma lista simples de Key frames, onde, o primeiro item, estivesse no tempo zero. Para tornar a criação dessa lista mais simples e intuitiva, criei uma coleção especializada. Observe:
class ModelAnimationKeyframeList : List<ModelAnimationKeyframe>
{
public ModelAnimationKeyframeList(
Vector3 startposition,
Vector3 startrotation)
{
this.AddStep(
startposition,
startrotation,
TimeSpan.FromSeconds(0)
);
}
public ModelAnimationKeyframeList Translate(
float dx = 0, float dy = 0, float dz = 0, int deltaseconds = 2
)
{
return this.AddStep(
new Vector3(dx, dy, dz),
Vector3.Zero,
deltaseconds
);
}
public ModelAnimationKeyframeList Rotate(
float dx = 0, float dy = 0, float dz = 0, int deltaseconds = 2
)
{
return this.AddStep(
Vector3.Zero,
new Vector3(dx, dy, dz),
deltaseconds
);
}
public ModelAnimationKeyframeList AddStep(
Vector3 deltaposition,
Vector3 deltarotation,
int deltaseconds = 2
)
{
return this.AddStep(deltaposition, deltarotation, TimeSpan.FromSeconds(deltaseconds));
}
public ModelAnimationKeyframeList AddStep(
Vector3 deltaposition,
Vector3 deltarotation,
TimeSpan deltatime
)
{
if (this.Count == 0)
this.Add(new ModelAnimationKeyframe(
deltaposition,
deltarotation,
deltatime)
);
else
{
var previous = this.Last();
this.Add(new ModelAnimationKeyframe(
previous.Position + deltaposition,
previous.Rotation + deltarotation,
previous.Time + deltatime)
);
}
return this;
}
public int IndexOf(TimeSpan offset)
{
if (offset > this.Last().Time) return -1;
if (offset < this.First().Time) return -1;
var result = 0;
while (this[result].Time <= offset)
result++;
return result;
}
}
Como pode perceber, alterei o construtor para definir a posição e rotação para o instante zero. Fora isso, defini alguns métodos “facilitadores” que permitem a criação de frames a partir de “deltas” para as posições e rotações anteriores.
Além disso, defini um IndexOf “turbinado” que identifica o índice do keyframe que estaria sendo processado em um determinado deslocamento de tempo.
Agora, perceba como é simples criar uma lista de keyframes para animação:
var list = new ModelAnimationKeyframeList(spaceship.Position, spaceship.Rotation)
.Translate(dz: 1600)
.Rotate(dy: MathHelper.PiOver2)
.Translate(dx: 1600)
.Rotate(dy: MathHelper.PiOver2)
.Translate(dz: -1600)
.Rotate(dy: MathHelper.PiOver2)
.Translate(dx: -1600)
.Rotate(dy: MathHelper.PiOver2);
Como pode ver, esse conjunto de keyframes define um movimento “quadrado”. Avançando 1600 unidades, virando 90 graus para esquerda, mais 1600 unidades …
Executando um Keyframed animation
Agora que já sabemos como definir os “quadros”, vamos ver como executar a animação. Observe:
class KeyframedModelAnimation
{
public ModelAnimationKeyframeList Frames
{ get; private set; }
public Vector3 Position
{ get; private set; }
public Vector3 Rotation
{ get; private set; }
public TimeSpan ElapsedTime
{ get; private set; }
public bool Looping
{ get; private set; }
public bool AutoReverse
{ get; private set; }
public KeyframedModelAnimation(
ModelAnimationKeyframeList frames,
bool looping = true,
bool autoreverse = true
)
{
this.Frames = frames;
this.Looping = looping;
this.ElapsedTime = TimeSpan.FromSeconds(0);
this.AutoReverse = autoreverse;
}
bool reversing = false;
public void Update(TimeSpan elapsed)
{
this.ElapsedTime += elapsed;
var last = this.Frames.Last();
if (this.ElapsedTime > last.Time && !this.Looping)
{
this.Position = last.Position;
this.Rotation = last.Rotation;
}
else
{
if (this.ElapsedTime > last.Time)
{
while (this.ElapsedTime > last.Time)
this.ElapsedTime -= last.Time;
reversing = !reversing && this.AutoReverse;
}
int index;
ModelAnimationKeyframe previous, target;
TimeSpan time;
if (!reversing)
{
time = this.ElapsedTime;
index = this.Frames.IndexOf(time);
previous = this.Frames[index - 1];
target = this.Frames[index];
}
else
{
time = last.Time - this.ElapsedTime;
index = this.Frames.IndexOf(time);
previous = this.Frames[index];
target = this.Frames[index - 1];
}
var currentFrameTime = time - previous.Time;
var endFrameTime = target.Time - previous.Time;
var amount = (float)currentFrameTime.TotalSeconds /
(float)endFrameTime.TotalSeconds;
this.Position = Vector3.Lerp(previous.Position, target.Position, amount);
this.Rotation = Vector3.Lerp(previous.Rotation, target.Rotation, amount);
}
}
}
Toda a lógica é semelhante a explicada no post anterior. A novidade, foi algum “malabarismo” para isolar a “parte da animação” que estamos executando. De qualquer forma, acho que o código é auto-explicativo.
Era isso.
![]()






Publicado em 05/11/2011
0