VHDL中的矩形/盒子碰撞

时间:2013-07-06 21:15:01

标签: collision-detection vhdl collision bounding-box

我正在使用VHDL在FPGA上创建Pong。我一直绞尽脑汁试图弄清楚如何做,以及矩形矩形碰撞的最佳解决方案是什么,我想我已经想出了最好的解决方案,尽管似乎有一个错误(如下所述)。

我接受了来自larsbutler's answer的建议,并使用此策略进行碰撞:

  
      
  • object.positionX + = object.velocityX
  •   
  • 检查/回应碰撞
  •   
  • object.positionY + = object.velocityY
  •   
  • 检查/回应碰撞
  •   

这个伪代码解释了我如何检查/响应冲突:

// right edge of ball in between objects left and right edge
// OR
// left edge of ball in between objects left and right edge
if((ball.right >= object.left && ball.right <= ball.right) || (ball.left >= object.left && ball.left <= object.right))
{
    xCollision = true;
}

// top edge of ball in between objects top and bottom edge
// OR
// bottom edge of ball in between objects top and bottom edge
if((ball.top >= object.top && ball.top <= object.bottom) || (ball.bottom <= object.bottom && ball.bottom >= object.top))
{
    yCollision = true;
}

// respond to collision
if xCollision and yCollision then
{
    // This code block is respective to each x or y update in order to resolve collision
}

请记住,屏幕的左上角是(0,0)。物体从它们的中心定位。这是一个图表:

Pong Diagram Setup

这是我想要回复的基本图表: (source)

Collision response diagram

问题:

此刻我正试图解决x碰撞问题。问题在于使用xPosition代码将球从球拍中取出以避免卡住。似乎if xVelocity < 0 then无法正确评估。假设球从左向右行进(xVelocity> 0)然后我们击中右侧的桨。 xVelocity将符号改为负(xVelocity <0)。问题if语句应该求值为true并递减xPosition以使其脱离paddle。这不会发生,而是跳过划桨,只是来回重复。我们添加或减去40的原因是用于测试,实际上是它在桨内的数量。

我的许多实现似乎陷入了这个陷阱xVelocity没有正确评估。如果你在if else中切换加号和减号,但是在我的脑海中没有任何逻辑意义,代码就可以工作。为什么它必须与我下面的相反? (请记住,xVelocity在此之前乘以-1。

-- respond to collision
if xCollision = '1' and yCollision = '1' then

    -- Change direction
    xVelocity <= xVelocity * (-1);
    -- Add glancing y velocity of paddle
    yVelocity <= yVelocity + (collisionObjects(i)(5)/4);

    -- If bouncing in the left direction
    if xVelocity < 0 then
        -- move outwards as much as we are inside the paddle
        -- Should be negating from xPosition as we are bouncing left and want to resolve that way
        xPosition <= xPosition - 40;
    else
        xPosition <= xPosition + 40;
    end if;
end if;

完整代码:(VHDL)

-- Ball collision is using discrete collision! 
-- not sweep collision which helps with small fast objects passing through each other


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_unsigned.all;

--note this line.The package is compiled to this directory by default.
--so don't forget to include this directory. 
library work;
--this line also is must.This includes the particular package into your program.
use work.my_package.all;

entity Ball is
    generic(
        numCollisionObjects: integer := 2;
        ballRadius : integer := 10;
        rgbColor : std_logic_vector(7 downto 0) := "111" & "111" & "11"
    );
    port(
        reset: in std_logic;
        clk: in std_logic;
        hCounter: in std_logic_vector(9 downto 0);
        vCounter: in std_logic_vector(9 downto 0);
        colObject: out type_collisionObject;
        collisionObjects: in type_collisionObjectArray(0 to numCollisionObjects-1);

        pixelOn: out std_logic;
        rgbPixel: out std_logic_vector(7 downto 0) := rgbColor
    );
end Ball;

architecture Behavioral of Ball is
    signal xPosition : integer := 0;
    signal yPosition : integer := 0;

    signal xVelocity : integer := 0;
    signal yVelocity : integer := 0;

    signal pixelBuffer : std_logic;
    signal RGBBuffer : std_logic_vector(7 downto 0) := rgbColor;

    signal colObjectBuffer: type_collisionObject;
begin
    pixelOn <= pixelBuffer;
    rgbPixel <= RGBBuffer;

    colObjectBuffer <= (xPosition, yPosition, ballRadius * 2, ballRadius * 2, xVelocity, yVelocity);
    colObject <= colObjectBuffer;

    animation: process(clk)
        variable update_clk_count: natural := 0;
        variable update_clk_prescaler: natural := 10000000; -- 833333; -- Slow because of debuging... 50 Mhz / clk_prescaler = desired speed
        --variable i: natural := 1;
        variable xCollision: std_logic := '0';
        variable yCollision: std_logic := '0';

        variable colObject_lastState: type_collisionObject;
    begin

        if rising_edge(clk) then
            -- While reset is high then we reset the positions
            if reset = '1' then

                xPosition <= SCREEN_RESX/2;
                yPosition <= SCREEN_RESY/2;

                xVelocity <= 3;
                yVelocity <= 0;

            else
                if update_clk_count >= update_clk_prescaler then

                    colObject_lastState := colObjectBuffer;

                    -- if we are hitting the left wall
                    if (xPosition - ballRadius + xVelocity) <= 0 then
                        RGBBuffer <= rgbColor;

                        if xVelocity < 0 then
                            xVelocity <= xVelocity * (-1);
                        end if;
                    end if;

                    -- if we are hitting the right wall
                    if (xPosition + ballRadius + xVelocity) >= 640 then
                        RGBBuffer <= rgbColor;

                        if xVelocity > 0 then
                            xVelocity <= xVelocity * (-1);
                        end if;
                    end if;

                    -- if we are hitting the top wall
                    if (yPosition - ballRadius + yVelocity) <= 0 then
                        RGBBuffer <= rgbColor;

                        if yVelocity < 0 then
                            yVelocity <= yVelocity * (-1);
                        end if;
                    end if;

                    -- if we are hitting the bottom wall
                    if (yPosition + ballRadius + yVelocity) >= 480 then
                        RGBBuffer <= rgbColor;

                        if yVelocity > 0 then
                            yVelocity <= yVelocity * (-1);
                        end if;
                    end if;

                    -- Update x position
                    xPosition <= xPosition + xVelocity;

                    -- Check for collision after x updates
                    if not(xVelocity = 0) then
                        for i in collisionObjects'range loop
                            xCollision := '0';
                            yCollision := '0';

                            -- right edge of ball in between objects left and right edge
                            -- OR
                            -- left edge of ball in between objects left and right edge
                            if (xPosition + ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition + ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) 
                                    OR (xPosition - ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition - ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) then
                                xCollision := '1';
                            end if;

                            -- top edge of ball in between objects top and bottom edge
                            -- OR
                            -- bottom edge of ball in between objects top and bottom edge
                            if (yPosition - ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition - ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) 
                                    OR (yPosition + ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition + ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) then
                                yCollision := '1';
                            end if;

                            -- respond to collision
                            if xCollision = '1' and yCollision = '1' then

                                -- Change direction
                                xVelocity <= xVelocity * (-1);
                                -- Add glancing y velocity of paddle
                                yVelocity <= yVelocity + (collisionObjects(i)(5)/4);

                                -- If bouncing in the left direction
                                if xVelocity < 0 then
                                    -- move outwards as much as we are inside the paddle
                                    -- Should be negating from xPosition as we are bouncing left and want to resolve that way
                                    xPosition <= xPosition - 40;
                                else
                                    xPosition <= xPosition + 40;
                                end if;
                            end if;

                        end loop;
                    end if;


                    -- Update y position
                    yPosition <= yPosition + yVelocity;

                    -- Check for collision after y updates
                    if not(yVelocity = 0) then
                        for i in collisionObjects'range loop
                            xCollision := '0';
                            yCollision := '0';

                            -- right edge of ball in between objects left and right edge
                            -- OR
                            -- left edge of ball in between objects left and right edge
                            if (xPosition + ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition + ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) 
                                    OR (xPosition - ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition - ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) then
                                xCollision := '1';
                            end if;

                            -- top edge of ball in between objects top and bottom edge
                            -- OR
                            -- bottom edge of ball in between objects top and bottom edge
                            if (yPosition - ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition - ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) 
                                    OR (yPosition + ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition + ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) then
                                yCollision := '1';
                            end if;

                            -- respond to collision
                            if xCollision = '1' and yCollision = '1' then

                                yVelocity <= yVelocity * (-1);

                                -- If ball is moving in same direction the paddle is
                                if (yVelocity < 0 and collisionObjects(i)(5) < 0) 
                                        OR (yVelocity > 0 and collisionObjects(i)(5) > 0) then
                                    yVelocity <= yVelocity + (collisionObjects(i)(5)/2);
                                end if;
                            end if;

                        end loop;
                    end if;

                    update_clk_count := 0;
                end if;
            end if;

            update_clk_count := update_clk_count + 1;
        end if;
    end process;



    drawing: process(hCounter, vCounter)
    begin
        -- If within pixel bounds of bar
        if hCounter >= (xPosition - ballRadius) and hCounter <= (xPosition + ballRadius) and vCounter >= (yPosition - ballRadius) and vCounter <= (yPosition + ballRadius) then
            pixelBuffer <= '1';
        else
            pixelBuffer <= '0';
        end if;
    end process;

end Behavioral;

来自my_package.vhd的相关信息:

constant SCREEN_RESX: integer := 640;
constant SCREEN_RESY: integer := 480;

-- 0: position X
-- 1: position Y
-- 2: size X 
-- 3: size Y
-- 4: velocityX
-- 5: velocityY
type type_collisionObject is array (0 to 5) of integer; 
type type_collisionObjectArray is array(natural range <>) of type_collisionObject; 

更新

我的碰撞检测不是防弹,也不是让我满意,但我似乎确实发现了我的错误。我没有任何线索,但在VHDL中,信号在进程结束前不会更新其值,并将更新到最后一个语句。这意味着如果你把它变成负数然后添加到它上面,你只会得到补充。

我希望在指南和教程中更多地强调这一点,因为这花费了我很多时间。

2 个答案:

答案 0 :(得分:2)

我没有仔细检查你的代码的每一行,但看起来好像你已经在从伪代码到VHDL的翻译中引入了许多错误机会。即使代码转换是完全正确的,跟踪伪代码要比跟踪代码要困难得多......

假设你信任伪代码(我可以看到它的一些问题: - )

为什么不翻译

if((ball.right >= object.left && ball.right <= ball.right) || (ball.left >= object.left && ball.left <= object.right))
{
    xCollision = true;
}

进入VHDL(纠正明显的)为

if (ball.right >= object.left and ball.right <= object.right) 
   or (ball.left >= object.left and ball.left <= object.right) then
   xCollision := true;
end if;

或只是

xCollision := (ball.right >= object.left and ball.right <= object.right) 
   or (ball.left >= object.left and ball.left <= object.right);

并适当地为ballobject(记录)和xCollision(布尔)创建数据类型?

进行下一步并将其作为函数或过程包装:

   function XCollision (ball, object : rect) return boolean is
   begin 
      return (ball.right >= object.left and ball.right <= ball.right) 
       or (ball.left >= object.left and ball.left <= object.right);
   end XCollision;

可以调用

   x_collision := false;
   for i in objects'range loop
      x_collision := x_collision or XCollision (ball, objects(i));
   end loop;

VHDL是一种高级语言,提供强大的类型模型 - 可能比编写伪代码的语言更好。以这种方式使用它。 (你已经开始使用包了。)

您可以更轻松地编写,分析和调试VHDL。 有关使用记录的替代方法,请创建枚举并使用它进行索引 一系列价值观。

type Sides is (left, right, top, bottom); 
type obj_type is array (sides) of integer;
variable ball, object : obj_type;

在这种情况下,XCollision现在可以

   function XCollision (ball, object : rect) return boolean is
   begin 
      return (ball(right) >= object(left) and ...);
   end XCollision;

上面没有任何根本不可合成的东西。

虽然我已经看到了2010年某些函数的Synplicity对象构建 - 它在某些方面远远落后于XST - 奇怪的是它对于返回值的OUT参数的等效过程感到满意。

那些认为FPGA编程(设计硬件)与软件编码不同的人绝对是正确的 - 在极限范围内 - 但是当他们建议削弱你创造干净,易懂的代码的能力时,他们会说它做了什么,做了什么呢?说。

硬件设计的不同之处在于:

  • 硬件可行吗?具有已知边界的FOR循环是可行的(给定足够大的FPGA) - 具有未知边界的WHILE循环生成未知数量的硬件......不可行!指针(VHDL访问类型)和“malloc”显然都是......
  • 硬件小又快吗?给定足够的对象,上面的“for”循环将溢出FPGA或生成如此长的逻辑链,使其无法满足您的速度目标。然后,您必须将其转换为流水线设计(大型和快速)或状态机(小而快,但需要很多周期)才能实现目标。
  • 使用适当的数据类型。如果我需要一个17位数字,我只需创建一个,并在C程序员可用的最佳可用大小(32位)上节省大量门。 (显然,有一些软件语言允许这样做,但他们的用户仍然不是大多数)
  • 并且毫无疑问是其他人,但这些IMO是最重要的。
  • 评论;并行处理方面和信号的使用是可靠的进程间通信的最大区别:虽然以上几点在单个进程中是有效的;信号和多个过程与大多数其他编程范例有很大不同。

答案 1 :(得分:0)

我查看了你的代码,但没有详细说明。我会建议你在实践中击中左墙或击中右球没有区别。我的意思是当它撞到左墙时,它会用减号改变方向。它与右墙相似。所以最好将两个不同的if子句与“或”结合起来。这将简化您的代码。

试试这个,希望它能奏效。

编辑:我用fpga做同样的游戏,但它运行正常。