Project1

标题: 如何判断精灵与精灵触碰? [打印本页]

作者: 有丘直方    时间: 2016-4-27 20:48
标题: 如何判断精灵与精灵触碰?
就是说,判断一个Sprite对象和另一个Sprite对象的非透明部分的任何像素是否处在了同一坐标,如果判断为true就返回true,否则返回false。
作者: 冷峻逸    时间: 2016-4-27 21:45
提示: 作者被禁止或删除 内容自动屏蔽
作者: 有丘直方    时间: 2016-4-30 21:56
本帖最后由 有丘直方 于 2016-5-1 07:14 编辑
  1. #===============================================================================
  2. # 判断精灵触碰用的脚本
  3. # 目前没有自行测试过。
  4. # 注释有点乱……但是看着方便
  5. #-------------------------------------------------------------------------------
  6. # Sprite 精灵类
  7. #===============================================================================
  8. class Sprite
  9.   #-----------------------------------------------------------------------------
  10.   # 判断是否接触——按照矩形计算,也是最简单的算法。
  11.   # 这个方法是自己最能想到的。
  12.   # 返回值:true表示触碰,false表示没有触碰
  13.   # 参数:other:判断接触的另外一个精灵
  14.   #-----------------------------------------------------------------------------
  15.   def touch_rect(other)
  16.     self.ox, self.oy = 0 # 首先确保原点在左上角
  17.     if self.x.between?(other.x = self.bitmap.width,  # 判断self的x坐标是否过于靠右
  18.                        other.x + other.bitmap.width) # 判断self的x坐标是否过于靠左
  19.                                                      # 如果self的x坐标适中,那么继续判断
  20.       if self.y.between?(other.y = self.biemap.height,  # 与之前相似的办法判断y坐标
  21.                          other.y + other.bitmap.height, # 同上……
  22.                                                         # 如果y坐标适中,那么确定触碰
  23.         return true                                     # 返回true
  24.       else           # 如果y坐标的条件不满足触碰
  25.                      # 那么确定没有触碰
  26.         return false # 返回false
  27.       end            # y坐标判定结束
  28.     else           # 如果x坐标的条件不满足触碰
  29.                    # 同样,确定没有触碰
  30.       return false # 返回false
  31.     end            # x坐标判定结束
  32.   end # touch_rect方法定义结束
  33.   #-----------------------------------------------------------------------------
  34.   # 判断是否接触——按照圆形计算,略微有点复杂,因为涉及到四则运算之外。
  35.   # 这个方法是结合了taroxd和冷峻逸的方法想出来的……
  36.   # 返回值:true表示触碰,false表示没有触碰
  37.   # 参数:other:判断接触的另外一个精灵
  38.   #-----------------------------------------------------------------------------
  39.   def touch_circle(other)
  40.     self.ox, self.oy = 0 # 确保原点在左上角
  41.     bool = nil # 先创建一个临时变量,后期返回它
  42.     num1 = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)     # 比较参数1:勾股平方和
  43.     num2 = self.bitmap.width / 2 + other.bitmap.width / 2 # 比较参数2:半径和平方
  44.     bool = (num1 <= num2) # 进行比较
  45.                           # 这里其实用到了勾股定理。
  46.                           # num1其实是在使用勾股定理通过两条直角边来计算斜边
  47.                           # num2则是利用圆半径永远相等的定理计算圆心的距离
  48.                           # 那么很显然,如果num1的长度不超过num2,两个精灵就相碰了
  49.     return bool # 返回运算结果。
  50.                 # 令我感到很奇怪的是这个方法的定义居然比touch_rect方法短
  51.   end # touch_circle方法定义结束
  52.   #-----------------------------------------------------------------------------
  53.   # 判断是否接触——按照非透明部分计算,很复杂,效率低。
  54.   # 这个方法是冷峻逸最先提出,也是被最先否定的,因为效率太低了
  55.   # 返回值:true表示触碰,false表示没有触碰
  56.   # 参数:other:判断接触的另外一个精灵
  57.   #-----------------------------------------------------------------------------
  58.   def touch_opacity(other)
  59.     return false unless self.touch_rect(other) # 如果没有矩形触碰则直接返回false
  60.     self.ox, self.oy = 0 # 惯例,让原点在左上角
  61.     tab1 = Table.new(self.bitmap.width, self.bitmap.height) # 这个二维表格是用来挨个判断不透明度用的
  62.     for x in 0...(self.bitmap.width)            # 用循环挨个获取像素点的Color
  63.       for y in 0...(self.bitmap.height)         # 分别用x、y两个临时变量接受像素坐标
  64.         tab1[x, y] = (self.bitmap.get_pixel(x, y).alpha > 0) # 在这里获取像素点是否透明
  65.       end                                       # 纵向获取结束
  66.     end                                         # 横向获取结束
  67.     tab2 = Table.new(other.bitmap.width, other.bitmap.height) # 这下轮到了判断触碰的精灵
  68.     for x in 0...(other.bitmap.width); for y in 0...(other.bitmap.height) # 循环
  69.       tab2[x, y] = (other.bitmap.get_pixel(x, y).alpha > 0) # 在这里获取像素点是否透明
  70.     end; end # 颜色获取结束
  71.     for x in 0...(self.bitmap.width); for y in 0...(self.bitmap.height) # 循环
  72.       if tab1[x, y] and tab2[x, y]                                 # 如果相对于精灵的位置相同的两点都不透明
  73.         if self.x + x == other.x + x and self.y + y == other.y + y # 如果相对于游戏程序窗口的位置也相同
  74.                                                                    # 确定两个精灵相碰
  75.           return true                                              # 返回true
  76.       end; end                                                     # 分歧结束
  77.     end # 循环结束
  78.     return false # 如果到现在还没有return,确定还没有触碰,返回false
  79.   end # touch_opacity方法定义结束
  80. end # Sprite类定义结束
  81. # 因为没有测试,所以能不能正常运行还不知道。不过应该可以了。
  82. # 第三种方法因为用到了很多循环,所以效率很低,不建议使用。
复制代码
  1. class Sprite
  2.   def touch_rect(other)
  3.     self.ox, self.oy, other.ox, other.oy = 0
  4.     return (self.x.between?(
  5.                             other.x - self.bitmap.width,
  6.                             other.x + other.bitmap.width
  7.                             ) and
  8.             self.y.between?(
  9.                             other.y - self.biemap.height,
  10.                             other.y + other.bitmap.height
  11.                             )
  12.             )
  13.   end
  14.   def touch_circle(other)
  15.     return false unless self.touch_rect(other)
  16.     self.ox, self.oy, other.ox, other.oy = 0
  17.     return (
  18.             Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) <=
  19.             self.bitmap.width / 2 + other.bitmap.width / 2
  20.             )
  21.   end
  22.   def touch_opacity(other)
  23.     return false unless self.touch_rect(other)
  24.     self.ox, self.oy, other.ox, other.oy = 0
  25.     tab_self = Table.new(self.bitmap.width, self.bitmap.height)
  26.     for x in 0...(self.bitmap.width)
  27.       for y in 0...(self.bitmap.height)
  28.         tab_self[x, y] = (self.bitmap.get_pixel(x, y).alpha > 0)
  29.       end
  30.     end
  31.     tab_other = Table.new(other.bitmap.width, other.bitmap.height)
  32.     for x in 0...(other.bitmap.width)
  33.       for y in 0...(other.bitmap.height)
  34.         tab_other[x, y] = (other.bitmap.get_pixel(x, y).alpha > 0)
  35.       end
  36.     end
  37.     ary = []
  38.     for self_x in 0...(self.bitmap.width)
  39.       for self_y in 0...(self.bitmap.height)
  40.         for other_x in 0...(other.bitmap.width)
  41.           for other_y in 0...(other.bitmap.height)
  42.             ary += (tab_self[self_x, self_y]             and
  43.                     tab_other[other_x, other_y]          and
  44.                     self.x + self_x == other.x + other_x and
  45.                     self.y + self_y == other.y + other_y
  46.                     )
  47.           end
  48.         end
  49.       end
  50.     end
  51.     return ary.include?(true)
  52.   end
  53. end
复制代码
  1. # 测试代码如下(此代码经过测试,没有BUG):
  2. class Sprite
  3.   def touch_rect(other)
  4.     return (self.x.between?(
  5.                             other.x - self.bitmap.width,
  6.                             other.x + other.bitmap.width
  7.                             ) and
  8.             self.y.between?(
  9.                             other.y - self.bitmap.height,
  10.                             other.y + other.bitmap.height
  11.                             )
  12.             )
  13.   end
  14. end
  15. module SceneManager
  16.   def self.first_scene_class
  17.     return Scene_Test
  18.   end
  19. end
  20. class Scene_Test < Scene_Base
  21.   def start
  22.     super
  23.     @test = Sprite.new
  24.     @test.bitmap = Bitmap.new(32, 32)
  25.     @test.bitmap.fill_rect(0, 0, 32, 32, Color.new(255, 0, 0))
  26.     @test.y = (640 - 32) / 2
  27.     @sprite = Sprite.new
  28.     @sprite.bitmap = Bitmap.new(12, 416)
  29.     @sprite.bitmap.fill_rect(0, 0, 12, 416, Color.new(255, 255, 255))
  30.     @sprite.x = 544 - 12 - 12
  31.   end
  32.   def update
  33.     super
  34.     @test.update
  35.     @sprite.update
  36.     @test.x += 1
  37.     if @test.touch_rect(@sprite)
  38.       p 'OK.'
  39.       SceneManager.goto(Scene_Title)
  40.     end
  41.   end
  42.   def terminate
  43.     super
  44.     @test.bitmap.dispose
  45.     @sprite.bitmap.dispose
  46.     @test.dispose
  47.     @sprite.dispose
  48.   end
  49. end
复制代码

作者: shitake    时间: 2016-5-3 05:43
这槽点满满的代码我都不知从何吐槽起。。。ORZ
先不说楼主的整体思路就有问题了,代码中的各种语法错误就有不少。。。贴代码前能自己先跑一边么,这连解释器都过不了的代码竟然还能测试,还能测试成功ORZ

我就只说说楼主的 第三个代码框里的touch_rect这个方法吧。。。

我们把问题稍微简化一下,假定sprite的width和sprite.bitmap.width相等,height等同。
然后把sprite什么的都扔掉不管,整个问题可以简化为两个rect的碰撞检测的问题,换算到数学上就是两个齐轴矩形是否相交。
再进一步,我们只考虑单个轴方向上碰撞(这里看X轴)的情况(x和y轴的判别方法同理)。

根据楼主的代码,得出如下的简化代码:
RUBY 代码复制
  1. # 一个rect差不多是如下这样的结构体
  2. rect = Struct.new(:x, :y, :w, :h)
  3.  
  4. # 转换过后的touch_rect方法
  5. def touch_rect(rect1, rect2)
  6.     rect1.x.between?(rect2.x - rect1.w, rect2.x + rect1.w)
  7. end


可以看出楼主的判别方法是判断rect1的x坐标是否在rect2.x±rect1.w这个区间内。

那么上动图:


在这里,点A、B所构成的区间即为rect1,点C、D所构成的区间为rect2,而虚线E、F则表示的是rect2.x±rect1.w这个判别区间。
从动图来看,前半部份并没问题,但是当rect1进入灰色区域时,问题就出现了,这时点A早就出了判别区间,这时会认为rect1和rect2并没发生碰撞,但是实际上rect1的一大半部分还在rect2内!真正的碰撞结束应该是当点A离开C、D区间!

而楼主在第三个代码框内的所谓的test只能说是没考虑全部情况的错误测试。(很明显代码中@sprite.width小于@test.width)

最简单的矩形碰撞都有问题了,其余的我就不说了。


实际上楼主所描述的问题是游戏开发中很重要的一大块——物理引擎的核心部分包围盒碰撞检测问题。
而一般包围盒碰撞检测的代码也不会直接放到sprite这样的基础渲染对象里(降低代码耦合)。




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1