LoginSignup
0
0

More than 3 years have passed since last update.

DXRuby:「当たり判定」を自分で作ってみよう A. 四角形の四隅の座標で判定する

Last updated at Posted at 2020-02-16

概要

この記事は中学高校生向けプログラミング教室の教材として作ったものを一部改変したものです。

今回の記事は、
DXRuby:「当たり判定」を自分で作ってみよう - Qiita

● 当たり(衝突)判定の方法
A. 四角形の四隅の座標で判定する
B. 円の中心からの距離で判定する
C. 色で判定する

の内の
A. 四角形の四隅の座標で判定する
になります。

atari_7b.png

次の記事
DXRuby:「当たり判定」を自分で作ってみよう B. 円の中心からの距離で判定する - Qiita

技術解説

  • 使用ライブラリ
  • 参考サイト については、上記記事を参照してください。

ソースコード

ライセンス

ソースコード、本解説ともにパブリックドメイン

プログラム解説

A. 四角形の四隅の座標で判定する

→ 「ブロック崩し」追加課題 1a);衝突判定の自作(四角) - noanoa 日々の日記
http://blog.livedoor.jp/noanoa07/archives/2046179.html

A-1a. ===を使った準備:判定相手が単独(atari_1.rb)

DXRubyのスプライトでは、===を使うと、衝突の有/無でtrue/fasleを返します。

ball(小さい正方形)が、block(大きい長方形)と衝突しているかを===で判定します。
衝突/非衝突 の結果をターミナル(コマンドプロンプト)にtrue/falseで出力しつつ、blockの色も/にします。
また、ウィンドウに衝突時はhit!の文字列を表示します。

atari_1.rb
require 'dxruby'

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 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 = (ball === block)
  p result

  if result
    Window.draw_font(0, 0, "hit!", font)
    block.image = image2
  else
    block.image = image1
  end

  block.draw
end

atari_1a.png

atari_1b.png

A-1b. atari?を自作:判定相手が単独(atari_2.rb)

atari?(jibun, aite)を作って、 衝突の有/無でtrue/fasleを返すようにします。

判定方法は、互いの四角形の四隅の座標を比較することにします。

  • 自分jibunx座標x0xx0、y座標はy0yy0

  • 相手aitex座標xxx、y座標はyyy

2つが重なっている条件は;

  • aitex座標の範囲xxx の中に、jibunx0 または xx0 があること

かつ

  • aitey座標の範囲yyy の中に、jibuny0 または yy0 があること

これをコーディングすると以下のようになります。

&&かつ||またはの意味)

(x <= x0  && x0  <= xx  ||
 x <= xx0 && xx0 <= xx)

 &&

(y <= y0  && y0  <= yy  ||
 y <= yy0 && yy0 <= yy)

また、スプライトの画像の高さを求めるには、

スプライト.image.width

高さスプライト.image.height

を使います。

→ DXRubyリファレンス:API INDEX;Sprite;image
http://mirichi.github.io/dxruby-doc/api/Sprite_23image.html

→ DXRubyリファレンス:API INDEX;Image;width,height
http://mirichi.github.io/dxruby-doc/api/Image.html

それでは、自作したatari?を使って、===を使ったコードを書き換えてみます。

atari_2.rb
require 'dxruby'

def atari?(jibun, aite)
  x0  = jibun.x
  xx0 = x0 + jibun.image.width

  y0  = jibun.y
  yy0 = y0 + jibun.image.height

  x   = aite.x
  xx  = x  + aite.image.width

  y   = aite.y
  yy  = y  + aite.image.height

  (x <= x0  && x0  <= xx ||
   x <= xx0 && xx0 <= xx) &&

  (y <= y0  && y0  <= yy ||
   y <= yy0 && yy0 <= yy)
end

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 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

atari_2a.png

atari_2b.png

A-2a. ===を使った準備:判定相手が配列(atari_3.rb)

DXRubyのスプライトでは、===は、相手がスプライトの配列でも、衝突の有/無でtrue/fasleを返します。

ballが、スプライトの配列blocksと衝突しているかを===で判定します。
blocksのいずれかと衝突していたら、ターミナル(コマンドプロンプト)にtrueで出力し、それ以外はfalseを出力します。
また、ウィンドウに衝突時はhit!の文字列を表示します。

atari_3.rb
require 'dxruby'

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 100, C_RED)

ball   = Sprite.new(300, 400, image0)

block1 = Sprite.new( 10, 200, image1)
block2 = Sprite.new(250, 200, 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 = (ball === blocks)
  p result

  if result
    Window.draw_font(0, 0, "hit!", font)
  end

  Sprite.draw(blocks)
end

atari_3a.png

atari_3b.png

atari_3c.png

atari_3d.png

A-2b. atari_array?を自作:判定相手が配列(atari_4.rb)

===の代わりに、atari_array?(jibun, array)を自作します。
判定相手の配列の要素を一つずつatari?で判定していきます。

自作したatari_array?を使って、コードを書き換えます。

atari_4.rb
require 'dxruby'

def atari?(jibun, aite)
  x0  = jibun.x
  xx0 = x0 + jibun.image.width

  y0  = jibun.y
  yy0 = y0 + jibun.image.height

  x   = aite.x
  xx  = x  + aite.image.width

  y   = aite.y
  yy  = y  + aite.image.height

  (x <= x0  && x0  <= xx ||
   x <= xx0 && xx0 <= xx) &&

  (y <= y0  && y0  <= yy ||
   y <= yy0 && yy0 <= yy)
end

def atari_array?(jibun, array)
  array.each do |a|
    if atari?(jibun, a)
      return true
    end
  end
  false
end

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 100, C_RED)

ball   = Sprite.new(300, 400, image0)

block1 = Sprite.new( 10, 200, image1)
block2 = Sprite.new(250, 200, 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

atari_4a.png

atari_4b.png

atari_4c.png

atari_4d.png

A-3a. checkを使った準備(atari_5.rb)

DXRubyの===や、自作したatari_array?では、どの相手に衝突したかは分かりませんでした。

一方、DXRubyのスプライトではcheckを使うと、衝突している相手すべてを配列に入れて返します。(衝突していない時は、何も入ってない配列を返す)

この配列をarrayとすると、0番目のarray.firstarray[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

プログラムでは、ballblocksのどれに最初に衝突しているかを check.firstで判定します。
最初に衝突しているブロックをターミナル(コマンドプロンプト)に出力し、そのブロックを赤色にします。それ以外はnilを出力します。
また、ウィンドウに衝突時はhit!の文字列を表示します。

atari_5.rb
require 'dxruby'

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 100, C_RED)

ball   = Sprite.new(300, 400, image0)

block1 = Sprite.new( 10, 200, image1)
block2 = Sprite.new(250, 200, 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.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

atari_5a.png

atari_5b.png

atari_5c.png

atari_5d.png

A-3b. atari_arrayを自作(atari_6.rb)

ぶつかった相手は最初のものだけ分かればよいので、DXRubyのcheck.first相当のatari_arrayを自作します。

つまり、atari_arrayは、衝突していないとnilを返し、衝突すると "ぶつかった最初のもの" を返します。

方法は、判定相手の配列の要素を一つずつatari?で判定していき、最初に衝突している要素を返します。

自作したatari_arrayを使って、コードを書き換えます。

atari_6.rb
require 'dxruby'

def atari?(jibun, aite)
  x0  = jibun.x
  xx0 = x0 + jibun.image.width

  y0  = jibun.y
  yy0 = y0 + jibun.image.height

  x   = aite.x
  xx  = x  + aite.image.width

  y   = aite.y
  yy  = y  + aite.image.height

  (x <= x0  && x0  <= xx ||
   x <= xx0 && xx0 <= xx) &&

  (y <= y0  && y0  <= yy ||
   y <= yy0 && yy0 <= yy)
end

def atari_array(jibun, array)
  array.each do |a|
    if atari?(jibun, a)
      return a
    end
  end
  nil
end

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 100, C_RED)

ball   = Sprite.new(300, 400, image0)

block1 = Sprite.new( 10, 200, image1)
block2 = Sprite.new(250, 200, 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

atari_6a.png

atari_6b.png

atari_6c.png

atari_6d.png""

A-4. 自作の当たり判定(四角)をSpriteクラスのメソッドにする(atari_7.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
    xx0 = x0 + self.image.width

    y0  = self.y
    yy0 = y0 + self.image.height

    x   = aite.x
    xx  = x  + aite.image.width

    y   = aite.y
    yy  = y  + aite.image.height

    (x <= x0  && x0  <= xx ||
     x <= xx0 && xx0 <= xx) &&

    (y <= y0  && y0  <= yy ||
     y <= yy0 && yy0 <= yy)
  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)というように書くことができるようになりました。

atari_7.rb
require 'dxruby'

class Sprite
  def atari?(aite)
    x0  = self.x
    xx0 = x0 + self.image.width

    y0  = self.y
    yy0 = y0 + self.image.height

    x   = aite.x
    xx  = x  + aite.image.width

    y   = aite.y
    yy  = y  + aite.image.height

    (x <= x0  && x0  <= xx ||
     x <= xx0 && xx0 <= xx) &&

    (y <= y0  && y0  <= yy ||
     y <= yy0 && yy0 <= yy)
  end

  def atari_array(array)
    array.each do |a|
      if self.atari?(a)
        return a
      end
    end
    nil
  end
end

image0 = Image.new( 50,  50, C_WHITE)
image1 = Image.new(200, 100, C_WHITE)
image2 = Image.new(200, 100, C_RED)

ball   = Sprite.new(300, 400, image0)

block1 = Sprite.new( 10, 200, image1)
block2 = Sprite.new(250, 200, 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

atari_7a.png

atari_7b.png

atari_7c.png

atari_7d.png

A-5. 自作の当たり判定(四角)を使ったブロック崩し(atari_8.rb)

「ブロック崩し」のプログラムblock28.rbを、自作のatari_arrayに書き換えてみましょう。

元の===check.firstを同じ動きをしているのがわかると思います。

atari_8.rb
require 'dxruby'

class Sprite
  def atari?(aite)
    x0  = self.x
    xx0 = x0 + self.image.width

    y0  = self.y
    yy0 = y0 + self.image.height

    x   = aite.x
    xx  = x  + aite.image.width

    y   = aite.y
    yy  = y  + aite.image.height

    (x <= x0  && x0  <= xx ||
     x <= xx0 && xx0 <= xx) &&

    (y <= y0  && y0  <= yy ||
     y <= yy0 && yy0 <= yy)
  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( 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.atari_array(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.atari_array(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

atari_8.png

参考

プログラミング初心者向け:DXRubyで 1ステップずつ作っていく「ブロック崩し」 - Qiita
で作った「ブロック崩し」のプログラムです。

block28.rb
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

block28.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0