• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.graphics.g3d.utils;
18 
19 import com.badlogic.gdx.graphics.g3d.ModelInstance;
20 import com.badlogic.gdx.graphics.g3d.model.Animation;
21 import com.badlogic.gdx.graphics.g3d.model.Node;
22 import com.badlogic.gdx.math.MathUtils;
23 import com.badlogic.gdx.utils.GdxRuntimeException;
24 import com.badlogic.gdx.utils.Pool;
25 
26 /** Class to control one or more {@link Animation}s on a {@link ModelInstance}. Use the
27  * {@link #setAnimation(String, int, float, AnimationListener)} method to change the current animation. Use the
28  * {@link #animate(String, int, float, AnimationListener, float)} method to start an animation, optionally blending onto the
29  * current animation. Use the {@link #queue(String, int, float, AnimationListener, float)} method to queue an animation to be
30  * played when the current animation is finished. Use the {@link #action(String, int, float, AnimationListener, float)} method to
31  * play a (short) animation on top of the current animation.
32  *
33  * You can use multiple AnimationControllers on the same ModelInstance, as long as they don't interfere with each other (don't
34  * affect the same {@link Node}s).
35  *
36  * @author Xoppa */
37 public class AnimationController extends BaseAnimationController {
38 
39 	/** Listener that will be informed when an animation is looped or completed.
40 	 * @author Xoppa */
41 	public interface AnimationListener {
42 		/** Gets called when an animation is completed.
43 		 * @param animation The animation which just completed. */
onEnd(final AnimationDesc animation)44 		void onEnd (final AnimationDesc animation);
45 
46 		/** Gets called when an animation is looped. The {@link AnimationDesc#loopCount} is updated prior to this call and can be
47 		 * read or written to alter the number of remaining loops.
48 		 * @param animation The animation which just looped. */
onLoop(final AnimationDesc animation)49 		void onLoop (final AnimationDesc animation);
50 	}
51 
52 	/** Class describing how to play and {@link Animation}. You can read the values within this class to get the progress of the
53 	 * animation. Do not change the values. Only valid when the animation is currently played.
54 	 * @author Xoppa */
55 	public static class AnimationDesc {
56 		/** Listener which will be informed when the animation is looped or ended. */
57 		public AnimationListener listener;
58 		/** The animation to be applied. */
59 		public Animation animation;
60 		/** The speed at which to play the animation (can be negative), 1.0 for normal speed. */
61 		public float speed;
62 		/** The current animation time. */
63 		public float time;
64 		/** The offset within the animation (animation time = offsetTime + time) */
65 		public float offset;
66 		/** The duration of the animation */
67 		public float duration;
68 		/** The number of remaining loops, negative for continuous, zero if stopped. */
69 		public int loopCount;
70 
AnimationDesc()71 		protected AnimationDesc () {
72 		}
73 
74 		/** @return the remaining time or 0 if still animating. */
update(float delta)75 		protected float update (float delta) {
76 			if (loopCount != 0 && animation != null) {
77 				int loops;
78 				final float diff = speed * delta;
79 				if (!MathUtils.isZero(duration)) {
80 					time += diff;
81 					loops = (int)Math.abs(time / duration);
82 					if (time < 0f) {
83 						loops++;
84 						while (time < 0f)
85 							time += duration;
86 					}
87 					time = Math.abs(time % duration);
88 				} else
89 					loops = 1;
90 				for (int i = 0; i < loops; i++) {
91 					if (loopCount > 0) loopCount--;
92 					if (loopCount != 0 && listener != null) listener.onLoop(this);
93 					if (loopCount == 0) {
94 						final float result = ((loops - 1) - i) * duration + (diff < 0f ? duration - time : time);
95 						time = (diff < 0f) ? 0f : duration;
96 						if (listener != null) listener.onEnd(this);
97 						return result;
98 					}
99 				}
100 				return 0f;
101 			} else
102 				return delta;
103 		}
104 	}
105 
106 	protected final Pool<AnimationDesc> animationPool = new Pool<AnimationDesc>() {
107 		@Override
108 		protected AnimationDesc newObject () {
109 			return new AnimationDesc();
110 		}
111 	};
112 
113 	/** The animation currently playing. Do not alter this value. */
114 	public AnimationDesc current;
115 	/** The animation queued to be played when the {@link #current} animation is completed. Do not alter this value. */
116 	public AnimationDesc queued;
117 	/** The transition time which should be applied to the queued animation. Do not alter this value. */
118 	public float queuedTransitionTime;
119 	/** The animation which previously played. Do not alter this value. */
120 	public AnimationDesc previous;
121 	/** The current transition time. Do not alter this value. */
122 	public float transitionCurrentTime;
123 	/** The target transition time. Do not alter this value. */
124 	public float transitionTargetTime;
125 	/** Whether an action is being performed. Do not alter this value. */
126 	public boolean inAction;
127 	/** When true a call to {@link #update(float)} will not be processed. */
128 	public boolean paused;
129 	/** Whether to allow the same animation to be played while playing that animation. */
130 	public boolean allowSameAnimation;
131 
132 	private boolean justChangedAnimation = false;
133 
134 	/** Construct a new AnimationController.
135 	 * @param target The {@link ModelInstance} on which the animations will be performed. */
AnimationController(final ModelInstance target)136 	public AnimationController (final ModelInstance target) {
137 		super(target);
138 	}
139 
obtain(final Animation anim, float offset, float duration, int loopCount, float speed, final AnimationListener listener)140 	private AnimationDesc obtain (final Animation anim, float offset, float duration, int loopCount, float speed,
141 		final AnimationListener listener) {
142 		if (anim == null) return null;
143 		final AnimationDesc result = animationPool.obtain();
144 		result.animation = anim;
145 		result.listener = listener;
146 		result.loopCount = loopCount;
147 		result.speed = speed;
148 		result.offset = offset;
149 		result.duration = duration < 0 ? (anim.duration - offset) : duration;
150 		result.time = speed < 0 ? result.duration : 0.f;
151 		return result;
152 	}
153 
154 	private AnimationDesc obtain (final String id, float offset, float duration, int loopCount, float speed,
155 		final AnimationListener listener) {
156 		if (id == null) return null;
157 		final Animation anim = target.getAnimation(id);
158 		if (anim == null) throw new GdxRuntimeException("Unknown animation: " + id);
159 		return obtain(anim, offset, duration, loopCount, speed, listener);
160 	}
161 
162 	private AnimationDesc obtain (final AnimationDesc anim) {
163 		return obtain(anim.animation, anim.offset, anim.duration, anim.loopCount, anim.speed, anim.listener);
164 	}
165 
166 	/** Update any animations currently being played.
167 	 * @param delta The time elapsed since last update, change this to alter the overall speed (can be negative). */
168 	public void update (float delta) {
169 		if (paused) return;
170 		if (previous != null && ((transitionCurrentTime += delta) >= transitionTargetTime)) {
171 			removeAnimation(previous.animation);
172 			justChangedAnimation = true;
173 			animationPool.free(previous);
174 			previous = null;
175 		}
176 		if (justChangedAnimation) {
177 			target.calculateTransforms();
178 			justChangedAnimation = false;
179 		}
180 		if (current == null || current.loopCount == 0 || current.animation == null) return;
181 		final float remain = current.update(delta);
182 		if (remain != 0f && queued != null) {
183 			inAction = false;
184 			animate(queued, queuedTransitionTime);
185 			queued = null;
186 			update(remain);
187 			return;
188 		}
189 		if (previous != null)
190 			applyAnimations(previous.animation, previous.offset + previous.time, current.animation, current.offset + current.time,
191 				transitionCurrentTime / transitionTargetTime);
192 		else
193 			applyAnimation(current.animation, current.offset + current.time);
194 	}
195 
196 	/** Set the active animation, replacing any current animation.
197 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
198 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
199 	 *         is completed. */
200 	public AnimationDesc setAnimation (final String id) {
201 		return setAnimation(id, 1, 1.0f, null);
202 	}
203 
204 	/** Set the active animation, replacing any current animation.
205 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
206 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
207 	 *           loop the animation.
208 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
209 	 *         is completed. */
210 	public AnimationDesc setAnimation (final String id, int loopCount) {
211 		return setAnimation(id, loopCount, 1.0f, null);
212 	}
213 
214 	/** Set the active animation, replacing any current animation.
215 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
216 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
217 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
218 	 *         is completed. */
219 	public AnimationDesc setAnimation (final String id, final AnimationListener listener) {
220 		return setAnimation(id, 1, 1.0f, listener);
221 	}
222 
223 	/** Set the active animation, replacing any current animation.
224 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
225 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
226 	 *           loop the animation.
227 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
228 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
229 	 *         is completed. */
230 	public AnimationDesc setAnimation (final String id, int loopCount, final AnimationListener listener) {
231 		return setAnimation(id, loopCount, 1.0f, listener);
232 	}
233 
234 	/** Set the active animation, replacing any current animation.
235 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
236 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
237 	 *           loop the animation.
238 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
239 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
240 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
241 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
242 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
243 	 *         is completed. */
244 	public AnimationDesc setAnimation (final String id, int loopCount, float speed, final AnimationListener listener) {
245 		return setAnimation(id, 0f, -1f, loopCount, speed, listener);
246 	}
247 
248 	/** Set the active animation, replacing any current animation.
249 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
250 	 * @param offset The offset in seconds to the start of the animation.
251 	 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation).
252 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
253 	 *           loop the animation.
254 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
255 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
256 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
257 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
258 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
259 	 *         is completed. */
260 	public AnimationDesc setAnimation (final String id, float offset, float duration, int loopCount, float speed,
261 		final AnimationListener listener) {
262 		return setAnimation(obtain(id, offset, duration, loopCount, speed, listener));
263 	}
264 
265 	/** Set the active animation, replacing any current animation. */
266 	protected AnimationDesc setAnimation (final Animation anim, float offset, float duration, int loopCount, float speed,
267 		final AnimationListener listener) {
268 		return setAnimation(obtain(anim, offset, duration, loopCount, speed, listener));
269 	}
270 
271 	/** Set the active animation, replacing any current animation. */
272 	protected AnimationDesc setAnimation (final AnimationDesc anim) {
273 		if (current == null)
274 			current = anim;
275 		else {
276 			if (!allowSameAnimation && anim != null && current.animation == anim.animation)
277 				anim.time = current.time;
278 			else
279 				removeAnimation(current.animation);
280 			animationPool.free(current);
281 			current = anim;
282 		}
283 		justChangedAnimation = true;
284 		return anim;
285 	}
286 
287 	/** Changes the current animation by blending the new on top of the old during the transition time.
288 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
289 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
290 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
291 	 *         is completed. */
292 	public AnimationDesc animate (final String id, float transitionTime) {
293 		return animate(id, 1, 1.0f, null, transitionTime);
294 	}
295 
296 	/** Changes the current animation by blending the new on top of the old during the transition time.
297 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
298 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
299 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
300 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
301 	 *         is completed. */
302 	public AnimationDesc animate (final String id, final AnimationListener listener, float transitionTime) {
303 		return animate(id, 1, 1.0f, listener, transitionTime);
304 	}
305 
306 	/** Changes the current animation by blending the new on top of the old during the transition time.
307 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
308 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
309 	 *           loop the animation.
310 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
311 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
312 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
313 	 *         is completed. */
314 	public AnimationDesc animate (final String id, int loopCount, final AnimationListener listener, float transitionTime) {
315 		return animate(id, loopCount, 1.0f, listener, transitionTime);
316 	}
317 
318 	/** Changes the current animation by blending the new on top of the old during the transition time.
319 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
320 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
321 	 *           loop the animation.
322 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
323 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
324 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
325 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
326 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
327 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
328 	 *         is completed. */
329 	public AnimationDesc animate (final String id, int loopCount, float speed, final AnimationListener listener,
330 		float transitionTime) {
331 		return animate(id, 0f, -1f, loopCount, speed, listener, transitionTime);
332 	}
333 
334 	/** Changes the current animation by blending the new on top of the old during the transition time.
335 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
336 	 * @param offset The offset in seconds to the start of the animation.
337 	 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation).
338 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
339 	 *           loop the animation.
340 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
341 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
342 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
343 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
344 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
345 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
346 	 *         is completed. */
347 	public AnimationDesc animate (final String id, float offset, float duration, int loopCount, float speed,
348 		final AnimationListener listener, float transitionTime) {
349 		return animate(obtain(id, offset, duration, loopCount, speed, listener), transitionTime);
350 	}
351 
352 	/** Changes the current animation by blending the new on top of the old during the transition time. */
353 	protected AnimationDesc animate (final Animation anim, float offset, float duration, int loopCount, float speed,
354 		final AnimationListener listener, float transitionTime) {
355 		return animate(obtain(anim, offset, duration, loopCount, speed, listener), transitionTime);
356 	}
357 
358 	/** Changes the current animation by blending the new on top of the old during the transition time. */
359 	protected AnimationDesc animate (final AnimationDesc anim, float transitionTime) {
360 		if (current == null)
361 			current = anim;
362 		else if (inAction)
363 			queue(anim, transitionTime);
364 		else if (!allowSameAnimation && anim != null && current.animation == anim.animation) {
365 			anim.time = current.time;
366 			animationPool.free(current);
367 			current = anim;
368 		} else {
369 			if (previous != null) {
370 				removeAnimation(previous.animation);
371 				animationPool.free(previous);
372 			}
373 			previous = current;
374 			current = anim;
375 			transitionCurrentTime = 0f;
376 			transitionTargetTime = transitionTime;
377 		}
378 		return anim;
379 	}
380 
381 	/** Queue an animation to be applied when the {@link #current} animation is finished. If the current animation is continuously
382 	 * looping it will be synchronized on next loop.
383 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
384 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
385 	 *           loop the animation.
386 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
387 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
388 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
389 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
390 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
391 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
392 	 *         is completed. */
393 	public AnimationDesc queue (final String id, int loopCount, float speed, final AnimationListener listener, float transitionTime) {
394 		return queue(id, 0f, -1f, loopCount, speed, listener, transitionTime);
395 	}
396 
397 	/** Queue an animation to be applied when the {@link #current} animation is finished. If the current animation is continuously
398 	 * looping it will be synchronized on next loop.
399 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
400 	 * @param offset The offset in seconds to the start of the animation.
401 	 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation).
402 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
403 	 *           loop the animation.
404 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
405 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
406 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
407 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
408 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
409 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
410 	 *         is completed. */
411 	public AnimationDesc queue (final String id, float offset, float duration, int loopCount, float speed,
412 		final AnimationListener listener, float transitionTime) {
413 		return queue(obtain(id, offset, duration, loopCount, speed, listener), transitionTime);
414 	}
415 
416 	/** Queue an animation to be applied when the current is finished. If current is continuous it will be synced on next loop. */
417 	protected AnimationDesc queue (final Animation anim, float offset, float duration, int loopCount, float speed,
418 		final AnimationListener listener, float transitionTime) {
419 		return queue(obtain(anim, offset, duration, loopCount, speed, listener), transitionTime);
420 	}
421 
422 	/** Queue an animation to be applied when the current is finished. If current is continuous it will be synced on next loop. */
423 	protected AnimationDesc queue (final AnimationDesc anim, float transitionTime) {
424 		if (current == null || current.loopCount == 0)
425 			animate(anim, transitionTime);
426 		else {
427 			if (queued != null) animationPool.free(queued);
428 			queued = anim;
429 			queuedTransitionTime = transitionTime;
430 			if (current.loopCount < 0) current.loopCount = 1;
431 		}
432 		return anim;
433 	}
434 
435 	/** Apply an action animation on top of the current animation.
436 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
437 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
438 	 *           loop the animation.
439 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
440 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
441 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
442 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
443 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
444 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
445 	 *         is completed. */
446 	public AnimationDesc action (final String id, int loopCount, float speed, final AnimationListener listener,
447 		float transitionTime) {
448 		return action(id, 0, -1f, loopCount, speed, listener, transitionTime);
449 	}
450 
451 	/** Apply an action animation on top of the current animation.
452 	 * @param id The ID of the {@link Animation} within the {@link ModelInstance}.
453 	 * @param offset The offset in seconds to the start of the animation.
454 	 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation).
455 	 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously
456 	 *           loop the animation.
457 	 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at
458 	 *           twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be
459 	 *           negative, causing the animation to played in reverse. This value cannot be zero.
460 	 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed.
461 	 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any).
462 	 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation
463 	 *         is completed. */
464 	public AnimationDesc action (final String id, float offset, float duration, int loopCount, float speed,
465 		final AnimationListener listener, float transitionTime) {
466 		return action(obtain(id, offset, duration, loopCount, speed, listener), transitionTime);
467 	}
468 
469 	/** Apply an action animation on top of the current animation. */
470 	protected AnimationDesc action (final Animation anim, float offset, float duration, int loopCount, float speed,
471 		final AnimationListener listener, float transitionTime) {
472 		return action(obtain(anim, offset, duration, loopCount, speed, listener), transitionTime);
473 	}
474 
475 	/** Apply an action animation on top of the current animation. */
476 	protected AnimationDesc action (final AnimationDesc anim, float transitionTime) {
477 		if (anim.loopCount < 0) throw new GdxRuntimeException("An action cannot be continuous");
478 		if (current == null || current.loopCount == 0)
479 			animate(anim, transitionTime);
480 		else {
481 			AnimationDesc toQueue = inAction ? null : obtain(current);
482 			inAction = false;
483 			animate(anim, transitionTime);
484 			inAction = true;
485 			if (toQueue != null) queue(toQueue, transitionTime);
486 		}
487 		return anim;
488 	}
489 }
490