FlasherのためのSilverlight入門(7) Spriteクラスを作る[1]

前回の「Viewを作る」の続きです。

Silverlightでのリソースの構成などがだいたい分かってきたようなところで、FlashのSprite的に動作するSystem.Windows.Controls.Canvasクラスのラッパークラスを作ります。ついでにTweenアニメーションも組み込んでしまいます。

まだ作り途中なんですが、説明要素が多いのでとりあえず。

 

Spriteクラス

どんな感じのプログラムになっているかをまずべたっと貼ります。 

 
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace VariousTest.mainApp
{

    /*
     *  FlashのSprite的なハンドリングを実現するためのクラス
     * 
     */

    public delegate void SpriteTweenFeedBack(string type, object instance);


    public class Sprite:DependencyObject
    {
        public const string T_MOVE = "tween_move";
        public const string T_ALPHA = "tween_alpha";

        private Canvas _layer; //このSpriteが描画されるCanvas
        public Sprite _parent; //親のSprite
        private List _children; //子のSprite
        private List _frames; // フレームとして格納されるImage
        
        private double _x;
        private double _y;
        private double _rotate_angle;
        private double _rotate_center_x;
        private double _rotate_center_y;
        private double _scale_center_x;
        private double _scale_center_y;
        private double _xscale;
        private double _yscale;
        public uint layerIndex;
        public double width;
        public double height;

        private RotateTransform rot_transform;
        private ScaleTransform scale_transform;
        internal TransformGroup transform;

        //一番ルートになるSpriteに適用するコンストラクタ
        public Sprite(Canvas canvasLayer) 
        {
            this._layer = canvasLayer;
            this._parent = null;
            init();
        }

        //空のSpriteを作る時のコンストラクタ(いらない?)
        public Sprite(Canvas canvasLayer, Sprite parent)
        {
            this._layer = canvasLayer;
            this._parent = parent;
            init();
        }

        //リソースのあるSpriteのコンストラクタ、生成後に親にaddChildする
        public Sprite(string[] resources, Size size, Point offset)
       {
            init();
            for (int i = 0; i < resources.Length; i++)
            {
                Image img = LoadImage(resources[i]);
                this._frames.Add(img);
            }

            this._layer = new Canvas();
            this._layer.Children.Add(this._frames[0]);

            this.width = size.Width;
            this.height = size.Height;
            this.x = offset.X;
            this.y = offset.Y;
            this._rotate_angle = 0;
            this._rotate_center_x = 0;
            this._rotate_center_y = 0;

            this.rot_transform = new RotateTransform();
            rot_transform.CenterX = this._rotate_center_x;
            rot_transform.CenterY = this._rotate_center_y;
            rot_transform.Angle = this._rotate_angle;

            this._scale_center_x = 0;
            this._scale_center_y = 0;
            this.scale_transform = new ScaleTransform();
            scale_transform.CenterX = this._scale_center_x;
            scale_transform.CenterY = this._scale_center_y;
            
            this.transform = new TransformGroup();
            this.transform.Children.Add(this.rot_transform);
            this.transform.Children.Add(this.scale_transform);
            this._layer.RenderTransform = this.transform;
       }

        private void init()
        {
            this._children = new List();
            this._frames = new List();
        }

        //
        // cspriteをこのSpriteの子にする
        //
        public int addChild(Sprite csprite)
        {
            this._children.Add(csprite);
            this._layer.Children.Add(csprite.getLayerCanvas());
            csprite.addedChild(this);

            return this._children.Count;
        }

        //
        // このSpriteの親にparentを設定する。(addChildから呼ばれる)
        // 実際のCanvasのリンクなどはaddChildで行う
        //
        internal void addedChild(Sprite parent){
            this._parent = parent;
        }

        public Canvas getLayerCanvas()
        {
            return this._layer;
        }


        /* #########################################################
        *
         * 
         *  moveTo 関係
         * 
         * 
         * #########################################################
         * */

        private SpriteTweenFeedBack tfback_moveto;

        public void moveTo(double tx, double ty, double duration)
        {
            moveTo(tx, ty, duration, 0, null, null);
        }

        public void moveTo(double tx, double ty, double duration, double delay)
        {
            moveTo(tx, ty, duration, delay, null, null);
        }

        public void moveTo(double tx, double ty, double duration, double delay, EasingFunctionBase ease)
        {
            moveTo(tx, ty, duration, delay, ease, null);
        }

        public void moveTo(double tx, double ty, double duration, double delay, SpriteTweenFeedBack tfback)
        {
            moveTo(tx, ty, duration, delay, null, tfback);
        }

        public void moveTo(double tx, double ty, double duration, double delay, EasingFunctionBase ease, SpriteTweenFeedBack tfback)
        {

            this.tfback_moveto = tfback;

            Storyboard stmove = new Storyboard();
            DoubleAnimation stmove_dbl = new DoubleAnimation();
            stmove_dbl.From = this.x;
            stmove_dbl.To = tx;
            stmove_dbl.Duration = TimeSpan.FromMilliseconds(1000 * duration);
            stmove_dbl.BeginTime = TimeSpan.FromMilliseconds(1000 * delay);

            DoubleAnimation stmove_dbl2 = new DoubleAnimation();
            stmove_dbl2.From = this.y;
            stmove_dbl2.To = ty;
            stmove_dbl2.Duration = TimeSpan.FromMilliseconds(1000 * duration);
            stmove_dbl2.BeginTime = TimeSpan.FromMilliseconds(1000 * delay);

            if (ease == null)
            {
                //デフォルトのイージング
                ease = new CircleEase();
                ease.EasingMode = EasingMode.EaseOut;
            }
            else
            {
                stmove_dbl.EasingFunction = ease;
                stmove_dbl2.EasingFunction = ease;
            }
            Storyboard.SetTarget(stmove_dbl, this);
            Storyboard.SetTargetProperty(stmove_dbl, new PropertyPath(Sprite.dp_x));
            Storyboard.SetTarget(stmove_dbl2, this);
            Storyboard.SetTargetProperty(stmove_dbl2, new PropertyPath(Sprite.dp_y));
            stmove.Children.Add(stmove_dbl);
            stmove.Children.Add(stmove_dbl2);
            stmove.Completed += this.onMoveToEnd;
            stmove.Begin();

        }

        private void onMoveToEnd(object sender, EventArgs e)
        {
            Storyboard stmove = (Storyboard)sender;
            stmove.Completed -= this.onMoveToEnd;
            System.Diagnostics.Debug.WriteLine("onMoveToEnd");

            if (this.tfback_moveto != null)
            {
                this.tfback_moveto(Sprite.T_MOVE, this);
            }
        }


        /**
         *  X座標のアニメーションのためのDependency設定など
         **/
        public static readonly DependencyProperty dp_x = DependencyProperty.RegisterAttached("x", typeof(double), typeof(Sprite),new PropertyMetadata(new PropertyChangedCallback(xChanged)));

        public double x
        {
            set {
                this._x = value;
                Canvas.SetLeft(this._layer, this._x);
            }
            get {
                return Canvas.GetLeft(this._layer); 
            }
        }

        private static void xChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Sprite s = (Sprite)d;
            s.xChanged(e);
        }
        protected virtual void xChanged(DependencyPropertyChangedEventArgs e)
        {
            double value = (double)e.NewValue;
            this.x = value;
        }


        /**
         *  Y座標のアニメーションのためのDependency設定など
         **/
        public static readonly DependencyProperty dp_y = DependencyProperty.RegisterAttached("y", typeof(double), typeof(Sprite), new PropertyMetadata(new PropertyChangedCallback(yChanged)));

        public double y
        {
            set
            {
                this._y = value;
                Canvas.SetTop(this._layer, this._y);
            }
            get
            {
                return Canvas.GetTop(this._layer); 
            }
        }

        private static void yChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Sprite s = (Sprite)d;
            s.yChanged(e);
        }
        protected virtual void yChanged(DependencyPropertyChangedEventArgs e)
        {
            double value = (double)e.NewValue;
            this.y = value;
        }


        /* #########################################################
        *
         * 
         *  alphaTo 関係
         * 
         * 
         * #########################################################
         * */

        private SpriteTweenFeedBack tfback_alphato;

        public void alphaTo(double ta, double duration)
        {
            alphaTo(ta,duration, 0, null, null);
        }

        public void alphaTo(double ta, double duration, double delay)
        {
            alphaTo(ta, duration, delay, null, null);
        }

        public void alphaTo(double ta, double duration, double delay, EasingFunctionBase ease)
        {
            alphaTo(ta,duration, delay, ease, null);
        }

        public void alphaTo(double ta, double duration, double delay, SpriteTweenFeedBack tfback)
        {
            alphaTo(ta,duration, delay, null, tfback);
        }

        public void alphaTo(double ta, double duration, double delay, EasingFunctionBase ease, SpriteTweenFeedBack tfback)
        {

            this.tfback_alphato = tfback;

            Storyboard stalpha = new Storyboard();
            DoubleAnimation stalpha_dbl = new DoubleAnimation();
            stalpha_dbl.From = this.alpha;
            stalpha_dbl.To = ta;
            stalpha_dbl.Duration = TimeSpan.FromMilliseconds(1000 * duration);
            stalpha_dbl.BeginTime = TimeSpan.FromMilliseconds(1000 * delay);

            if (ease == null)
            {
                //デフォルトのイージング
                ease = new CircleEase();
                ease.EasingMode = EasingMode.EaseOut;
            }
            else
            {
                stalpha_dbl.EasingFunction = ease;
            }
            Storyboard.SetTarget(stalpha_dbl, this);
            Storyboard.SetTargetProperty(stalpha_dbl, new PropertyPath(Sprite.dp_alpha));
            stalpha.Children.Add(stalpha_dbl);
            stalpha.Completed += this.onAlphaToEnd;
            stalpha.Begin();

        }

        private void onAlphaToEnd(object sender, EventArgs e)
        {
            Storyboard stalpha = (Storyboard)sender;
            stalpha.Completed -= this.onAlphaToEnd;
            System.Diagnostics.Debug.WriteLine("onAlphaToEnd");

            if (this.tfback_alphato != null)
            {
                this.tfback_alphato(Sprite.T_ALPHA, this);
            }
        }


        /**
         *  alphaのアニメーションのためのDependency設定など
         **/
        public static readonly DependencyProperty dp_alpha = DependencyProperty.RegisterAttached("alpha", typeof(double), typeof(Sprite), new PropertyMetadata(new PropertyChangedCallback(alphaChanged)));

        public double alpha
        {
            set
            {
                this._layer.Opacity = value;
            }
            get
            {
                return this._layer.Opacity;
            }

        }

        private static void alphaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Sprite s = (Sprite)d;
            s.alphaChanged(e);
        }
        protected virtual void alphaChanged(DependencyPropertyChangedEventArgs e)
        {
            double value = (double)e.NewValue;
            this.alpha = value;
        }

      




        public bool visible
        {
            set {
                if (value)
                {
                    this._layer.Visibility = Visibility.Visible;
                }
                else
                {
                    this._layer.Visibility = Visibility.Collapsed;
                }
            }
            
            get {
                if (this._layer.Visibility == Visibility.Visible)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public double rotation
        {
            set
            {
                this._rotate_angle = value;
                rot_transform.Angle = this._rotate_angle;
            }
            get
            {
                return this._rotate_angle;
            }

        }

        public double scaleX
        {
            set
            {
                this.scale_transform.ScaleX = value;
            }

            get
            {
                return this.scale_transform.ScaleX;

            }

        }

        public double scaleY
        {
            set
            {
                this.scale_transform.ScaleY = value;
            }

            get
            {
                return this.scale_transform.ScaleY;

            }

        }





        private Image LoadImage(string resource_name)
        {
            Image img = null;
            img = new Image();
            img.Source = LoadBitmap(resource_name);

            return img;
        }

        private BitmapImage LoadBitmap(string resource_name)
        {
            BitmapImage bmp = new BitmapImage(new Uri(resource_name, UriKind.Relative));

            return bmp;
        }


    }
}

進捗としては30%ぐらいですが、すでに大分長いです。現時点でこのクラスが実現していることは、

・Spriteクラスとして操作するCanvasを生成し、指定したリソースを張り付ける。

・別のSpriteを子に持たせ、入れ子にすることができる

・x,y,alpha,scaleX,scaleY,visible,rotationといった要素を簡潔に扱えるようにsetter/getterを設定。

・moveTo, alphaTo 、それぞれ目的地への移動、透明度の変化をするTweenアニメーションのショートカット関数を作成

と、こんな感じです。

 

setter/getterの設定

 
public double y
        {
            set
            {
                this._y = value;
                Canvas.SetTop(this._layer, this._y);
            }
            get
            {
                return Canvas.GetTop(this._layer); 
            }
        }

C#でのsetter/getterの文法については、上のような書き方になります。setterで渡される値は、「value」という変数に自動的に収まっています。

Canvasクラスでのx,y,rotationなどの要素の設定はAS3と比べるとちょっと冗長です。詳しくはCanvasクラスのAPIを見ると分かりますが...(このAPIがまた見にくいけど)

 

座標関係

x,y は上のような感じで、なぜかStatic関数から取得します。this._layerは、取得したいCanvas。x座標はGetLeft, y座標はGetTop関数で取得します。

 

透明度

次にalpha。これは単純でOpacityというプロパティです。

 

可視・不可視

visibleかどうかは、Visibilityというプロパティを設定しますが、これはboolではなくてVisibility.Visible/ Visibility.Collapsedという値をとります。System.Windows.Visibilityという列挙体で定義されています。列挙体はAS3にはない概念ですが、指定した特定の値しかとらない変数です。例えば曜日などは、月~日の値までしかとらないはずなので定義しておくことでプログラムが明確になります。以下のように定義し使うことができます。

 
enum DayOfTheWeek
{
  mon, tue, wed, thu,fri, sat, sun
}

if( d == DayOfTheWeek.mon){
  // monday
}

 

rotation

rotationはちょっとややこしくなっています。scaleもそうなのですがオブジェクトの変形などを表現するTransformクラスがありそれを適用する形で表現します。1点AS3と違って便利なのはこのクラスを使うことで、回転角と同時に回転軸となる点を指定することができます。

 
this._rotate_angle = 100;
this._rotate_center_x = 0;
this._rotate_center_y = 0;
this.rot_transform = new RotateTransform();
rot_transform.CenterX = this._rotate_center_x;
rot_transform.CenterY = this._rotate_center_y;
rot_transform.Angle = this._rotate_angle;
this.transform = new TransformGroup();
this.transform.Children.Add(this.rot_transform);
this._layer.RenderTransform = this.transform;

そのCanvasの(0,0)を中心に100度回転させるには上のようなプログラムになります。回転の中心点のx,y座標、Angleを指定したRotateTransformクラスを作り、CanvasのRenderTransformプロパティに渡します。

上の例では、RotateTransform以外の変形をかける可能性があるので、TransformGroupというTransformをまとめるクラスに追加した上でTransformGroupクラスのインスタンスをRenderTransformプロパティに渡しています。この設定は一度だけ行えばよく、この後、回転角を変化させるには、rot_transform.Angle = 200; のようにすることで適用できます。

 

scale

scaleもrotationと同じくTransformクラスを使います。ScaleTransformというクラスがあります。

 
this._scale_center_x = 0;
this._scale_center_y = 0;
this.scale_transform = new ScaleTransform();
scale_transform.CenterX = this._scale_center_x;
scale_transform.CenterY = this._scale_center_y;
scale_transform.ScaleX = 2;
scale_transform.ScaleY = 2;
this.transform.Children.Add(this.scale_transform);
this._layer.RenderTransform = this.transform;

この場合、幅・高さが2倍なります。ちょっと省略していますが、this.transformはrotateの時と同じく、TransformGroupのインスタンスです。

 

 

と、主要な要素はこんな感じででした。他の要素はCanvasクラスのAPIを見てみてください。Transform系では他に、SkewTransform、TranslateTransform、MatrixTransformといったものがあります。

だいぶ長くなったので、Tween関係は次回に。

コメントする
トラックバック(0)

トラックバックURL: http://blog.tokyoace4.com/cgi-bin/mt/mt-tb.cgi/154