本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

イメージベース・パスを使用した2Dアニメーション

固定オブジェクトのアニメーションから重要なコーディングを抜粋します

Barry Feigenbaum, Ph.D., Senior Consulting IT Architect, Federated Integration Test team, IBM China Development Lab 
Barry Feigenbaum博士は、IBM Worldwide Accessibility Centerのメンバーであり、IBM製品を障害者にとってアクセシブルなものにするための支援を行うチームの一員です。Feigenbaum博士は複数の本と記事を出版しており、複数の特許を持ち、JavaOneのような開発者会議で講演をしています。彼は、オースティンのテキサス大学にて、コンピュータサイエンスの非常勤助教授を務めています。
Tom Brunet (tomab@cs.wisc.edu), Ph.D. candidate, University of Wisconsin
Tom Brunetはウィスコンシン大学マジソン校の大学院生です。彼はオースティンのテキサス大学からコンピュータサイエンスのB.S.を受け、学部長から優秀卒業生に選ばれました。大学研究中に、彼は、4年間IBM Researchにて働きました。Data Abstraction Researchグループでひと夏を過ごし、Accessibility Centerで残りの日々を過ごしました。彼はまた、Nina Amenta博士の研究助手学生としても同時に働きました。

概要: あなたが望むものを描く事ができ、プログラムに残りの作業を行わせる事が出来る場合に、なぜ、あなたのアニメシーケンスをコード化するのでしょう?この記事では、Barry FeigenbaumとTom Brunetが、2Dアニメーションの固定オブジェクトの動作シーケンスを生成するために、可逆画像、Swingテクノロジー、および著者自身のJavaベースのアニメーションエンジンを組み合わせる方法を示します。

日付:  2004年 1月 09日
レベル:  中級 この記事の原文:  英語
アクティビティー: 2156 ビュー
お気軽にご意見・ご感想をお寄せください: 


二次元(2D)アニメーションでは、頻繁に、前もって定義したパターン(時にコントロールパスと呼ばれる)で2D領域内の一つまたは複数のオブジェクトを移動させる事が必要になります。この種のアニメーションでは、2つの問題を解決する必要があります。

  • オブジェクトが従うべきコントロールパスを指定する方法。
  • 選択されたパスに沿ってオブジェクトを移動させる方法。

この記事では、可逆画像、Swingテクノロジー、およびJavaベースのアニメーションエンジンを使用してこれらの問題を解決する方法を示します。私たちのアニメオブジェクトのために軌道を描くことから始め、その後、定義されたコントロールパスに沿ってオブジェクトを動かすためにアニメーションエンジンを使用します。

可逆画像(に記述されたような)は作成も加工も簡単ですが、それらを使用した方法は、あなたの思うとおりに精巧に作ることができます。サンプル・アニメーションシーケンスを使用し、より精巧な動作シーケンスを作成するために、異なる色のセットをどのように使用する事ができるのか学びます。また、望ましいコントロールパスを選び出すために画像を加工し、背景画像に対してコントロールパスをレイヤーにし、アニメーションシーケンスのためのオブジェクト(Swing GUIコンポーネント)を作成し、アニメーションプロセスを完了させるために、定義したコントロールパスに沿ってそれらを動かす方法を学びます。より詳細な情報は、参考文献を参照してください。

注: この記事は、あなたが一般論としてJavaプログラミングの、特にSwing GUI構築の実用的な知識を持つことを前提とします。Java 2Dを使用してJavaプラットフォーム上の画像を操作するという付加的な経験は役立つことでしょう。

失ってはいけないものは?

可逆画像とは、イメージピクセルがすべて永久に保持されるものです。画像は正確に保存することができるか、オリジナルの正確な複製として修復することができる形式でなければなりません。

可逆画像を作成する為に、あなたはMicrosoft Paint、Jasc Paint Shop Pro、およびいくつかのカスタムアプリケーションを含む、様々なアプリケーションプログラムを使用出来ます。ファイルに画像を保存するか、またはRAMの中でのみ、それらを作成することができます。画像は、圧縮されていないか、またはzip圧縮のような、可逆圧縮スキームを使用して圧縮され、保存されなければなりません。一般的な可逆画像形式には、Microsoft Bitmap (BMP)とPortable Network Graphics (PNG)形式があります。GIF (Graphics Interchange Format)およびJPEG (Joint Photographic Experts Group)ファイル用によく使用されるような不可逆圧縮スキームは、この記事に記述されたアニメーション技術には有効ではありません。


すべてコントロールの問題です

最も一般的な形式として、コントロールパスは、任意のn次元の空間を通る特定の位置および特定の時に得られるビヘイビアを表します。私たちは、2D空間を通る1つ以上のオブジェクトによって得られるパスとして、コントロールパスを定義します。あなたは、オブジェクトの位置をその位置のビヘイビアにマッピングすることによって、コントロールパスを表します。その後、プログラムは定義されたオブジェクトごとに繰り返し、マップ内のオブジェクト位置のビヘイビアを調べ、そして示された動作をオブジェクトに実行します。最も単純なコントロールパス以外については、コード中でこのようなマッピングを行うことは、時間消費やエラーの元になりえます。したがって、描画プログラムのほうが、より適切です。

コンロトールパスは、時間不変(その場合それらは静的です)、あるいは時間変数(その場合それらは動的です)になりえます。可逆画像が画像ファイルに含まれている場合、それは時間不変か、あるいは静的です。可逆画像がRAMに含まれており、直接使用される場合、それは時間変数か、あるいは動的です。この記事では、私たちは静的なコントロールパスに着目します。定義されたビヘイビアの種類はある程度の加工に影響もしますが、正しい編集プログラムを用いることで、静止画像の生成はより簡単になりえます。


今夜は楽しく過ごしましょう!

アニメーションについて学ぶよい方法は、アニメーションをあなた自身で作成することです。私たちは、残りの記事全体にわたって論じた概念を説明するために、アニメーションサンプルを使用します。サンプルは、火災避難のアニメシーケンスです。したがって、いくつかの図のために避難経路を表すコントロールパスを生成します。背景画像として、図1で間取り図の一部分を用います。図6には標準サイズの背景画像があります。

コードのダウンロード

参考文献から、アニメーションエンジンおよびサンプル・アニメーションシーケンス用の完全なソースコードを入手してください。

図1.背景画像の一部分
背景画像の一部分

私たちは、任意の値の配列からコントロールマッピングを生成することができます。配列のために画像(図2で示されるもの)を使用することは、各位置でのビヘイビアを表わすための色値の使用を可能にします。各色値のサイズ(色ビット数)は画像形式に依存します。図2は、火災避難シーケンス用のコントロールパスのいくつかを示します。


図2.コントロールパスのうちのいくつか
コントロールパスのうちのいくつか

コントロールパス画像がアニメーション背景にどのように適合するか確かめるため、図3に見られるように、背景画像の上にコントロール画像をかぶせます。


図3.透過レイヤーを用いて組合せた画像
透過レイヤーを用いて組合せた画像

色をつけます

イメージが生成された後、それは容易に必要とされるマッピングに変換することができます。私たちは、単に画像の色ごとに繰り返し、各色値にビヘイビアを割り当てます。したがって、例えば私たちは、マッピングなしあるいはデフォルトのビヘイビアを示すために白(典型的な全ての値である)を使用できます。カスタム・ビヘイビアを表わすためには黒(典型的な0値である)を使用できます。もし私たちの画像によってマッピングされていた場合、オブジェクトが同じビヘイビアを共有する(すなわち同色、例えば黒といった色)位置に遭遇したとき、それはその位置に定義された方向へ進むでしょう。位置が同じビヘイビアを共有していない場合、それは引き返さずに、そのビヘイビアを共有する隣接した位置を見つけるでしょう。

私たちは、異なる色で他のビヘイビアを表わすことができます。定義されていない色値は無視されます。したがって、背景のピクセル(図3の薄い灰色のピクセルのような)は無視することができます。

リスト1は、マッピングがどのように遂行されるか示します。画像は、特定の色値が最初に走査されます。次に、各色ピクセルの位置は、コントロール・ステイトのマップのその位置にコントロール・ステイトを定義するために使用されます。避難サンプルにおいては、6つのビヘイビアが様々なSTATE_xxx定数によって定義されます。


リスト1.コントロールパス画像の処理
                
Map map = new HashMap();
  :
public final static int STATE_UNKNOWN = -1;
public final static int STATE_NONE = 0;
public final static int STATE_HALLWAY = 1;
public final static int STATE_INTERSECTION = 2;
public final static int STATE_HINT = 3;
public final static int STATE_START = 4;
public final static int STATE_EXIT = 5;
  :
/** Process the control image */
void processControl(Image img, int x, int y, int w, int h) 
{
    int pmap[] = new int[w * h];
    PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pmap, 0, w);
    try {
        pg.grabPixels();
        if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
            System.err.println("image fetch error");
        }
        else {
            Integer none = new Integer(STATE_NONE);
            Integer hall = new Integer(STATE_HALLWAY);
            Integer start = new Integer(STATE_START);
            Integer exit = new Integer(STATE_EXIT);
            Integer hint = new Integer(STATE_HINT);
            Integer inter = new Integer(STATE_INTERSECTION);

            // for each position
            for (int i = 0; i < pmap.length; i++) {
                int red   = (pmap[i] >> 16) & 0xff;
                int green = (pmap[i] >>  8) & 0xff;
                int blue  = (pmap[i]      ) & 0xff;
                if      (red == 255 && green == 255 && blue == 255) 
                    ; // don't bother to add NONE to map
                else if (red == 0 && green == 0 && blue == 0)
                    map.put(new Integer(i), hall);
                else if (red == 0 && green == 255 && blue == 0)
                    map.put(new Integer(i), start);
                else if (red == 200 && green == 0 && blue == 0)
                    map.put(new Integer(i), exit);
                else if (red == 255 && green == 0 && blue == 0)
                    map.put(new Integer(i), hint);
                else if (red == 0 && green == 0 && blue == 255)
                    map.put(new Integer(i), inter);
            }
        }
    } 
    catch (InterruptedException e) {
        System.err.println("image processing interrupted");
    }
}


変化は人生のスパイス

特定の色値がリスト1で使用されますが(すなわち、赤が200、緑が0、青が0)、私たちは、色範囲をサポートするために容易にコードを拡張することができます。色範囲の使用は、描画プログラムの中で使用される色選択の精度を軽減し、それにより、パス画像の作成をより容易にします。

より広い色選択肢の使用は、あなたがさらに多くのステイトを定義し、またよりいっそう複雑なビヘイビアについて記述することも可能にします。例えば、重複するコントロールパスを作成するRGBスキームの中で、カラー・バンド差を使用することができます。もし上記のステイトの各々が、異なる色によってではなく単色の強度差によってエンコードされれば、3つの独立したコントロールパスをお互いの上にかぶせることができるでしょう。もちろん、単色の強度差の使用は、ビヘイビアの間の微妙な色差の識別をより難しくします。ほとんどの画像編集プログラムは、この問題を緩和して、選択されたピクセルの正確な色値を表示します。

3つ以上のコントロールパスを定義することも可能です。もしbitmaskによって各色値にアクセスすれば、画像形式(通常は24、アルファ値を使用している場合は32)のビット数によってのみ制限されるでしょう。パス用にシングルビットを使用することは可能ですが、カラー・バンドを使用するより複雑です。プログラムに別々のイメージ・コントロールパスを統合させるか、あるいは単一の画像および加法的画法を使用する必要があるでしょう。もし重複するパス(すなわち、1つの位置に複数のステイトが存在する)をサポートする必要がない場合、各位置で2^24(あるいは2^32)のステイトを持つことができます。さらに、これらの2つのアプローチを合わせることもできます。例えば、他のステイトのためにbitmaskによる赤のバンド、緑のバンドと青のバンドを使用します。

図4は、避難シミュレーションのために使用された完全なコントロールパス画像を示します。多数の色の使用、および異なる位置で異なるビヘイビアを表わすために、色がどのように使用されるかに注意してください。


図4.全体のコントロールパス
全体のコントロールパス

図5は、より明瞭化するためにコントロールパスの特定の部分を拡大します。


図5.コントロールパスの部分詳細
コントロールパスの部分詳細

命懸けで走ってください!

ステイト・マップを定義したので、私たちは2D空間のオブジェクトについて取り掛かることができます。サンプルの避難アプリケーションは、エンティティクラスのインスタンスとして、移動可能なオブジェクトをモデル化します。2つの主となるサブクラスが定義されています。人間(Person)とアラーム(Alarm)です。アラームが停止している間、人間は移動することができます。エンティティインターフェースは、リスト2で定義されます。


リスト2.エンティティインターフェース
                
interface Entity {
    void addToPanel(JPanel panel, boolean shared);
    
    void updateTick();
}

addToPanel()メソッドは、オブジェクトを表わすために1つ以上のSwingコンポーネントを作成し、提供されるパネルにそれらを加えます。コンポーネントは主としてアイコンセットを備えたJLabelsです。パネルは主として2D空間のインプリメンテーションです。その背景にはアニメーション背景を表示します。

updateTick()メソッドは、アニメーションの各サイクルを通して、オブジェクト自体をアニメーションさせます。Alarmオブジェクトは、点滅効果を作成するためにそれらの色を変更します。Personオブジェクトは、移動をします。

リスト3で実装されているように、Alarmオブジェクトは点滅する単純なコンポーネントです。


リスト3.Alarm.updateTick:点滅する
                
public void updateTick() {
    if (++tick % CYCLE == 0) {
        opaque = !opaque;
    }
}

PersonオブジェクトはAlarmより複雑です。それらは、リスト4で示されるように、定義されたコントロールパスに沿って動き回ります。


リスト4.Person.updateTick:パスに沿って移動する
                
/** Move one step along the path */
public synchronized void updateTick() {
    tick++;
    Integer tock = stops.get(new Integer(tick));
    if (tock != null) {   // adjust startTime if requested
        startTick = tick + tock.intValue();
    }
    if (tick < startTick) return;    // not my time yet
    if (isAtExit()) return;

    // Process individual movement
    Point2D location = getPosition();
    int x = (int)location.getX();
    int y = (int)location.getY();
    switch (manager.stateAt(x, y)) {
        case BuildingManager.STATE_EXIT:
            atExit = true;
            break;

        case BuildingManager.STATE_START:
        case BuildingManager.STATE_INTERSECTION:
            // process any hints
            if      (manager.stateAt(x - 1, y) == 
                     BuildingManager.STATE_HINT)
                setDirection(Person.DIR_WEST);
            else if (manager.stateAt(x + 1, y) == 
                     BuildingManager.STATE_HINT)
                setDirection(Person.DIR_EAST);
            else if (manager.stateAt(x, y + 1) == 
                     BuildingManager.STATE_HINT)
                setDirection(Person.DIR_SOUTH);
            else if (manager.stateAt(x, y - 1) == 
                     BuildingManager.STATE_HINT)
                setDirection(Person.DIR_NORTH);

            // no hints, select a direction
            if (getDirection() == DIR_NONE) {
                if      (manager.stateAt(x - 1, y) != 
                         BuildingManager.STATE_NONE)
                    setDirection(Person.DIR_WEST);
                else if (manager.stateAt(x + 1, y) != 
                         BuildingManager.STATE_NONE)
                    setDirection(Person.DIR_EAST);
                else if (manager.stateAt(x, y + 1) != 
                         BuildingManager.STATE_NONE)
                    setDirection(Person.DIR_SOUTH);
                else if (manager.stateAt(x, y - 1) != 
                         BuildingManager.STATE_NONE)
                    setDirection(Person.DIR_NORTH);
            }

        case BuildingManager.STATE_HALLWAY:
        case BuildingManager.STATE_HINT:
            // effect motion in selected direction
            int tempX = x;
            int tempY = y;
            switch (getDirection()) {
                case DIR_EAST:  x += 1; break;
                case DIR_WEST:  x -= 1; break;
                case DIR_NORTH: y -= 1; break;
                case DIR_SOUTH: y += 1; break;
            }
            int check = manager.stateAt(x, y);
            if (check == manager.STATE_UNKNOWN ||
                check == manager.STATE_NONE) {
                // went off the path, backup
                x = tempX;
                y = tempY;
                if (getDirection() == DIR_EAST || 
                    getDirection() == DIR_WEST) {
                    if (manager.stateAt(x, y + 1) != 
                        BuildingManager.STATE_NONE && 
                        manager.stateAt(x, y + 1) != 
                        BuildingManager.STATE_UNKNOWN) {
                        setDirection(Person.DIR_SOUTH);
                        y += 1;
                    }
                    else {
                        // Only direction not checked is north
                        setDirection(Person.DIR_NORTH);
                        y -= 1;
                    }
                } 
                else {
                    if (manager.stateAt(x + 1, y) != 
                        BuildingManager.STATE_NONE && 
                        manager.stateAt(x + 1, y) !=
                        BuildingManager.STATE_UNKNOWN) {
                        setDirection(Person.DIR_EAST);
                        x += 1;
                    }
                    else {
                        // Only direction not checked is south
                        setDirection(Person.DIR_WEST);
                        x -= 1;
                    }
                }
            }
            setNextPoint(new Point(x, y));
    }
}

このかなり複雑な方法は、基本的に現在位置の周囲のマップを検査します。その後、進むべき最良の新しい位置を選択します。それは、可能な限り同じ方向へ進むように作動します。ヒントは、好ましい方向を示す印であることを覚えておいてください。それらは通常はスタート位置または交差位置で使用されます。

Personは、時期を早めて(すなわちパスの終端に到着する前に)停止することができ、あるいは定時の動作開始を遅らせるよう設定できます。図6で示されるように、Personオブジェクトは、それらの動作を描くために画像(あるいはヒストリー)の退避痕跡を離れることができます。


図6.ヒストリーの影とともに移動する人間
ヒストリーの影とともに移動する人間

誰でも方向転換します

リスト5はエンティティを移動させるためのロジックを示します。このプロセスは、アニメーション・サイクルごとに一回実行されます。


リスト5.すべてのエンティティを移動させる
                
/** Move the entities around the pattern */
public void moveEntities() {
    // update (move) the people 
    for (Iterator iter = people.iterator(); iter.hasNext();) {
        Object next = iter.next();
        if (next instanceof Person) {
            ((Person)next).updateTick();
        }
    }
    // update the other entities
    for (Iterator iter = entities.iterator(); iter.hasNext();) {
        Object next = iter.next();
        if (next instanceof Entity) {
            ((Entity)next).updateTick();
        }
    }
}

アニメーション中の新しいフレームの各々は、リスト6に示されるプロセスによって作成されます。エンティティがフレームにそれら自身を加えることに注意してください。


リスト6.現在位置にエンティティを追加する
                
/** Advance the animation */
public void prepareNextFrame(boolean update, boolean shared) {
    setBackground(Color.black);
    if (update) {
        manager.moveEntities();
    }
    mainPanel.setBounds(getBounds());
    mainPanel.removeAll();
    // add the people 
    for (Iterator iter = manager.getPeople().iterator(); 
         iter.hasNext();) {
        Person person = (Person)iter.next();
        person.addToPanel(mainPanel, shared);
    }
    // add the entities
    for (Iterator iter = manager.getEntities().iterator(); 
         iter.hasNext();) {
        Entity entity = (Entity)iter.next();
        entity.addToPanel(mainPanel, shared);
    }
}

リスト7のコードで、連続動作するアニメーションを実現します。delay値は、アニメーションをどのくらいの速さで実行するか、そして、どのくらいのCPU時間が掛かるかもコントロールします。


リスト7.アニメーションの生命のために
                
public void runUpdates(int delay) {
    TimerTask task = new TimerTask() {
        public void run() {
            SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        prepareNextFrame();
                        repaint();
                        if (manager.getPeople().size() == 0) {
                            cancel();
                        }
                    }
            });
        }
    };
    (new Timer(true)).scheduleAtFixedRate(task, 0, delay);
}

以下は、アニメシーケンスの色々なスナップショットです。図7は、回避シミュレーションの、行動が白熱し始める直前(およそ10パーセント完了時)の状態を示します。


図7.スタート付近の避難シーケンス
スタート付近の避難シーケンス

図8は、およそ50パーセント完了時のアニメシーケンスを示します。


図8.いくらかの処理進行後の避難シーケンス
いくらかの処理進行後の避難シーケンス

図9は、完了間際のシーケンスを示します。(シーケンスは実際には永久に実行されますが)


図9.終了間際の避難シーケンス
終了間際の避難シーケンス

図10で、エンティティがどのようにコントロールパスに沿って移動するのかが解ります。(それらはあなたに、実際にその動作をどのように実現するか、よりよいアイディアを与えるはずです。)


図10.コントロールパスに沿って動作するエンティティ
コントロールパスに沿って動作するエンティティ

シーンを描く

クラスBuildingViewerは、オブジェクトを移動するためのコンテナを作成します。paintChldren()メソッドは、最初に背景画像、次に警告メッセージ、そして最後に様々なエンティティを表わすサブコンポーネントを生成します。


リスト8.BuildingViewer.paintChildren
                
private int paintCount;
private static final Color evacColor = new Color(255, 0, 0, 128);
  :
/** draw the background, massage and entities */
public void paintChildren(Graphics g) {
    paintCount++;

    if (background != null) {
        Graphics g2 = g.create();
        try {
            // draw the background
            g2.drawImage(background.getImage(),
                         (int)getLocation().getX(), 
                         (int)getLocation().getY(),
                         (int)getLocation().getX() + getWidth(), 
                         (int)getLocation().getY() + getHeight(),
                         0, 0, 
                         background.getIconWidth(), 
                         background.getIconHeight(),
                         Color.black, null);



            // draw the alert message (if any)
            if (alertMessage != null) {
                if (paintCount % alertPeriod >= (alertPeriod / 2)) { 
                    Font f = g2.getFont();
                    Font f2 = f.deriveFont((float)alertSize);
                    FontMetrics fm = Toolkit.getDefaultToolkit().
                        getFontMetrics(f2);
                    int fHeight = fm.getHeight(), 
                        fAscent = fm.getAscent();
                    int sWidth = fm.stringWidth(alertMessage);
                    g2.setFont(f2);
                    Graphics2D g2d = (Graphics2D)g2;
                    g2d.setStroke(new BasicStroke(10));
                    g2.setColor(evacColor);
                    g2.drawString(alertMessage, 
                                  (getWidth() - sWidth) / 2, 
                                  (getHeight() - fHeight) / 2 + 
                                      Ascent);
                }
            }
        } finally {
            g2.dispose();
        }
    }
    super.paintChildren(g);
}

効果的なアニメーションを作成するために、アニメーションするオブジェクトが必要です。リスト9は、与えられた入力に基づいて、同種類の一組のPersonエンティティ(すなわち、障害者、健常者、消防士など)を作成するコードです。


リスト9.同種類のエンティティを作成する
                
/** initialize paths */
protected static void initLoop(BuildingManager manager, ImageIcon icon,
                               int[] locs, String[] names, 
                               int[] starts, int[] appear, int[][][] stops)
{
    LinkedList startPts = (LinkedList)manager.
                         getAvailableStartingPoints();
    // for all specified locations - create a Person 
    for (int i = 0; i < locs.length; i++) {
        JLabel label = new JLabel(names[i], icon, JLabel.CENTER);
        label.setFont(new Font(label.getFont().getName(),
                               label.getFont().getStyle(), 20));
        Person person = new Person(manager, label,
                                   (Point2D)startPts.get(
                                     Math.min(startPts.size() - 1, locs[i])),
                                    starts[i]);
        person.setAppearTick(appear[i]);

        // defines stop locations for each Person
        for (int j = 0; j < stops[i].length; j++) {
            person.addStop(stops[i][j][0], stops[i][j][1]);
        }
        manager.addEntity(person);
    }
}

リスト10はリスト9のinitLoopコードを使用して、一組のPersonエンティティを定義します。このコードは、作成するオブジェクトに関する情報を提供するために、いくつかの(locs配列の長さに基づいた)平行配列を使用します。コントロールパスによって与えられるとおりに、locs配列は一組の定義されたスタート位置にインデックスを与えます。starts値は、Personの移動開始の予約時刻を指定します。appear値は、Personが可視的になる時(多くは移動開始前)の予約時刻を定義します。stops値は、各Personが持つことができる(おそらく複数の)停止地点を指定します。

下記は手入力の値として示されていますが、その位置のエンティティのステイトを表す新しい色を加えることにより、コントロールパスからこれらの入力値のほとんどを取得することが可能です。この機能拡張は、これらの値の入力を単純化することができ、コントロールパスを変更する場合に、エラーの発生を抑えることができます。


リスト10.すべてのPersonエンティティを作成する
                
/** Make some demo people */
static public void createPeople(BuildingManager manager, 
                                ImageIcon employIcon,
                                ImageIcon fireIcon,
                                ImageIcon disabledIcon)
{
    // Main character - ALEX
    int locs[] = new int[] {42};
    String names[] = new String[] {"Alex"};
    int starts[] = new int[] { 300 };
    int appear[] = new int[] { 0 };
    int stops[][][] = new int[][][] {{}};
    initLoop(manager, employIcon, locs, names, starts, appear, stops);

    // Some disabled people
    locs = new int[] { 39, 45 };
    names = new String[] { "Karen", "Mike" };
    starts = new int[] { 0, 0};
    appear = new int[] { 0, 0 };
    stops = new int[][][] {{{1, 164}, {560, 20}}, 
                           {{1, 141}, {460, 30}}};
    initLoop(manager, disabledIcon, locs, names, starts, appear, stops);

    // Some Assisters
    locs = new int[] {44, 49, 37, 46};
    names = new String[] { "Tom", "Joe", "Cathy", "Larry" };
    starts = new int[] { 0, 0, 0, 0};
    appear = new int[] { 0, 0, 0, 0 };
    stops = new int [][][] {{{120, 52}, {560, 20}},
                            {{155, 24}, {560, 20}}, 
                            {{122, 27}, {460, 30}}, 
                            {{100, 59}, {460, 30}}};
    initLoop(manager, employIcon, locs, names, starts, appear, stops);

    // A firemen
    locs  = new int[] { 25 };
    names = new String[] { "FD", "FD 2", "FD 3" };
    starts = new int[] { 400, 400, 400};
    appear = new int[] { 400, 400, 400 };
    stops = new int [][][] {{},{}, {}};
    initLoop(manager, fireIcon, locs, names, starts, appear, stops);

      :
      :  **** many additional definition sets omitted ****
      :
}

そして最後に、リスト11は、Alarmエンティティを作成する方法を示します。一目瞭然ですが、私たちは、必要ならばより多くのアラームを簡単に追加することができます。


リスト11.アラームを作成する
                
/** Make some demo alarms */
static public void createAlarms(BuildingManager manager) {
    final int alarms[] = { 12 };
    LinkedList startPts = (LinkedList)manager.
                              getAvailableStartingPoints();
    for (int i = 0; i < alarms.length; i++) {
        manager.addEntity(
            new Alarm(manager, (Point2D)startPts.get(alarms[i])));
    }
}


結論

この記事では、2Dアニメーションのモーションパス生成のための、可逆画像、Swingテクノロジー、およびカスタム・アニメーションエンジンの使用方法を示しました。この方法は、素早く解りやすいやり方でコントロールパスによってそれを作成することで、アニメーションの視覚化を可能にします。この技術の利点のいくつかを以下に示します。

使いやすさ
ほとんどのイメージ・エディタには、直線、曲線およびその他の形を生成する多くの方法があります。これらのオプションは、エラーを減少させると同時に、手動で素早くいくつかのパスを生成することを可能にします。いくつかのビヘイビアについては、これは非常に便利です。

関連画像
アニメーションが、(図1のような)背景画像に関連して移動している場合、私たちはその画像内の、アイテムの領域内のオブジェクトを移動させたいと思うかもしれません(例えば通路の領域内のオブジェクトの動作を続けさせるなど)。多くのイメージ・エディタでは、背景画像の上にコントロール画像を生成するために、透過レイヤーを使用することが出来るでしょう。その後、(コントロール画像を生成すると同時に、両方の画像が準備されるので、)背景画像と適合させるために容易にコントロールパスを作成することができます。

加法的画法
色を混合することによって、私たちは一つの位置で複数のビヘイビアをエンコードすることができます。例えば、RGBカラーの使用で、私たちは、あるオブジェクトが従うべきパスを表すために赤色(0xFF0000)を、そして別のオブジェクトが従うべきパスをエンコードするために緑色(0x00FF00)を使用することができます。加法的画法を使用すると、パスが交差する地点は黄色(0xFFFF00)となります。

この加法的モデルでは、例えば32ビットのカラーモデルを使用する場合、32までの異なるビヘイビアは、bitmaskのような、与えられた位置から容易に抽出することができます。私たちは1つの単純なビヘイビアについてのみ記述しましたが、エンコードすることができるビヘイビアの数は、画像形式での各色に割り当てられたビット数によってのみ制限されています。

私たちはまた、パスのセットのまわりのオブジェクトを移動させるために単純なアニメーションエンジンについて示し、記述してきました。エンティティと呼ばれ、サンプル中でJLabelとしてインプリメントされた各オブジェクトは、その位置および(または)外観を更新するために、周期的に動作します。長期にわたるタイマースレッドは、このプロセスを作動させるために割り当てられます。JPanelは、オブジェクトのコンテナ、および背景を描く手段としても使用されます。この記事で紹介されたアニメーションサンプルおよびJavaベースのアニメーションエンジンの完全なコードに関しては、参考文献を参照してください。


参考文献

  • この記事中で使用されたアニメーションエンジンおよびサンプル・アニメーションシーケンスのソースコードをダウンロードできます。

  • JFCとSwingに関してさらに学習するためには、java.sun.com を見てください。

  • Java 2D APIおよびJava Advanced Imaging APIについても知りたいと思うかもしれません。

  • Mitch Goldsteinのチュートリアル、Java 2D入門は、Java 2DがGUIプログラミングにもたらす高度な描画、テキストレイアウトおよび画像操作の利点を一歩一歩教えます。

  • Creating Java2D composites for rollover effects」(developerWorks,、2002年9月)でJava 2D APIを使用した画像作成と操作についてさらに学びましょう。

  • 英国のTechnical Advisory Service for Images (TASI)は、可逆画像圧縮技術の部門を含み、デジタル画像の様々なファイル形式および圧縮技術の役立つ概要を提供しています。

  • Yakov Nekrichは、可逆画像圧縮に関する優れたリンク集を作成しています。

  • この記事で示された画像タイプ(すなわちJPEGPNGおよびGIF)についてさらに学びましょう。

  • Barry Feigenbaumはまた「アクセシビリティを考慮したコーディング」(developerWorks、2002年10月)を書いています。この記事はよりアクセシブルなJavaアプリケーションを構築するためにSwing/JFCおよびユニークなアクセシビリティ・ツールキットを使用する方法をあなたに教えます。

  • developerWorksJava technologyゾーンには、Javaプログラミングのすべての面に関する多数の記事があります。

  • フリーのJavaベース・プログラミングのチュートリアルの一覧については、developerWorksJava tutorialsページを見てください。

著者について

Barry Feigenbaum博士は、IBM Worldwide Accessibility Centerのメンバーであり、IBM製品を障害者にとってアクセシブルなものにするための支援を行うチームの一員です。Feigenbaum博士は複数の本と記事を出版しており、複数の特許を持ち、JavaOneのような開発者会議で講演をしています。彼は、オースティンのテキサス大学にて、コンピュータサイエンスの非常勤助教授を務めています。

Tom Brunetはウィスコンシン大学マジソン校の大学院生です。彼はオースティンのテキサス大学からコンピュータサイエンスのB.S.を受け、学部長から優秀卒業生に選ばれました。大学研究中に、彼は、4年間IBM Researchにて働きました。Data Abstraction Researchグループでひと夏を過ごし、Accessibility Centerで残りの日々を過ごしました。彼はまた、Nina Amenta博士の研究助手学生としても同時に働きました。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=218809
ArticleTitle=イメージベース・パスを使用した2Dアニメーション
publish-date=01092004
author1-email=feigenba@us.ibm.com
author1-email-cc=
author2-email=tomab@cs.wisc.edu
author2-email-cc=

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。