Processing プログラミング

ブロック崩しを作ろう!part 3 〜反射処理〜

はじめに

目的

初心者向けProcessing講座の総まとめ、実践編ということで、ブロック崩しを作成しようと思います!
初心者向け講座の一覧は、以下のプログラミングコンテンツから確認してみてください!

プログラミングコンテンツトップページ

実際のプログラムはどのような感じかを掴んでいただいて、自分でも何かしら作れるようになっていただけると幸いです!
コードを一つ一つ意図なども説明するので、考え方なども参考にしてみてください!
※時短したい方は各記事最後のコードを丸コピでもOKです。

最終成果物

最終成果物として、以下のようなブロック崩しが初心者でも作れます!

プログラムを1行ずつ解説するので4partに分かれますが、誰でも理解できます!

前回のpart 1,2もぜひご参照ください!

ブロック崩しを作ろう!part1 〜図形描写〜

2024/12/15  

ブロック崩しを作成しよう!プログラミング講座の最終章part1!
今回は図形描写まで!
すべてのコードに説明があるからわかりやすい!

ブロック崩し解説part2
ブロック崩しを作ろう!part2 〜ブロック・弾の移動〜

2024/12/22  

はじめに 目的 初心者向けProcessing講座の総まとめ、実践編ということで、ブロック崩しを作成しようと思います!初心者向け講座の一覧は、以下のプログラミングコンテンツから確認してみてください! ...

今回やること

part 1:図形の描写
part 2:ブロック、弾の移動
part 3:反射処理
part 4:シーン遷移、各種調整

管理者
管理者

今回はpart 3です!
メインと言える反射処理を行います!
長いですが頑張りましょう!!

今回使う知識

各解説のリンクを貼っているので、詳細を知りたい方は解説記事もご覧ください!

ブロック崩し制作 part 3

STEP 0 ファイルを開く

前回作ったファイルを開いてください
ファイル > 開く (最近開いたファイルでもOK)

新規ファイル作成

STEP 1 壁との反射判定

壁反射の考え方

はじめに、壁とはProcessingの画面のウインドウの端のことです。

壁の定義の確認

右、左、上側の壁に当たると弾は反射し、下側は反射せずに通過させるようにしたいですね。
壁に当たると跳ね返るようにするためには、ざっくりと以下のように処理をする必要があります。

  • 右、左、上の壁に当たったか判定をする
  • 反射する壁に当たったのであれば、反射の処理をする

なので、まず壁に当たったたかを判定するところから始めましょう!

壁に当たっているという状況

まず、下記の3つの状況を見て、壁に当たっている状況とは、どのような時かを考えてみましょう。

左側は当たっていない。真ん中は接している。右側は当たっている。
というのがわかると思います。

厳密には真ん中の接している時に、壁に当たっている。と判定をすれば良いのですが、それだけだと上手くいきません
なぜなら、弾は一定の幅で移動しているため、壁と接する状況が100%再現されることは少ないからです。

一定の歩幅で歩いている時に、たまたま足が横断歩道の線とちょうど接する状態になるイメージしてもらうとわかりやすいです。

なので、壁に当たったと判断するためには、真ん中の画像のちょうど接する時だけでなく、右側の画像の多少めり込んだ場合も合わせて考える必要があります。

数値的に壁に当たったと判定する

コンピュータは数値的にしか判断できないので、壁に当たっている状況を数値的に考えてみましょう!

壁に当たっていると判断するために使う情報は、弾の中心半径と、壁の内側の座標です。
これらをどう使えばいいのか、下の画像を見ながら少し考えてみましょう!

壁と弾が衝突しているケース

解答を述べると、
壁の内側と弾の中心との距離が、弾の半径以下であれば、壁に当たったと判断できます。

この考えが理解できたら、あとはコーディングです!

壁に当たっていると判断するコーディング

左、右、上の壁に対して同じような処理を行うので、関数にして処理しましょう。
関数は、全体のコードの一番最後に追加しておきます。

戻り値の型は、int
関数名は、FlgByHitWall
引数は、以下の計4つですべてint型にします。

  • 現在の弾の座標
  • 上限
  • 下限
  • 現在の方向フラグの値
FlgByHitWall関数

弾の位置が、
下限と上限の範囲内であれば、現在のフラグの値を返し、
上限を超えていると、-1
下限を超えていると、1を返します。

if文の外にもreturnを書いておかないと、実装時のエラーになってしまいます。
なので、冗長にはなりますが、if文の外にも現在のフラグの値を返す文を書いておきます。

STEP 2 壁との反射処理

STEP 1で、壁に当たったか判断する関数を作成したので、メイン処理に組み込みましょう!
座標を移動させる前に判断したいので、draw関数の始めの方に記載しましょう。

壁反射処理

上記のように書くと、左、右、上側の壁のみに当たった場合、反射するようになります。
一度入力して実行して確かめてみましょう!

また、なぜ上記のように引数を与えるのかを説明します!
その際、FlgByHitWall関数で定義した引数の順番を覚えておきましょう!(STEP 1)

x方向について

関数の内部的な処理では、
width - ball_radious より ball_pos_xが大きければ、-1を返し、
ball_radious より ball_pos_xが小さければ、1を返す動作をします。

width が画面の右端。0 が画面の左端を意味しているので、
width - ball_radiousは、画面の右端から、弾の半径分左。
ball_radiousは、画面の左端から、弾の半径分右を表します。

つまり、画面右端より右であれば-1を返し、
画面左端より左であれば1を返します。

弾の中心が画面の範囲内であれば、今のflgの値 (第四引数)
範囲外であれば内側に反射される。ような流れです。

y方向について

関数の内部的な処理では、
ball_pos_y より ball_pos_yが大きければ、-1を返し、
ball_radious より ball_pos_yが小さければ、1を返す動作をします。

ball_pos_y より、ball_pos_yが大きいと言う状況は起こり得ないので、
下側の反射に関しては発生しない。といった状況です。

0 が画面の上端を意味しているので、
ball_radiousは、画面の上端から、弾の半径分下を表します。

つまり、画面上端より上であれば1を返し、
画面下端より下は処理しません

弾の中心が画面の範囲内であれば、今のflgの値 (第四引数)
範囲外であれば内側に反射される。ような流れです。

STEP 3 操作ブロックとの反射判定

操作ブロックとの反射の考え方

操作ブロックとの反射は、基本的には壁反射と同じような考え方です。

ただし、壁は単方向(横か縦)で考えればよかったですが、
操作ブロックは移動するので、複数方向で考える必要があります。

具体的な条件は後述します!

反射処理の実装方針

前のSTEPで作成した FlgByHitWall関数を再利用したいと思います。
この関数は、上限を超えていれば-1下限を下回っていれば1を返し、その他は第四引数の値を返すものでした。

これをうまく利用して、
縦、横の条件が両方条件に合った時のみ処理をするようにします。

新たに関数は作成せず、if文の複数条件の考えを用います。

そして、具体的な条件は以下の2つですね。

  • 縦方向)操作ブロックの高さ上に弾が来た時
  • 横方向)操作ブロックが描写されている部分に弾が来た時
操作ブロックの反射するケース

では、実装していきましょう!

STEP 4 操作ブロックとの反射の実装

結論を言うと、以下のように実装すれば操作ブロックでの反射が実現できます。
入力して実際に動かしてみましょう!

操作ブロックとの反射の実装

横位置の判定

50行目は横位置の判定です。

block_pos_xは操作ブロックの左端の位置を表します。
ball_pos_xが操作ブロックの左端から、操作ブロックの右端までの間にあれば、0を返します。

操作ブロックの判定可視化

縦位置の判定

51行目は縦位置の判定です。

block_pos_yは操作ブロックの上端の位置を表します。
ball_pos_yが操作ブロックの上端から、操作ブロックの下端までの間にあれば、0を返します。

処理の詳細

横方向、縦方向の条件が両方とも0どちらも条件を満たす)の時に、弾を上側に返したいので、
それぞれの判定結果がどちらも0の時、縦方向のフラグを-1にする(上側に反射する)処理をしています。

STEP 5 障害物との反射判定

最後に障害物との反射処理を実装します!
障害物は円なので、少し工夫が必要です。

障害物と弾(円と円)の衝突判定

考えはシンプルです。
障害物の中心と、弾の中心の距離が、
障害物との半径と、弾の半径の合計より小さければ衝突したと判定します。

円と円の衝突判定可視化

障害物との衝突判定の関数

計算処理が必要になるので、関数で処理を分けましょう!

戻り値の型は、boolean
関数名は、IsObjRefrection
引数は、以下の5つです。

  • int型)弾の現在のx座標
  • int型)弾の現在のy座標
  • int型)弾の半径
  • int[]型)障害物の座標(x,y)
  • int型)障害物の半径

引数の数を減らして見やすくするために、障害物の座標は配列で受け取るようにしています。

IsObjRefrection関数
処理の詳細

円と円の中心間の距離は、三平方の定理より
縦の座標の差と、横の座標の差をそれぞれ2乗して足し、平方根をとって求められます。

今回は中心との距離と、半径の合計の距離を比較するだけなので、平方根はとらず、2乗だけした値の比較を行っています。

また、pow(a,n)で、aをn乗するという処理をしてくれます。
単純にa*aとしてもOKです!

STEP 6 障害物反射の実装

結論をいうと、以下のように実装することで、障害物との反射が実装できます。

障害物の描写の処理内に、青色部分を追加しました。

障害物との反射の実装

for文で障害物を一つ一つ処理しているので、そこに衝突判定のコーディングも一緒に入れてあげます。
新しくfor文を作成するのは冗長ですが、分けても問題はありません。

ひとまず、縦も横もそのまま返すようにx,yのフラグを両方反転するようにしています。

ここまで実装すると、
操作ブロック、壁、障害物の3つのどれかに当たると反射するようになったと思います!

まとめ

お疲れ様でした!
かなり長くなりましたが、これで反射処理は完成です!

このぐらいの実装をパパッとするためにはそれなりの経験は要りますが、始めは当然難しいものです...。
ただ、長いコードの内、全ての意味がわかっているのではないでしょうか?

次回はゲーム性を上げる処理を追加していきます!
次回の最終章の記事もぜひご覧ください!

管理者
管理者

Take It Easy!Take It Breezy!
本当にお疲れ様でした!

今回までのコード

/* TAKEのIT風万記 
ブロック崩し サンプルコード*/

//操作ブロック
int block_pos_x; //ブロックの横位置
int block_pos_y; //ブロックの縦位置
int block_width = 100; //幅
int block_height = 10; //高さ

//弾
int ball_pos_x; //弾の横位置
int ball_pos_y; //弾の縦位置
int ball_radious = 5; //弾の半径
int ball_speed = 4; //弾のデフォルトスピード
float[] speed = {ball_speed, ball_speed}; //実際の弾の速度用配列(ゲーム性のため)
int ball_move_x_flg = 1; //横方向フラグ:1で右方向、-1で左方向
int ball_move_y_flg = -1; //縦方向フラグ:1で下方向、-1で上方向

//障害物
int obs_num = 10;//障害物の数
int obs_radious = 20; //障害物の半径
int[][] obs_pos = new int[obs_num][2]; //障害物の位置 [障害物No][0:x、1:y]

void setup(){
  size(500,500);
  
  //操作ブロック初期位置
  block_pos_x = width/2;
  block_pos_y = height - block_height;
  
  //弾初期位置
  ball_pos_x = width/2;
  ball_pos_y = block_pos_y - block_height;
  
  //障害物初期位置
  for (int i = 0; i < obs_num; i++) {
    obs_pos[i][0] = int(random(0, width));
    obs_pos[i][1] = int(random(0, height * 7/10));
  }
  
}

void draw(){
  /* 反射処理 */
  //壁反射
  ball_move_x_flg = FlgByHitWall(ball_pos_x, width -ball_radious,ball_radious, ball_move_x_flg);
  ball_move_y_flg = FlgByHitWall(ball_pos_y, ball_pos_y ,ball_radious, ball_move_y_flg);
  
  //操作ブロックとの反射
  if(FlgByHitWall(ball_pos_x, block_pos_x + block_width, block_pos_x, 0) == 0 &&
  FlgByHitWall(ball_pos_y + ball_radious, block_pos_y + block_height, block_pos_y, 0) == 0){
    ball_move_y_flg = -1;
  }
  
  //座標更新
  block_pos_x = mouseX - block_width/2;
  
  //弾位置計算
  ball_pos_x += speed[0] * ball_move_x_flg;
  ball_pos_y += speed[1] * ball_move_y_flg;
  
  /* 描写系 */
  background(0);
  //操作ブロック
  rect(block_pos_x, block_pos_y, block_width, block_height);
  
  //弾
  ellipse(ball_pos_x, ball_pos_y, ball_radious*2, ball_radious*2);
  
  //障害物ブロック
  for(int i = 0; i < obs_num; i++){
    //反射判定
    if(IsObjRefrection(ball_pos_x,ball_pos_y,ball_radious,obs_pos[i],obs_radious)){
      ball_move_x_flg *= -1;
      ball_move_y_flg *= -1;
    }
    ellipse(obs_pos[i][0], obs_pos[i][1], obs_radious*2, obs_radious*2);
  }
}

//壁反射フラグを返す関数
int FlgByHitWall(int ball_pos, int upper, int lower, int currentFlg){
  if(lower <= ball_pos && ball_pos <= upper){
    return currentFlg;
  }else if(ball_pos < lower){
    return 1;
  }else if(upper < ball_pos){
    return -1;
  }
  return currentFlg;
}

//障害物との反射判定
boolean IsObjRefrection(int ball_pos_x, int ball_pos_y, int ball_rad, int[] obs_pos, int obj_rad) {
  return pow(obs_pos[0] - ball_pos_x, 2) + pow(obs_pos[1] -ball_pos_y, 2) < pow(obj_rad + ball_rad, 2);
}
  • この記事を書いた人
  • 最新記事
投稿主

TAKE

TAKEのIT風万記管理人のTAKEです!
20代前半。現役エンジニア
このブログは初学者でも気軽に読めるようなIT関連の情報や頑張る人を応援する色々なことを展開しています!

-Processing, プログラミング