■ プログラム
1: import java.applet.*; 2: import java.awt.*; 3: import java.awt.event.*; 4: 5: public class Caveman extends Applet implements Runnable, MouseListener, KeyListener{ 6: Thread t; 7: Image buffer; 8: Graphics bufferg; 9: 10: Font normal = new Font("SansSerif", Font.BOLD, 16); // フォントのオ ブジェクトを生成 11: Font small = new Font("Serif", Font.PLAIN, 12); 12: Font big = new Font("Serif", Font.BOLD, 36); 13: Font stress = new Font("SansSerif",Font.BOLD, 20); 14: 15: boolean start = false; // ゲームスタートしたかどうか 16: boolean col = false; // 衝突したかどうか 17: boolean over= false; // ゲームオーバーしたかどうか 18: Block b = new Block(); // ブロック生成 19: Jiki j[] = new Jiki[5]; // 自機を宣言 20: int score = 0; // 点数、及び開始からの時間 21: int top = 20; // 上下の幅 22: int bottom; 23: int margin = 5; // タイトル画面での枠幅 24: int x[] = { 20, 15, 10, 5, 0 }; // 各自機の初期座標 25: int y[] = { 40, 35, 40, 45, 50 }; 26: int highscore = 0; // ハイスコアを保存 27: int winWidth, winHeight; // ウインドウの幅と高さ 28: public void init() { 29: 30: setFont(normal); // フォントを設定 31: 32: // 自機を生成 33: for(int a = 0; a < 5; a++){ 34: j[a] = new Jiki(x[a],y[a]); 35: } 36: addMouseListener(this); 37: addKeyListener(this); 38: requestFocus(); 39: 40: // スレッドを開始 41: t = new Thread(this); 42: t.start(); 43: 44: // アプレットサイズの取得 45: Dimension d = getSize(); 46: winWidth = d.width; 47: winHeight = d.height; 48: bottom = winHeight - 75; 49: 50: // バッファを作成 51: buffer = createImage(winWidth, winHeight); 52: } 53: 54: public void run(){ 55: try{ 56: while(true) { 57: 58: // ゲーム中で、衝突してない状態 59: if(start & !col){ 60: 61: // 自機の現在の座標を記録 62: for(int a = 0; a < 4; a++){ 63: j[a].log_y = j[a].y; 64: } 65: 66: // 後続の自機に座標を伝える 67: for(int a = 0; a < 4; a++){ 68: j[a+1].y = j[a].log_y; 69: } 70: 71: j[0].move(); // 先頭の自機を動かす 72: 73: // ブロックを生成するかどうか 74: if(b.time > 5){ 75: b.newBlock = true; 76: b.time = 0; 77: } 78: // ブロックを生成する処理 79: if(b.newBlock){ 80: if(b.num == 255){ b.num = 0; } 81: b.x[b.num] = 190; 82: b.y[b.num] = b.topLimit + (int)(Math.random() * b.bottomLimit); 83: b.num++; 84: b.newBlock = false; 85: } 86: 87: b.time++; // ブロックを生成してからの時間を更新 88: score++; // スコアを更新 89: 90: // ブロックの移動 91: for(int a = 0; a < b.num; a++){ 92: b.x[a] -= 5; 93: } 94: 95: // 地面と天井を動かす処理 96: if(score % 10 == 0){ 97: if(score > 100 & score < 500 || score > 700 & score < 900){ 98: top++; 99: bottom--; 100: b.topLimit++; 101: b.bottomLimit--; 102: } 103: } 104: 105: 106: // 衝突を判定 107: if(b.num < 9){ // 最初に描画されたブロックが画面端に届いてない場合 108: for(int a = 0; a < b.num; a++){ 109: 110: // ブロックとの衝突を判定 111: if(( j[0].x+5 >= b.x[a] & j[0].x <= b.x[a] + b.width) 112: & (j[0].y+5 >= b.y[a] & j[0].y <= b.y[a] + b.height)){ 113: col = true; 114: } 115: 116: // 天井、地面との衝突を判定 117: else if( j[0].y <= top-1 || j[0].y + 5 >= bottom+1){ 118: col = true; 119: } 120: } 121: }else{ // 最初に描画されたブロックが画面端に届いた場合 122: for(int a = b.num-8; a < b.num; a++){ 123: 124: // ブロックとの衝突を判定 125: if(( j[0].x+5 >= b.x[a] & j[0].x <= b.x[a] + b.width) 126: & (j[0].y+5 >= b.y[a] & j[0].y <= b.y[a] + b.height)){ 127: col = true; 128: } 129: 130: // 天井、地面との衝突を判定 131: else if( j[0].y <= top-1 || j[0].y + 5 >= bottom+1){ 132: col = true; 133: } 134: } 135: } 136: } 137: 138: repaint(); // 再描画 139: Thread.sleep(100); // ウィンドウを更新する前に休止 140: } 141: } 142: catch(Exception e){ 143: } 144: } 145: 146: public void update(Graphics g){ 147: paint(g); 148: } 149: 150: public void paint(Graphics g){ 151: 152: // バッファのグラフィックコンテキストを取得 153: if(bufferg == null){ 154: bufferg = buffer.getGraphics(); 155: } 156: 157: // タイトル画面での描画 158: if(!start){ 159: 160: // 全体を塗りつぶす 161: bufferg.setColor(Color.black); 162: bufferg.fillRect(0, 0, winWidth, winHeight); 163: 164: // 枠内を描画 165: bufferg.setColor(Color.blue); 166: bufferg.fillRect(margin ,margin ,winWidth - (margin*2), winHeight - (margin*2)); 167: 168: // タイトル等の文字を描画 169: bufferg.setColor(Color.white); 170: bufferg.setFont(small); 171: bufferg.drawString("似非", 17, 40); 172: bufferg.setFont(big); 173: bufferg.drawString("Caveman", 20, 70); 174: 175: bufferg.setFont(normal); 176: bufferg.drawString("HISCORE: " + highscore, 40, 160); 177: bufferg.drawString("SCORE : " + score, 40, 130); 178: 179: bufferg.setFont(stress); 180: bufferg.drawString("Click to Start!", 40, 200); 181: 182: }else{ 183: 184: // ゲーム中の描画 185: if(!over){ 186: 187: // 全体を塗りつぶす 188: bufferg.setColor(Color.yellow); 189: bufferg.fillRect(0, 0, winWidth, winHeight); 190: 191: // 自機を描画 192: bufferg.setColor(Color.black); 193: for(int a = 0; a < 5; a++){ 194: bufferg.drawRect(j[a].x, j[a].y, 5, 5); 195: } 196: 197: // ブロックの描画 198: bufferg.setColor(Color.black); 199: if(b.num < 9){ 200: for(int a = 0; a < b.num; a++){ 201: bufferg.fillRect(b.x[a], b.y[a], b.width, b.height); 202: } 203: }else{ 204: for(int a = b.num-8; a < b.num; a++){ 205: bufferg.fillRect(b.x[a], b.y[a], b.width, b.height); 206: } 207: } 208: 209: // 天井と壁を描画 210: bufferg.setColor(Color.blue); 211: bufferg.fillRect(0, 0, 200, top); 212: bufferg.fillRect(0, bottom, winWidth, 200); 213: bufferg.setColor(Color.black); 214: bufferg.drawLine(0, top, winWidth, top); 215: bufferg.drawLine(0, bottom, winWidth, bottom); 216: 217: // スコアを描画 218: bufferg.setFont(normal); 219: bufferg.setColor(Color.white); 220: bufferg.drawString("SCORE: " + score, 50, 190); 221: bufferg.drawString("Click: UP" , 50, 210); 222: } 223: 224: // ゲームオーバー時の描画 225: if(col){ 226: 227: // ゲームオーバー時の各文字列を描画 228: bufferg.setFont(normal); 229: bufferg.setColor(Color.red); 230: bufferg.drawString("GAME OVER" , 50, 100); 231: highscore = Math.max(highscore,score); 232: bufferg.drawString("HIGHSCORE: " + highscore, 40, 140); 233: bufferg.setColor(Color.white); 234: bufferg.drawString("Push enter key", 30, 230); 235: 236: // ゲームオーバー 237: over = true; 238: } 239: } 240: // ウインドウを更新 241: g.drawImage(buffer, 0, 0, this); 242: 243: } 244: 245: public void mouseClicked(MouseEvent ev){ 246: 247: // ゲームスタート 248: if(!start){ 249: start = true; 250: score = 0; 251: } 252: } 253: 254: public void mouseEntered(MouseEvent ev){ 255: } 256: 257: public void mouseExited(MouseEvent ev){ 258: } 259: 260: public void mousePressed(MouseEvent ev){ 261: 262: // 自機を上昇させる 263: if(!over){ 264: for(int a = 0; a < 5; a++){ 265: j[a].up = true; 266: j[a].time = 1; 267: } 268: } 269: } 270: 271: public void mouseReleased(MouseEvent me){ 272: 273: // 自機を下降させる 274: if(!over){ 275: for(int a = 0; a < 5; a++){ 276: j[a].up = false; 277: j[a].time = 1; 278: } 279: } 280: } 281: 282: public void keyPressed(KeyEvent ke){ 283: } 284: 285: public void keyReleased(KeyEvent ke) { 286: 287: // 押したキーの値を取得 288: int key = ke.getKeyCode(); 289: 290: // ゲームオーバー時の処理 291: if(over){ 292: switch(key){ 293: case KeyEvent.VK_ENTER: 294: col = false; 295: over = false; 296: top = 20; bottom = 175; 297: 298: // 自機の再初期化 299: for(int a = 0; a < 5; a++){ 300: j[a].jikiInit(); 301: } 302: // ブロックの再初期化 303: b.blockInit(); 304: start = false; 305: break; 306: } 307: } 308: } 309: 310: public void keyTyped(KeyEvent ke) { 311: } 312: 313: // プレイヤーが操作する物体のクラス 314: class Jiki{ 315: 316: int x, y; // 現在の座標 317: int log_x, log_y; // 座標のログ 318: int time; // 上昇、下降してからの経過時間 319: int x_first, y_first; // 座標の初期値 320: float gravity; // 天井と壁への引力 321: boolean up; // マウスがクリックされてるかどうか 322: int vy_up, vy_down; // 上昇、下降時の速度 323: 324: Jiki(int x, int y){ // コンストラクタ 325: this.x = x; this.y = y; 326: time = 1; 327: gravity = 0.5f; 328: x_first = x; y_first = y; 329: log_x = 0; log_y = 0; 330: up = false; 331: vy_up = 0; vy_down = 0; 332: gravity = 0.5f; 333: } 334: 335: // マウスがクリックされてるかどうかで上昇、下降のどちらかを行う 336: void move(){ 337: if(!up){ 338: y += ((int)(time * time * gravity / 4)) + vy_up + 1; 339: vy_down = (int)(time * gravity); 340: }else if(up){ 341: y -= ((int)(time * time * gravity / 4)) - vy_down - 1; 342: vy_up = -(int)(time * gravity); 343: } 344: 345: // 上昇、下降してからの時間を更新 346: j[0].time++; 347: } 348: 349: // ゲームをリスタートする場合に再初期化する 350: void jikiInit(){ 351: x = x_first; y = y_first; 352: log_x = 0; log_y = 0; 353: time = 1; 354: up = false; 355: vy_up = 0; vy_down = 0; 356: } 357: } 358: 359: // 前面から押し寄せてくるブロックのクラス 360: class Block { 361: 362: int x[] = new int[256]; // ブロックの座標 363: int y[] = new int[256]; 364: int width, height; // ブロックの幅と高さ 365: int topLimit, bottomLimit; // ブロックが出現する高さの上限と下限 366: int time; // ブロックが出現する時間の間隔 367: int num; // 生成されたブロックの個数 368: boolean newBlock; // ブロックを新たに出現させるかどうか 369: 370: Block(){ // コンストラクタ 371: for(int a = 0; a < 256; a++){ 372: x[a] = 0; y[a] = 0; 373: } 374: width = 5; height = 30; 375: topLimit = 20; bottomLimit = 125; 376: time = 0; num = 0; 377: newBlock = false; 378: } 379: 380: // ゲームをリスタートする場合に再初期化する 381: public void blockInit(){ 382: for(int a = 0; a < num; a++){ 383: x[a] = 0; y[a] = 0; 384: } 385: topLimit = 20; bottomLimit = 125; 386: time = 0; num = 0; 387: newBlock = false; 388: } 389: } 390: }
■ 実行結果
1~3:各パッケージをインポートします。アプレットを作 成するので java.applet.* を、GUI を用いるので java.awt.* を、イベントを 処理するので java.awt.event.* をそれぞれインポートします。
6:スレッドを扱うために Thread クラスの変数を宣言し
ます。
スレッドを用いる場合、
7~8:ダブルバッファリングを用いるために Image 、
Graphics クラスの変数を宣言します。
通常、repaint() メソッドを実行するとウィンドウ全体を背景色で塗りつぶして
消去し、その後 paint() メソッドを実行してアプレットの出力を表示します。
しかしその方法だと、ウィンドウ上で描画が頻繁に行われるので「ちらつき」が
起こります。ダブルバッファリングを用いるとそのちらつきを防ぐことができま
す。
ダブルバッファリングでは、まずオフスクリーンというもう一つのウィンドウを
作成します。このウィンドウは画面には表示されません。そしてオフスクリーン
に対して全ての描画を行い、最後にそれをウィンドウにコピーします。
Image クラスはこのオフスクリーンを作成するため、Grahpics クラスは描画操
作を実行するために用います。
10~13:描画の際に用いるフォントのオブジェクトを生成 します。
15~27:各インスタンス変数を宣言、初期化します。
内部クラスとは、他のクラスやインタフェース、及びブロック内に宣言され たクラスのうち、static 宣言されてないクラスのことです。内部クラスの特徴 としては45~48:このアプレットのサイズを取得し、静的に用いる ためにそれぞれ変数に値を代入します。
51:createImage() メソッドを実行し、ダブルバッファリ ングに用いるバッファを作成します。
71:move() メソッドを用いて、先頭の自機を動かします。 move() メソッドは Jiki クラスで宣言されてるメソッドです。
74~77:ブロックを一定時間毎に生成する処理です。ここ では、ブロックを5ミリ秒毎に生成するので、条件式を b.time < 5 としていま す。
96~103:天井と地面を動かす処理です。ゲーム開始から一 定時間経過すると、天井と地面、及びブロックが生成される上限と下限を移動さ せます。
316~322:各インスタンス変数を宣言します。int 型の変 数 x_first、y_first はコンストラクタで初期化した際のx、y の値を保持して おく変数です。
324~334:Jiki クラスのコンストラクタを定義します。渡 された引数をそれぞれ X 座標、Y 座標の初期値とします。
336~347:move() メソッドを定義します。変数 up の状態 によって物体を上昇させる処理か下降させる処理のどちらかの処理をします。そ の後、上昇、下降してからの時間を更新します。それぞれ + 1、- 1 としている のはマウスを連続でクリックしたときに物体が地面と平行に移動するのを防ぐ為 です。
350~356:jikiInit() メソッドを定義します。このメソッ ドは、ゲームオーバー時からタイトル画面に戻る際に各変数を初期化する処理を します。
381~388:blockInit() メソッドを定義します。このメソッ ドは、ゲームオーバー時からタイトル画面に戻る際に各変数を初期化する処理を します。