概要
この記事は中学高校生向けプログラミング教室の教材として作ったものを一部改変したものです。
今回の記事は、
・DXRuby:「当たり判定」を自分で作ってみよう - Qiita
● 当たり(衝突)判定の方法
A. 四角形の四隅の座標で判定する
B. 円の中心からの距離で判定する
C. 色で判定する
の内の
B. 円の中心からの距離で判定する
になります。
前の記事
・DXRuby:「当たり判定」を自分で作ってみよう A. 四角形の四隅の座標で判定する - Qiita
次の記事
・DXRuby:「当たり判定」を自分で作ってみよう C. 色で判定する
技術解説
- 使用ライブラリ
- 参考サイト
については、上記記事を参照してください。
ソースコード
ライセンス
ソースコード、本解説ともにパブリックドメイン
プログラム解説
B. 円の中心からの距離で判定する
→ 「ブロック崩し」追加課題 1b);衝突判定の自作(円) - noanoa 日々の日記
http://blog.livedoor.jp/noanoa07/archives/2046462.html
B-1a. ===
を使った準備:判定相手が単独(atari_en0.rb)
DXRubyのスプライトでは、===
を使うと、衝突の有/無でtrue
/fasle
を返します。
ball
(小さい円)が、block
(大きい円)と衝突しているかを===
で判定します。
衝突/非衝突 の結果をターミナル(コマンドプロンプト)にtrue
/false
で出力しつつ、block
の色も赤
/白
にします。
また、ウィンドウに衝突時はhit!
の文字列を表示します。
===
はデフォルトでは四角形で衝突を判定するので、 このままでは円同士が接触する前に衝突
と判定されています。
require 'dxruby'
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block = Sprite.new(200, 130, image1)
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
result = (ball === block)
p result
if result
Window.draw_font(0, 0, "hit!", font)
block.image = image2
else
block.image = image1
end
block.draw
end
B-1b. ===
を使った準備:衝突判定範囲を円に設定(atari_en1.rb)
スプライト.collision = [配列]
で衝突範囲の設定ができます。配列に3つの要素を指定すると円で判定するようになります。
スプライト.collision = [x, y, r]
;中心(x, y)、半径rの円
これで、円同士がぎりぎり接した時に衝突
と判定されるようになります。
→ DXRubyリファレンス:API INDEX;Sprite#collision=
require 'dxruby'
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block = Sprite.new(200, 130, image1)
ball.collision = [ 50, 50, 50]
block.collision = [100, 100, 100]
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
result = (ball === block)
p result
if result
Window.draw_font(0, 0, "hit!", font)
block.image = image2
else
block.image = image1
end
block.draw
end
B-1c. atari?
を自作:判定相手が単独(atari_en2.rb)
atari?(jibun, aite)
を作って、 衝突の有/無でtrue
/fasle
を返すようにします。
判定方法は、互いの円の中心からの距離を比較します。
- それぞれを円と考えて半径を決める(長方形の長辺と短辺の平均を半径とする)
- 互いの半径を足した長さを求める
- それぞれの中心の座標を求める
- 互いの中心からの距離を求める
-
中心から中心の距離
と半径の和
を比較する
# radius;半径
# center;中心
rad_x0 = jibun.image.width / 2 # jibunの横幅の 1/2
rad_y0 = jibun.image.height / 2 # jibunの縦幅の 1/2
(rad_x0 + rad_y0) / 2 # jibunの半径(長辺と短辺の平均で考える)
x0 = jibun.x # jibunの左上隅のx座標
y0 = jibun.y # jibunの左上隅のy座標
cen_x0 = x0 + rad_x0 # jibunの中心のx座標
cen_y0 = y0 + rad_y0 # jibunの中心のy座標
中心から中心の距離
の求め方は、直角三角形のピタゴラスの定理を使います。
距離**2 = 中心間のx座標の差**2 + 中心間のy座標の差**2
(Rubyでは、**2
は 2乗
の意味)
実際は距離の2乗
同志で比較していますが、これは平方根sqrt
より2乗**2
の計算の方が高速なためです。
それでは、自作したatari?
を使って、===
を使ったコードを書き換えてみます。
require 'dxruby'
def atari?(jibun, aite)
x0 = jibun.x
rad_x0 = jibun.image.width / 2
cen_x0 = x0 + rad_x0
y0 = jibun.y
rad_y0 = jibun.image.height / 2
cen_y0 = y0 + rad_y0
x1 = aite.x
rad_x1 = aite.image.width / 2
cen_x1 = x1 + rad_x1
y1 = aite.y
rad_y1 = aite.image.height / 2
cen_y1 = y1 + rad_y1
# 半径は長径と短径の1/2に設定
# 半径同士の和
cir_dis = (rad_x0 + rad_y0) / 2 + (rad_x1 + rad_y1) / 2
# 2乗で比較する
cir_dis_2 = cir_dis **2
dist_2 = (cen_x0 - cen_x1) ** 2 + (cen_y0 - cen_y1) ** 2
cir_dis_2 >= dist_2
end
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block = Sprite.new(200, 200, image1)
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
result = atari?(ball, block)
p result
if result
Window.draw_font(0, 0, "hit!", font)
block.image = image2
else
block.image = image1
end
block.draw
end
B-2a. ===
を使った準備:判定相手が配列(atari_en3.rb)
DXRubyのスプライトでは、===
は、相手がスプライトの配列でも、衝突の有/無でtrue
/fasle
を返します。
ball
が、スプライトの配列blocks
と衝突しているかを===
で判定します。
blocks
のいずれかと衝突していたら、ターミナル(コマンドプロンプト)にtrue
で出力し、それ以外はfalse
を出力します。
また、ウィンドウに衝突時はhit!
の文字列を表示します。
require 'dxruby'
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block1 = Sprite.new(110, 130, image1)
block2 = Sprite.new(390, 130, image1)
ball.collision = [ 50, 50, 50]
block1.collision = [100, 100, 100]
block2.collision = [100, 100, 100]
blocks = [block1, block2]
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
result = (ball === blocks)
p result
if result
Window.draw_font(0, 0, "hit!", font)
end
Sprite.draw(blocks)
end
B-2b. atari_array?
を自作:判定相手が配列(atari_en4.rb)
===
の代わりに、atari_array?(jibun, array)
を自作します。
判定相手の配列の要素を一つずつatari?
で判定していきます。
自作したatari_array?
を使って、コードを書き換えます。
require 'dxruby'
def atari?(jibun, aite)
x0 = jibun.x
rad_x0 = jibun.image.width / 2
cen_x0 = x0 + rad_x0
y0 = jibun.y
rad_y0 = jibun.image.height / 2
cen_y0 = y0 + rad_y0
x1 = aite.x
rad_x1 = aite.image.width / 2
cen_x1 = x1 + rad_x1
y1 = aite.y
rad_y1 = aite.image.height / 2
cen_y1 = y1 + rad_y1
# 半径は長径と短径の1/2に設定
# 半径同士の和
cir_dis = (rad_x0 + rad_y0) / 2 + (rad_x1 + rad_y1) / 2
# 2乗で比較する
cir_dis_2 = cir_dis **2
dist_2 = (cen_x0 - cen_x1) ** 2 + (cen_y0 - cen_y1) ** 2
cir_dis_2 >= dist_2
end
def atari_array?(jibun, array)
array.each do |a|
if atari?(jibun, a)
return true
end
end
false
end
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block1 = Sprite.new(110, 130, image1)
block2 = Sprite.new(390, 130, image1)
blocks = [block1, block2]
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
result = atari_array?(ball, blocks)
p result
if result
Window.draw_font(0, 0, "hit!", font)
end
Sprite.draw(blocks)
end
B-3a. check
を使った準備(atari_en5.rb)
DXRubyの===
や、自作したatari_array?
では、どの相手に衝突したかは分かりませんでした。
一方、DXRubyのスプライトではcheck
を使うと、衝突している相手すべてを配列に入れて返します。(衝突していない時は、何も入ってない配列を返す)
この配列をarray
とすると、0番目のarray.first
(array[0]
でも同じ)が最初に衝突した相手です。また、array.first
に何か入っていれば衝突している、空(nil)
ならば衝突していないという、当たり判定にも使えます。
→ DXRubyリファレンス:Spriteを使うためのチュートリアル;衝突したオブジェクトを取得する
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html
→ DXRubyリファレンス:API INDEX;Sprite;check
http://mirichi.github.io/dxruby-doc/api/Sprite_23check.html
プログラムでは、ball
がblocks
のどれに最初に衝突しているかを check.first
で判定します。
最初に衝突しているブロックをターミナル(コマンドプロンプト)に出力し、そのブロックを赤色にします。それ以外はnil
を出力します。
また、ウィンドウに衝突時はhit!
の文字列を表示します。
require 'dxruby'
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block1 = Sprite.new(110, 130, image1)
block2 = Sprite.new(390, 130, image1)
ball.collision = [ 50, 50, 50]
block1.collision = [100, 100, 100]
block2.collision = [100, 100, 100]
blocks = [block1, block2]
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
col = ball.check(blocks).first
p col
if col
Window.draw_font(0, 0, "hit!", font)
col.image = image2
Sprite.draw(blocks)
col.image = image1
else
Sprite.draw(blocks)
end
end
B-3b. atari_array
を自作(atari_en6.rb)
ぶつかった相手は最初のものだけ分かればよいので、DXRubyのcheck.first
相当のatari_array
を自作します。
つまり、atari_array
は、衝突していないとnil
を返し、衝突すると "ぶつかった最初のもの
" を返します。
方法は、判定相手の配列の要素を一つずつatari?
で判定していき、最初に衝突している要素を返します。
自作したatari_array
を使って、コードを書き換えます。
require 'dxruby'
def atari?(jibun, aite)
x0 = jibun.x
rad_x0 = jibun.image.width / 2
cen_x0 = x0 + rad_x0
y0 = jibun.y
rad_y0 = jibun.image.height / 2
cen_y0 = y0 + rad_y0
x1 = aite.x
rad_x1 = aite.image.width / 2
cen_x1 = x1 + rad_x1
y1 = aite.y
rad_y1 = aite.image.height / 2
cen_y1 = y1 + rad_y1
# 半径は長径と短径の1/2に設定
# 半径同士の和
cir_dis = (rad_x0 + rad_y0) / 2 + (rad_x1 + rad_y1) / 2
# 2乗で比較する
cir_dis_2 = cir_dis **2
dist_2 = (cen_x0 - cen_x1) ** 2 + (cen_y0 - cen_y1) ** 2
cir_dis_2 >= dist_2
end
def atari_array(jibun, array)
array.each do |a|
if atari?(jibun, a)
return a
end
end
nil
end
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block1 = Sprite.new(110, 130, image1)
block2 = Sprite.new(390, 130, image1)
blocks = [block1, block2]
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
col = atari_array(ball, blocks)
p col
if col
Window.draw_font(0, 0, "hit!", font)
col.image = image2
Sprite.draw(blocks)
col.image = image1
else
Sprite.draw(blocks)
end
end
B-4. 自作の当たり判定(円)をSpriteクラスのメソッド
にする(atari_en7.rb)
元の===
とcheck
は、Sprite
クラスのメソッド(命令)なので、書き方としては、ball.check(blocks)
のようになります。
自作のatari_array
も同じような書き方になるように、Sprite
クラスのメソッドにしてみます。
クラス
やメソッド
についての説明はここでは説明を省きますので、詳しくはRubyのテキストを見てください。
クラスメソッドを追加する方法は簡単で、以下のようにします。
class Sprite
def 追加したいメソッド
#追加したいメソッドの内容
end
end
Sprite
クラスにatari?
とatari_array
追加します。
ちなみに、jibun
(自分)に相当するものは、self
と書きます。
class Sprite
def atari?(aite)
x0 = self.x
rad_x0 = self.image.width / 2
cen_x0 = x0 + rad_x0
y0 = self.y
rad_y0 = self.image.height / 2
cen_y0 = y0 + rad_y0
x1 = aite.x
rad_x1 = aite.image.width / 2
cen_x1 = x1 + rad_x1
y1 = aite.y
rad_y1 = aite.image.height / 2
cen_y1 = y1 + rad_y1
# 半径は長径と短径の1/2に設定
# 半径同士の和
cir_dis = (rad_x0 + rad_y0) / 2 + (rad_x1 + rad_y1) / 2
# 2乗で比較する
cir_dis_2 = cir_dis **2
dist_2 = (cen_x0 - cen_x1) ** 2 + (cen_y0 - cen_y1) ** 2
cir_dis_2 >= dist_2
end
def atari_array(array)
array.each do |a|
if self.atari?(a)
return a
end
end
nil
end
end
これで、クラスメソッドになったので、今までのatari_array(ball, blocks)
という書き方ではなく、ball.atari_array(blocks)
というように書くことができるようになりました。
require 'dxruby'
class Sprite
def atari?(aite)
x0 = self.x
rad_x0 = self.image.width / 2
cen_x0 = x0 + rad_x0
y0 = self.y
rad_y0 = self.image.height / 2
cen_y0 = y0 + rad_y0
x1 = aite.x
rad_x1 = aite.image.width / 2
cen_x1 = x1 + rad_x1
y1 = aite.y
rad_y1 = aite.image.height / 2
cen_y1 = y1 + rad_y1
# 半径は長径と短径の1/2に設定
# 半径同士の和
cir_dis = (rad_x0 + rad_y0) / 2 + (rad_x1 + rad_y1) / 2
# 2乗で比較する
cir_dis_2 = cir_dis **2
dist_2 = (cen_x0 - cen_x1) ** 2 + (cen_y0 - cen_y1) ** 2
cir_dis_2 >= dist_2
end
def atari_array(array)
array.each do |a|
if self.atari?(a)
return a
end
end
nil
end
end
image0 = Image.new(100, 100).circle_fill( 50, 50, 50, C_WHITE)
image1 = Image.new(200, 200).circle_fill(100, 100, 100, C_WHITE)
image2 = Image.new(200, 200).circle_fill(100, 100, 100, C_RED)
ball = Sprite.new(300, 400, image0)
block1 = Sprite.new(110, 130, image1)
block2 = Sprite.new(390, 130, image1)
blocks = [block1, block2]
font = Font.new(32)
Window.loop do
ball.x = Input.mouse_pos_x
ball.y = Input.mouse_pos_y
ball.draw
col = ball.atari_array(blocks)
p col
if col
Window.draw_font(0, 0, "hit!", font)
col.image = image2
Sprite.draw(blocks)
col.image = image1
else
Sprite.draw(blocks)
end
end
B-5. 自作の当たり判定(円)を使ったブロック崩し(atari_en8.rb)
「ブロック崩し」のプログラムblock28.rb
のブロックを円
に変えたものを、自作のatari_array
に書き換えてみましょう。
円にしたブロック
とボール
の判定だけですが、元のcheck.first
を同じ動きをしているのがわかると思います。
require 'dxruby'
class Sprite
def atari?(aite)
x0 = self.x
rad_x0 = self.image.width / 2
cen_x0 = x0 + rad_x0
y0 = self.y
rad_y0 = self.image.height / 2
cen_y0 = y0 + rad_y0
x1 = aite.x
rad_x1 = aite.image.width / 2
cen_x1 = x1 + rad_x1
y1 = aite.y
rad_y1 = aite.image.height / 2
cen_y1 = y1 + rad_y1
# 半径は長径と短径の1/2に設定
# 半径同士の和
cir_dis = (rad_x0 + rad_y0) / 2 + (rad_x1 + rad_y1) / 2
# 2乗で比較する
cir_dis_2 = cir_dis **2
dist_2 = (cen_x0 - cen_x1) ** 2 + (cen_y0 - cen_y1) ** 2
cir_dis_2 >= dist_2
end
def atari_array(array)
array.each do |a|
if self.atari?(a)
return a
end
end
nil
end
end
img_bar = Image.new(100, 20, C_WHITE)
img_hwall = Image.new( 20, 480, C_BLUE)
img_vwall = Image.new(640, 20, C_BLUE)
img_ball = Image.new( 20, 20).circle_fill(10, 10, 10, C_RED)
img_block = Image.new(28, 28).circle_fill(14, 14, 14, C_GREEN) # ブロックを円に
img_block_y = Image.new(28, 28).circle_fill(14, 14, 14, C_YELLOW) # ブロックを円に
bar = Sprite.new( 0, 460, img_bar)
lwall = Sprite.new( 0, 0, img_hwall)
rwall = Sprite.new(620, 0, img_hwall)
twall = Sprite.new( 0, 0, img_vwall)
walls = [bar, lwall, rwall, twall]
ball = Sprite.new(300, 400, img_ball)
dx = 2
dy = -2
def move(sprite, speed_x, speed_y)
sprite.x += speed_x
sprite.y += speed_y
end
blocks = []
20.times do |x| # ブロックの組み方を少し変更
5.times do |y|
blocks << Sprite.new(21 + 30 * x, 21 + 30 * y, img_block)
end
end
Window.loop do
bar.x = Input.mouse_pos_x
Sprite.draw(walls)
move(ball, dx, 0)
if ball === walls
ball.x -= dx
dx = -dx
end
coll_x = ball.atari_array(blocks)
if coll_x
coll_x.image = img_block_y
coll_x.draw #一瞬色が変わって表示
coll_x.vanish #消える
ball.x -= dx
dx = -dx
end
move(ball, 0, dy)
if ball=== walls
ball. y -= dy
dy = -dy
end
coll_y = ball.atari_array(blocks)
if coll_y
coll_y.image = img_block_y
coll_y.draw #一瞬色が変わって表示
coll_y.vanish #消える
ball. y -= dy
dy = -dy
end
ball.draw
Sprite.draw(blocks)
end
参考
プログラミング初心者向け:DXRubyで 1ステップずつ作っていく「ブロック崩し」 - Qiita
で作った「ブロック崩し」のプログラムです。
require 'dxruby'
img_bar = Image.new(100, 20, C_WHITE)
img_hwall = Image.new( 20, 480, C_BLUE)
img_vwall = Image.new(640, 20, C_BLUE)
img_ball = Image.new( 20, 20).circle_fill(10, 10, 10, C_RED)
img_block = Image.new( 58, 18, C_GREEN)
img_block_y = Image.new( 58, 18, C_YELLOW)
bar = Sprite.new( 0, 460, img_bar)
lwall = Sprite.new( 0, 0, img_hwall)
rwall = Sprite.new(620, 0, img_hwall)
twall = Sprite.new( 0, 0, img_vwall)
walls = [bar, lwall, rwall, twall]
ball = Sprite.new(300, 400, img_ball)
dx = 2
dy = -2
def move(sprite, speed_x, speed_y)
sprite.x += speed_x
sprite.y += speed_y
end
blocks = []
10.times do |x|
5.times do |y|
blocks << Sprite.new(21 + 60 * x, 21 + 20 * y, img_block)
end
end
Window.loop do
bar.x = Input.mouse_pos_x
Sprite.draw(walls)
move(ball, dx, 0)
if ball === walls
ball.x -= dx
dx = -dx
end
coll_x = ball.check(blocks)
if coll_x[0]
coll_x[0].image = img_block_y
coll_x[0].draw #一瞬色が変わって表示
coll_x[0].vanish #消える
ball.x -= dx
dx = -dx
end
move(ball, 0, dy)
if ball === walls
ball. y -= dy
dy = -dy
end
coll_y = ball.check(blocks)
if coll_y[0]
coll_y[0].image = img_block_y
coll_y[0].draw #一瞬色が変わって表示
coll_y[0].vanish #消える
ball. y -= dy
dy = -dy
end
ball.draw
Sprite.draw(blocks)
end