ドラゴンボーンズ Getting Started ガイド
Peter Huang (@fans8)
the DragonBones team (@dragonbonesteam)
和訳 harayoki (@harayoki)

ドラゴンボーンズ Getting Started ガイドは、Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license に基づいて公開されています。

ドラゴンボーンズは2Dのボーンアニメーションのためのオープンソースプロジェクトです。"ドラゴンボーンズデザインパネル"というスケルトンアニメーションの設定を行うパネルと、ActionScriptによるボーンアニメーション再生のためのライブラリで構成されます。

デザインパネルは実際にはFlashの拡張機能で作られているため、デザイナーが慣れ親しんでいるタイムラインアニメーション作成フローを基に2Dスケルトンアニメーションが効率的に作成できます。

  • 従来のタイムラインアニメーション作成に基づいたワークフロー。

  • ボーン(骨構造)を適用することによって、アニメーションのクオリティを向上させ、正確で自然なものとします。また、すべてのボーンはプログラムから動的に制御が可能です。

  • ボーン個々に対してアニメーションの再生時間や再生までの遅延時間を設定することができます。これは、より少ないキーフレーム数で生き生きとした複雑なアニメーションを作成できる事を意味します。

  • 全体のアニメーションは各パーツがトゥイーンする事で構成されます。この構成では、スプライトシートによるコマアニメと比較して、より小さなビットマップ画像と少ないメモリ消費量ですませることができます。

  • スケルトンアニメーションの見た目を表現する実装部分とロジックの実装部分は分離されています。また、見た目の振る舞いは、アニメーションの再生に影響を与えることなく、動的に変更することができます。

  • このプロジェクトのアニメーション再生の仕組みは、簡単にFlashの(昔ながらの)ディスプレイリストや、Starlingフレームワーク、はたまた他の2Dフレームワークなどに簡単に適用することができます。

プロジェクトのアドレスはhttp://dragonbones.github.comです。

  • Adobe Flash Pro5.5以上が必要です。ここから入手できます。

  • ドラコンボーンズの最新版を入手して、デザインパネルをインストールしてください。デザインパネルは、"SkeltonAmimationDesignPanel.zxp"というファイルで、Flash Proの拡張機能として提供されています。また、"SkeltonAnimationLibrary"というフォルダ内にあるActionScript3のスケルトンアニメーションライブラリも手に入れてください。

  • 下記のチュートリアルで、どのようにアニメーションを作っていけばよいか説明します。

次に、簡単なサンプルを通して、ドラゴンボーンズの使い方を紹介します。

関連ソースは、ここからダウンロードしてください。

デザイナーのあなたがしなければならない事は、アニメーションキャラクターの各パーツ全てを用意することです。その後,Flash Pro内にてそれらを正しく組み立て、キーフレームを設定し、ドラゴンボーンズデザインパネルを用いて作業を終わらせます。

  1. デザインパネルのインストール後、Flashを立ち上げて、"DragonBones_tutorial_Start.fla"をソースコードパッケージから開いてください。

  2. Flash Proのライブラリパネルを開くと、このサンプル例のシンボルを確認することができます。

    partsフォルダーには"Dragon"のアニメーションを構成する全てのパーツが含まれています。パーツ名を確認することでそれがドラゴンのどの部分であるのか推測することができます。明確な名前を付けることは良い指針です。(よね?)

  3. ライブラリ内の"Dragon"ムービークリップをダブルクリックして、編集モードに入ります。どのようにこのアニメーションが構成されているのか、理解してください。後にボーンアニメーションを編集する際に役立ちます。

    タイムラインの各レイヤー名に注目してください。なぜなら、関連したボーンの名前としてレイヤー名が使用されるからです。デザインパネルは各ボーンパーツをレイヤー名と関連づけて自動的に認識します。

    “label"という名前のついたレイヤーにも注目してください。ここでは、現在のフレームのモーションに対して”walk"というラベル名を設定しています。2つ以上のモーションを扱いたい場合には、それぞれのモーションの開始フレームに対して、ラベル名を設定していく必要があります。また、これはとても重要なのですが、labelレイヤーは常にレイヤーの一番上に配置される必要があります。デザインパネルはその前提で動作するからです。

    タイムラインに、モーショントゥイーンの設定が無い事を不思議に思うかもしれません。アニメーション制作において実際に必要なのは、キーフレームを打つ事であって、それぞれをつなぐ補完処理はデザインパネルが行ってくれます。

    デザイナーとしてはトゥイーンの設定を行ってしまうかもしれませんが、それも問題ありません。(現在のドラゴンボーンズのバージョンではクラシックトゥイーンのみをサポートします。新しいタイプのトゥイーンは今後のバージョンで対応される予定です。)

  4. 上記の準備が全て終わったならば、Window>Other>Panels>SkeltonAnimationDesignPanelの順でFlashProのメニューをたどり、デザインパネルを開きます。下記にデザインパネルの詳しい使い方を記します。

  5. importボタンを押すと下記のようなポップアップが開きます。

    importウィンドウの中では、テクスチャーのレイアウトや、そのサイズや余白を設定する事ができます。importオプションでは選択されたライブラリ上のアイテムを読み込ませたり、全てのライブラリアイテムを読み込ませたり、またはデザインパネルによって書き出された、png/swf/zipファイルを読み込ませるができます。

    ライブラリ内のドラゴンを選択し、デザインパネル上の"SelectedItems"を選択し、最後に"OK"ボタンをクリックします。

  6. ドラゴンのムービークリップをデザインパネルに読み込むと、下記のような画面が現れます。

    Armature List: 読み込まれたムービークリップの一覧です。一覧に現れたアイテム名はライブラリ上の関連するムービークリップ名と同じで、その名前を用いてアニメーション用のオブジェクトがプログラムによって作られます。ドラゴンボーンズでは、モーションを持っている各ムービークリップをArmatureと呼んでいます。

    Behavior List: アニメーションオブジェクトに対応するモーション名の一覧です。この名前はムービークリップ内のラベル名に対応しており、モーション再生時にプログラムから利用されます。

    Bone Tree: アニメーションオブジェクトの骨組みの構成です。各アイテム名はムービークリップ内のレイヤー名と対応しています。

    Texture List: 各ボーンに対応したテクスチャ名の一覧です。(訳注:このパネルは存在しないのでは?)

  7. 各モーションでは、このような設定を行うことが可能です。

    Total Times: 各モーションの継続時間です。値が大きいほどモーションはゆっくりになります。

    Blending Times: あるモーションから別のモーションへ切り替わる際に必要なトゥイーンのフレーム数です。例として、"going down"という名前のモーションがあったとし、タイムライン上では1フレームだけの長さを持っているとします。もしこのパラメータを6に設定すると、アニメーションの再生プログラムは、他のモーションからこの"going down"モーションに切り替わる際に、追加で6フレームを挿入して、より自然にモーションが切り替わるようにします。

    Keyframe Ease: あるモーション内のキーフレーム間のイージング値です。

    Loop: リピート再生を行うかどうかを設定します。キャラクターを歩かせたり走らせたりする際などに使います。

  8. 上記の用にパラメータを設定すると、デザインパネル内の左下にあるプレビューウィンドウのアニメーションが更新されているのが確認できます。

    プレビューウィンドウ内の左下に配置されているボタンで見やすいようにプレビュー表示の大きさを調整する事ができます。

  9. それでは、今からBoneのツリー構造の中にBone同士の従属関係を設定していきます。下の図にあるように、デフォルトのBoneツリービューでは全てのBoneはお互いに関係を持っていません。

    パネルの右下で、選択されたBoneがプレビューウィンドウ内でのハイライトされる際のカラーを設定する事ができます。

    リスト内ではあるBoneをドラッグする事によって、他のBoneの子要素として設定する事ができます。下にあるように、bodyをメインのボーンとし、headとtailとupper limbとlower limbをまメインのBoneの子要素として設定します。同時にupper limbはupper armとlower armを子要素として含んでいます。このツリー構造によってそれぞれのBone間の従属関係を直接確認する事ができます。

  10. モーションのパラメーターを個々のBoneに設定する事もできます。Boneツリーパネル上で目的のBoneを選択し、そのままパラメータを調整するだけです。

    Total Frames Scale: 選択中のBone(に設定してあるモーション)の総フレーム数に対する倍率(%)を設定します。

    Play Delay: Boneに設定してあるモーションの再生遅延時間を設定します。

    この2つのパラメーターには注意をしてください。これらのパラーメーターをチューニングした後は、たった2つのキーフレームしかタイムラインに存在しない際であったとしても、キャラクターのモーションがとても複雑な振る舞いをする事が確認できるでしょう。

    このサンプルでは、”Tween delay”値をtailのパーツに対して-10で設定し、tail tipパーツに対しては-50で設定しています。そうする事で、ドラゴンの尻尾の降りの動きがボディの動きに対して自然に追従する事がわかると思います。(^^)

  11. スケルトンアニメーションを良い具合に調整した後には、Expotボタンをクリックしましょう。

    Exportポップアップ内には、たくさんの種類の書き出しフォーマットが選択肢として並んでいるので、あなたのプロジェクトの要件にマッチするものがみつかるでしょう。

    SWF(データ内包): スケルトンアニメーションのデータを内部にAS3Obj形式で含んだSWFファイルです。ベクターベースのスケルトンアニメーションが必要な場合、このフォーマットを選択すると良いでしょう。もちろん、元々のアニメーションパーツもベクター形式である必要があります。

    PNG(XML内包): スケルトンアニメーションのデータを内部にXML形式で含んだPNGファイルです。

    PNG(データ内包): スケルトンアニメーションのデータを内部にAS3Obj形式で含んだPNGファイルです。

    Zip(XMLとSWF): XMLファイルとSWFファイルが含まれたZipファイルを出力します。スケルトンアニメーションのデータとビジュアル要素を別々に保持するために用います。

    Zip(XMLとPNG): XMLファイルとPNGファイルが含まれたZipファイルを出力します。スケルトンアニメーションのデータとビジュアル要素を別々に保持するために用います。

    Zip(XMLと複数のPNG): XMLファイルと複数のPNGファイルが含まれたZipファイルを出力します。スケルトンアニメーションのデータとビジュアル要素を別々に保持するために用います。各ボーンに対して一つのPNG画像を割り当てます。

    Zip(JSONとSWF): JSONファイルとSWFファイルが含まれたZipファイルを出力します。スケルトンアニメーションのデータとビジュアル要素を別々に保持するために用います。

    Zip(JSONとPNG): JSONファイルとPNGファイルが含まれたZipファイルを出力します。スケルトンアニメーションのデータとビジュアル要素を別々に保持するために用います。

    Zip(JSONと複数のPNG): JSONファイルと複数のPNGファイルが含まれたZipファイルを出力します。スケルトンアニメーションのデータとビジュアル要素を別々に保持するために用います。各ボーンに対して一つのPNG画像を割り当てます。

    背景色のオプションはデフォルトでは選択されていません。これは、出力されたテクスチャアトラスの背景が透明である事を示します。このオプションをマークして、主力されるアトラス画像の背景色を選択する事ができます。

    このサンプルでは、”PNG(XML内包)”を選んでOKボタンを押し、”DragonWalk.png”を選択されたフォルダに書き出します。

    一つのPNG画像だけが書き出されている事に注目してください。XMLデータとスケルトンデータを結合した形式での出力を選択したため、このPNGファイルはスケルトンアニメーションに必要な全てのデータを含んでいます。

    出力倍率のドロップダウンリストを用いて書き出しスケールを設定すると、異なった解像度をもったデバイス用に、異なったサイズのテクスチャアトラスを出力する事ができます。

    デザイナーの仕事は、これで全てとなります!

デザイナーが関連するアセットとデーターを、PNGやSWFなどフォーマットで出力した後、デベロッパーはそれらのファイルをスケルトンアニメーションライブラリを通じてプログラムに読み込み、キャラクターのアニメーションをコントロールする事ができます。これはほんの数ステップの簡単な仕事です。その後に、どのようにスケルトンアニメーションライブラリを使って、Flashの昔ながらのディスプレイリスト、もしくはGPUを用いたStarlingフレームワークとともに、キャラクターを描画する方法を学んでいきます。

スケルトンアニメーションライブラリをダウンロードし、あなたのプロジェクトにそのプロジェクトをインポートしてください。基本的なスケルトンアニメーションに関するコンセプトを実際のコーディングの前に確認していきましょう。

Factory: このFactoryをまず最初に用いてスケルトンアニメーションを構築していきます。読み込まれたファイルのデータをパースして、スケルトンのコンテナ(=Armature)を作り出す役割を負っています。

Armature: ArmatureはFlashPro上のデザインパネルから書き出されたMovieClipに基づいて動作するアニメーションの入れ物と考える事ができます。Armatureはスケルトンの管理とアニメーションの再生に関しての責任を持っています。

ではまず、スケルトンアニメーションライブラリを用いて、昔ながらのディスプレイリストとともにキャラクターを作成するサンプルの内容を確認してみましょう。DB_Tutorial_Walk_DisplayList.asを開いてください。

  1. 関連アセット(外部ファイル)をプロジェクトに読み込みます。

                                        [Embed(source = "../assets/Dragon1.swf", mimeType = "application/octet-stream")]  
                                        private static const ResourcesData:Class; 

    このswfアセットはFlashProのスケルトンアニメーションデザインパネルから書き出された物で、XMLデータが内包された物です。もちろんアプリケーションが立ち上がってから(Embedするのではなく)動的に読み込む事もできます。

  2. factoryを作成し、parseData命令でインポートされたリソースをパースさせます。その後の処理はtextureCompleteHandlerのコールバック関数内で処理させます。

                                        factory = new NativeFactory(); 
                                        factory.addEventListener(Event.COMPLETE, textureCompleteHandler);
                                        factory.parseData(new ResourcesData());
  3. コールバック関数内で、buildArmature命令を用いてArmatureを作成します。

                                        armature = factory.buildArmature("Dragon");

    armature.display(=Sprite)をstageに追加します。

                                        addChild(armature.display as Sprite);  

    アニメーションの描画更新処理のため、WorldClockにarmatureを登録します。

                                        WorldClock.clock.add(armature);

    その後、何かしらのアニメーションを再生させます。

                                        armature.animation.play("walk");
  4. 最後に、エンターフレームイベントのハンドラ内で、毎回WorldClock.clock.advancedTime(-1)を実行するようにします。

これら数行のコードで、スケルトンアニメーションをあなたのプロジェクトの中で扱う事ができます。なかなか良い感じだと思いませんか?

この次に、Starlingでスケルトンアニメーションを作成する方法について述べます。Starlingは、Stage3Dを用いた、2Dのための有名なオープンソースフレームワークで高いクオリティのゲームを作る事ができます。詳しくはStarlingのオフィシャルサイト(http://starlingframework.org)を確認してください。

人によってはStarlingでスプライトシートを用いたアニメーションを再生する事にすでに慣れ親しんでいるかもしれません。しかし、スプライトシートアニメーションには、特に複雑なアニメーションを扱う際に、メモリを浪費してしまう大きな問題があります。下の画像を見てください、これはシンプルな歩行アニメーションですが、とても大きなテクスチャー画像を必要としてしまっています。

ドラゴンボーンズはこの問題を解決します。より少ない画像アセット(テクスチャ)を用いて、よりリッチなアニメーションを提供します。

ドラゴンボーンズはStarlingとシームレスに連携し、GPUの力を借りた2Dのスケルトンアニメーションを作り上げます。

サンプルプロジェクト内のDBStarlingWalk.asを開いてください。Starling専用のfactoryを生成している事に気づくと思います。コードは以下の通りです。

                            factory = new StarlingFactory();

そして、その他のコードは、上述のディスプレイリストをつかったサンプルとほぼ同じです。

Starlingフレームワークを使う際には、アニメーションのアセットとして、(xmlデータを内包した)pngファイルか、(同じくxmlデータを内包した)SWFファイルを扱う事ができます。SWFが持っているのはベクターベースのグラフィックスデータですが、スケルトンアニメーションライブラリはこれをビットマップテクスチャーに動的に変換するので、プロジェクトのファイルサイズを減少させる事ができます。

次に、1つのスケルトンアニメーションデータに含まれている複数のモーションを、キーボド操作にあわせてコントロールする方法を学んでいきます。

  1. DragonBones_Tutorial_multiBehavior.flaを開き、ライブラリ内のDragonをダブルクリックします。タイムラインに4つのモーション(stand/walk/jump/fail)が確認できるはずです。各フレームラベル名は各モーションの最初のフレームに設定されています。このような状態であると、デザインパネルが各モーションを認識する事ができます。

  2. デザインパネルを開くと、モーション一覧がリストに並んでいるかと思います。一つのモーションを選択し、トータルフレーム数やトゥイーンの具合など、詳細パラメータをボーンに対して設定する事ができます。

  3. Belnding editorウィンドウ内のBlending Timesに注目してください。

    このオプションはあるモーションから次のモーションに切り替わる際の移行時間を設定するのに使います。例として、jumpモーションのBlending Timesを0.13にしています、この設定で他のモーションからjumpモーションに切り替わるのに0.13秒かかるようになります。

    ドラゴンボーンズはモーションの切り替わりがスムーズに行われるよう、自動的に追加のフレームを挿入します。この効果はプレビューパネル内で確認する事ができます。

  4. それぞれのモーションの調整を終えた後、”export”ボタンを押してスケルトンアニメーションのデータを書き出します。

  5. DBStarlingMultiBehaviour.asを開きます。このコードの内容は、Starlingを使ってキーボードでスケルトンアニメーションを操作するデモとなっています。

        package
        {
            import dragonBones.Armature;
            import dragonBones.animation.WorldClock;
            import dragonBones.factorys.StarlingFactory;
            
            import flash.ui.Keyboard;
            
            import starling.display.Sprite;
            import starling.events.EnterFrameEvent;
            import starling.events.KeyboardEvent;
            import starling.text.TextField;
            import flash.events.Event;
            
            
            public class DBStarlingMultiBehavior extends Sprite
            {
                [Embed(source = "../assets/Dragon2.png", mimeType = "application/octet-stream")]
                public static const ResourcesData:Class;
                
                private var factory:StarlingFactory;
                private var armature:Armature;
                private var armatureClip:Sprite;
        
                private var isLeft:Boolean;
                private var isRight:Boolean;
                private var isJumping:Boolean;
                private var moveDir:int=0;
                private var speedX:Number = 0;
                private var speedY:Number = 0;
                private var textField:TextField
                
                public function DBStarlingMultiBehavior()
                {
                    factory = new StarlingFactory();
                    factory.addEventListener(Event.COMPLETE, textureCompleteHandler);
                    factory.parseData(new ResourcesData());
        
                }
                
                private function textureCompleteHandler(e:Event):void
                {
                    armature = factory.buildArmature("Dragon");
                    armatureClip = armature.display as Sprite;
                    armatureClip.x = 400;
                    armatureClip.y = 550;
                    addChild(armatureClip);
                    WorldClock.clock.add(armature);
                    updateBehavior()
                    addEventListener(EnterFrameEvent.ENTER_FRAME, onEnterFrameHandler);
        
                    stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyEventHandler);
                    stage.addEventListener(KeyboardEvent.KEY_UP, onKeyEventHandler);
                    
                    textField=new TextField(400,30,"A-move left,D-move right,W-jump","Verdana",16,0,true)
                    textField.x=60;
                    textField.y=2;
                    addChild(textField);
                }
                
                private function onKeyEventHandler(e:KeyboardEvent):void
                {
                    switch (e.keyCode)
                    {
                        case Keyboard.A :
                        case Keyboard.LEFT :
                            isLeft=e.type == KeyboardEvent.KEY_DOWN;
                            break;
                        case Keyboard.D :
                        case Keyboard.RIGHT :
                            isRight=e.type == KeyboardEvent.KEY_DOWN;
                            break;
                        case Keyboard.W :
                        case Keyboard.UP :
                            jump();
                            break;
                    }
                    var dir:int;
                    if (isLeft && isRight) 
                    {
                        dir=moveDir;
                        return;
                    }
                    else if (isLeft)
                    {
                        dir=-1;
                    }
                    else if (isRight)
                    {
                        dir=1;
                    }
                    else 
                    {
                        dir=0;
                    }
                    if(dir==moveDir)
                    {
                        return;
                    }
                    else
                    {
                        moveDir=dir;
                    }
                    updateBehavior()
                }
            
                private function onEnterFrameHandler(_e:EnterFrameEvent):void
                {
                    updateMove();
                    WorldClock.clock.advanceTime(-1);
                }
        
                private function updateBehavior():void 
                {
                    if (isJumping)
                    {
                        return;
                    }
                    if (moveDir == 0)
                    {
                        speedX = 0;
                        armature.animation.gotoAndPlay("stand");
                    }
                    else
                    {
                        speedX=6*moveDir;
                        armatureClip.scaleX = -moveDir;
                        armature.animation.gotoAndPlay("walk");
                    }
                }
                private function updateMove():void
                {
                    if (speedX != 0) 
                    {
                        armatureClip.x += speedX;
                        if (armatureClip.x < 0) 
                        {
                            armatureClip.x = 0;
                        }
                        else if (armatureClip.x > 800) 
                        {
                            armatureClip.x = 800;
                        }
                    }
                    if (isJumping)
                    {
                        if (speedY <= 0 && speedY + 1 > 0 ) 
                        {
                            armature.animation.gotoAndPlay("fall");
                        }
                        speedY += 1;
                    }
                    if (speedY != 0) 
                    {
                        armatureClip.y += speedY;
                        if (armatureClip.y > 540) 
                        {
                            armatureClip.y = 550;
                            isJumping = false;
                            speedY = 0;
                            updateBehavior();
                        }
                    }
                }
                private function jump():void 
                {
                    if (isJumping) 
                    {
                        return;
                    }
                    speedY = -25;
                    isJumping = true;
                    armature.animation.gotoAndPlay("jump");
                }
            }
        }
  6. armature.animation.gotoAndPlay()命令を呼ぶだけで、アニメーションを制御できる事がわかるでしょう。

    この命令は、対象となるモーション名だけでなく、アニメーション時間とループ再生を行うかどうかを指定して呼び出す事ができます。

    ドラゴンボーンズはそれぞれのアニメーションに対して、様々なイベントを提供します。

    Movement change (モーションが切り替わった)イベント:

                                    armature.addEventListener(dragonbones.events.Event.MOVEMENT_CHANGE, aramtureEventHandler);

    Movement start (モーションが再生開始した)イベント:

                                    armature.addEventListener(dragonbones.events.Event.START, aramtureEventHandler);

    Movement complete (モーションが再生完了した)イベント

                                    armature.addEventListener(dragonbones.events.Event.COMPLETE, aramtureEventHandler);

    Movement loop complete (モーションの1ループが再生完了した)イベント:

                                    armature.addEventListener(dragonbones.events.Event.LOOP_COMPLETE, aramtureEventHandler);

    様々なイベントとモーションを扱う事で複雑なゲームも簡単に作る事ができる事でしょう。

面白いゲームを作るには、単純に事前に定義されたスケルトンアニメーションを再生する機能だけでは十分では無い事もあるでしょう。例えばキャラクターのモーションを動的にコントロールするなどです。幸い、ドラゴンボーンズではスケルトンの各ボーンに対して個々にアクセスし、コントロールする術が用意されています。この仕組みにより、キャラクターはどんなモーションにでも対応できる事になります。

この例では、スケルトンをマウスの動きでコントロールしてみます。まず、ムクドリのキャラクターがマウスの動きに追従し、ドラゴンと一定の距離を保った状態で画面に表示されます。同時に、ドラゴンの顔と腕がムクドリの動きに追従し、角度を変え、様々なポーズをとります。面白いですよね。

それでは、DBStarlingControlBone.asを開いてみましょう。下記が関連するコードです。

    package
    {
        import dragonBones.Armature;
        import dragonBones.Bone;
        import dragonBones.animation.WorldClock;
        import dragonBones.factorys.StarlingFactory;
        
        import flash.geom.Point;
        import flash.ui.Mouse;
        
        import starling.display.Image;
        import starling.display.Sprite;
        import starling.events.EnterFrameEvent;
        import starling.events.TouchEvent;
        import starling.textures.Texture;
        import flash.events.Event;
        
        
        public class DBStarlingControlBone extends Sprite
        {
            [Embed(source = "../assets/Dragon2.png", mimeType = "application/octet-stream")]
            public static const ResourcesData:Class;
            
            [Embed(source = "../assets/starling.png")]
            private static const starlingImg:Class;
            
            private var factory:StarlingFactory;
            private var armature:Armature;
            private var armatureClip:Sprite;
            
            private var mouseX:Number = 0;
            private var mouseY:Number = 0;
            private var moveDir:int=0;
            private var dist:Number;
            private var speedX:Number = 0;
            private var starlingBird:Image;
            private var _r:Number;
            
            private var _head:Bone;
            private var _armR:Bone;
            private var _armL:Bone;
            
            public function DBStarlingControlBone()
            {
                factory = new StarlingFactory();
                factory.addEventListener(Event.COMPLETE, textureCompleteHandler);
                factory.parseData(new ResourcesData());
            }
            private function textureCompleteHandler(e:Event):void
            {
                armature = factory.buildArmature("Dragon");
                armatureClip = armature.display as Sprite;
                
                armatureClip.x = 400;
                armatureClip.y = 550;
                addChild(armatureClip);
                WorldClock.clock.add(armature);
                updateBehavior(0)
                addEventListener(EnterFrameEvent.ENTER_FRAME, onEnterFrameHandler);
                stage.addEventListener(TouchEvent.TOUCH, onMouseMoveHandler);
                
                starlingBird=new Image(Texture.fromBitmap(new starlingImg()))
                addChild(starlingBird);
                Mouse.hide();
                //get the bones which you want to control
                _head = armature.getBone("head");
                _armR = armature.getBone("armUpperR");
                _armL = armature.getBone("armUpperL");
                
            }
            
            private function onEnterFrameHandler(_e:EnterFrameEvent):void
            {
                checkDist();
                updateMove();
                updateBones();
                WorldClock.clock.advanceTime(-1);
            }
            
            private function checkDist():void
            {
                dist = armatureClip.x-mouseX;
                if(dist<150)
                {
                    updateBehavior(1)
                }
                else if(dist>190)
                {
                    updateBehavior(-1)
                }
                else
                {
                    updateBehavior(0)
                }
                
            }
            
            private function onMouseMoveHandler(_e:TouchEvent):void
            {
                var _p:Point = _e.getTouch(stage).getLocation(stage);
                mouseX = _p.x;
                mouseY = _p.y;
                starlingBird.x=mouseX-73;
                starlingBird.y=mouseY-73;
            }
            private function updateBehavior(dir:int):void 
            {
                if(moveDir==dir)return;
                moveDir=dir;
                if (moveDir == 0)
                {
                    speedX = 0;
                    armature.animation.gotoAndPlay("stand");
                }
                else
                {
                    speedX=6*moveDir;
                    armature.animation.gotoAndPlay("walk");
                }
            }
            private function updateMove():void
            {
                if (speedX != 0) 
                {
                    armatureClip.x += speedX;
                    if (armatureClip.x < 0) 
                    {
                        armatureClip.x = 0;
                    }
                    else if (armatureClip.x > 800) 
                    {
                        armatureClip.x = 800;
                    }
                }
            }
            private function updateBones():void
            {
                //update the bones' pos or rotation
                _r = Math.PI + Math.atan2(mouseY - armatureClip.y+armatureClip.height/2, mouseX - armatureClip.x);
                if (_r > Math.PI)
                {
                    _r -= Math.PI * 2;
                }
                _head.node.rotation = _r*0.3		
                _armR.node.rotation = _r *0.8;
                _armL.node.rotation = _r * 1.5;
                
                starlingBird.rotation=_r*0.2;
            }
        }
    }

上記のコードでは、dragonBones.Armature.getBone(_name:String):Bone命令が、特定のボーンの参照を得るのに使われているのがわかると思います。ボーンのnodeオブジェクトは位置、回転角度、拡大率、傾き等のデータを持っています。ゲームロジックの必要に応じてこれらの値を設定し、動的なボーンのコントロールを行う事ができるようになっています。

上記コード例のupdateBones()メソッドに注目してください。このメソッドでは、マウスの位置とスケルトンの中心点から角度を計算し、その値を元にドラゴンの頭と腕の角度を変更しています。これにより、面白い効果を得ています。

ゲーム中によくでてくる要望として(あるキャラクターの服装や装備を変更するというような)テクスチャーを動的に切り替える、という事があると思います。そしてこれは、ドラゴンボーンズアニメーションライブラリによって、対応したボーンのテクスチャーを切り替える事で容易に実現する事ができます。

このボーンテクスチャ変更の仕組みを扱う際には、新たにテクスチャを作成し関連したボーンに対応させる、という処理を行います。この新しいテクスチャは、他の何らかの手段によって作成された外部ファイルから作り出された物でも良いし、デザインパネルから作成されたテクスチャアトラスに含まれていても良いです。 ここで、どうやってFlashProからタイムラインアニメーションに含まれないテクスチャを書き出せばいいのか?という疑問が生じる事でしょう。(その方法を下記に示します。)

  1. このチュートリアルのサンプルファイルであるDragonBones_Tutorial_ChangeClothes.flaを開くと、黄色い服を着たかわいい?ドラゴンがおり、その服が”clothes"というレイヤー上で様々なアニメーションをつけられている事が確認できるでしょう。

  2. clothTextureTempオブジェクトをダブルクリックして、内部の作りを確認すると、4つの服のパーツが、各レイヤーに配置されている事が確認できると思います。各レイヤー名は適切に名付けられています。それぞれの服は同じ大きさで同じ形状であるため、完全に重なって配置されています。しかし実際のプロジェクトでは、服や武器は様々なサイズを取りうるはずです。したがって各パーツは、元テクスチャと適切な関連をもった大きさで配置される必要があります。

    最初のフレームラベルが”temp”と名付けられている事に注目してください。デザインパネルはフレームラベルが設定されていないアニメーションを読み込もうとしないからです。

  3. ”Dragon”と”clothTextureTemp”をライブラリ内で選択してください。そしてデザインパネルを開き、"ライブラリ上の選択アイテムのみ読み込む"設定でimportボタンを押してください。ArmatureList内にclothTextureTempという名前のスケルトンが存在し、内部にclothes1〜clothes4というボーンを持っている事が確認できると思います。この事について深く考える必要はありません。デザイン的な面からではなく、全てのテクスチャを1つにまとめて出力する目的でこのような構成にしています。

  4. 必要であれば、スケルトンの各パラーメータに調整を入れます。その後、”Export"ボタンを押して、全てのテクスチャを書き出してください。書き出された”DragonWithClothes.png”ファイルには、画像イメージとXMLデータ両方が含まれています。そして、このテクスチャアトラスにはすべての服のテクスチャが含まれているのがわかると思います。(^^)

  5. それではコードとして、実際にどのようにテクスチャを変更するのかを見ていきましょう。チュートリアル3で作られたDBStarlingMultiBehavior.asを開いてください。ここにテクスチャを切り替えるコードを追記していきます。まず、importされたテクスチャをDragonWithClothes.pngに変更します。

                                        [Embed(source = "../assets/DragonWithClothes.png", mimeType = "application/octet-stream")]
  6. そして、2つの変数設定を行います。全ての(服の)テクスチャ名を含んだ配列と、選択中の服を指し示すためのインデックス番号です。

                                        private var textures:Array = ["parts/clothes1", "parts/clothes2", "parts/clothes3", "parts/clothes4"];
                                        private var textureIndex:int = 0;

    テクスチャ名が”parts/clothes1”であって、”clothes1"ではない事に注意してください。これはFlashProのライブラリ構成として、異なるフォルダに同じ名前のアイテムを持ち得るためです。DragonBonesはディレクトリパスも含めてデータを書き出します。実際にDragonBones_Tutorial_ChangeClothes.fla内ライブラリのディレクトリ構成を確かめれば、この事が理解できると思います。

  7. テクスチャの変更をキー操作で行わせるために、”C"キーを押した際のイベントハンドラを設定します。

        case Keyboard.C:
            if (e.type == KeyboardEvent.KEY_UP) {
                changeClothes(); 
            }
            break;
  8. changeClothes()命令を下記のように作成します。

        private function changeClothes():void {
            //loop the texture
            textureIndex++;
            if (textureIndex >= textures.length) {
                textureIndex = textureIndex - textures.length; 
            }
            //get the image instant from textureData
            var _textureName:String = textures[textureIndex];
            var _image:Image = factory.getTextureDisplay("_textureName") as Image;
            // assign image to bone.display for chaging clothes effect. (using bone.display to dispose)
            var _bone:Bone = armature.getBone("clothes"); _bone.display.dispose();
            _bone.display = _image;
        }

    ここでは、StarlingFactory.getTextureDisplay(_textureData: TextureData, _fullName:String):Image命令を用いて、デザインパネルから書き出したテクスチャの参照を得ています。そして着替えの演出を行うため、関連したボーンにそのテクスチャを関連づけています。上記で述べた通り、このテクスチャーは(デザインパネルから書き出した物ではなく)別の外部リソースから得られた物でも問題ありません。

    ドラゴンボーンズライブラリの扱いに慣れた後には、もっと柔軟な実装を行う事もできるようになる事でしょう。例えばテクスチャの切り替えだけでなく、テクスチャを追加したり削除したり、スケルトン構成を動的に変更したりする、等です。

    さあ、これでゲームのキャラクターのテクスチャーを動的に変更する方法はわかったかと思います。さっそく試してみて下さいね!(^^)