2d игра: огонь в движущейся цели, предсказывая пересечение снаряда и юнита

Хорошо, все это происходит в приятном и простом 2D мире … 🙂

Предположим, что у меня есть статический объект A в положении Apos и линейно движущийся объект B в Bpos с bVelocity, и боеприпасы со скоростью Avelocity …

Как бы я узнал, что A должен стрелять, чтобы попасть в B, учитывая линейную скорость B и скорость боеприпасов A?

Прямо сейчас цель находится на текущей позиции объекта, а это значит, что к тому времени, когда мой снаряд попал туда, блок перешел на более безопасные позиции 🙂

Сначала поверните оси так, чтобы AB был вертикальным (путем вращения)

Теперь разделим вектор скорости B на компоненты x и y (скажем, Bx и By). Вы можете использовать это, чтобы вычислить компоненты x и y вектора, в который нужно стрелять.

B --> Bx | | V By Vy ^ | | A ---> Vx 

Вам нужно Vx = Bx и Sqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo .

Это должно дать вам вектор, который вам нужен в новой системе. Преобразуйте обратно в старую систему, и вы закончите (сделав поворот в другом направлении).

Я написал контрольную подпрограмму для xtank некоторое время назад. Я попытаюсь выложить, как я это сделал.

Отказ: я, возможно, совершил одну или несколько глупых ошибок где-нибудь здесь; Я просто пытаюсь восстановить рассуждения с помощью моих ржавых математических навыков. Тем не менее, я сначала перейду к преследованию, так как это программирование Q & A вместо математического classа 🙂

Как это сделать

Это сводится к решению квадратичного уравнения вида:

 a * sqr(x) + b * x + c == 0 

Обратите внимание, что sqr означает квадрат, а не квадратный корень. Используйте следующие значения:

 a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed) b := 2 * (target.velocityX * (target.startX - cannon.X) + target.velocityY * (target.startY - cannon.Y)) c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y) 

Теперь мы можем посмотреть на дискриминант, чтобы определить, есть ли у нас возможное решение.

 disc := sqr(b) - 4 * a * c 

Если дискриминант меньше 0, забудьте о поражении своей цели – ваш снаряд никогда не сможет добраться туда вовремя. В противном случае рассмотрите два варианта решения:

 t1 := (-b + sqrt(disc)) / (2 * a) t2 := (-b - sqrt(disc)) / (2 * a) 

Заметим, что если disc == 0 то t1 и t2 равны.

Если нет других соображений, таких как вмешательство в препятствия, просто выберите меньшее положительное значение. (Отрицательные значения t потребуют стрельбы назад вовремя, чтобы использовать!)

Подставьте выбранное значение t обратно в уравнения позиции цели, чтобы получить координаты ведущей точки, на которую вы должны нацеливаться:

 aim.X := t * target.velocityX + target.startX aim.Y := t * target.velocityY + target.startY 

отвлечение

В момент времени T снарядом должно быть (евклидово) расстояние от пушки, равное истекшему времени, умноженному на скорость снаряда. Это дает уравнение для круга, параметрическое по истекшему времени.

 sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y) == sqr(t * projectile_speed) 

Аналогично, в момент Т мишень перемещалась вдоль своего вектора по времени, умноженному на его скорость:

 target.X == t * target.velocityX + target.startX target.Y == t * target.velocityY + target.startY 

Снаряд может попасть в цель, когда его расстояние от пушки соответствует расстоянию снаряда.

 sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y) == sqr(target.X - cannon.X) + sqr(target.Y - cannon.Y) 

Замечательно! Подставляя выражения для target.X и target.Y дает

 sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y) == sqr((t * target.velocityX + target.startX) - cannon.X) + sqr((t * target.velocityY + target.startY) - cannon.Y) 

Подставляя другую сторону уравнения, получаем:

 sqr(t * projectile_speed) == sqr((t * target.velocityX + target.startX) - cannon.X) + sqr((t * target.velocityY + target.startY) - cannon.Y) 

… вычитая sqr(t * projectile_speed) с обеих сторон и перевернув его:

 sqr((t * target.velocityX) + (target.startX - cannon.X)) + sqr((t * target.velocityY) + (target.startY - cannon.Y)) - sqr(t * projectile_speed) == 0 

… теперь разрешаем результаты возведения в квадрат подвыражений …

 sqr(target.velocityX) * sqr(t) + 2 * t * target.velocityX * (target.startX - cannon.X) + sqr(target.startX - cannon.X) + sqr(target.velocityY) * sqr(t) + 2 * t * target.velocityY * (target.startY - cannon.Y) + sqr(target.startY - cannon.Y) - sqr(projectile_speed) * sqr(t) == 0 

… и группировать подобные термины …

 sqr(target.velocityX) * sqr(t) + sqr(target.velocityY) * sqr(t) - sqr(projectile_speed) * sqr(t) + 2 * t * target.velocityX * (target.startX - cannon.X) + 2 * t * target.velocityY * (target.startY - cannon.Y) + sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y) == 0 

… затем объединить их …

 (sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)) * sqr(t) + 2 * (target.velocityX * (target.startX - cannon.X) + target.velocityY * (target.startY - cannon.Y)) * t + sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y) == 0 

… давая стандартное квадратичное уравнение по t . Поиск положительных вещественных нhive этого уравнения дает (нулевые, одно или два) возможные места попадания, которые можно сделать с помощью квадратичной формулы:

 a * sqr(x) + b * x + c == 0 x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a) 

+1 на отличном ответе Джеффри Хантина. Я googled вокруг и нашел решения, которые были либо слишком сложны, либо не были конкретно о том, в каком я был заинтересован (простой планетарной скорости в 2D-пространстве). Именно он мне нужен для создания автономного JavaScript-решения ниже.

Один момент, который я хотел бы добавить, состоит в том, что есть пара особых случаев, за которыми вы должны следить, в дополнение к тому, что дискриминант является отрицательным:

  • «a == 0»: происходит, если цель и снаряды движутся с одинаковой скоростью. (решение линейно, а не квадратично)
  • «a == 0 и b == 0»: если цель и снаряд неподвижны. (без решения, если c == 0, т. е. src & dst – одна и та же точка).

Код:

 /** * Return the firing solution for a projectile starting at 'src' with * velocity 'v', to hit a target, 'dst'. * * @param Object src position of shooter * @param Object dst position & velocity of target * @param Number v speed of projectile * @return Object Coordinate at which to fire (and where intercept occurs) * * Eg * >>> intercept({x:2, y:4}, {x:5, y:7, vx: 2, vy:1}, 5) * = {x: 8, y: 8.5} */ function intercept(src, dst, v) { var tx = dst.x - src.x, ty = dst.y - src.y, tvx = dst.vx, tvy = dst.vy; // Get quadratic equation components var a = tvx*tvx + tvy*tvy - v*v; var b = 2 * (tvx * tx + tvy * ty); var c = tx*tx + ty*ty; // Solve quadratic var ts = quad(a, b, c); // See quad(), below // Find smallest positive solution var sol = null; if (ts) { var t0 = ts[0], t1 = ts[1]; var t = Math.min(t0, t1); if (t < 0) t = Math.max(t0, t1); if (t > 0) { sol = { x: dst.x + dst.vx*t, y: dst.y + dst.vy*t }; } } return sol; } /** * Return solutions for quadratic */ function quad(a,b,c) { var sol = null; if (Math.abs(a) < 1e-6) { if (Math.abs(b) < 1e-6) { sol = Math.abs(c) < 1e-6 ? [0,0] : null; } else { sol = [-c/b, -c/b]; } } else { var disc = b*b - 4*a*c; if (disc >= 0) { disc = Math.sqrt(disc); a = 2*a; sol = [(-b-disc)/a, (-b+disc)/a]; } } return sol; } 

У Джеффри Хэнтина есть хорошее решение этой проблемы, хотя его вывод слишком сложный. Вот более чистый способ получить его с помощью некоторого результирующего кода внизу.

Я буду использовать xy для представления векторного точечного произведения, и если векторная величина квадрата, это означает, что я усеиваю ее самой.

 origpos = initial position of shooter origvel = initial velocity of shooter targpos = initial position of target targvel = initial velocity of target projvel = velocity of the projectile relative to the origin (cause ur shooting from there) speed = the magnitude of projvel t = time 

Мы знаем, что положение снаряда и мишени по времени t можно описать с помощью некоторых уравнений.

 curprojpos(t) = origpos + t*origvel + t*projvel curtargpos(t) = targpos + t*targvel 

Мы хотим, чтобы они были равны друг другу в какой-то точке (точка пересечения), поэтому давайте установим их равными друг другу и решим для свободной переменной – projvel .

 origpos + t*origvel + t*projvel = targpos + t*targvel turns into -> projvel = (targpos - origpos)/t + targvel - origvel 

Давайте забудем о понятии происхождения и целевого положения / скорости. Вместо этого давайте работать в относительных терминах, так как движение одной вещи относительно другого. В этом случае мы имеем relpos = targetpos - originpos и relvel = targetvel - originvel

 projvel = relpos/t + relvel 

Мы не знаем, что такое projvel , но мы знаем, что хотим, чтобы projvel.projvel был равен speed^2 , поэтому мы будем квадратизировать обе стороны, и мы получаем

 projvel^2 = (relpos/t + relvel)^2 expands into -> speed^2 = relvel.relvel + 2*relpos.relvel/t + relpos.relpos/t^2 

Теперь мы можем видеть, что единственной свободной переменной является время, t , а затем мы будем использовать t для решения для projvel . Мы будем решать для t с квадратичной формулой. Сначала отделите его на a , b и c , а затем решите для корней.

Прежде чем приступить к решению, помните, что мы хотим наилучшего решения, где t наименьшее, но мы должны убедиться, что t не является отрицательным (в прошлом вы не можете что-то ударить)

 a = relvel.relvel - speed^2 b = 2*relpos.relvel c = relpos.relpos h = -b/(2*a) k2 = h*h - c/a if k2 < 0, then there are no roots and there is no solution if k2 = 0, then there is one root at h if 0 < h then t = h else, no solution if k2 > 0, then there are two roots at h - k and h + k, we also know r0 is less than r1. k = sqrt(k2) r0 = h - k r1 = h + k we have the roots, we must now solve for the smallest positive one if 0 

Теперь, если у нас есть значение t , мы можем подключить t обратно к исходному уравнению и решить для projvel

  projvel = relpos/t + relvel 

Теперь, чтобы стрелять снарядом, получившееся глобальное положение и скорость для снаряда

 globalpos = origpos globalvel = origvel + projvel 

И вы сделали!

Моя реализация моего решения в Lua, где vec * vec представляет векторный точечный продукт:

 local function lineartrajectory(origpos,origvel,speed,targpos,targvel) local relpos=targpos-origpos local relvel=targvel-origvel local a=relvel*relvel-speed*speed local b=2*relpos*relvel local c=relpos*relpos if a*a<1e-32 then--code translation for a==0 if b*b<1e-32 then return false,"no solution" else local h=-c/b if 0 

Ниже приведен код цели на основе полярных координат на C ++.

Чтобы использовать с прямоугольными координатами, вам нужно сначала преобразовать целевую относительную координату в угол / расстояние, а целевые скорости x / y – угол / скорость.

«Скорость» – скорость снаряда. Единицы скорости и targetSpeed ​​не имеют значения, так как в расчете используется только отношение скоростей. Выходной сигнал – это угол, на который должен стрелять снаряд, и расстояние до точки столкновения.

Алгоритм из исходного кода доступен по адресу http://www.turtlewar.org/ .

 // C++ static const double pi = 3.14159265358979323846; inline double Sin(double a) { return sin(a*(pi/180)); } inline double Asin(double y) { return asin(y)*(180/pi); } bool/*ok*/ Rendezvous(double speed,double targetAngle,double targetRange, double targetDirection,double targetSpeed,double* courseAngle, double* courseRange) { // Use trig to calculate coordinate of future collision with target. // c // // BA // // a C b // // Known: // C = distance to target // b = direction of target travel, relative to it's coordinate // A/B = ratio of speed and target speed // // Use rule of sines to find unknowns. // sin(a)/A = sin(b)/B = sin(c)/C // // a = asin((A/B)*sin(b)) // c = 180-ab // B = C*(sin(b)/sin(c)) bool ok = 0; double b = 180-(targetDirection-targetAngle); double A_div_B = targetSpeed/speed; double C = targetRange; double sin_b = Sin(b); double sin_a = A_div_B*sin_b; // If sin of a is greater than one it means a triangle cannot be // constructed with the given angles that have sides with the given // ratio. if(fabs(sin_a) <= 1) { double a = Asin(sin_a); double c = 180-ab; double sin_c = Sin(c); double B; if(fabs(sin_c) > .0001) { B = C*(sin_b/sin_c); } else { // Sin of small angles approach zero causing overflow in // calculation. For nearly flat triangles just treat as // flat. B = C/(A_div_B+1); } // double A = C*(sin_a/sin_c); ok = 1; *courseAngle = targetAngle+a; *courseRange = B; } return ok; } 

Вот пример, где я разработал и внедрил решение проблемы outlookирующего таргетинга с использованием рекурсивного алгоритма: http://www.newarteest.com/flash/targeting.html

Я должен попробовать некоторые из других представленных решений, потому что кажется более эффективным рассчитать его за один шаг, но решение, которое я придумал, состояло в том, чтобы оценить целевую позицию и фид, которые возвращаются в алгоритм, чтобы создать новый более точную оценку, повторяющуюся несколько раз.

Для первой оценки я «стреляю» в текущее положение цели, а затем использую тригонометрию, чтобы определить, где будет цель, когда выстрел достигнет позиции, о которой идет стрелка. Затем на следующей итерации я «стреляю» в эту новую позицию и определяю, где будет цель на этот раз. Примерно через 4 повтора я получаю с точностью до пикселя.

Я просто взломал эту версию для прицеливания в 2d-пространстве, я не очень хорошо ее тестировал, но, похоже, это работает. Идея этого заключается в следующем:

Создайте вектор, перпендикулярный вектору, направленному от морды к цели. Для возникновения столкновения скорости мишени и снаряда вдоль этого вектора (оси) должны быть одинаковыми! Используя довольно простой косинус, я пришел к этому коду:

 private Vector3 CalculateProjectileDirection(Vector3 a_MuzzlePosition, float a_ProjectileSpeed, Vector3 a_TargetPosition, Vector3 a_TargetVelocity) { // make sure it's all in the horizontal plane: a_TargetPosition.y = 0.0f; a_MuzzlePosition.y = 0.0f; a_TargetVelocity.y = 0.0f; // create a normalized vector that is perpendicular to the vector pointing from the muzzle to the target's current position (a localized x-axis): Vector3 perpendicularVector = Vector3.Cross(a_TargetPosition - a_MuzzlePosition, -Vector3.up).normalized; // project the target's velocity vector onto that localized x-axis: Vector3 projectedTargetVelocity = Vector3.Project(a_TargetVelocity, perpendicularVector); // calculate the angle that the projectile velocity should make with the localized x-axis using the consine: float angle = Mathf.Acos(projectedTargetVelocity.magnitude / a_ProjectileSpeed) / Mathf.PI * 180; if (Vector3.Angle(perpendicularVector, a_TargetVelocity) > 90.0f) { angle = 180.0f - angle; } // rotate the x-axis so that is points in the desired velocity direction of the projectile: Vector3 returnValue = Quaternion.AngleAxis(angle, -Vector3.up) * perpendicularVector; // give the projectile the correct speed: returnValue *= a_ProjectileSpeed; return returnValue; } 

Я видел много способов решить эту проблему математически, но это был компонент, относящийся к проекту, который должен был выполнять мой class в старшей школе, и не у всех в этом classе программирования был фон с исчислением или даже векторы в этом отношении , поэтому я создал способ решить эту проблему с помощью более простого подхода к программированию. Точка пересечения будет точной, хотя она может попасть в 1 кадр позже, чем в математических вычислениях.

Рассматривать:

 S = shooterPos, E = enemyPos, T = targetPos, Sr = shooter range, D = enemyDir V = distance from E to T, P = projectile speed, Es = enemy speed 

В стандартной реализации этой проблемы [S, E, P, Es, D] все получаются, и вы решаете либо найти T, либо угол, на котором стрелять, чтобы вы набрали T в правильное время.

Основной аспект этого метода решения проблемы – рассмотреть диапазон стрелков как круг, охватывающий все возможные точки, которые можно снимать в любой момент времени. Радиус этой окружности равен:

 Sr = P*time 

Где время рассчитывается как итерация цикла.

Таким образом, чтобы найти расстояние, пройденное врагом при заданной итерации времени, мы создаем вектор:

 V = D*Es*time 

Теперь, чтобы решить проблему, мы хотим найти точку, в которой расстояние от цели (T) до нашего стрелка (S) меньше, чем диапазон нашего стрелка (Sr). Вот несколько псевдокода реализации этого уравнения.

 iteration = 0; while(TargetPoint.hasNotPassedShooter) { TargetPoint = EnemyPos + (EnemyMovementVector) if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange)) return TargetPoint; iteration++ } в iteration = 0; while(TargetPoint.hasNotPassedShooter) { TargetPoint = EnemyPos + (EnemyMovementVector) if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange)) return TargetPoint; iteration++ } 

Я создал функцию общего доступа Unity C # здесь:
http://ringofblades.com/Blades/Code/PredictiveAim.cs

Это для 3D, но вы можете легко изменить это для 2D, заменив Vector3s на Vector2s и используя вашу ось вниз для гравитации, если есть гравитация.

Если теория вас интересует, я просматриваю вывод математики:
http://www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php

В принципе, концепция пересечения на самом деле здесь не нужна. Насколько вы используете движение снарядов, вам просто нужно нажать под определенным углом и создать экземпляр во время съемки, чтобы получить точное расстояние от вашей цели от источника, а затем как только вы пройдете дистанцию, вы можете рассчитать соответствующую скорость, с которой он должен выстрелить, чтобы попасть в цель.

Следующая ссылка делает концепцию понятной и считается полезной, может помочь: движение снаряда всегда попадает в движущуюся цель

Я схватил одно из решений отсюда, но никто из них не учитывает движение стрелка. Если ваш стрелок движется, вы можете принять это во внимание (так как скорость стрелка должна быть добавлена ​​к скорости вашего пули при стрельбе). На самом деле все, что вам нужно сделать, это вычесть скорость вашего стрелка от скорости цели. Поэтому, если вы используете вышеприведенный код брофы (который я бы рекомендовал), измените строки

  tvx = dst.vx; tvy = dst.vy; 

в

  tvx = dst.vx - shooter.vx; tvy = dst.vy - shooter.vy; 

и вы должны быть настроены.

Interesting Posts

Firefox: одно слово с несколькими полями пароля

Как я могу перечислить все процессы, запущенные в Windows?

Является ли шифрование файловой системы хорошим, если кто-то ворует мой компьютер, пока он включен?

Запуск задач в foreach Loop использует значение последнего элемента

Как грациозно обрабатывать сигнал SIGKILL в Java

Метод перегрузки web api на основе типа параметра

Весна: загрузка файла RESTFUL Web Service

Как создать наложение справки, как вы видите в нескольких приложениях для Android и ICS?

Как получить доступ к моей конфигурации модема ADSL удаленно через Интернет

Насколько точна текущая скорость CURL в индикаторе выполнения?

Программно подключаться к сопряженному устройству Bluetooth

C ++ Лучший способ получить целочисленное деление и остаток

Могу ли я контролировать расположение пользовательских настроек .NET, чтобы избежать потери настроек при обновлении приложений?

Какие функции zsh вы используете?

Почему геттер называется так много раз с помощью атрибута rendered?

Давайте будем гением компьютера.