July
---
2003




Animation in Flash vs. Animation in C#, round II

Here is the second part of the comparisons of Actionscript and C#, with some good examples. A large surface, is divided in four sections. Clicking on any of the colored section moves that section into the center, applying the selected animation mode.

I had used this approach for a UI prototype I was experimenting with...

C# Flash
shot shot

Download (11kb)

Download (482kb)

Let's see how I did this functionality with C#. The C# compiler is very strict in the sense that it will warn you (relentlessly over and over again) if you do not define a variable and is type or any other error is found before compiling. Other than that there are many more differences, but it has the same eventhandlers that in C# are added to the object. Instead of using a setInterval, I use a Timer, which works exactly the same way. (source (10kb))

C# code

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Diagnostics;

namespace NextGen_Demo_Animation{
    public class Form1 : System.Windows.Forms.Form{
        private System.Windows.Forms.PictureBox pictureBox1;
        private System.Windows.Forms.Label label1;
        private System.ComponentModel.IContainer components;
        private System.Timers.Timer timer1;
        private System.Windows.Forms.ComboBox comboBox1;

        //Miguel's init variables declaratio-----------------------------
        private int nowquadrant    = 1;
        private int toquadrant    = 1;

        //constants defining limits
        const int llimit        = 50;
        const int rlimit        = 400;
        const int tlimit        = 35;
        const int blimit        = 300;

        private float t;
        private float d;
        private float b;
        private float c;

        private float a=0;
        private float s=0;
        private float p=0;

        private int timeNow;
        private int timeStart;
        private int propxStart;
        private int propyStart;
        private int propxDest;
        private int propyDest;
        private int timeDest;


        //create 2-dimensional array to contain position info for each quadrant
        private int[,] Arr_quadrants = new int [,]{
			{0,0 },
			{0, 0},
			{-360, 0},
			{0, -264},
			{-360, -264}};

        //this array stores current start position when click is performed
        private int [] Arr_posstart = new int []{0,0};
        //---------------------------------------------------------------    
        
        public Form1(){
            InitializeComponent();
        }

        protected override void Dispose( bool disposing ){
            if( disposing ){
                if (components != null){
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        private void InitializeComponent(){
            Resources.ResourceManager resources=
			new System.Resources.ResourceManager(typeof(Form1));
            this.pictureBox1 = new System.Windows.Forms.PictureBox();
            this.label1 = new System.Windows.Forms.Label();
            this.timer1 = new System.Timers.Timer();
            this.comboBox1 = new System.Windows.Forms.ComboBox();
            ((System.ComponentModel.ISupportInitialize)
            (this.timer1)).BeginInit();
            this.SuspendLayout();
            //
            // pictureBox1
            //
            this.pictureBox1.Enabled = false;
            this.pictureBox1.Image=
            ((Drawing.Bitmap)(resources.GetObject("pictureBox1.Image")));
            this.pictureBox1.Name = "pictureBox1";
            this.pictureBox1.Size = new System.Drawing.Size(800, 600);
            this.pictureBox1.TabIndex = 0;
            this.pictureBox1.TabStop = false;
            //
            // label1
            //
            this.label1.BackColor = System.Drawing.SystemColors.Info;
            this.label1.Location = new System.Drawing.Point(40, 32);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(152, 152);
            this.label1.TabIndex = 1;
            this.label1.Text = "label1";
            //
            // timer1
            //
            this.timer1.Interval = 1;
            this.timer1.SynchronizingObject = this;
            this.timer1.Elapsed += 
            new System.Timers.ElapsedEventHandler(this.timer1_Elapsed);
            //
            // comboBox1
            //
            this.comboBox1.Items.AddRange(new object[] {
                                                           "linear",
                                                           "easeinquad",
                                                           "easeoutquad",
                                                           "easeinoutquad",
                                                           "easeincubic",
                                                           "easeoutcubic",
                                                           "easeinoutcubic",
                                                           "easeinquart",
                                                           "easeinexpo",
                                                           "easeoutexpo",
                                                           "elasticout"});
            this.comboBox1.Location = new System.Drawing.Point(200, 160);
            this.comboBox1.MaxDropDownItems = 11;
            this.comboBox1.Name = "comboBox1";
            this.comboBox1.Size = new System.Drawing.Size(121, 21);
            this.comboBox1.TabIndex = 2;
            this.comboBox1.Text = "linear";
            //
            // Form1
            //
            this.AutoScale = false;
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(442, 338);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
            this.comboBox1,
            this.label1,
            this.pictureBox1});
            
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Form1";
            this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
            this.Text = "NextGen Animation Test by Miguel";
            this.TopMost = true;
            this.MouseDown += 
            new System.Windows.Forms.MouseEventHandler(this.Form_MouseDown);
            ((System.ComponentModel.ISupportInitialize)(this.timer1)).EndInit();
            this.ResumeLayout(false);

        }
        #endregion

        static void Main(){
            Application.Run(new Form1());
            }

        
        //Miguel's methods below------------------------------------------
        private void Form_MouseDown(
        object sender, 
        System.Windows.Forms.MouseEventArgs e) {
            //This handles the MouseEventHandler for the form
            //first find out wich quadrant to go to according to cursor position
            determinequadrant(e.X,e.Y);
            
            //initialize the variables, times and timer component
            if(!timer1.Enabled && nowquadrant != toquadrant){
                t=0;b=0;c=0;d=0;
                timeStart = 
                (int)((DateTime.Now.Second*1000)+DateTime.Now.Millisecond);
                timeDest  = 
                (int)(((DateTime.Now.Second+1)*1000)+DateTime.Now.Millisecond);

                Arr_posstart[0] = this.Arr_quadrants[nowquadrant,0];
                Arr_posstart[1] = this.Arr_quadrants[nowquadrant,1];

                timer1.Stop();
                timer1.Enabled = false;
                timer1.Enabled = true;

            }else{
                //MessageBox.Show("busy animating. \n\nLeave me alone...");
            }
        }
        
        private int tween(int prop){
            //this is the method that gets repeatedly 
            //called to calculate position of the image
            this.t = 
            (float)((Now.Second*1000)+Now.Millisecond-timeStart);
            this.b = 
            (float)Arr_posstart[prop];
            this.c = 
            (float)(this.Arr_quadrants[toquadrant,prop] - Arr_posstart[prop]);
            this.d = 
            (float)timeDest - timeStart;

            return getFormula(this.comboBox1.SelectedItem.ToString());
        }

        private int getFormula(string animType){
            //adjust formula to selected algoritm from combobox
            switch (animType) {
                case "linear":
                    // simple linear tweening - no easing
                    return (int)(c*t/d+b);
                    break;

                case "easeinquad":
                    // quadratic (t^2) easing in-accelerating from zero velocity
                    return (int)(c*(t/=d)*t + b);
                    break;
                
                case "easeoutquad":
                    // quadratic (t^2) easing out-decelerating to zero velocity
                    return (int)(-c*(t=t/d)*(t-2)+b);
                    break;
                
                case "easeinoutquad":
                    // quadratic easing in/out-acceleration until halfway, 
                    //then deceleration
                    if((t/=d/2)<1)return
                    (int)(c/2*t*t+b);else return(int)(-c/2*((--t)*(t-2)-1)+b);
                    break;
                
                case "easeincubic":
                    // cubic easing in - accelerating from zero velocity
                    return (int)(c*(t/=d)*t*t + b);
                    break;

                case "easeoutcubic":
                    // cubic easing in - accelerating from zero velocity
                    return (int)(c*((t=t/d-1)*t*t + 1) + b);
                    break;
                
                case "easeinoutcubic":
                    // cubic easing in - accelerating from zero velocity
                    if ((t/=d/2)<1)return(int)(c/2*t*t*t+b);
                    else return(int)(c/2*((t-=2)*t*t+2)+b);
                    break;

                case "easeinquart":
                    // quartic easing in - accelerating from zero velocity
                    return (int)(c*(t/=d)*t*t*t + b);
                    break;

                case "easeinexpo":
                    // exponential (2^t) easing in - 
                    //accelerating from zero velocity
                    if (t==0) return (int)b; else return 
                    (int)(c*Math.Pow(2,(10*(t/d-1)))+b);
                    break;
                
                case "easeoutexpo":
                    // exponential (2^t) easing out - 
                    //decelerating to zero velocity
                    if (t==d) return (int)(b+c); else return 
                    (int)(c * (-Math.Pow(2,-10*t/d)+1)+b);
                    break;

                case "elasticout":
                    //Elastic easing out: exponentially decaying sine wave
                    p=(float)(d*(0.3));
                    if (t==0) return (int)b;                    
                    if ((t/=d)==1) return (int)(b+c);
                    if(a< Math.Abs(c)){a=c;s=p/4;}else{s =
                    (float)(p/(2*Math.PI)*Math.Asin(c/a));};
                    return (int)(a*Math.Pow(2,-10*t) * 
                    Math.Sin( (t*d-s)*(2*Math.PI)/p )+c+b);
                    break;

                default:
                    return (int)(c*t/d+b);
                    break;
            }
        }
        private void determinequadrant(int x, int y){
            switch(nowquadrant){
                case 1:
                    if(x > rlimit){
                        if(y < blimit) toquadrant = 2; else toquadrant = 4;
                    }else{
                        if(y > blimit) toquadrant = 3;
                    }
                break;
                case 2:
                    if(x < llimit){
                        if(y < blimit) toquadrant = 1; else toquadrant = 3;
                    }else{
                        if(y > blimit) toquadrant = 4;
                    }
                break;
                case 3:
                    if(x > rlimit){
                        if(y < tlimit) toquadrant = 2; else toquadrant = 4;
                    }else{
                        if(y < tlimit) toquadrant = 1;
                    }
                break;
                case 4:
                    if(x < llimit){
                        if(y < tlimit) toquadrant = 1; else toquadrant = 3;
                    }else{
                        if(y < tlimit) toquadrant = 2;
                    }
                break;
            }
        }
        
        private void timer1_Elapsed(
        object sender, 
        System.Timers.ElapsedEventArgs e) {
            label1.Text = "t: "+t + "\nb: " + b+"\nc: " + 
            c + "\nd: " + d + "\ntimeDest :"+timeDest + 
            "\ntimeStart: "+timeStart + "\nxpos: " + pictureBox1.Location.X + 
            "\nYpos: " + this.pictureBox1.Location.Y + "\nDateTime.Now = " + 
            (int)((DateTime.Now.Second*1000)+DateTime.Now.Millisecond) + 
            "\n\n"+timeDest +"==" + 
            (int)((DateTime.Now.Second*1000)+DateTime.Now.Millisecond );
            
            //if time has run out, then reset and stop
            if(
            (int)timeDest <= 
            (int)((DateTime.Now.Second*1000)+DateTime.Now.Millisecond) || 
            (timeDest-1000) > 
            (int)((DateTime.Now.Second*1000)+DateTime.Now.Millisecond) ) {
            this.timer1.Stop();this.timer1.Enabled = 
            false;t=0;b=0;c=0;d=0;a=0;s=0;p=0;
            
            this.pictureBox1.Location=new System.Drawing.Point(
            this.Arr_quadrants[toquadrant,0], this.Arr_quadrants[toquadrant,1]);
            nowquadrant = toquadrant;
            }else{
                this.pictureBox1.Location = 
                new System.Drawing.Point(tween(0), tween(1));
            }
        }
    }
}			
			

Now lets's see how it is replicated in Actionscript. This Flash movie uses scripted animation with the help of Robert Penner's easing algoritms. The original tween method in Actionscript was originally written by Zeh, but I have modified it a bit. (source (7kb))

Flash Actionscript code

// ActionScript Document by Miguel Moreno (started: 04/18/2003)
//==========================================================================
function init(){
    //assign the eventHandlers to each of the boxes
    for(i=1;i<=4;++i){
        //embeds the id as a property into the box
        shell["box_"+i].ID = i;
        shell["box_"+i].useHandCursor = false;    
        shell["box_"+i].onRelease = function(){
			if(this.ID != select_id)box_click(this.ID)};
    }    
    
    //defines the positions for the shell and final position of the selected box
    //Arr_posx_shell     = new Array(0, -922, 0, -922);
    //Arr_posy_shell     = new Array(0, 0, -692, -692);
    Arr_posx_shell     = new Array(-40, -360, -40, -360);
    Arr_posy_shell     = new Array(-40, -40, -270, -270);
    
    //populates the array and the dropdown combobox
    Arr_combo = new Array("linear",
		"easeinquad",
		"easeoutquad",
		"easeinoutquad",
		"easeincubic",
		"easeoutcubic",
		"easeinoutcubic",
		"easeinquart",
		"easeinexpo",
		"easeoutexpo",
		"elasticout");
    combobox.setDataProvider(Arr_combo);
}

//run init() function
init();

//eventHandler when box is clicked on - starts the animation
function box_click(id){
    shell.tween(
    ["_x","_y", "_xscale", "_yscale"], [Arr_posx_shell[id-1], 
    Arr_posy_shell[id-1], 78.1, 78.1], 1, 
    Arr_combo[combobox.getSelectedIndex()], undefined, "adjust_menu", id);
}

// ActionScript Document tween animation code
MovieClip.prototype.tween=function(
	prop, 
	propDest, 
	timeSeconds, 
	animType, 
	delay, 
	callback, 
	clickid){
	
	// Sets default values if undefined/invalid
    if (timeSeconds < 0.01){timeSeconds = 2;}
    if (
    animType == undefined || 
    animType == ""){animType = "easeOutExpo";} // default equation!
    if (delay == undefined){delay = 0;}
            
    // Starts tweening.. prepares to create handling mcs
    var properties = new Array(); // Properties. Ex, "_x"
    var oldProperties = new Array(); // Old value. Ex, 0
    var newProperties = new Array(); // New (target) value. Ex, 100

    if (typeof(prop) == "string") {
        // Single property
        properties.unshift(prop);
        oldProperties.unshift(this[prop]);
        newProperties.unshift(propDest);
     }else {
        // Array of properties
        for (var i in prop) oldProperties.unshift (this[prop[i]]);
        for (var i in prop) properties.unshift (prop[i]);
        for (var i in propDest) newProperties.unshift (propDest[i]);
     }
      
     for (var i=0; i< oldProperties.length; i++) {
        if (newProperties[i] != undefined) {
            this.$_tweenCount++;
    
            var newMC = "_doTweenHolderMovieClip_"+properties[i];
          
              //if it already exists, delete it
            if (this[newMC]) {
                this[newMC].removeMovieClip();
                this.$_tweenCount--;
            }
            
            //creates the temporary object and sets its properties-------//
            this.createEmptyMovieClip(newMC, ++this.$_tweenIndex);       //
            this[newMC]._prop         = properties[i];                   //
            this[newMC]._propStart  = oldProperties[i];                  //
            this[newMC]._propDest     = newProperties[i];                //
            this[newMC]._timeStart  = getTimer();                        //
            this[newMC]._timeDest     = getTimer()+(timeSeconds*1000);   //
            this[newMC]._animType     = animType;                        //
            this[newMC]._delay      = delay;                             //
            this[newMC]._callback     = i == 0 ? callback : undefined;   //
            this[newMC]._clickid      = clickid//------------------------//
            
            //simulates a Timer function call as it 
            //performs function call on every frame pass
            this[newMC].onEnterFrame = function() {                    
                // if time has run out trigger block------------------------//
                if (this._timeDest + (this._delay*1000) <= getTimer()) {    //
                    // Past the destiny time: ended.                        //
                    if (!this.ended) {                                      //
                        // Ended, first frame. Set it to its final value    //
                        //to make sure there are no discrepancies           //
                        this._parent[this._prop] = this._propDest;          //
                                                                            //
                        if (this._callback != undefined) {                  //
                            // Calls this._callback from the _parent scope  //
                            _parent._parent[this._callback](this._clickid); //                                                    //
                        }                                                   //
                                                                            //
                        ended = true; // to be deleted next frame (below)   //
                    }else{                                                  //
                        this._parent.$_tweenCount--;                        //
                        this.removeMovieClip();                             //
                    }//-----------------------------------------------------//        
                }else{
                    //if not, keep calling below function
                    this.ended = false;
                    this._parent[this._prop] = findTweenValue (
                    this._propStart, 
                    this._propDest, 
                    this._timeStart, 
                    getTimer()-(this._delay*1000), 
                    this._timeDest, 
                    this._animType);
                }
            }
        }
    }
}

//tween animation algorithms ------------------------------------------------
this.findTweenValue=function(
	_propStart, 
	_propDest, 
	_timeStart, 
	_timeNow, 
	_timeDest, 
	_animType){
    
    var t = _timeNow - _timeStart;
    var b = _propStart;
    var c = _propDest - _propStart;
    var d = _timeDest - _timeStart;
    
    poep = "t = " + t + "\nb = " + b + "\nc = " + c + "\nd = " + d

    switch (_animType.toLowerCase()) {
        case "linear":
            // simple linear tweening - no easing
            return c*t/d + b;
        break;
        case "easeinquad":
            // quadratic (t^2) easing in - accelerating from zero velocity
            return c*(t/=d)*t + b;
        break;
        case "easeoutquad":
            // quadratic (t^2) easing out - decelerating to zero velocity
            return -c *(t/=d)*(t-2) + b;
        break;
        case "easeinoutquad":        
            if ((t/=d/2) < 1) return c/2*t*t + b;
            return -c/2 * ((--t)*(t-2) - 1) + b;
        break;
        case "easeincubic":        
            return c*(t/=d)*t*t + b;
        break;        
        case "easeoutcubic":        
            return c*((t=t/d-1)*t*t + 1) + b;
        break;        
        case "easeinquart":        
            return c*(t/=d)*t*t*t + b;
        break;
        case "elasticout":        
            if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
            if (a < Math.abs(c)) { a=c; var s=p/4; }
            else var s = p/(2*Math.PI) * Math.asin (c/a);
            return -(a*Math.pow(2,10*(t-=1)) * 
            Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
        break;
        case "easeinoutcubic":        
            if ((t/=d/2) < 1) return c/2*t*t*t + b;
            return c/2*((t-=2)*t*t + 2) + b;
        break;        
        case "easeinexpo":
            // exponential (2^t) easing in - accelerating from zero velocity
            return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
        break;
        case "easeoutexpo": //selected by defaulr
            // exponential (2^t) easing out - decelerating to zero velocity
            return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
        break;
    }
}