Animation lib

Posted by on August 6, 2009

Dependent on the time-lapse lib, the animation code provides you with several count methods to manipulate your animations in an easy and comprehensible way.

If you add a sprite atlas library (I’ll share later) you’ll be able to manipulate sprites with animation in quite a flexible way without too much hassle.

The idea is to encapsulate the animation functionality in such a way that it makes it easy to work with. You have the ability to start, stop, pause, change speed, etc. You can create animations that range from any frame to any frame and that can animate in many ways such as sequential (loop), play-once and ping-pong (back and forth, or rather the inverse). By making use of the time-lapse library we can easily create time-based animation.

The base code:

;##############################################################################
;$$$	file: Animation.pb - date: 05:05 p.m. 06/08/2009			$$$
;$$$	author: Gustavo Julio Fiorenza (info@gushh.net || info@gushh.com.ar)	$$$
;$$$					aka: gushh, dagcrack.			$$$
;$$$ 	description: provides several count modes for animation purposes	$$$
;##############################################################################

EnableExplicit
XIncludeFile "TimeLapse.pb" ; Used for timing.

Structure ANIMATION
	*timer.TIMELAPSE	; The timer's speed is "per frame", use the Get/SetLength functions to overcome this.
	frame_max.i		; Maximum global frame
	frame_min.i		; Minimum global frame
	frame_now.i		; Current local frame
	mode.i			; The animation mode (loop, play once, ping-pong, etc).
	status.i		; Internal var used for keeping track of the animation "status". It's used as a bool.
	is_playing.i		; Used to determine whether the animation is playing or not.
EndStructure

Enumeration 1
	#ANIMATION_LOOP
	#ANIMATION_PLAYONCE
	#ANIMATION_PINGPONG
EndEnumeration


Procedure.i AnimationCreate( Speed.i, MinFrame.i, MaxFrame.i, CurrentFrame.i = 0, Mode.i = #ANIMATION_LOOP, *callback.i = #Null )
	
	Define.ANIMATION *this = AllocateMemory( SizeOf(ANIMATION) )
	
	If *this
		
		With *this
			\timer		= TimeLapseCreate( Speed, *callback, *this ) ; We're passing this instance as the timer's user-data, useful in callbacks.
			\frame_max 	= MaxFrame
			\frame_min	= MinFrame
			\frame_now	= MinFrame + CurrentFrame
			\mode 		= Mode
			\status		= 0
			\is_playing	= #True
		EndWith
		
		ProcedureReturn *this
		
	EndIf
	
EndProcedure

Procedure.i AnimationDestroy( *this.ANIMATION )
	
	If *this
	
		If *this\timer
			TimeLapseDestroy(*this\timer)
		EndIf
		
		FreeMemory(*this)
		*this = #Null
		ProcedureReturn *this
		
	EndIf
	
EndProcedure

Procedure.i AnimationUpdate( *this.ANIMATION )
	
	If *this
		If *this\is_playing
			If TimeLapseUpdate( *this\timer )
				
				Select *this\mode
					
					Case #ANIMATION_LOOP
						
						*this\frame_now + 1
						
						If *this\frame_now > *this\frame_max
							*this\frame_now = *this\frame_min
						EndIf
						
					Case #ANIMATION_PINGPONG
						
						If *this\status
							
							*this\frame_now - 1
							
							If *this\frame_now < = *this\frame_min
								*this\status = #True - *this\status
							EndIf
						
						Else
							
							*this\frame_now + 1
							
							If *this\frame_now => *this\frame_max
								*this\status = #True - *this\status
							EndIf
							
						EndIf
						
					Case #ANIMATION_PLAYONCE
						
						If *this\status
							; status is set to one, this means we already played the animation.
						Else
							
							*this\frame_now + 1
							
							If *this\frame_now => *this\frame_max
								*this\status = #True - *this\status
							EndIf
							
						EndIf
						
				EndSelect	
				
				ProcedureReturn #True ; always return true unless you want to invalidate the update call!
				
			EndIf
		EndIf
	EndIf
	
EndProcedure

The helper functions:

Procedure.i AnimationGetCurrentFrame( *this.ANIMATION )
	ProcedureReturn *this\frame_now - *this\frame_min
EndProcedure

Procedure.i AnimationSetCurrentFrame( *this.ANIMATION, Frame.i )
	*this\frame_now = (Frame - *this\frame_min)
EndProcedure

Procedure.i AnimationGetTotalFrames( *this.ANIMATION )
	ProcedureReturn (*this\frame_max - *this\frame_min)
EndProcedure

Procedure.i AnimationGetMode( *this.ANIMATION )
	ProcedureReturn *this\mode
EndProcedure

Procedure.i AnimationSetMode( *this.ANIMATION, NewMode.i )
	*this\mode = NewMode
EndProcedure

Procedure.i AnimationGetSpeed( *this.ANIMATION )
	ProcedureReturn TimeLapseGet( *this\timer )
EndProcedure

Procedure.i AnimationSetSpeed( *this.ANIMATION, Speed.i )
	TimeLapseSet( *this\timer, Speed )
EndProcedure

Procedure.i AnimationPlay( *this.ANIMATION )
	*this\is_playing	= #True
EndProcedure

Procedure.i AnimationPause( *this.ANIMATION )
	*this\is_playing	= #False
EndProcedure

Procedure.i AnimationStop( *this.ANIMATION )
	*this\is_playing	= #False
	*this\frame_now		= *this\frame_min
EndProcedure

Procedure.i AnimationReset( *this.ANIMATION )
	AnimationStop(*this)
	AnimationPlay(*this)
EndProcedure

Procedure.i AnimationGetLength( *this.ANIMATION )
	ProcedureReturn AnimationGetTotalFrames( *this ) * AnimationGetSpeed( *this )
EndProcedure

Procedure.i AnimationSetLength( *this.ANIMATION, NewLength.i )
	AnimationSetSpeed( *this, NewLength / AnimationGetTotalFrames( *this ) )
EndProcedure

Thanks to the base lib, you can also use callbacks, this are useful for executing events on known frames (say you’re writing a game and you want a sound to be played at frame 12 from the jumping cycle… easy!)

A minimal example:

Define.ANIMATION *anim

If OpenConsole()
	*anim = AnimationCreate( 250, 0, 9, 0, #ANIMATION_PINGPONG )
	If *anim
		Repeat
			If AnimationUpdate( *anim ) ; the reason frame 0 is not seen in the first print is because we update before we display.
				PrintN( Str( AnimationGetCurrentFrame( *anim ) ) )
			EndIf
			Delay(10)
		Until Inkey() = Chr(27)
		
		AnimationDestroy(*anim)
	EndIf
	CloseConsole()
EndIf

A somewhat bloated example with callbacks can be found in here.

The playback functionality could be extended, however I found little need for other methods — That’s basically my excuse for “I ran out of ideas” — One thing I’d do is decouple the “play once” functionality from the playing mode, so essentially you’d be able to play a loop or ping-pong animation just once.

That’s pretty much it. Once again, this is trivial code but it’s a must and I’m sure someone will benefit from it.

The only reason I didn’t post a graphical example is because you need a way of dealing with sprite sheets and that’s the job for yet another library. Should you use the sprite library, you’d still need to feed the ClipSprite() function with meaningful data, there is no point in doing it by hand. That said, I’ll post the “sprite atlas” library tomorrow, hopefully.

Cheers

2 Comments on Animation lib

Closed

  1. thebearcat says:

    This is great stuff!! thanks!!

    I implemented it in my game and it works flawlessly! its way better than what i had.
    I wouldn’t mind to have a delay option so after the frames are played the animation waits X amount of time but other than that its a great idea!

    One thing i noticed is that you cant do fast animations with this code, how can i go around that? oh and will you release the sprite library?, what is it about?, did you implement a whole framework?, do tell!

    • GuShH says:

      Thanks for the suggestion. It seems like a useful addition, I’ll be adding it eventually. Good to know it works for you.

      Animations are timed through TimeLapse which uses two timing methods depending on the current platform, under Windows it uses timeGetTime() however we’re not defining a resolution, so this could be the problem in your case. Although if you need “very fast” animations you might want to look into the performance counter, needless to say it’s beyond the scope of this little library but should it prove a requirement, I’ll modify it accordingly.

      The “sprite library” is a wrapper around the official libs, providing atlas support (sprite sheets), it was initially designed for a friend with retro games in mind (no alpha channel, etc) so I’ll probably end up modifying it quite a bit before release. It only supports uniform grids to avoid using metadata. A more thorough solution would be to pack the sprites and automatically generate the metadata however it’s once again beyond the scope of the library.

      Cheers.