【问题标题】:Rectangle/Box Collision in VHDLVHDL中的矩形/框碰撞
【发布时间】:2013-07-04 15:20:50
【问题描述】:

我正在使用 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)。对象从它们的中心定位。这是一张图:

这是我想要的响应的基本图表:(source)

问题:

此时我只是在尝试处理 x 碰撞。问题在于 xPosition 代码将球从桨中取出以避免卡住。似乎if xVelocity &lt; 0 then 评估不正确。假设球从左向右移动(xVelocity > 0),然后我们击中右侧的球拍。 xVelocity 将符号变为负数(xVelocity

我的许多实现似乎都陷入了 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 中,信号直到进程结束才会更新其值,并将更新到最后一条语句。意思是如果你把它变成负数然后加到它上面,你只会得到加法。

我希望在指南和教程中更加强调这一点,因为这会花费我大量的时间。

【问题讨论】:

    标签: collision-detection vhdl collision bounding-box


    【解决方案1】:

    我没有详细检查你的每一行代码,但看起来你在从伪代码到 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 是最重要的。
    • 关于 cmets;并行处理方面和使用信号进行可靠的进程间通信是最大的区别:而以上几点在单个进程中有效;信号和多进程与大多数其他编程范例有很大不同。

    【讨论】:

    • 您的建议大大提高了代码的清晰度。在改变信号或使用功能时是否有某种位的设置时间?在我对冲突代码块的响应中似乎发生了一些奇怪的事情。
    • 我了解到您不能保证信号的值会在一个时钟周期内发生变化。我认为这是我一直在处理这个实现和我过去的版本的错误。我非常感谢改进我的代码的提示,明天我会在原帖上发布我的状态。
    • 啊,那么您一直在混淆信号和变量,它们的行为不同,并且有充分的理由。更接近于说(使用时钟进程)你可以保证信号不会在一个时钟周期内改变......你需要这个问答:stackoverflow.com/questions/13954193/… - 希望它能解决问题。
    【解决方案2】:

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

    试试这个,希望它会工作。

    编辑:我用 fpga 制作了同样的游戏,它运行良好。

    【讨论】:

    • 你是对的,没有区别,我的代码反映了这一点。请记住,它会检查两次碰撞。一次在 X 之后,一次在 Y 更新之后。您必须更具体地说明哪个部分未简化。此外,我正在实施 Brian Drummond 在他的回答中所说的关于如何通过添加类型(记录)和函数来简化的内容。
    • 你为什么使用这个代码? '如果 xVelocity
    • 如果我们从左向右移动(xVelocity > 0)并且我们击中了右桨,xVelocity 乘以 -1,所以 xVelocity 变为负数。然后我们进入 if 语句,它应该评估为真,并将球在反弹方向上远离桨。实际上,您需要将该语句更改为 'if xVelocity > 0 then' 以使其正常工作,这违背了我的逻辑......我们有 if 语句的原因是因为屏幕的每一侧都有一个桨(左右)处于垂直位置,并且每个桨的另一侧彼此相对。
    猜你喜欢
    • 2023-03-16
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多