Last week when we were going to code some animations for Ninja Trials, we realized that the 'Animation' class from libGDX would't let us making animations with different durations for each frame, which we need for almost every animation in the game.
For example, if we want to make a face whith eyes that blink, we'd like to get something like this:
(eyes blink for 0.1 sec)
But if every frame's duration needs to be the same, we'll get something like this:
(unless we insert several duplicated frames, and that doesn't seem to be efficient)
As you can see we needed more control over animation's time.
We didn't mean to reinvent the wheel, so we searched again and again but found nothing, then DanPelGar started to modify the 'Animation' class. Now we are using the 'AnimationNinja' class.
These are the additions to the original class:
/** Constructor, storing the frame duration, key frames and play type.
*
* @param frameDuration the time between frames in seconds. An array is given with the different times.
* @param keyFrames the {@link TextureRegion}s representing the frames.
* @param playType the type of animation play (NORMAL, REVERSED, LOOP, LOOP_REVERSED, LOOP_PINGPONG, LOOP_RANDOM) */
public AnimationNinja (float[] frameDuration, Array keyFrames, int playType) {
this.constructorArray = true;
this.frameDuration = frameDurationArray[0];
this.animationDuration = 0;
this.frameDurationArray = new float[frameDuration.length];
for (int i = 0; i < frameDuration.length; i++) {
this.animationDuration += frameDuration[i];
this.frameDurationArray[i] = frameDuration[i];
}
this.keyFrames = new TextureRegion[keyFrames.size];
for (int i = 0, n = keyFrames.size; i < n; i++) {
this.keyFrames[i] = keyFrames.get(i);
}
this.playMode = playType;
}
//A different method is used if an array of frames was set
/** Returns the current frame number.
* @param stateTime
* @return current frame number */
public int getKeyFrameIndexIfArray (float stateTime) {
if(keyFrames.length == 1)
return 0;
//With the difference between the actual time and the animation duration we will know the exact frame
float frameNumberFloat = (stateTime % animationDuration);
float frameTime[] = new float[keyFrames.length];
//the float framePosition is the sum of the duration of the actual frame plus all the previous one -> Accumulated duration
float framePosition[] = new float[keyFrames.length];
for (int i = 0; i < framePosition.length; i++) {
if (i == 0) {
framePosition[i] = frameDurationArray[i];
continue;
}
framePosition[i] = frameDurationArray[i] + framePosition[i - 1];
}
//With the framePosition and the actual time we can know which frame Number is the actual one
int frameNumber = 0;
if (frameNumberFloat < framePosition[0])
frameNumber = 0;
if (frameNumberFloat > framePosition[frameDurationArray.length - 1])
frameNumber = frameDurationArray.length - 1;
for (int i = 0; i < frameTime.length - 1; i++) {
if (frameNumberFloat < framePosition[i + 1] && frameNumberFloat >= framePosition[i])
frameNumber = i + 1;
}
//the rest of the method stays the same
switch (playMode) {
case NORMAL:
frameNumber = Math.min(keyFrames.length - 1, frameNumber);
break;
case LOOP:
frameNumber = frameNumber % keyFrames.length;
break;
case LOOP_PINGPONG:
frameNumber = frameNumber % ((keyFrames.length * 2) - 2);
if (frameNumber >= keyFrames.length)
frameNumber = keyFrames.length - 2 - (frameNumber - keyFrames.length);
break;
case LOOP_RANDOM:
frameNumber = MathUtils.random(keyFrames.length - 1);
break;
case REVERSED:
frameNumber = Math.max(keyFrames.length - frameNumber - 1, 0);
break;
case LOOP_REVERSED:
frameNumber = frameNumber % keyFrames.length;
frameNumber = keyFrames.length - frameNumber - 1;
break;
default:
// play normal otherwise
frameNumber = Math.min(keyFrames.length - 1, frameNumber);
break;
}
return frameNumber;
}
If you are starting using libGDX there are high chances that you are in the same situation as we were, so we thought that putting here the solution we got would be a good idea. Here is the class
AnimationNinja.java
Code's license is the same that libGDX uses, the
Apache 2.0 (you can use it free of charge, in commercial and non-commercial projects).
Greetings!
La semana pasada cuando íbamos a hacer algunas animaciones del Ninja Trials, nos dimos cuenta de que la clase "Animation" de libGDX no nos permitía hacer animaciones con duraciones diferentes para cada fotograma, cosa que necesitamos para casi todas las animaciones del juego.
Por ejemplo, si queremos hacer una cara que parpadee, lo normal es que busquemos algo así:
(cierra los ojos durante 0.1 seg)
Pero si todos los fotogramas tienen que durar el mismo tiempo, obtendremos esto:
(a no ser que metiésemos muchos fotogramas repetidos, cosa que no parece muy eficiente)
Como veis, necesitábamos más control sobre las animaciones.
Para intentar no reinventar la rueda, abrimos un navegador, buscamos y buscamos pero nada hallamos, por lo que DanPelGar se puso manos a la obra para modificar la clase "Animation". Ahora usamos la clase "AnimationNinja".
Estos son los añadidos a la clase original:
/** Constructor, storing the frame duration, key frames and play type.
*
* @param frameDuration the time between frames in seconds. An array is given with the different times.
* @param keyFrames the {@link TextureRegion}s representing the frames.
* @param playType the type of animation play (NORMAL, REVERSED, LOOP, LOOP_REVERSED, LOOP_PINGPONG, LOOP_RANDOM) */
public AnimationNinja (float[] frameDuration, Array keyFrames, int playType) {
this.constructorArray = true;
this.frameDuration = frameDurationArray[0];
this.animationDuration = 0;
this.frameDurationArray = new float[frameDuration.length];
for (int i = 0; i < frameDuration.length; i++) {
this.animationDuration += frameDuration[i];
this.frameDurationArray[i] = frameDuration[i];
}
this.keyFrames = new TextureRegion[keyFrames.size];
for (int i = 0, n = keyFrames.size; i < n; i++) {
this.keyFrames[i] = keyFrames.get(i);
}
this.playMode = playType;
}
//A different method is used if an array of frames was set
/** Returns the current frame number.
* @param stateTime
* @return current frame number */
public int getKeyFrameIndexIfArray (float stateTime) {
if(keyFrames.length == 1)
return 0;
//With the difference between the actual time and the animation duration we will know the exact frame
float frameNumberFloat = (stateTime % animationDuration);
float frameTime[] = new float[keyFrames.length];
//the float framePosition is the sum of the duration of the actual frame plus all the previous one -> Accumulated duration
float framePosition[] = new float[keyFrames.length];
for (int i = 0; i < framePosition.length; i++) {
if (i == 0) {
framePosition[i] = frameDurationArray[i];
continue;
}
framePosition[i] = frameDurationArray[i] + framePosition[i - 1];
}
//With the framePosition and the actual time we can know which frame Number is the actual one
int frameNumber = 0;
if (frameNumberFloat < framePosition[0])
frameNumber = 0;
if (frameNumberFloat > framePosition[frameDurationArray.length - 1])
frameNumber = frameDurationArray.length - 1;
for (int i = 0; i < frameTime.length - 1; i++) {
if (frameNumberFloat < framePosition[i + 1] && frameNumberFloat >= framePosition[i])
frameNumber = i + 1;
}
//the rest of the method stays the same
switch (playMode) {
case NORMAL:
frameNumber = Math.min(keyFrames.length - 1, frameNumber);
break;
case LOOP:
frameNumber = frameNumber % keyFrames.length;
break;
case LOOP_PINGPONG:
frameNumber = frameNumber % ((keyFrames.length * 2) - 2);
if (frameNumber >= keyFrames.length)
frameNumber = keyFrames.length - 2 - (frameNumber - keyFrames.length);
break;
case LOOP_RANDOM:
frameNumber = MathUtils.random(keyFrames.length - 1);
break;
case REVERSED:
frameNumber = Math.max(keyFrames.length - frameNumber - 1, 0);
break;
case LOOP_REVERSED:
frameNumber = frameNumber % keyFrames.length;
frameNumber = keyFrames.length - frameNumber - 1;
break;
default:
// play normal otherwise
frameNumber = Math.min(keyFrames.length - 1, frameNumber);
break;
}
return frameNumber;
}
Como es muy posible que si estáis empezando a usar libGDX os encontréis en nuestra misma situación, pensamos que sería una buena idea poner aquí la solución que usamos nosotros.
Aquí tenéis la clase
AnimationNinja.java
La licencia del código es la misma de libGDX, la
Apache 2.0 (la podéis usar sin coste alguno, tanto en proyectos comerciales como no comerciales).
¡Saludos!