Game Development Community

Sprite property interpolation code

by Jason McIntosh · in Torque Game Builder · 04/28/2005 (9:36 pm) · 5 replies

Well, I'm in need of animatable properties, but they aren't ready yet, so I created a stop-gap in TorqueScript for my current game.

Here's the interpolation code (crude linear), a "sizer" object which will scale an image on both dimensions within a given timeframe (milliseconds), and a "fader" object which does the same, except for alpha blending values. These will optionally "ping-pong" their effects so there's a nice pulsing effect. This also can be extended to do rotations, probably translations too. And it's framerate independent.

You don't have to understand the code to use it. I'll give an example at the end. :)

// timeStart = SimTime when interpolating began
// timeTotal = how long interpolation should take (milliseconds)
// interval = startValue - endValue: for example, 1.0 (end) - 0.0 (start) = 1.0 (interval)
function interpUp( %timeStart, %timeTotal, %interval )
{
	%now = getSimTime();
	%total = %now - %timeStart;
	if ( %total < %timeTotal )
	{
		%percent = %total / %timeTotal;
		return %interval * %percent;
	}
	else
	{
		return %interval;
	}
}

// timeStart = SimTime when interpolating began
// timeTotal = how long interpolation should take (milliseconds)
// interval = startValue - endValue: for example, 1.0 (start) - 0.0 (end) = 1.0 (interval)
function interpDown( %timeStart, %timeTotal, %interval )
{
	%now = getSimTime();
	%total = %now - %timeStart;
	if ( %total < %timeTotal )
	{
		%percent = %total / %timeTotal;
		return %interval - (%interval * %percent);
	}
	else
	{
		return 0;
	}
}

(continued...)

(Edit: I added code for fading.)

#1
04/28/2005 (9:36 pm)
function sizer::onAdd( %this )
{
	%this.sizing = false;
	%this.repeat = false; // set to true to ping-pong the sizing.
}

function sizer::onRemove( %this )
{
	if ( %this.sizing == true )
		cancel( %this.nextUpdate );

	%this.image.setSize( %this.sizeXY ); // Set back to original size
}

// Values to size to, eg: 5, 10 -> sizes the object on the X component.
function sizer::setX( %this, %min, %max )
{
	%this.sizeXY = %this.image.getSize(); // Cache original size
	
	%this.xMin = %min;
	%this.xMax = %max;
	if ( %this.xMax > %this.xMin )
		%this.xInterval = %this.xMax - %this.xMin;
	else
		%this.xInterval = %this.xMin - %this.xMax;
}

// Values to size to, eg: 5, 10 -> sizes the object on the Y component.
function sizer::setY( %this, %min, %max )
{
	%this.yMin = %min;
	%this.yMax = %max;
	if ( %this.yMax > %this.yMin )
		%this.yInterval = %this.yMax - %this.yMin;
	else
		%this.yInterval = %this.yMin - %this.yMax;
}

function sizer::sizeUp( %this, %sizeTime )
{
	if ( %this.sizing == true )
		cancel( %this.nextUpdate ); // Interrupt if in progress

	if ( %this.xMin > %this.xMax )
	{
		// Swap in case we're reversing direction.
		%t = %this.xMin;
		%this.xMin = %this.xMax;
		%this.xMax = %t;
	}	

	if ( %this.yMin > %this.yMax )
	{
		// Swap in case we're reversing direction.
		%t = %this.yMin;
		%this.yMin = %this.yMax;
		%this.yMax = %t;
	}	

	%this.sizeTime = %sizeTime;
	%this.sizing = true;
	%this.sizeStart = getSimTime();
	%this.lastUpdate = %this.sizeStart;
	%this.nextUpdate = %this.schedule( 20, "sizeUpUpdate" );

	%this.image.setSize( %this.xMin SPC %this.yMin );
}

function sizer::sizeDown( %this, %sizeTime )
{
	if ( %this.sizing == true )
		cancel( %this.nextUpdate ); // Interrupt if in progress

	if ( %this.xMin > %this.xMax )
	{
		// Swap in case we're reversing direction.
		%t = %this.xMin;
		%this.xMin = %this.xMax;
		%this.xMax = %t;
	}	

	if ( %this.yMin > %this.yMax )
	{
		// Swap in case we're reversing direction.
		%t = %this.yMin;
		%this.yMin = %this.yMax;
		%this.yMax = %t;
	}	

	%this.sizeTime = %sizeTime;
	%this.sizing = true;
	%this.sizeStart = getSimTime();
	%this.lastUpdate = %this.sizeStart;
	%this.nextUpdate = %this.schedule( 20, "sizeDownUpdate" );

	%this.image.setSize( %this.xMax SPC %this.yMax );
}

function sizer::sizeUpUpdate( %this )
{
	%x = interpUp( %this.sizeStart, %this.sizeTime, %this.xInterval );
	%y = interpUp( %this.sizeStart, %this.sizeTime, %this.yInterval );

//	echo( "sizeUpUpdate x = " @ %x+%this.xMin @ " (" @ %this.xMax @ "), y = " @ %y+%this.yMin @ " (" @ %this.yMax @ ")" );

	if ( (%x + %this.xMin) < %this.xMax || (%y + %this.yMin) < %this.yMax )
	{
		// Hasn't reached max on both components yet.
		%this.nextUpdate = %this.schedule( 20, "sizeUpUpdate" );
	}
	else
	{
		// Got to max on x and y components, done.
		%this.sizing = false;
		if ( %this.repeat == true )
			%this.sizeDown( %this.sizeTime ); // Start opposite sizing.
	}
	
	%this.image.setSize( (%this.xMin + %x) SPC (%this.yMin + %y) );
}	

function sizer::sizeDownUpdate( %this )
{
	%x = interpDown( %this.sizeStart, %this.sizeTime, %this.xInterval );
	%y = interpDown( %this.sizeStart, %this.sizeTime, %this.yInterval );

//	echo( "sizeDownUpdate x = " @ %x+%this.xMin @ " (" @ %this.xMin @ "), y = " @ %y+%this.yMin @ " (" @ %this.yMin @ ")" );

	if ( (%x + %this.xMin) > %this.xMin || (%y + %this.yMin) > %this.yMin )
	{
		%this.nextUpdate = %this.schedule( 20, "sizeDownUpdate" );
	}
	else
	{
		%this.sizing = false;
		if ( %this.repeat == true )
			%this.sizeUp( %this.sizeTime );
	}
	
	%this.image.setSize( (%this.xMin + %x) SPC (%this.yMin + %y) );
}

// Proxy function to start a size with a schedule()
function startSizeUp( %sizer, %sizeTime )
{
	%sizer.sizeUp( %sizeTime );
}

function startSizeDown( %sizer, %sizeTime )
{
	%sizer.sizeDown( %sizeTime );
}

(continued...)
#2
04/28/2005 (9:37 pm)
Example usage:

%image = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
	....etc....

	// Scale from fullscreen to small, then back, over and over
	%sizeomatic = new ScriptObject( sizer ) { image = %image; };
	%sizeomatic.repeat = true;
	%sizeomatic.setX( 100, 25 ); // x start, x end
	%sizeomatic.setY( 75, 18 ); // y start, y end
	
	%sizeomatic.sizeDown( 4000 ); // do it in 4 seconds

That's it.
#3
04/28/2005 (10:24 pm)
Here's code for fading an image.

function fader::onAdd( %this )
{
	%this.fading = false;
	%this.repeat = false; // Set to true to "ping-pong" the fade.
	%this.setAlphaRange( 0, 255 ); // Provide common default.
}

function fader::onRemove( %this )
{
	if ( %this.fading == true )
		cancel( %this.nextUpdate );

	%this.image.setBlendColour( %this.alpha ); // Reset to original alpha
}

// Set the alpha start and end values, from 0 - 255 for each. 0 = totally invisible.
function fader::setAlphaRange( %this, %min, %max )
{
	%this.alpha = %this.image.getBlendColour(); // Cache original alpha value

	if ( %min < 0 )
		%min = 0;
	
	if ( %max > 255 )
		%max = 255;
		
	%this.min = %min;
	%this.max = %max;

	if ( %this.max > %this.min )
		%this.interval = %this.max - %this.min;
	else
		%this.interval = %this.min - %this.max;
}

function fader::fadeIn( %this, %fadeTime )
{
	if ( %this.fading == false )
		cancel( %this.nextUpdate ); // Interrupt if in progress

	if ( %this.min > %this.max )
	{
		// Swap in case we're reversing direction.
		%t = %this.min;
		%this.min = %this.max;
		%this.max = %t;
	}	

	%this.fadeTime = %fadeTime;
	%this.fading = true;
	%this.fadeStart = getSimTime();
	%this.lastUpdate = %this.fadeStart;
	%this.nextUpdate = %this.schedule( 20, "fadeInUpdate" );

	%this.image.setBlendColour( "255 255 255" SPC %this.min );
}

function fader::fadeOut( %this, %fadeTime )
{
	if ( %this.fading == false )
		cancel( %this.nextUpdate ); // Interrupt if in progress

	if ( %this.min > %this.max )
	{
		// Swap in case we're reversing direction.
		%t = %this.min;
		%this.min = %this.max;
		%this.max = %t;
	}	

	%this.fadeTime = %fadeTime;
	%this.fading = true;
	%this.fadeStart = getSimTime();
	%this.lastUpdate = %this.fadeStart;
	%this.nextUpdate = %this.schedule( 20, "fadeOutUpdate" );

	%this.image.setBlendColour( "255 255 255" SPC %this.max );
}

function fader::fadeInUpdate( %this )
{
	%alpha = interpUp( %this.fadeStart, %this.fadeTime, %this.interval );

	if ( (%alpha + %this.min) < %this.max )
	{
		// Hasn't reached max alpha.
		%this.nextUpdate = %this.schedule( 20, "fadeInUpdate" );
	}
	else
	{
		// Got to max alpha, done.
		%this.fading = false;
		if ( %this.repeat == true )
			%this.fadeOut( %this.fadeTime ); // Start opposite fading.
	}
	
	%this.image.setBlendColour( "255 255 255" SPC (%this.min + %alpha) );
}	

function fader::fadeOutUpdate( %this )
{
	%alpha = interpDown( %this.fadeStart, %this.fadeTime, %this.interval );

	if ( (%alpha + %this.min) > %this.min )
	{
		%this.nextUpdate = %this.schedule( 20, "fadeOutUpdate" );
	}
	else
	{
		%this.fading = false;
		if ( %this.repeat == true )
			%this.fadeIn( %this.fadeTime );
	}
	
	%this.image.setBlendColour( "255 255 255" SPC (%this.min + %alpha) );
}

// Proxy function to start a fade with a schedule()
function startFadeIn( %fader, %fadeTime )
{
	%fader.fadeIn( %fadeTime );
}

function startFadeOut( %fader, %fadeTime )
{
	%fader.fadeOut( %fadeTime );
}

(continued...)
#4
04/28/2005 (10:28 pm)
Example usage:

%image = new fxStaticSprite2D() { scenegraph = t2dSceneGraph; };
(...etc...)

%fadeomatic = new ScriptObject( fader ) { image = %image; };
%fadeomatic.min = 0; // alpha min, 0 = totally invisible
%fadeomatic.max = 128; // 50% visible
%fadeomatic.repeat = true; // ping-pong the fade
%fadeomatic.fadeOut( 3000 ); // fade out in 3 seconds

The alpha min/max defaults to 0/255 (totally invisible/totally visible), so you can leave that part out if the default is ok.

Pretty easy. :)
#5
04/29/2005 (9:11 am)
Will definitely come in handy Jason! Thanks!:)