12/15 アプレットプログラミング


質問と回答

Q.
BallGameを動かしてみたが,キーボード入力を受け付けない.
A.
X window systemでは,そのウィンドウ上にマウスカーソルを移動させ ないと,そのウィンドウへのキーボード入力とはみなされません.
Q.
 > gimpでボールのイメージを作り、
 > ball.gifという名前で保存しようとしたら、
 > GIF:Sorry,can't save RGB images as Gifs - convert to INDEXED or GRAY first.
 > そして、
 > Save failed:ball.gif
 > と表示されて、保存ができません。
 > どうすればよいのですか?

A.
前回の授業で,実演したのですが,マウスの右ボタンを押して,Image -> Indexedを選んでから問いに答えて色数を減らしてから save して下さい.
Q.
 > また、
 > ”ボールのイメージはgimp等で作っても良いし,
 >                  どこかから持ってきても良い.” 
 > と、書いてありましたが、
 > ”どこかから持ってくる”というのは、
 > 具体的にはどういったことですか?
A.
ネットワーク上でフリーの画材ページを探して持ってくるということを想定 しています.
Q.
自宅のPCで,JPEG形式の画像ファイルを drawImageで表示できたが,学校の マシンに持ってきたら,色が変になってしまった.
A.
ファイルからのイメージのロードと描画で触れ ましたが,現在の Solaris用 JDK には,16 bitカラーのXサーバで画像ファイ ルから持ってきたイメージを表示させると色が化けるという致命的なバグがあ ります.画像の品質は落ちますが,
convert test.jpg test.gif
gif16bit test.gif > test1.gif
のようにして,gifに変換してからgif16bitで化けないgifファイルに変換して 下さい.
Q.
透過型(transparent)のgifファイルを作ったのに gif16bit を通したら 四角い枠が出るようになってしまった.
A.
gif16bitのバグによるものですが,ちょっと簡単には直せません.
Q.
Not a game, but a excercise in CP1(Wed/tanaka) の N が画面にうまく表示されないのは 何とかならないのでしょうか。 どうでもいいことですが、ちょっと気になりました。
A.
(0,200)の座標から書き始めているのですが,ウィンドウのボーダーの 分だけ外にはみ出てしまうようですね.(10,200)位から書き始めればOKですが, このあたり,フォントの大きさによらずに右にもはみ出ないように表示させよ うとすると,結構めんどうになります.

前回の課題について

課題

下のプログラムは,テレビゲームの初期の傑作である「ブロック崩し(Break Out)」からブロックを除いたものである.このプログラムに以下の変更を加え なさい. 時間が余ったら,オプション課題として,「ゲームとして楽しめるようにプ ログラムを変更する」,「GUI部品を使って機能を追加する」という課題もやっ てみてください.

サンプルプログラムではゲームを遊んでいるわけではないことを示すために,

    g.drawString("Not a game, but a excercise in CP1(Wed/tanaka)",0,200);
という文字列を表示するようにしてあります.この表示は取り除かないで下さ い.
import java.awt.*;
import java.awt.event.*;

class BallGame extends Frame implements KeyListener{
    // ボールの座標,速度のx, y成分の宣言
  int ball_x=100,ball_y=100,ball_vx=16,ball_vy=12;
    // ボールの大きさ
  int ball_size=20;
    // 画面の幅,高さ
  int width=300, height=600;
    // バーの座標
  int bar_x=0, bar_y=550;
    // バーの速度
  int bar_vx=0;
    // バーの幅,高さ
  int bar_width=60,bar_height=10;
    // BallGameクラスのコンストラクタ
  public BallGame(){
      // 親クラスである Frameのコンストラクタを呼ぶ
    super();
      // ウィンドウのサイズを変更
    setSize(width,height);
      // ウィンドウを表示
    setVisible(true);
    addKeyListener(this);
  }
  public static void main(String[] args){
      // BallGameクラスのインスタンスを作成
    BallGame frame=new BallGame();
      // スレッドを獲得
    Thread t=Thread.currentThread();
    for(;;){
        // 0.1秒(100ミリ秒)待つ
      try { t.sleep(100); }
      catch(Exception e){}
        // BallGameクラスのインスタンス frame のtimeTickメソッドの呼び出し
      frame.timeTick();
    }
  }
  void timeTick(){
      // バーの移動
    bar_x=bar_x+bar_vx;
      // 左端から飛び出そうになったら左端に合わせる
    if(bar_x<0) bar_x=0;
      // 右端から飛び出そうになったら右端に合わせる
    else if(bar_x+bar_width >width) bar_x=width-bar_width;
      // ボールの移動.古い座標を保存
    int old_x=ball_x;
    int old_y=ball_y;
      // 速度に従って,次の座標を決定
    ball_x=ball_x+ball_vx;
    ball_y=ball_y+ball_vy;
      // 左端から飛び出そうになったら,反射させる
    if(ball_x<0){
      ball_x= -ball_x;
      ball_vx= -ball_vx;
    }
      // 右端から飛び出そうになったら,反射させる
    else if(ball_x >width-ball_size){
      ball_x=(width-ball_size)-(ball_x-(width-ball_size));
      ball_vx= -ball_vx;
    }
      // 上端から飛び出そうになったら,反射させる
    if(ball_y<0.0){
      ball_y= -ball_y;
      ball_vy= -ball_vy;
    }
      // バーのある線を通過しそうになったら,
    else if(ball_y >bar_y && old_y<=bar_y){
        // バーのある線を横切るX座標を計算
      int x=old_x+(ball_x-old_x)*(bar_y-old_y)/(ball_y-old_y);
        // バーと接触している場合は反射させる.
      if(bar_x <= x && x<=bar_x+bar_width){
        ball_y=bar_y-(ball_y-bar_y);
        ball_vy= -ball_vy;
      }
    }
      // 下端から飛び出そうになったら,反射させる
    else if(ball_y > height){
      ball_vy=-ball_vy;
    }
      // 再表示
    repaint();
  }
  public void paint(Graphics g){
      // 描画色を黒にする.
    g.setColor(Color.black);
      // 全体を塗り潰す
    g.fillRect(0,0,width,height);
      // 描画色を白にする.
    g.setColor(Color.white);
      // 警告文字列を(0,200)の点を左下にして描く
    g.drawString("Not a game, but a excercise in CP1(Wed/tanaka)",0,200);
      // 描画色を赤にする.
    g.setColor(Color.red);
      // ボールを描く
    g.fillOval(ball_x,ball_y,ball_size,ball_size);
      // オフスクリーンイメージへの描画色を青にする.
    g.setColor(Color.blue);
      // バーを描く
    g.fillRect(bar_x,bar_y,bar_width,bar_height);
  }
    // キーが押された時にこのメソッドが呼ばれる.
  public void keyReleased(KeyEvent e){}
  public void keyTyped(KeyEvent e){}
  public void keyPressed(KeyEvent e){
    int key=e.getKeyChar();
      // 'h' のキーが押された時は,バーの移動速度を -10に
    if(key=='h'){
      bar_vx= -10;
    }
      // 'l' のキーが押された時は,バーの移動速度を 10に
    else if(key=='l'){
      bar_vx=10;
    }
      // 'j' のキーが押された時は,バーの移動速度を 0に
    else if(key=='j'){
      bar_vx=0;
    }
    else if(key=='q'){
      System.exit(0);
    }
  }
}

解答例

必要最小限の解答の例をあげる.
import java.awt.*;
import java.awt.event.*;

class BallGame extends Frame implements KeyListener{
    // ボールの座標,速度のx, y成分の宣言
  int ball_x=100,ball_y=100,ball_vx=16,ball_vy=12;
    // ボールの大きさ
  int ball_size=20;
    // 画面の幅,高さ
  int width=300, height=600;
    // バーの座標
  int bar_x=0, bar_y=550;
    // バーの速度
  int bar_vx=0;
    // バーの幅,高さ
  int bar_width=60,bar_height=10;
    // BallGameクラスのコンストラクタ
  Image image;
  public BallGame(){
      // 親クラスである Frameのコンストラクタを呼ぶ
    super();
      // ウィンドウのサイズを変更
    setSize(width,height);
      // ウィンドウを表示
    setVisible(true);
    image=Toolkit.getDefaultToolkit().getImage("ball.gif");
    addKeyListener(this);
  }
  public static void main(String[] args){
      // BallGameクラスのインスタンスを作成
    BallGame frame=new BallGame();
      // スレッドを獲得
    Thread t=Thread.currentThread();
    for(;;){
        // 0.1秒(100ミリ秒)待つ
      try { t.sleep(100); }
      catch(Exception e){}
        // BallGameクラスのインスタンス frame のtimeTickメソッドの呼び出し
      frame.timeTick();
    }
  }
  void timeTick(){
      // バーの移動
    bar_x=bar_x+bar_vx;
      // 左端から飛び出そうになったら左端に合わせる
    if(bar_x<0) bar_x=0;
      // 右端から飛び出そうになったら右端に合わせる
    else if(bar_x+bar_width >width) bar_x=width-bar_width;
      // ボールの移動.古い座標を保存
    int old_x=ball_x;
    int old_y=ball_y;
      // 速度に従って,次の座標を決定
    ball_x=ball_x+ball_vx;
    ball_y=ball_y+ball_vy;
      // 左端から飛び出そうになったら,反射させる
    if(ball_x<0){
      ball_x= -ball_x;
      ball_vx= -ball_vx;
    }
      // 右端から飛び出そうになったら,反射させる
    else if(ball_x >width-ball_size){
      ball_x=(width-ball_size)-(ball_x-(width-ball_size));
      ball_vx= -ball_vx;
    }
      // 上端から飛び出そうになったら,反射させる
    if(ball_y<0.0){
      ball_y= -ball_y;
      ball_vy= -ball_vy;
    }
      // バーのある線を通過しそうになったら,
    else if(ball_y >bar_y && old_y<=bar_y){
        // バーのある線を横切るX座標を計算
      int x=old_x+(ball_x-old_x)*(bar_y-old_y)/(ball_y-old_y);
        // バーと接触している場合は反射させる.
      if(bar_x <= x && x<=bar_x+bar_width){
        ball_y=bar_y-(ball_y-bar_y);
        ball_vy= -ball_vy;
      }
    }
      // 下端から飛び出そうになったら,反射させる
    else if(ball_y > height){
      ball_vy=-ball_vy;
    }
      // 再表示
    repaint();
  }
  Image offScreenImage;
  Graphics offScreenGraphics;
  public void update(Graphics g){
    if(offScreenImage==null){
      // 現在のフレームのサイズを求める
      Dimension d=getSize();
      // オフスクリーンイメージを作成
      offScreenImage=createImage(d.width,d.height); 
      // オフスクリーンイメージに描画するための Graphics オブジェクト
      offScreenGraphics=offScreenImage.getGraphics(); 
    }
    paint(offScreenGraphics); // 次の画面のイメージを作る.
    g.drawImage(offScreenImage,0,0,this); // イメージを本物のスクリーンに書き込む
  }
  public void paint(Graphics g){
      // 描画色を黒にする.
    g.setColor(Color.black);
      // 全体を塗り潰す
    g.fillRect(0,0,width,height);
      // 描画色を白にする.
    g.setColor(Color.white);
      // 警告文字列を(0,200)の点を左下にして描く
    g.drawString("Not a game, but a excercise in CP1(Wed/tanaka)",0,200);
      // 描画色を赤にする.
    g.setColor(Color.red);
      // ボールを描く
    g.drawImage(image,ball_x,ball_y,this);
      // オフスクリーンイメージへの描画色を青にする.
    g.setColor(Color.blue);
      // バーを描く
    g.fillRect(bar_x,bar_y,bar_width,bar_height);
  }
    // キーが押された時にこのメソッドが呼ばれる.
  public void keyReleased(KeyEvent e){}
  public void keyTyped(KeyEvent e){}
  public void keyPressed(KeyEvent e){
    int key=e.getKeyChar();
      // 'h' のキーが押された時は,バーの移動速度を -10に
    if(key=='h'){
      bar_vx= -10;
    }
      // 'l' のキーが押された時は,バーの移動速度を 10に
    else if(key=='l'){
      bar_vx=10;
    }
      // 'j' のキーが押された時は,バーの移動速度を 0に
    else if(key=='j'){
      bar_vx=0;
    }
    else if(key=='q'){
      System.exit(0);
    }
  }
}

解説

上の変更は, という最小限のものです.以下のような工夫をしてくれた人もいました. それぞれ,難易度に応じて加点します.
次に進む