人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ボールを動かす

ボールが動くアニメーションを作ります。Javaでアニメーションを作る方法はいくつかありますが、ここでは最も一般的なスレッドを用いて実装します。動きがつくとゲームっぽくなってきます。

move_ball.jar

アニメーションの仕組み

テレビとかでやってるアニメってどうやって作るか知ってます?ある絵を描いて、また位置を少しずらして絵を描いてという作業を何回も何回も繰り返しているんだそうです。たくさんの少しずつずらした絵を何枚も何枚も描いて順に画面に表示させると動いているように見えるんですね。30分のアニメでもかなりの枚数を描くようです。

私は小さいときにこれを聞いてまさか!と思いました。以前は、描いた絵をカメラの前で動かしてるんだと思ってました(笑)。少しずつずらして描くなんてものすごく大変で不可能だと思ったわけです。ただぱらぱら漫画のことふっと思い出して妙に納得したのを覚えています。ぱらぱら漫画ってのはノートのすみに方に少しずつ違う絵を描いていってぱらぱらめくると動いてるように見えるって遊びで昔流行っていました(今はどうかな?)。アニメーションは少しずつ違う絵を何枚も描いて順に表示してできているんだということを実感したわけです。

アニメの一枚一枚の絵をフレーム(こま)と呼びます(JFrameのFrameとは違います)。昔の漫画(ドラえもんとか)にはセル画といってアニメの1フレームを描いた絵がおまけでついていました。このフレームを順に画面に表示するわけです。

プログラムでアニメーションを作るときも仕組みは同じです。すこしずつ絵をずらして描いたフレームを用意し、順に表示しています。コードでは、

    while (true) {
        // ボールを速度分だけ移動させる
        x += vx;
        y += vy;

        // ボールを再描画
        repaint();
        
        …
    }

の部分です。ボールの位置(x,y)を少しずらして(vx,vy分だけ)1フレームを描き、またボールの位置をずらして1フレームを描くというのを whileループで繰り返しています。フレームを描くのがアニメーターさんじゃなくてコンピュータってこと以外、テレビのアニメと同じでしょ?ちなみにフレームを実際に描画する処理はpaintComponent()に書きます。上のコードはrepaint()でpaintComponent()を呼び出しています。

スレッド

スレッドとは処理の流れのことです。プログラムを実行するとメインスレッドが開始され、順次動作が進みます。コードではMainPanel()コンストラクタの実行はメインスレッドが担当します。そしてMainPanel()の中で新しいスレッドを作成してrun()を実行する別の処理の流れを作り出しています

    // スレッドを起動
    thread = new Thread(this);
    thread.start();

new Thread()でスレッドオブジェクトを作成してstart()でスレッドを開始します。この新しいスレッドがrun()を実行するという仕掛けです。run()は直接呼び出せないので注意!start()を呼び出すとrun()が実行されます。run()はRunnableを実装しているクラスに実装します。Thread()に与えている引数thisはこのクラス(MainPanel)が Runnableを実装していることを表しています。run()の中は先ほど解説したアニメーション処理を実行しています。つまり、アニメーション処理はメインスレッドではなく、新しく作られたスレッドが担当しています。

run()ではThread.sleep()で休止しています。これはなぜ必要なのでしょうか?

    // 20ミリ秒だけ休止
    try {
        Thread.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

ためしに上の部分をはずして実行してみましょう。ボールが見えますか?あまりにも早く動いて一瞬で見えなくなってしまうはずです。コンピュータの速度はものすごく速いので休止を入れないととんでもなく速いアニメーションになってしまうのです。Thread.sleep()はアニメーションの速度を調節するために必要です(CPUを独占せず他のスレッドに制御を渡すという役目もあります)。

ボールの描画

ボールを描いている部分はpaintComponent()内の

    // 青いボールを描く
    g.setColor(Color.BLUE);
    g.fillOval(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE);

です。(x,y)は円の中心の座標、SIZEは円の直径の大きさであることに注意しましょう。図解すると下のようになります。

f:id:aidiary:20090827215311g:plain

fillOval()は左上の座標と幅、高さを引数に取るため上のコードになっています。次回からは(x,y)を円の左上の座標として円を描きます。

ボールの移動

最後にボールの移動処理について解説します。ボールが移動する仕組みを図示すると下のようになります。

f:id:aidiary:20090827215312g:plain

ここで重要なのは、x方向とy方向の移動を別々に考えるということです(物理のテストでいい点とるためにも重要です)。上のプログラムではx方向の移動(x += vx)とy方向の移動(y += vy)を別々に考えています。別々の移動を合わせることによって、つまりx方向とy方向の移動を合わせることによって斜めの移動を実現することができます。

移動速度は(vx,vy)を変えることで変更できます。速度というのは単位時間でどれくらい移動するかを表した量です。数字(の絶対値)が大きいほど速く移動します。数字を大きくしたりマイナスにしたりといろいろ変えて実験してみましょう。

実行するとわかりますが、このボール、端へ行くと見えなくなっちゃいます。次回はボールがウィンドウ枠のところで跳ね返る処理を実装してみます。