Могу ли я прыгнуть с А на Б?

10

Я делаю некоторый элементарный ИИ для своего бокового скроллера, и мне нужно знать, может ли ИИ-подразделение достичь точки В из точки А, просто сделав прыжок.

Траектория полета моих персонажей немного необычна, так как они могут применять силу в воздухе (как, например, в Jazz Jackrabbit 2), так что в отличие от классической траектории снаряда, которая примерно ...

путь, по которому брошенный или запущенный снаряд пойдет (...) без движения.

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

Чтобы проиллюстрировать это, вот как выглядит кривая полета моего персонажа, если я прыгаю и постоянно нажимаю «левую кнопку» (на левом конце она выглядит по-другому, именно здесь я выполнял маневры в воздухе): введите описание изображения здесь

Сила, применяемая во время полета, всегда параллельна оси X, поэтому F = (-f, 0), если я держу «влево», и F = (f, 0), если я держу «вправо».

Он может двигаться очень как прыгун с трамплина:

введите описание изображения здесь

Так что он сильно отличается от классической траектории, которая представляет собой просто параболу (источник: wikipedia ):

введите описание изображения здесь

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

Это делается путем приложения небольшой силы в противоположном направлении движения :

b2Vec2 vel = body->GetLinearVelocity();
float speed = vel.Normalize(); //normalizes vector and returns length
body->ApplyForce( AIR_RESISTANCE_MULT * speed * speed * -vel, body->GetWorldCenter() );

AIR_RESISTANCE_MULT - это константа, которая в моем случае равна 0,1.

Давайте предположим, что мой персонаж - бесконечно малая точка.

И я НЕ принимаю во внимание препятствия, поэтому мой вопрос звучит так ...

Как определить (по крайней мере, надежно угадать), учитывая начальную скорость V, импульс J = (0, -j), который я применяю к персонажу при прыжке, гравитацию G = (0, g) , силу F = (+ -f , 0) постоянно применяется во время полета и AIR_RESISTANCE_MULT, если мы действительно решили принять во внимание сопротивление воздуха (это необязательно) , лежит ли точка ниже кривой, проведенной путем, по которому будет идти мой персонаж?

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

редактировать: я решил решить эту проблему с помощью моделирования, как предлагает Джейсон, но как справиться с таким случаем? введите описание изображения здесь

Должен ли я нарисовать отрезок от C до D и проверить, находится ли желаемая точка ниже этого отрезка?

Или мне следует выполнить двоичный поиск по временным шагам между C и D, чтобы найти точку, которая достаточно близка на горизонтальном расстоянии к желаемой точке, и только затем проверить вертикальную разницу? (кажется немного излишним для меня)

Патрик Чачурски
источник
Я думаю, что нашел решение для случая, когда мы не учитываем сопротивление воздуха: gamedev.stackexchange.com/questions/37916/…
Патрик Чачурски,

Ответы:

4

Как вы утверждаете, лучший выбор - приблизить, в этом случае использовать числовую схему. Разделите время на большие временные шаги (скажем, 100-300 мс) и используйте параболическое приближение для каждого временного шага. Силы везде одинаковы, кроме сопротивления воздуха. Параболический путь в основном для постоянного ускорения, но с сопротивлением воздуха ускорение изменяется, потому что сила зависит от скорости. Разумное приближение - рассматривать сопротивление воздуха постоянным на каждом временном шаге. Но использование квадратичной (то есть параболической) аппроксимации при интегрировании позволяет обрабатывать гораздо большие временные шаги. Затем вы просто вычисляете, пока парабола не пересечет нужную точку в горизонтальном направлении, а затем сравните высоты.

РЕДАКТИРОВАТЬ: немного подробнее о сравнении. Вы знаете, что за промежуток времени (который может быть много в игровых кадрах), что игрок пересекает цель <targetx,targety>. Их путь описывается положением, <ax*t^2 + bx*t + cx, ay*t^2 + by*t + cy>где:

ax = 1/2 * accel.x
bx = velocity.x
cx = position.x

tвремя через временной шаг ( 0 <= t <= dt) и аналогично для y. Поэтому, когда t=0персонаж находится на предыдущей позиции, а когда t=dt- на следующей. Обратите внимание, что это в основном обновление Эйлера с dtзаменой на, tтак что мы можем рассчитывать в любом месте вдоль траектории. Теперь мы знаем, что x-позиция является квадратичной функцией, поэтому мы можем решить ax*t^2 + bx*t + cx = targetx и получить (до) два раза за шаг, на котором персонаж находится прямо над или под целью. Затем мы выбрасываем любые решения, которые не находятся в диапазоне [0,dt], поскольку их нет в текущем временном шаге. (Для надежности добавьте небольшую константу к концам диапазона, чтобы у вас не было проблем с округлением). Теперь у нас не могло быть решений (после фильтрации), и в этом случае мы не достигли цели на этом временном шаге. В противном случае мы оцениваем ay*t^2 + by*t + cyрешения и сравниваем это с targety. Обратите внимание, что вы можете быть выше цели в одной точке вашей траектории и ниже ее позже (или наоборот). Вам нужно будет интерпретировать такие ситуации в соответствии с тем, что вы хотите сделать.

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

Бонусные баллы за использование переменных шагов, например, 100 мс за первую секунду (десять баллов), 200 мс за следующие два (еще десять баллов), 400 мс за 4 секунды и т. Д. Фактически, когда ваш персонаж приближается к предельной скорости, изменение в сопротивление падает, и вам все равно не нужны большие временные шаги. Таким образом, вы можете обрабатывать действительно длинные прыжки без слишком большой обработки, поскольку сложность для T секунд составляет O (log T), а не O (T).

Вы также можете смоделировать то, что происходит, когда персонаж перестает ускоряться на полпути через свой прыжок или начинает усиливать другой путь. С вышеупомянутым трюком сложность O ((log T) ^ 2), что не так уж и плохо.


источник
+1, Отличный ответ! Как я мог не учитывать фактическое моделирование. Не могли бы вы уточнить «параболическое приближение» (я не совсем понимаю)? Вы имеете в виду метод интегрирования скоростей, например, RK4 и Эйлера? Если да, не могли бы вы объяснить это или хотя бы дать ссылку на некоторую информацию о том, как это сделать?
Патрик Чачурски
1
Обычно вы делаете x'= x + v*dt. Вместо этого используйте x' = x + v*dt + 1/2*a*dt*dt. Когда dtоно маленькое, dt^2оно крошечное, поэтому обычно оно не учитывается в традиционной интеграции Эйлера в играх. Здесь dtне маленький, поэтому вам нужен срок ускорения. Поскольку dtвозводится во вторую степень, это квадратичное интегрирование, а путь - это парабола, следовательно, параболическое приближение. RK4, по существу, рассчитывает высшие производные и, следовательно, может дать кубическое, квартичное, квинтическое и т. Д. Приближения. RK4 для этого, скорее всего, перебор, так как стабильность не важна.
и я полагаю, что сама скорость должна быть интегрирована, как в традиционном Эйлере? v' = v + a*dt
Патрик Чачурски
1
Ага. У вас нет рывка, вы предполагаете, что это ноль.
Пожалуйста, посмотрите на редактирование.
Патрик Чачурски
4

Ура! Я это сделал!

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

введите описание изображения здесь

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

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

Вот полный код на Lua, использованный для решения исходной проблемы (код предполагает, что у вас есть собственная подпрограмма "debug_draw" и собственный векторный класс с базовыми методами, такими как "length_sq" (длина в квадрате), "normalize" или операторы +, * :

function simple_integration(p, dt)
    local new_p = {}

    new_p.acc = p.acc
    new_p.vel = p.vel + p.acc * dt 
    new_p.pos = p.pos + new_p.vel * dt
    -- uncomment this if you want to use quadratic integration
    -- but with small timesteps even this is an overkill since Box2D itself uses traditional Euler
    -- and I found that for calculations to be accurate I either way must keep the timesteps very low at the beginning of the jump
     --+ p.acc * dt * dt * 0.5

    return new_p
end

function point_below_segment(a, b, p)
    -- make sure a is to the left
    if a.x > b.x then a,b = b,a end

    return ((b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x)) < 0
end

-- returns true or false
function can_point_be_reached_by_jump
(
gravity, -- vector (meters per seconds^2)
movement_force, -- vector (meters per seconds^2)
air_resistance_mult, -- scalar
queried_point, -- vector (meters)
starting_position, -- vector (meters)
starting_velocity, -- vector (meters per seconds)
jump_impulse, -- vector (meters per seconds)
mass -- scalar (kilogrammes)
)

    local my_point = {
        pos = starting_position,
        vel = starting_velocity + jump_impulse/mass
    }

    local direction_left = movement_force.x < 0
    local step = 1/60

    while true do           
        -- calculate resultant force
        my_point.acc = 
        -- air resistance (multiplier * squared length of the velocity * opposite normalized velocity)
        (vec2(my_point.vel):normalize() * -1 * air_resistance_mult * my_point.vel:length_sq()) / mass
        -- remaining forces
        + gravity + movement_force/mass

        -- I discard any timestep optimizations at the moment as they are very context specific
        local new_p = simple_integration(my_point, step)

        debug_draw(my_point.pos, new_p.pos, 255, 0, 255, 255)
        debug_draw(new_p.pos, new_p.pos+vec2(0, -1), 255, 255, 0, 255)

        if (direction_left and new_p.pos.x < queried_point.x) or (not direction_left and new_p.pos.x > queried_point.x) then
            if point_below_segment(new_p.pos, my_point.pos, queried_point) then
                debug_draw(new_p.pos, my_point.pos, 255, 0, 0, 255)
                return true
            else
                debug_draw(new_p.pos, my_point.pos, 255, 255, 255, 255)
                return false
            end
        else 
            my_point = new_p
        end
    end

    return false
end

Accept идет к Джейсону за то, что он направил меня в правильном направлении! Спасибо!

Патрик Чачурски
источник
2

Возможно, вы захотите «просто рассчитать» ответ, но я уверен, что он окажется недостаточным, как только вы его получите, из-за высокой интерактивности вашей физики «свободного падения».

Попробуйте использовать другой подход: поиск. Вот как это делается для AI Super Mario: http://aigamedev.com/open/interview/mario-ai/

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

Йонас Бетел
источник
1
Это практично только для определенных миров. В частности, Марио ограничивает размер поискового графа, будучи приблизительно линейным, имея ограниченное число скоростей и имея превосходную эвристику. В зависимости от игры, это может быть не так. Кроме того, вычислительно эффективен относительно, так как этот ИИ, вероятно, должен был бы работать для более чем одного персонажа / врага, тогда как в Марио есть только один для управления.