Я работаю над созданием Pong на FPGA с использованием VHDL. Я ломал голову в течение нескольких дней, пытаясь понять, как это сделать и какое лучшее решение для столкновения прямоугольника с прямоугольником, и я думаю, что нашел лучшее решение, хотя, похоже, есть одна ошибка (объяснено ниже).
Я воспользовался советом из ответа larsbutler и использовал эту стратегию для столкновения:
- объект.позицияX += объект.скоростьX
- проверять/реагировать на коллизии
- объект.позицияY += объект.скоростьY
- проверять/реагировать на коллизии
Этот псевдокод объясняет, как я проверяю/отвечаю на коллизии:
// 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). Объекты располагаются от их центра. Вот схема:
Это базовая диаграмма того, каким должен быть ответ: (источник)
Проблема:
В данный момент я просто пытаюсь работать над x-столкновением. Проблема заключается в том, что код xPosition вытаскивает мяч из ракетки, чтобы не застрять. Кажется, что if xVelocity < 0 then
неправильно оценивает. Скажем, мяч движется слева направо (xVelocity > 0), а затем мы ударяем по ракетке с правой стороны. xVelocity изменит знак на отрицательный (xVelocity ‹ 0). Проблема, если утверждение должно оцениваться как истинное и уменьшать xPosition, чтобы вывести его из весла. Однако этого не происходит, а вместо этого прыгает через весло и просто повторяется вперед и назад. Причина, по которой мы добавляем или вычитаем 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 сигнал не обновляет свое значение до конца процесса и обновляется до последнего оператора. Это означает, что если вы сделаете его отрицательным, а затем прибавите к нему, вы получите только прибавление.
Я бы хотел, чтобы это было больше подчеркнуто в руководствах и учебниках, потому что это стоило мне кучу времени.