JAVAプログラミング講座
宇宙のJAVAさん


くま先生の ノベルゲームをつくっちゃお(全4回)

第2回 読み込んだ文章を画面に表示しよう


 

エボ・クマ みなさん、こんにちは。

エボ・クマ では、さっそく前回の続きを作っていきましょう。

みゃあ めずらしくまともな講義のスタートだみゃ。

エボ・クマ 何を言うのです! 私はいつでも真面目です。

うし君 さすがは、くま先生!

エボ・クマ 前回の講義の最後にJAVAコンソールにテキストを表示するプログラムを書きましたね?
覚えてますか?

うし君 確認のための表示プログラムだも〜

エボ・クマ そうです。
ちゃんとテキストファイルが読み込まれているのがわかったので、もう必要ありません。
消してください。

  if ( s==null ) break;
    scene[i] = s;
  }

  for(int i=0; i<1000; i++) {
    if(scene[i] != null) {
      System.out.println(">" + scene[i] + "\n");
    }
  }

}

 

エボ・クマ さて今回は読み込んだ文章を画面に表示したいと思います。
1画面で文章を15行表示したいと思います。

エボ・クマ 画面に表示するための変数を用意します。 変数名は screen[] としましょう。
当然文字列を入れるので、String クラスです。 グローバル変数で初期宣言します。
この screen[] 文字列が、実際に画面に表示されるわけです。

// グローバル変数

String scene[];
String screen[];

エボ・クマ 次に scene[] 同様、init() で配列の初期化をします。
画面には15行表示する事にしたので、15個の配列で初期化します。

public void init() { // 初期処理

scene = new String[1000];
screen = new String[15];

エボ・クマ さて、では実際の文章表示プログラムを書きます。
第1部の STORY1 で言ったとおり、全ての作画に関するプログラムは paint() に書きます。
まず、画面全体を灰色で塗りつぶします。

public void paint(Graphics g) { // 作画処理

  g.setColor( Color.gray );
  g.fillRect( 0, 0, 480, 360 );

エボ・クマ setColor で色(灰色)を設定し、fillRect で塗りつぶされた四角を描きます。
今回のアプレットの全体サイズが 480x360 なので、これで全ゲーム画面が灰色になります。

みゃあ できたみゃ。 文章の表示はどうするみゃ?

エボ・クマ 文章の作画は drawString 命令を使います。
この命令で 変数 screen[] の中に入っている文字を、15行にわたり表示します。
for文で15回ループをさせ、上から順に描いていきます。

エボ・クマ 最初に、右と下にわずかにずらして黒色の文字を描きます。
次に白色の文字を描きます。
これで、文字に影がつけることが出来ます。
文字のサイズは 17 にします。

public void paint(Graphics g) { // 作画処理

  g.setColor( Color.gray );
  g.fillRect( 0, 0, 480, 360 );

  g.setFont(new Font( "Dialog", Font.BOLD, 17 ));
  for ( int i=0; i<15; i++ ) {
    if( screen[i]!=null ){
      g.setColor( Color.black );
      g.drawString( screen[i], 3+2, (i*22)+22+1);
      g.setColor( Color.white );
      g.drawString( screen[i], 3, (i*22)+22);
    }
  }


} // paint()

みゃあ if( screen[i]!=null ) って、何だみゃ??

エボ・クマ もしも screen[] の中に null が入っている行があったら、その行は表示しないようにしているのです。

うし君 ももも? 文字の表示のY座標がおかしいも。

g.drawString( screen[i], 3, (i*22)+22);

なんで、Y座標を22ドット増やすのかも?

エボ・クマ あ、それはですね。 画像などは表示指定が左上が基準になってますよね。

エボ・クマ ところが drawString 命令で作画する文字列は、文字列の左下が基準になっているんです。

文字列の作画

エボ・クマ こんな、感じです。 だからY座標を増やしてやらないと、1番上の行が表示されないのです。

うし君 なるほども〜 (0,0)に作画された画像は表示されるけど、(0,0)に作画された文字列は、画面からはみ出してしまって消えてしまうのか〜

うし君 でも screen[] は宣言と初期化をしただけだから、なにも入っていないも〜
これじゃ何も表示されないも。 どうするんだも〜

エボ・クマ まぁ、そう急がないで下さい。
もちろんこれから、screen[] に文章を入れる為の関数を作ります!

 

エボ・クマ テキストファイルの内容が入った scene[] から、画面に作画される文字列を screen[] に内容をコピーする関数を作ります。
関数名は SetText() にします。

うし君 早速作るも〜

エボ・クマ その前にい。
テキストファイルから読み込んだ文章は、普通15行以上ありますよね?

みゃあ まぁ、ノベルだから15行以上だみゃ。 最大で1000行だしみゃ。

エボ・クマ となると、ゲームでは15行ずつしか表示出来ないので、数回に分けて表示する必要があります。
そんな訳で、「現在 scene[] の何行目までを表示し終わったか?」という事を変数に保存しておく必要があります。
その変数を now_line という名前でグローバル宣言します。

// グローバル変数

String scene[];
String screen[];
int now_line;

エボ・クマ あとは init() で初期値を入れます。 初期値は 0 とします。

public void init() { // 初期処理

scene = new String[1000];
screen = new String[15];
now_line = 0;

エボ・クマ では、関数 SetText() を作っていきましょう。
引数でコピーする行番号を指定できるようにします。
つまり、 SetText(10) という命令で、scene[10] から scene[25] までの内容が、screen[0] から screen[15] にコピーされるわけです。
SetText関数の返り値として、読み終わったテキストの次の行を返すようにします。
つまり SetText(10) の返り値は、通常は 26 になるわけです。
そして、次の文章を表示したい時は、SetText(26) とすればよいのです。

SetText() 説明

エボ・クマ 図のような感じになります。
引数で指定した番号から15行分を scene[] から screen[] にコピーするわけです。

うし君 でも、全ての文章を読み(コピー)終わった場合は、返り値はどうなるも?

エボ・クマ いい所に気がつきましたね。 scene[] の全ての文章を読み終わったら、返り値は 9999 としましょう。
これはこのプログラムのルールになります。
では実際に 関数SetText() をプログラミングします。
場所はどこでもいいのですが、LoadScene() 関数の下に書くことにします。

public int SetText( int line ) {

}

エボ・クマ では関数の中身を書いていきましょう。
最初に screen[] の全てに null を入れて初期化します。

public int SetText( int line ) {

  for ( int i=0; i<15; i++ ) {
    screen[i] = null;
  }

エボ・クマ 次に scene[] から screen[] に文字列をコピーします。

public int SetText( int line ) {

  for ( int i=0; i<15; i++ ) {
    screen[i] = null;
  }

  for ( int i=0; i<15; i++ ) {
    screen[i] = scene[line+i];
  }

エボ・クマ しかしこれだと、scene[] の文章が無くなっても、screen[] にコピーしてしまいます。
文章が終わったらコピーするのを止めなければなりません。
「null が出てきたら文章の最後!」という事にしてもいいのですが、
より明示的に文章の最後を指定する為に、文章の最後に必ず「END」という文字の行を入れることにしましょう!
「END」
という文字列が出てきたら、scene[] の文章を全て読み終わったということなので、返り値として 9999 を返すことにします。

public int SetText( int line ) {

  for ( int i=0; i<15; i++ ) {
    screen[i] = null;
  }

  for ( int i=0; i<15; i++ ) {
    if (scene[line+i].equals("END")) return 9999;
    screen[i] = scene[line+i];
  }

うし君 あれ?

if ( scene[line+i]=="END" ) return 9999;

じゃ、ないのかも?

エボ・クマ うーむ、ちっと違うんですよ。 例えば int 型 の場合は、

int a;

if ( a==27 ) return 9999;

というのはOKです。
しかし String 型は特別で、if 文の条件式で == が使えないんですよ!

うし君 そうなのかも! なぜかも?

エボ・クマ まぁ、めんどくさい理由があるのです・・・
そのため String型(String クラス)には、文字列が同じかどうか比較するメソット(関数)を持っています。
それが equals() です。

if (scene[line+i].equals("END")) return 9999;

つまり、String型の scene[line+i] の内容が END という文字列だったら、return 9999 が実行されます。

うし君 なるほども。 そうなると読み込むテクストファイルの最後に END という行を書かないといけないも〜

エボ・クマ そうです。 テキストファイルに書く(文章以外の)特殊命令をこの講座では「タグ」と呼ばせていただきますが、END は「テキストファイルの最終行を表すタグ」になります。

エボ・クマ さて、もしも15行読み終わった時点で、そのすぐ下の行が「END」だったらどうなりますか?

みゃあ 一見、まだ文章が続くように考えられるが、事実上は文章は終わりだみゃ・・・

エボ・クマ そうです。 そこで15行読み終わったあとで、次の行が END かどうか調べます。
もし END だったら、全ての文章を読み終わったということで 9999 を返り値とします。

public int SetText( int line ) {

  for ( int i=0; i<15; i++ ) {
    screen[i] = null;
  }

  for ( int i=0; i<15; i++ ) {
    if (scene[line+i].equals("END")) return 9999;
    screen[i] = scene[line+i];
  }

  if (scene[line+15].equals("END")) return 9999;
  return line+15;
}

エボ・クマ そして、SetText() の最後に、return line+15 を書きます。
つまり、まだ文章が下に続く場合の返り値です。
15行コピーされたので、引数に15をたした数字が返り値となります。

うし君 これで、ゲームは動くのかも?

エボ・クマ SetText 関数は完成しましたが、それを呼びださなくてはなりません。
init() LoadScene("start.txt") の下に、SetText() を呼び出すプログラムを追加します。

public void init() { // 初期処理

  scene = new String[1000];
  screen = new String[15];
  now_line = 0;
  
  LoadScene("start.txt");
  now_line = SetText( 0 );
  repaint();


  // マウス処理アクションリスナー定義と登録
  addMouseListener(new MouseAction());
} // init()

エボ・クマ ついでにその後、再作画 repaint() をします。
LoadScene() でテキストファイルを読み込んだ直後なので、SetText(0) で scene[] の1行目(番号は0)から15行分、 screen[] に文章をコピーします。
そして、コピーした最後の行の次の行を返り値として、now_line に入れます。

エボ・クマ これで、プログラムがスタートすると init() LoadScene("start.txt") start.txt が読み込まれます。
次に、SetText(0) で読み込まれた文章の1行目(番号0)から15行目(番号14)までが、screen[] にコピーされます。
SetText(0) は返り値として、コピーされた最後の行の次の行番号(通常は 15)を返しますので、それを now_line に入れます。
で、作画 paint()screen[] の文章が画面に表示されるというわけです。

エボ・クマ ちょっと動かしてみてください。  

エボ・クマ 今回使用した文章は、「爆裂健 日記BEST」より抜粋された小説です。
このコーナー用に新たに小説を書こうと思ったのですが、時間が無いので間に合わせです。

エボ・クマ  これが「start.txt」です。
ちゃんと最終行に「END」が書き込まれています。

 

うし君 も〜! なんだかノベルゲームらしくなってきたも!

みゃあ でも、全部の文章見られないみゃ。
マウスをクリックしても、文章が進まないみゃ!

エボ・クマ では、マウスボタンを押すと次の文章に進むようにします。

マウスボタンを押すと呼ばれる関数は mousePressed(MouseEvent e) です。
ここにボタンが押された時の処理を書きます。

public void mousePressed(MouseEvent e) {
  // マウスダウンイベント

  if( now_line==9999 ) return;
  now_line = SetText( now_line );
  repaint();


} // mousePressed()

エボ・クマ まず、もしも now_line9999 だったら、これ以上文章が存在しないということなので、マウスボタンを押されても何もしません。
で、now_line 9999でない場合は、SetText( now_line ) で次の表示文章を scene[] から screen[] にコピーします。  そして SetText() の返り値を now_line に入れます。
で、再作画 sapaint() です!

うし君 なるほども。 これで now_line 9999 になるまで(文章が読み終わるまで)、マウスボタンを押すたび次々文章が表示されるも〜 

エボ・クマ あと、今回は start.txt しかテキストファイルを読まないので必要ないのですが、将来いろいろなテキストファイルを LoadScene() で読むことになります。
LoadScene() が呼び出されたら now_line0 に初期化しないとまずいので、それを書きます。

public void LoadScene( String filename ) {

  BufferedReader ds = null;
  String s = null;

  now_line = 0;

エボ・クマ これで、今回の講義は終了です。 動かしてみてください   

みゃあ マウスボタンを押すたびに、ちゃんと文章が進んでいくみゃ!

うし君 あれ? ネットスケープだと文字がはみ出しているも??

エボ・クマ そうなんです。 InternetExplorerNetscape では、文字を表示した時の文字間が違うのです。
Netscape の方が文字間が広いので、文字がはみ出してしまうのです。
こういうものは、統一してくれないと困るのですが・・・ 現在はブラウザによって文字間が違うのが現状です。

エボ・クマ まぁ、勘弁してください。
解決策としては、「文字列を1文字ずつ切り出し、等間隔で1文字ずつ作画いく!」という手があります。
これなら1行作画でないので、等間隔に文字が配置出来るとおもいます。

うし君 それだも! それをやるも〜

エボ・クマ めんどっちいので、パス!

エボ・クマ そのうち気が向いたら、やりましょう・・・

うし君 だめも〜な、講師だも〜

エボ・クマ くま ですから・・・

 

エボ・クマ 次回は、いろいろなタグを増やして、画像を表示したりフラグを処理したりしましょう!

エボ・クマ では、また次回お会いしましょう!

みゃあうし君 はーい!

 

テキストファイルのフォーマット(タグ一覧)


END  ・・・  テキストファイルの最後に必ず書く

 

 

- 第3話につづく -


宇宙のJAVAさん (C) BakuretuKen 2000

戻る