Java入門|スレッドでアニメーションを作る

アニメーション入門。スレッドの力で、画面の中に動きと時間の流れを生み出そう。

これまでのGUIアプリケーションでは、ウィンドウを表示し、部品を置き、マウス操作に反応して画面を書き換えるところまで見てきました。
ここまででも、JavaのGUIはかなり表情豊かになってきましたが、さらに一歩進むと、画面の内容を時間の経過に合わせて変化させることができます。これがアニメーションです。

ドラゴンボールの世界でたとえるなら、修行場の表示パネルに数字や記号が固定で表示されるだけではなく、悟空の気の高まりに合わせてメーターが変化したり、特訓の進行に合わせて表示が少しずつ切り替わったりするような状態です。
つまり、画面がただの静止した表示ではなく、時間とともに変化するものになるわけです。

Javaでこのようなアニメーションを作るときに大切になるのが、スレッドです。
スレッドを使うことで、一定の間隔で画面を更新し続ける処理を動かせます。ここでは、スレッドで何をしているのか、なぜ repaint() と sleep() を組み合わせるのか、そして paint() がどのように表示の変化を支えているのかを、ドラゴンボールの世界観でやさしく整理していきます。

アニメーションとは何か

アニメーションとは、画面の表示内容を少しずつ変化させ、それを連続して見せることで動いているように見せる仕組みです。

今回のような基本例では、数字や文字列を一定時間ごとに切り替えることで、変化の流れを作ります。

流れをまとめると、こうなります。

手順内容
1表示する値を変える
2repaint() で再描画する
3少し待つ
4また値を変える
5この流れを繰り返す

ドラゴンボール風にいうと、修行カウンターの数値が 0、1、2、3… と少しずつ進み、それが一定の間隔で表示板に映し出されるイメージです。
このように、値の変化再描画を繰り返すことで、画面に時間の流れが生まれます。

なぜスレッドを使うのか

アニメーションでは、画面を1回描くだけでは足りません。
一定の間隔で何度も描き直す必要があります。

そのために使うのがスレッドです。
スレッドを使うことで、画面更新のための処理を連続的に動かせます。

ドラゴンボール風にいうと、これは修行場の表示係が、1回だけ数字を書いて終わるのではなく、一定時間ごとに表示板を書き換え続ける役目を持っているようなものです。
スレッドがあることで、その表示更新を時間に沿って進められるようになります。

サンプルプログラム:スレッドを使ったアニメーションの例

今回は、修行の進行段階を表す数字が、1秒ごとに変化するプログラムです。

ファイル名:Sample7.java

import java.awt.*;
import java.awt.event.*;

public class Sample7 extends Frame implements Runnable
{
    int step;

    public static void main(String[] args)
    {
        Sample7 sm = new Sample7();
    }

    public Sample7()
    {
        super("修行カウント表示");

        addWindowListener(new SampleWindowListener());

        // 新しいスレッドを開始する
        Thread th;
        th = new Thread(this);
        th.start();

        setSize(420, 320);
        setVisible(true);
    }

    public void run()
    {
        try {
            for (int i = 0; i < 10; i++) {
                // 表示する段階を更新する
                step = i;

                // 画面を再描画する
                repaint();

                // 1秒待つ
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
        }
    }

    public void paint(Graphics g)
    {
        g.setFont(new Font("SansSerif", Font.BOLD, 26));
        g.drawString("修行段階 " + step + " に到達", 90, 150);
    }

    class SampleWindowListener extends WindowAdapter
    {
        public void windowClosing(WindowEvent e)
        {
            System.exit(0);
        }
    }
}

このプログラムでできること

このプログラムを実行すると、「修行カウント表示」というタイトルのウィンドウが表示されます。
そして、画面の中央あたりに

  • 修行段階 0 に到達
  • 修行段階 1 に到達
  • 修行段階 2 に到達

というように、表示が約1秒ごとに変化していきます。
最終的には 9 まで進みます。

つまり、このプログラムでは次のことをしています。

できること内容
スレッドを開始するアニメーション用の処理を動かす
値を変えるstep の値を順番に更新する
画面を描き直すrepaint() を呼ぶ
時間を空けるThread.sleep(1000) を使う
表示するpaint() で文字列を描く

Runnable を実装する意味

このプログラムでは、クラス宣言が次のようになっています。

public class Sample7 extends Frame implements Runnable

ここで Runnable を実装しているのは、スレッドとして動かす処理をこのクラスに持たせるためです。

Runnable には run() メソッドがあり、スレッドが開始されると、この run() の中の処理が実行されます。
つまり、このクラスは

  • ウィンドウとしての役割
  • スレッド処理を持つ役割

の両方を持っています。

ドラゴンボール風にいうと、修行場の表示パネルそのものが、同時に「表示を自動更新する係」の役割も持っているような状態です。

Thread を作って start() する流れ

コンストラクタの中には、次のような記述があります。

Thread th;
th = new Thread(this);
th.start();

この流れを分けて考えると分かりやすいです。

記述役割
new Thread(this)このオブジェクトをもとにスレッドを作る
th.start()スレッドを開始する

ここで start() を呼ぶと、run() メソッドが動き始めます。

大切なのは、run() を自分で直接呼ぶのではなく、start() を呼ぶことでスレッドとして実行することです。
これによって、時間の流れを持った連続処理が始まります。

run() メソッドで何をしているか

アニメーションの中心になるのが run() メソッドです。

public void run()
{
    try {
        for (int i = 0; i < 10; i++) {
            step = i;
            repaint();
            Thread.sleep(1000);
        }
    } catch (InterruptedException e) {
    }
}

この中では、次の3つを繰り返しています。

値を更新する

step = i;

今どの段階なのかを表す値を変更しています。

画面を再描画する

repaint();

新しい値で画面を描き直すように依頼しています。

少し待つ

Thread.sleep(1000);

1000ミリ秒、つまり約1秒だけ処理を休ませています。

この3つの流れを何度も繰り返すことで、画面の表示が時間ごとに変化して見えるわけです。

for文がアニメーションの流れを作る

for文の部分は、アニメーションの進行表のような役割を持っています。

for (int i = 0; i < 10; i++)

この繰り返しによって、i の値は 0 から 9 まで順番に変わります。
そして、そのたびに step に代入され、表示内容が更新されます。

ドラゴンボール風にいうと、これは修行の進行段階が 0 から 9 まで順に進むスケジュール表のようなものです。
各段階ごとに表示板を書き換えることで、アニメーションとして見えるようになります。

repaint() の役割

アニメーションでは repaint() がとても大切です。

repaint();

このメソッドは、画面をもう一度描き直してほしいときに呼び出します。
今回のプログラムでは、step の値を変えたあとに repaint() を呼ぶことで、新しい値を画面に反映させています。

もし repaint() を呼ばなければ、step の中身が変わっていても、画面の見た目はすぐには変わりません。
つまり、値の更新だけではアニメーションにならず、再描画の指示が必要なのです。

Thread.sleep(1000) の役割

このプログラムでは、表示の変化を見やすくするために sleep() を使っています。

Thread.sleep(1000);

これは、スレッドを約1秒間停止する命令です。
この待ち時間があるおかげで、数字や文字の変化がゆっくり見えます。

もし sleep() がなければ、0 から 9 までの変化が一瞬で終わってしまい、ほとんどアニメーションとして見えません。
つまり、sleep() は変化の間隔を作る役割を持っています。

ドラゴンボール風にいうと、修行段階の表示を一瞬で全部切り替えるのではなく、1段階ずつ少し間を空けて見せるための待機時間です。

paint() で何を表示しているか

画面に実際の文字を描いているのは paint() メソッドです。

public void paint(Graphics g)
{
    g.setFont(new Font("SansSerif", Font.BOLD, 26));
    g.drawString("修行段階 " + step + " に到達", 90, 150);
}

ここでは、step の値を文字列の中に組み込んで表示しています。

たとえば step が 3 なら、

修行段階 3 に到達

という文字が描かれます。

このように、paint() はその時点の値を見て画面を描く役割を持っています。
スレッドが値を変え、repaint() が再描画を依頼し、paint() がその結果を見せる、という流れです。

アニメーションの流れを表で整理

ここまでの動きを順番に整理すると、こうなります。

順番起こること
1コンストラクタでスレッドを作る
2start() でスレッドを開始する
3run() が実行される
4step の値を更新する
5repaint() を呼ぶ
6paint() が呼ばれる
7画面に現在の値が描かれる
8sleep() で1秒待つ
9この流れを繰り返す

この流れが分かると、アニメーションの基本構造がかなり見えやすくなります。

スレッドと描画がどうつながるか

このテーマの大事なところは、スレッドが直接画面を描くのではなく、値の更新と再描画の依頼をしていることです。
そして、実際の描画は paint() が担当しています。

役割を分けると、次のようになります。

役割担当
時間の流れを作るスレッド
表示する値を変えるrun()
描き直しを依頼するrepaint()
実際に描画するpaint()

この分担が見えると、GUIアニメーションの考え方がかなり整理しやすくなります。

この図が示していること

この図では、main() から Sample7 のオブジェクトが作られ、そこから新しい Thread が開始され、run() の中で step の値が更新され、repaint() を通じて paint() が呼ばれ、ウィンドウの表示が1秒ごとに切り替わる流れを示します。
つまり、アニメーションはスレッドが時間の流れを作り、paint() がその時点の状態を描画することで成立していることが分かります。
また、Thread.sleep(1000) が表示の間隔を作る役割を持っていることも分かります。

アニメーションが成立する理由

このサンプルでアニメーションが成立する理由をひとことでいうと、値の変化・再描画・待機時間が繰り返されるからです。

もう少し細かくいうと、次の3つがそろっているからです。

表示内容が変わる

step が 0 から 9 まで変化します。

毎回描き直される

repaint() によって paint() が呼ばれます。

間隔がある

sleep() によって約1秒ごとに変化します。

この3つが合わさることで、画面が段階的に変わっていくように見えます。

もっと発展させると何ができるか

今回の例は文字を変えるだけのシンプルなアニメーションですが、考え方を広げるといろいろ応用できます。

応用例できること
位置を変える図形や画像を横に動かす
色を変える一定時間ごとに色を切り替える
サイズを変える気の玉が大きくなる表現をする
複数の値を使うキャラクターの位置と表情を同時に変える

ドラゴンボール風にいうと、修行メーターの数値を変えるだけでなく、悟空の気の玉が少しずつ右へ進んだり、大きくなったりするような画面にもつながっていきます。

はじめて学ぶときに押さえたい見方

最初は、スレッドと描画が同時に出てくるので少し複雑に見えるかもしれません。
そんなときは、次の4つに分けて考えると分かりやすいです。

どこで時間の流れを作っているのか

run() と Thread.sleep(1000) です。

どこで値を変えているのか

for文の中で step を更新しています。

どこで画面更新を指示しているのか

repaint() です。

どこで実際に描いているのか

paint() です。

この4つの役割が見えると、スレッドアニメーションの基本がかなりつかみやすくなります。

この内容でつかんでおきたいこと

今回のテーマでいちばん大切なのは、スレッドを使うことで、一定時間ごとに画面を更新できるということです。
そして、アニメーションはスレッドだけでできるのではなく、paint() と repaint() と組み合わせることで成り立っています。

押さえておきたいポイントを整理すると、次の通りです。

ポイント内容
Runnableスレッドで動かす処理を持たせるための仕組み
Thread新しく動く流れを作る
start()スレッドを開始する
run()スレッドが実行する処理を書く場所
repaint()画面を描き直すよう依頼する
paint()実際の描画を行う
Thread.sleep()表示の間隔を作る

この流れがつかめてくると、JavaのGUIは「押したら変わる」だけでなく、「時間とともに動く」ものへ広がって見えてきます。
ここまで来ると、画面に動きを持たせる楽しさがかなりはっきり感じられるようになります。