*シラネーヨとマターリしよーヨ(Java)

「シラネーヨ」がマウスカーソルを追いかけます。マウスカーソルをつかまえると,「マターリしよーヨ」とおねだりします。

アプレットが起動しない方へ

Windowsの場合

ウェブブラウザでアプレットを実行するのではなくて,アプレットヴューワ(jview.exeまたはwjview.exe)を使います。

[スタート] → [ファイル名を指定して実行] をクリックすると,「ファイル名を指定して実行」ダイアログが表示されます。「名前」の欄に

wjview /a http://yumi-ii.squares.net/shiraneyo/archives/2001/01/20010130000001.html

と入力後[OK]をクリックしてください。単体のウィンドウ開きアプレットが起動します。

Macintos(Mac OS X),Linuxの場合

Sunのアプレットヴューワ(appletviewer)を使います。「ターミナル」を起動しプロンプト画面から

appletviewer http://yumi-ii.squares.net/shiraneyo/archives/2001/01/20010130000001.html

と入力後[return]キーを押してください。単体のウィンドウ開きアプレットが起動します。

技術資料

今回もMicrosoft Visual J++ 6.0を使いました。あまり細かいことを考えずに拵えてみたのだが,意外ときれいにまとまりました(設計図どおり作っただけだから当たり前なんだけど)。

おおまかな構造図

重要な点は以下の4項目ほどです。

  • Characterは一つのスレッドとして動作する。
  • Characterの数に制限はない。
  • Characterの振る舞いはrun()メソッドに記述される。
  • アニメーションの画像は,Action抽象クラスのサブクラスにカプセル化されている。

ちなみに,マウスイベントを処理するクラスとか,Action抽象クラスはCharacterクラスの内部クラスとして定義されているとか,そういう話はどうでもいいことなので,図には含めていません。

大まかな構造図
ソースファイル

アプレットを作るときに開発ツールが用意しているテンプレートを使ったので,コードに変なコメントが入っています。

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/**
 * このクラスは、HTML ホストページから PARAM タグを読み、アプレット
 * の color および label プロパティを設定します。プログラムの実行は init() 
 * メソッドから始まります。 
 */
public class Mattari extends Applet
{
  /**
    * アプレットのためのエントリ ポイントです。 
   */
  public void init()
  {
    initForm();
    usePageParams();

    // TODO: 構築用のコードを追加します。initForm の呼び出し前には追加しないでください。

    Character agent = new Character();
    Thread agentThread = new Thread(agent, "agent-thread");

    setLayout(null);
    add(agent);

    addMouseListener(agent.createMouseListener());
    addMouseMotionListener(agent.createMouseMotionListener());
    agentThread.start();
  }

  private  final String backgroundParam = "background";
  private  final String foregroundParam = "foreground";

  /**
   * このアプレットを含む HTML ページのパラメータを読み、プロパティを
   * 設定します。
   */
  private void usePageParams()
  {
    final String defaultBackground = "C0C0C0";
    final String defaultForeground = "000000";
    String backgroundValue;
    String foregroundValue;

    /** 
     * アプレットを含む HTML ページから、以下のタグを読みます。
     * <PARAM NAME="background" VALUE="rrggbb">
     * <PARAM NAME="foreground" VALUE="rrggbb">
     */
    backgroundValue = getParameter(backgroundParam);
    foregroundValue = getParameter(foregroundParam);

    if ((backgroundValue == null) ||
      (foregroundValue == null))
    {
      /**
       * HTML のタグに問題がありました。
       * 既定値で生成します。
       */
      backgroundValue = defaultBackground;
      foregroundValue = defaultForeground;
    }

    /**
     * アプレットのラベル文字列、背景色および前景色
     * を設定します。
     */
    this.setBackground(stringToColor(backgroundValue));
    this.setForeground(stringToColor(foregroundValue));
  }

  /**
   * "rrggbb" 形式の文字列を awt.Color オブジェクトに変換します。
   */
  private Color stringToColor(String paramValue)
  {
    int red;
    int green;
    int blue;

    red = (Integer.decode("0x" + paramValue.substring(0,2))).intValue();
    green = (Integer.decode("0x" + paramValue.substring(2,4))).intValue();
    blue = (Integer.decode("0x" + paramValue.substring(4,6))).intValue();

    return new Color(red,green,blue);
  }

  /**
   * デザイン ツールがアプレットのプロパティを表示するための外部インターフェイスです。
   */
  public String[][] getParameterInfo()
  {
    String[][] info =
    {
      { backgroundParam, "String", "Background color, format \"rrggbb\"" },
      { foregroundParam, "String", "Foreground color, format \"rrggbb\"" },
    };
    return info;
  }

  /**
   * アプレットおよび、そのコンポーネントの値を初期化します
   */
  void initForm()
  {
    this.setBackground(Color.lightGray);
    this.setForeground(Color.black);
  }
}

class Character extends Panel implements Runnable {
  Image[] imgs = {
    null,
    getToolkit().getImage(getClass().getResource("img01.gif")),
    getToolkit().getImage(getClass().getResource("img02.gif")),
    getToolkit().getImage(getClass().getResource("img03.gif")),
    getToolkit().getImage(getClass().getResource("img04.gif")),
    getToolkit().getImage(getClass().getResource("img05.gif")),
    getToolkit().getImage(getClass().getResource("img06.gif")),
    getToolkit().getImage(getClass().getResource("img07.gif")),
    getToolkit().getImage(getClass().getResource("img08.gif")),
    getToolkit().getImage(getClass().getResource("img09.gif")),
    getToolkit().getImage(getClass().getResource("img10.gif")),
    getToolkit().getImage(getClass().getResource("img11.gif")),
  };

  abstract class Action {
    Image[] actionFrames;
    int frameCount = 0;

    public void play() {
      Image[] actionFrames = getFrames();
      frameCount = (frameCount + 1) % actionFrames.length;
    }
    public void paint(Graphics g) {
      Image[] actionFrames = getFrames();
      g.drawImage(actionFrames[frameCount], 0, 0, null);
    }
    protected abstract Image[] getFrames();
  }

  final class LeftTurn extends Action {
    Image[] frames1 = {imgs[1], imgs[2]};
    Image[] frames2 = {imgs[11], imgs[2]};
    public Image[] getFrames() {
      return (Math.random() < 0.90) ? frames1 : frames2;
    }
  }

  final class RightTurn extends Action {
    Image[] frames1 = {imgs[8], imgs[9]};
    Image[] frames2 = {imgs[10], imgs[9]};
    public Image[] getFrames() {
      return (Math.random() < 0.90) ? frames1 : frames2;
    }
  }

  final class Lost extends Action {
    Image[] frames = {
      imgs[6], imgs[3], imgs[6], imgs[3],
      imgs[3], imgs[3], imgs[7], imgs[7],
      imgs[7], imgs[7], imgs[7]
    };
    public Image[] getFrames() {
      return frames;
    }
  }

  final class Mattari extends Action {
    Image[] frames = {imgs[5]};
    public Image[] getFrames() {
      return frames;
    }
  }

  Action currentAction;
  Hashtable actionTable;

  Image buffer = null;
  Graphics bufferGraphics;
  
  Character() {
    MediaTracker tracker = new MediaTracker(this);
    for (int i = 0; i < imgs.length; i ++) {
      if (imgs[i] != null) {
        tracker.addImage(imgs[i], i);
      }
    }
    try {
      tracker.waitForAll();
    } catch (InterruptedException ie) {}

    /////

    actionTable = new Hashtable();
    actionTable.put("left", new LeftTurn());
    actionTable.put("right", new RightTurn());
    actionTable.put("lost", new Lost());
    actionTable.put("mattari", new Mattari());

    setBounds(0, 0, 292, 126);
    setEnabled(false); 
  }

  public void play() {
    currentAction.play();
  }

  public void update(Graphics g) {
    if (buffer == null) {
      Dimension d = this.getSize();
      buffer = createImage(d.width, d.height);
      bufferGraphics = buffer.getGraphics();
    }

    if (currentAction != null) {
      currentAction.paint(bufferGraphics);
    }

    g.drawImage(buffer, 0, 0, this);
  }
    
  public void paint(Graphics g) {
    update(g);
  }

  public void setAction(String action) {
    currentAction = (Action)actionTable.get(action);
  }

  Point mousePos = null;  
  
  public void run() {
    while (true) {
      if (mousePos == null) {
        this.setAction("lost");
      } else {
        Point p0 = mousePos;
        Point p1 = this.getLocation();
        int step = 10;

        int dx = p0.x > p1.x ? step : -step;
        int dy = p0.y > p1.y ? step : -step;

        if (Math.abs(p0.x - p1.x) < 8) {
          dx = p0.x - p1.x;
        }

        this.setAction((p0.x <= p1.x) ? "left" : "right");

        if (Math.abs(p0.x - p1.x) < 8 && Math.abs(p0.y - p1.y) < 8) {
          this.setAction("mattari");
          dx = 4;
          dy = 4;
        }
        this.setLocation(p1.x + dx, p1.y + dy);
      }

      this.play();
      this.repaint();

      try {
        Thread.sleep(250);
      } catch (InterruptedException e) {}
    }
  }

  public MouseListener createMouseListener() {
    return new MouseAdapter() {
      public void mouseExited(MouseEvent e) {
        mousePos = null;
      }
    };
  }

  public MouseMotionListener createMouseMotionListener() {
    return new MouseMotionAdapter() {
      public void mouseMoved(MouseEvent e) {
        mousePos = e.getPoint();
      }
    };
  }
}

関連リンク

yumi-ii/shiraneyo AAアプリ研究室, Copyright (c) MURONO 2001-2002, 2004-2005, murono@mtc.biglobe.ne.jp