Game Development Community

Fading (and Beyond)

by Eric Robinson · in Torque Game Builder · 03/17/2007 (11:42 pm) · 19 replies

Last year I followed the old Fading Tutorial and got fading to work enough to give my prototype legs. I started a second project and found that I could really use a good, general set of fading functions. What I came up with was the following:
$FadeGranularity = 50;

[b]// Fades an object to a specified opacity [0.0, 1.0] over the specified amount of time (in ms).
// [i]NOTE-[/i] %command is any string accepted by the eval() function.[/b]
function t2dSceneObject::fade(%this, %toAlpha, %time, %command)
{
     [b]//-------------------- ERROR CHECKING --------------------//[/b]
     if($FadeGranularity < 1)
     {
          echo("\c2FADE: Invalid $FadeGranularity value of \c1" @ $FadeGranularity @ "\c2.");
          return;
     }
     if((%toAlpha < 0) || (%toAlpha > 1))
     {
          echo("\c2FADE: Invalid Target Alpha value.  Received \c1" @ %toAlpha @ "\c2.");
          return;
     }

     [b]//------------------------ FADING ------------------------//[/b]
     if(%time > $FadeGranularity)
     {
          [b]// Find the number of updates left.[/b]
          %updatesRemaining = %time / $FadeGranularity;
          %alpha = %this.getBlendAlpha();

          [b]// Essentially we will calculate a Delta value for the alpha using the number of calls left.
          //	 The expected change in alpha is human readable as:
          //		"Alpha-Distance left to travel divided by the number of increments."
          //  Note that negative Delta values mean 'fading out'.[/b]

          %alpha += (%toAlpha - %alpha) / %updatesRemaining;

          [b]// Set the alpha and schedule the next call.[/b]
          %this.setBlendAlpha(%alpha);
          %this.schedule($FadeGranularity, fade, %toAlpha, %time - $FadeGranularity, %command);
     }
     else
     {
          [b]// Time's up.  Finish.[/b]
          %this.setBlendAlpha(%toAlpha);
          if(%command !$= "")
          {
               // Target reached.  Issue "Callback".
               eval(%command);
          }
     }     
}
This one function can do everything. It will fade in, fade out, and properly issue callbacks. One thing I found really frustrating in the tutorial was the inherent callback limitations. The above code makes callbacks incredibly dynamic. Take a look at some example function calls:
[b]// Fades an object out in two seconds.[/b]
%object.fade(0, 2000);

[b]// Fade an object out in one second.  Once faded, write a message to the console.[/b]
%object.fade(0, 1000, "echo(\"Goodbye cruel world!\");");

[b]// Fade an object to half opacity in half a second and then back in to full opacity in one second.[/b]
%object.fade(0.5, 500, %object.getID() @ ".fade(1, 1000);");

[b]// Set an object to fade in and out in one second [i]ad infinitum.[/i][/b]
%objID = %object.getID();
%object.fadeOutCallback = %objID @ ".fade(1, 500, " @ %objID @ ".fadeInCallback);";
%object.fadeInCallback = %objID @ ".fade(0, 500, " @ %objID @ ".fadeOutCallback);";
%object.fade(0, 500, %object.fadeOutCallback);
The first two examples simply show the fade in and fade out functionality. The third shows the power of this type of callback. Eval() is incredibly powerful and allows for some really interesting dynamism.

#1
03/17/2007 (11:42 pm)
The code can also be generalized. I took five minutes to build a GrowOrShrink function to grow or shrink the size of an object on screen:
[b]// Resizes an object linearly to a specified size over the specified amount of time.
// Note that this function also uses the $FadeGranularity global variable.[/b]
function t2dSceneObject::GrowOrShrink(%this, %toWidth, %toHeight, %time, %command)
{	
     if(%time > $FadeGranularity)
     {
          %width = %this.getWidth();
          %height = %this.getHeight();
          %updatesRemaining = %time / $FadeGranularity;
	
          [b]// Essentially we will calculate a Delta value for the alpha using the number of calls left.
          //	 The expected change in value is human readable as:
          //		"value-Distance left to travel divided by the number of increments."[/b]

          %width += (%toWidth - %width) / %updatesRemaining;		
          %height += (%toHeight - %height) / %updatesRemaining;

          [b]// Set the values and schedule the next call.[/b]
          %this.setWidth(%width);
          %this.setHeight(%height);
          %this.schedule($FadeGranularity, GrowOrShrink, %toWidth, %toHeight, %time - $FadeGranularity, %command);
     }
     else
     {
          [b]// Time's up.  Finish.[/b]
          %this.setWidth(%toHeight);
          %this.setHeight(%toHeight);
          if(%command !$= "")
          {
               [b]// Target reached.  Issue "Callback".[/b]
               eval(%command);
          }
     }
}
Further, it wouldn't take much to make a completely generalized version of the function (you could call it something like a LimitBasedIncrementor(...)).

And it gets better, for those of you who are afraid of repeated divisions, an enhanced version:
[b]// Fades an object to a specified opacity over the specified amount of time.
//  Fails to adapt to sudden changes in opacity.[/b]
function t2dSceneObject::fadeFast(%this, %toAlpha, %time, %command)
{
     [b]//-------------------- ERROR CHECKING --------------------//[/b]
     if($FadeGranularity < 1)
     {
          echo("\c2FADE: Invalid $FadeGranularity value of \c1" @ $FadeGranularity @ "\c2.");
          return;
     }
     if((%toAlpha < 0) || (%toAlpha > 1))
     {
          echo("\c2FADE: Invalid Target Alpha value.  Received \c1" @ %toAlpha @ "\c2.");
          return;
     }

     [b]//------------------------ FADING ------------------------//[/b]
     %alpha = %this.getBlendAlpha();
     %updatesRemaining = %time / $FadeGranularity;
     %delta = (%toAlpha - %alpha) / %updatesRemaining;
     %this.fadeFastHelp(%toAlpha, %delta, %time, %command);
}

[b]// Blindly fades based on a preset increment to a specified opacity [in percentage] over the specified amount of time..
//  Does not adapt to sudden changes in opacity.
//  Does not adapt to sudden changes in $FadeGranularity.[/b]
function t2dSceneObject::fadeFastHelp(%this, %toAlpha, %delta, %time, %command)
{
     if(%time > $FadeGranularity)
     {
          %this.setBlendAlpha(%this.getBlendAlpha()+%delta);
          %this.schedule($FadeGranularity, fadeFastHelp, %toAlpha, %delta, %time - $FadeGranularity, %command);
     }
     else
     {
          [b]// Time's up.  Finish.[/b]
          %this.setBlendAlpha(%toAlpha);
          if(%command !$= "")
          {
               [b]// Target reached.  Issue "Callback".[/b]
               eval(%command);
          }
     }
}
There's a trade-off here. The code is reduced to exactly two divisions [which occur during the setup]. The drawback is that the fading doesn't take into consideration any external modifications to the opacity after the initial call to fadeFast().

Note that all three of the above functions use the same $FadingGranularity variable. This value specifies the update interval in milliseconds: a larger value might appear choppy whereas a smaller value will appear generally smoother. The code could easily be modifiable to depend on a per-object basis.

Overall, I believe that the code simplicity, functionality, expandability, and speed make this an overall much nicer implementation of fading than the one presented in the rather dated tutorial. As such, I am considering editing the old tutorial or writing a brand new one. Before I go ahead and do that, though, I'd be interested in hearing the community's thoughts. So, err... thoughts?
#2
03/18/2007 (12:47 am)
Very nice, do you have plans to place this stuff on TDN as tutorials or 'code snippets' ... ?

Useful code snippets are always nice ...
#3
03/18/2007 (3:39 pm)
Nice. I too have been playing around with fading. I based mine off the fade routines that used in the Tankbusters demo.

Another feature that I wanted was the ability to fade all objects in a scene.

Let me see if I can find . . .
Ah here we go.
//Fade all objects in a scene
function t2dSceneGraph::fadeAlpha(%sceneGraph, %time, %alpha, %inc, %objcallback, %sceneCallback)
{
      //Get all the Scene Objects
      %sceneObjectList = %sceneGraph.getSceneObjectList();

      //Set a fade schedule for each object in the scene
      for (%i = 0; %i < getWordCount(%sceneObjectList); %i++)
      {
              %sceneObject = getWord(%sceneObjectList, %i);   //Get each scene object
              %sceneObject.fadeToAlpha(%time, %alpha, %inc, %objcallback); //Set a fade for each object
      }

      //Take our last %sceneObject and if we have a scene callback wait for it to finish fading.
      if( !(%sceneCallBack $= ""))
	      %sceneGraph.waitForFade( %sceneObject, %sceneCallBack);

}

function t2dSceneGraph::waitForFade( %sceneGraph, %obj, %sceneCallBack)
{
//Wait for the specified object ot finish fading checking every 10 ms
     if(isEventPending(%obj.alphaFadeSchedule))  //alphaFadeSchedule is set in the object fade function
	     %sceneGraph.schedule(10, "waitForFade", %obj, %sceneCallBack ); 
     else
	     schedule(0, 0, %sceneCallBack);
}

Obiously it would be easy to weak for what ever fade mechanism you choose.
I found it handy to have a callback for each object fade and also one for when the scene finished fading.
It could use some additional refinements, it currently attempts to fade every object in the scene. You could use some sort of object identification to only fade objects that are actually rendered to the display.

You should have a look at how they did it in tankbusters. If you own the pro version of TGB they also use fade routines in the level builder.

Maybe you will find it useful.
#4
03/18/2007 (5:05 pm)
Good stuff Eric, I could put this to immediate use.
#5
03/18/2007 (5:47 pm)
@David: That's kind of the idea. Would that be helpful to you?

@Guy: I seem to have re-implemented what GG did with their FadeToAlpha function in the TGB code (incidentally, I have a version of the function called "FadeToAlpha", too, that simply calls my general function with a preset time). I did not implement an event management system, which they did, but could easily include that as a portion of the tentative Fade Tutorial (it's a whole two or so lines of code). As for the overall code itself, I believe mine to be cleaner. Certain calls occur extraneously under certain circumstances, making the code a bit tougher to read interpret.

@Todd: Do you think it would be useful to put this up as a Tutorial? Or should I just leave it here in the forums?
#6
03/18/2007 (6:09 pm)
@Eric, I believe it would be very helpful for others -- as a TDN Article of some fashion or another
#7
03/18/2007 (11:58 pm)
I have started a tutorial on TDN. I was considering putting it up as a script resource but feel that it's more valuable as a learning device as lots of interesting concepts are used in it. That and the old tutorial is getting really dated.

I'll be working on it more tonight/tomorrow.
#8
03/19/2007 (12:29 am)
Looking good!


Now if someone would just do the needed work to let us fade GUIs too.... =)
#9
03/19/2007 (12:58 am)
Great job throwing this up on TDN... btw feel free to re-link the old one to your new one. I originally did that tutorial well over a year ago and long before TGB (called T2D then) had even a Level Builder. It's always awesome seeing great community members step up and contribute good materials to help others out.
#10
03/19/2007 (8:07 am)
Great Job Eric! Thanks for the contribution to the community.
#11
03/19/2007 (8:51 am)
Nice work Eric!
#12
04/21/2007 (11:35 pm)
I have finished the tutorial. Take a look at the (essentially) final version here.

Comments and suggestions are definitely welcome!
#13
05/02/2007 (11:24 am)
Eric,
that is such a great tutorial! so helpful.
and those functions are awesome. now i have to go through my game and rewrite a ton of stuff!

thank you.
#14
05/09/2007 (6:03 pm)
Looks great -- love the layout
#15
04/15/2010 (10:40 am)
Thank you , this has been huge help.
#16
04/16/2010 (6:36 am)
Haha, no problem! I'm glad to hear the tutorial and/or this post are still useful!
#17
04/16/2010 (9:29 am)
A flash from the past.
#18
11/13/2010 (3:01 am)
Sorry for the "new kid on the block" question, but what does the [b] mean?

I'm trying to schedule a fade out on text object levelText.

function bgClass::onLevelLoaded(%this, %scenegraph)
{
$circlesLeft =0;
levelText.text = "Level" SPC player.world SPC "-" SPC player.level;

levelText.schedule(3000, );

}

thanks.
#19
11/13/2010 (5:16 am)
it's BBCode, probably from the old layout ... anything in [] should be ignored, or treated like HTML. [b] was for bold.