広告
広告
↓発売日:2017年04月04日↓
Java 第2版 入門編 ゼロからはじめるプログラミング (プログラミング学習シリーズ) 新品価格 |
今回は自機バーの表示・移動、ボールとの当たり判定をつけるトコロまでをやっていきたいと思います。
キー入力処理に関する事などはこちらの前半部分にて解説しておりますので参考にして頂ければと思います。
今回からクラス分けをしていきますが、あまりよろしくない設計も含まれておりますのであくまでもJAVAの文法の確認程度にと思ってください。
先によく使う定数に名前を付けてクラスとして分けてしまいます。
class Define{ private Define(){} /*ボールの半径*/ public static final int BALL_R = 5; /*ボールのスピード*/ public static final int BALL_SPEED = 5; /*自機バーのスピード*/ public static final int BAR_SPEED = 5; /*自機バーの横幅・縦幅*/ public static final int BAR_WIDTH = 50; public static final int BAR_HEIGHT = 10; /*フィールドの横幅・縦幅*/ public static final int FIELD_WIDTH = 300; public static final int FIELD_HEIGHT = 480; /*ボールと自機バーの初期位置*/ public static final int BALL_FIRST_X = BAR_WIDTH / 2; public static final int BALL_FIRST_Y = FIELD_HEIGHT - (BAR_HEIGHT + BALL_R); public static final int BAR_FIRST_X = 0; public static final int BAR_FIRST_Y = FIELD_HEIGHT - BAR_HEIGHT; /*表示位置調整*/ public static final int X_POSI = 170; }
定数用のクラスですが、まずコンストラクタに
private Define(){}
「private」をつける事でインスタンス化自体を防ぐ事ができます。
そして「final」をつける事で値の変更を禁止して「public static」にする事によって「Define.BALL_R」などのように全てのクラスでそのまま使えるようにします。
最後の「X_POSI」はフィールドの表示位置を調整する為のものです。
最初は画面の左端にフィールドを作り、表示する時だけこの「X_POSI」でずらして画面の中心などに表示するようにします。
次に共通部分である、「x,y」座標、それぞれのスピード、大きさなどをまとめたベースとなるクラスを作ります。
abstract class Base{ protected int width,height; protected int x,y; protected int x_speed; protected int y_speed; Base(){ width = 0; height = 0; x = 0; y = 0; x_speed = 0; y_speed = 0; } int get_width(){ return width; } int get_height(){ return height; } void set_x(int x){ this.x = x; } void set_y(int y){ this.y = y; } int get_x(){ return x; } int get_y(){ return y; } abstract void init(); abstract void move(); abstract void draw(GraphicsContext gc); }
継承される前提という事で「abstarct」をつけて抽象クラスに、サブクラスでそのまま使うので「protected」で変数を用意、おそらく必要であろうメソッドとして初期化「init()」・移動「move()」・描画「draw()」を用意しました。
あとはオブジェクト指向において、変数に直接アクセスするのは基本的に避けた方が良いので間接的に値を確認、設定する「get~」・「set~」いわゆるゲッター、セッターメソッドをそれぞれの変数に対して用意します。
これでベースとなるクラスは完成です。
では自機バー、ボール、フィールドなどの各要素を作っていきます。
まずはフィールドです。
class Field extends Base{ Field(){ width = Define.FIELD_WIDTH; height = Define.FIELD_HEIGHT; } void init(){} void move(){} void draw(GraphicsContext gc){ gc.setStroke(Color.BLACK); gc.strokeRect(Define.X_POSI + 0,0,width,height); } }
さきほど作ったベースとなるクラス「Base」を継承します。
これで「Base」にある変数、メソッドを利用できるようになります。
ここでは横幅、縦幅を表す「width,height」と2つの変数を使っております。
ぶっちゃけあまり効率の良い継承とは言い難い部分もありますがそこは入門用プログラムという事で目をつむってください。
コンストラクタです。
Field(){ width = Define.FIELD_WIDTH; height = Define.FIELD_HEIGHT; }
冒頭に作った定数を使って初期化します。
縦幅「480」・横幅「300」のフィールドになります。
そしてスーパークラス「Base」で用意した「abstract」なメソッド
void init(){} void move(){} void draw(GraphicsContext gc){ }
をオーバーライドします。
「abstract」なので中身があろうがなかろうが実装しなければなりません。
描画部分の「draw()」は最低限のプログラムで「BreakoutMain」に用意した「GraphicsContext gc」に描いていくようなカタチになるので引数として受け取りこれに描いていきます。
void draw(GraphicsContext gc){ gc.setStroke(Color.BLACK); gc.strokeRect(Define.X_POSI + 0,0,width,height); }
「strokeRect」は塗りつぶしのない枠だけの四角を描きます。
「setStroke」でその「枠」の色を変えます。
続いてボールです。
class Ball extends Base{ private int r; Ball(){ r = Define.BALL_R; x = Define.BALL_FIRST_X; y = Define.BALL_FIRST_Y; x_speed = Define.BALL_SPEED; y_speed = Define.BALL_SPEED; } void init(){} int get_r(){ return r; } void ch_x_speed(){ x_speed *= -1; } void ch_y_speed(){ y_speed *= -1; } void move(){ x += x_speed; y += y_speed; } void draw(GraphicsContext gc){ gc.setStroke(Color.BLACK); gc.strokeOval(Define.X_POSI + x - r,y - r, r * 2,r * 2); } }
同じく「Base」を継承します。
そして新しくボールの半径を表す「private int r」を用意します。
継承などで他のクラスで使われる予定のない変数は「private」をつけて他からのアクセスを禁止します。
あとはボールの方向を変える部分、ボールを動かす部分、ボールを描画する部分を用意します。
広告
そして自機バーです。
class Bar extends Base{ private Field field; private Key key; Bar(Field field,Key key){ this.field = field; this.key = key; width = Define.BAR_WIDTH; height = Define.BAR_HEIGHT; x = Define.BAR_FIRST_X; y = Define.BAR_FIRST_Y; x_speed = Define.BAR_SPEED; } void init(){} void move(){ if(key.get_right() > 0 && (x + width) < field.get_width()){ x += x_speed; } if(key.get_left() > 0 && x > 0){ x -= x_speed; } } void draw(GraphicsContext gc){ gc.setFill(Color.RED); gc.fillRect(Define.X_POSI + x,y,width,height); } }
自機バーはキー入力を受け付けて移動する部分と、移動できる範囲を決めるのにさきほど作ったフィールドの大きさなどの情報が必要なので引数として「Key」と「Field」のオブジェクトを受け取ります。
こうすればこの「class Bar」内でキー入力を受け付けたり、フィールドの情報を得る事ができます。
void move(){ if(key.get_right() > 0 && (x + width) < field.get_width()){ x += x_speed; } if(key.get_left() > 0 && x > 0){ x -= x_speed; } }
移動部分において「key.get_right()」などで入力情報を受け取り、「field.get_width()」などでフィールドの横幅の情報を得て移動を制限しております。
あとは描画部分「draw()」で「fillRect()」というメソッドを使って塗りつぶしの四角を描いております。
当たり判定をまとめたクラス「CollisionDetection」です。
class CollisionDetection{ private BreakoutThread breakoutthread; private Ball ball; private Bar bar; private Field field; CollisionDetection(BreakoutThread breakoutthread,Ball ball,Bar bar,Field field){ this.breakoutthread = breakoutthread; this.ball = ball; this.bar = bar; this.field = field; } void move(){ if(ball.get_x() >= field.get_width()){ ball.set_x(field.get_width() - ball.get_r()); ball.ch_x_speed(); } if(ball.get_x() <= 0){ ball.set_x(ball.get_r()); ball.ch_x_speed(); } if(ball.get_y() <= 0){ ball.set_y(ball.get_r()); ball.ch_y_speed(); } if(ball.get_y() >= field.get_height()){ breakoutthread.set_game_state(10); } if (ball.get_x() > bar.get_x() && ball.get_x() < (bar.get_x() + bar.get_width()) && ball.get_y() >= bar.get_y()){ ball.set_y(ball.get_y() - ball.get_r()); ball.ch_y_speed(); } } }
当たり判定は「ボール、自機バー、フィールド」全ての座標、大きさなどの情報が必要になるので、同じように引数として「Ball,Bar,Field」全ての情報を受け取ります。
「BreakoutThread」と大元のオブジェクトを受け取っているのは当たり判定後もしゲームオーバーの状態などになった時にその「BreakoutThread」内で場面を切り替える為です。
こちらについては後ほど説明いたします。
パッと見ややこしそうですが、フィールドとボールの当たり判定部分は前回やった画面いっぱいの当たり判定の範囲をフィールドの幅に合わせただけなので、ほぼ変わりはないです。
if(ball.get_y() >= field.get_height()){ breakoutthread.set_game_state(10); }
こちらが底辺の判定時、底辺を超えてゲームオーバーの時に「BreakoutThread」内に用意した場面を切り替える為の変数「game_state」の値をこちらのメソッドで操作しているトコロです。
そしてボールと自機バーとの当たり判定ですね。
何やら長ったらしい当たり判定ですが要は
「ボールのx座標が自機バーの左端から右端の間にいる時に、ボールのy座標が自機バーのy座標を超えた場合」
みたいな当たり判定になります。
if (ball.get_x() > bar.get_x() && ball.get_x() < (bar.get_x() + bar.get_width())
この部分が「ボールのx座標が自機バーの左端から右端の間にいる時」で
ball.get_y() >= bar.get_y()
この部分が「ボールのy座標が自機バーのy座標を超えた場合」になりますね!
あらためて何やら長ったらしくて申し訳ないです。
とてもシンプルな当たり判定なので正確さは欠きますが、なかなか反応は良いのでまあこちらでお許しください。
これでなんとなくの各クラスがそろったので大元の「BreakoutThread」でインスタンス化、適切に設置していきます。
インスタンス化はただ「new」するだけなので注意も何もないですが、さきほど説明したようにそれぞれのオブジェクトを渡しあう場合、インスタンス化する順序を間違えるとその渡したオブジェクトがまだ存在していない事態が発生しえるのでそのへんに少し注意が必要です。
「BreakoutThread」のコンストラクタです。
BreakoutThread(GraphicsContext gc,Key key){ this.gc = gc; this.key = key; Font theFont = Font.font("Serif",FontWeight.BOLD,20); gc.setFont(theFont); /*引数のないものからインスタンス化していく*/ ball = new Ball(); field = new Field(); gameover = new GameOver(); bar = new Bar(field,key); cd = new CollisionDetection(this,ball,bar,field); game_state = 0; }
コメントのように引数のないものからインスタンス化していくのが良いです。
あと言いそびれましたが
Font theFont = Font.font("Serif",FontWeight.BOLD,20); gc.setFont(theFont);
こちらはフォントの大きさ、タイプを変えるものです。
あと「class GameOver」はただ文字で「GAME OVER」と表示するだけのものになります。
ではそれぞれのオブジェクトを適切に設置していきます。
まず今回からゲームオーバーの処理なども入るので簡単にゲームループ内を場面分けします。
@Override public void handle(long time){ gc.clearRect(0,0,640,480); key.calc_key_count(); switch(game_state){ case 0: /*オープニング・初期化など*/ break; case 5: /*メインのゲームループ*/ break; case 10: /*エンディングなど*/ break; default: break; } }
場面を分ける為の変数を適当に用意して「switch」文によって場面を分けていきます。
今回の場合は「int game_state」という変数ですね!
この値を切り替える事によってコメントのように簡単に場面を分ける事ができます。
もちろんもっと細かく値を設定して場面を分ける事もできますし、このブロック崩しのみならずいろいろな場面で使えるテクニックなのでぜひ使ってください。
switch(game_state){ case 0: game_state = 5; break; case 5: ball.move(); bar.move(); cd.move(); ball.draw(gc); bar.draw(gc); field.draw(gc); break; case 10: bar.draw(gc); field.draw(gc); gameover.draw(gc); break; default: break; }
とりあえずはいきなりゲームが始まってゲームオーバー時に場面が切り替わりゲームオーバーを表示するようにそれぞれを設置しました。
描画の「draw()」には引数として「GraphicsContext gc」を渡しております。
「move()」で動かして「draw()」でその結果を表示しているだけなので簡単ですね!
ボール、自機バー、フィールドを表示しました!
ここまでの中間ソースになります。
それでは次回はブロックを表示したいと思います。
広告
↓発売日:2017年04月18日↓
Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで 新品価格 |
↓発売日:2016年06月25日↓
新品価格 |
↓発売日:2016年08月31日↓
新品価格 |
↓発売日:2018年11月21日↓
新品価格 |
↓発売日:2016年12月15日↓
新品価格 |
↓発売日:2016年09月28日↓
新品価格 |