Wednesday, June 06, 2007

Animation 1: Moving stuff around

Animation 1: Moving stuff around

There are two basic kinds of animation techniques that UJML allows. The first, which I will cover in this lesson, is sliding/moving. The other kind is strip animation (like animated gifs).

Sliding or moving an image on the screen is very simple in UJML. It relies on the <delay> tag to pause briefly between painting so that the shown image appears to move smoothly.

I usually create a boolean state-variable called sTick whose job it is to delay for a few milliseconds, then call a function called step() which calculates the next paint position. The step() function then clears and sets a state variable called sDisplay which paints the image at the updated position.

Naturally, you should use whatever naming scheme is most comfortable for you.

Here is a barebones implementation of a moving image. Note that the state variables exist only to display the image or to fire a timer event. The positioning code is contained in the step() function.

<state-variables>
   <state-var name="sDisplay" type="boolean"/>
   <state-var name="sTick" type="boolean"/>
</state-variables>
<variables>
   <var name="mImgX" type="int"/>
   <var name="mImgY" type="int"/>
</variables>
<functions>
   <function name="step" type="void">
      <script>
         sTick = false;
         // Calculate the next position to display the image
         mImgX = mImgX + 10;
         mImgY = mImgY + 10;
         if (_gt(mImgX, mScrWidth))
            mImgX = 0;
         if (_gt(mImgY, mScrHeight))
            mImgY = 0;

         _clear_state(sDisplay);
         sDisplay = true;
         sTick = true;
      </script>
   </function>
</functions>
<states>
   <state var="sDisplay">
      <transition value="true">
         <display>
            <image>
               <url>&IMGURL;</url>
               <x><eval>mImgX</eval></x>
               <y><eval>mImgY</eval></y>
               <width>&IMGW;</width>
               <height>&IMGH;</height>
            </image>
         </display>
      </transition>
   </state>
   <state var="sTick">
      <transition value="true">
         <delay>33</delay>
         <script>
            step();
         </script>
      </transition>
   </state>
</states>


A common twist on this is to determine the "slide distance" and the "slide duration" of the movement, then use the timer to move some percentage of the way towards the end point of the slide. You will see this commonly in menus where a button press will cause the sliding menu to be shown and clicking an item in the menu will cause the menu to slide off the screen (or to a docking position).

In order to do this, you must remember the time you started the movement in order to determine what percentage of "slide distance" you need to move.

<variables>
   <var name="mSlideStart" type="int"/>
</variables>
<functions>
   <function name="startSlide" type="void">
      <script>
         mSlideStart = _msec(); // save the starting time
         step();
      </script>
   </function>
   <function name="step" type="void">
      <variables>
         <var name="timedelta" type="int"/>
      </variables>
      <script>
         sTick = false;
         _clear_state(sDisplay);

         timedelta = mSlideStart - _msec();
         if (_gte(timedelta, &SLIDETIME;))
         {
            // The slide has completed
            mImgX = &IMGPOSX;;
            mImgY = &IMGPOSY;;
            sDisplay = true;
         }
         else
         {
            // The slide is only partially through
            // Get the fractional amount to move
            mImgX = &IMGPOSX; * timedelta / &SLIDETIME;;
            mImgY = &IMGPOSY; * timedelta / &SLIDETIME;;
            sDisplay = true;
            sTick = true; // do another tick until &SLIDETIME is reached
         }
      </script>
   </function>
</functions>


In this example, the program now has a function called startSlide() which is used to begin the sliding process. While the elapsed time is less than the total sliding time, the step() function calculates the distance to move based on the elapsed time and the total distance to move. If the elapsed time has not reached the total time, the sTick timer is set to fire another event later. Once the elapsed time has passed the expected total time, the image is placed at its final position and displayed. The timer is not set again (until the user calls startSlide() again).

Other improvements can be made to this sliding technique. Using a logarithmic sliding distance is a clever way to simulate accelerating objects. Likewise, a callback function signalling the "slide end" can be created to allow the sliding item a way to notify the user that it has completed its slide. Inverting the sign of the slide distance can be used to reverse a slide (very useful for hiding a menu).

In all, this is a very simple technique based solely on painting, moving, and delaying. All animation in UJML is based on this technique.