Чи можу я перейти з А до В?


10

Я роблю деякий рудиментарний ШІ для свого бічного скролера, і мені потрібно знати, чи може AI-одиниця досягти точки B з точки A, просто перескочивши.

Траєкторія польоту моїх героїв трохи незвична, оскільки вони можуть застосовувати силу в повітрі (як, наприклад, у 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, якщо ми дійсно вирішимо враховувати опір повітря (це необов'язково) , чи лежить точка нижче кривої, намальованої траєкторією мого персонажа?

Я буквально не маю ідеї з чого почати з розрахунками, і насправді мене не обов’язково цікавить точна відповідь; добре працює злом / наближення було б чудово, оскільки AI жодним чином не повинен діяти ідеально.

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

Чи слід намалювати відрізок від C до D і перевірити, чи знаходиться бажана точка нижче цього відрізка?

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


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

Відповіді:


4

Як ви заявляєте, найкращий вибір - приблизний, у цьому випадку з використанням числової схеми. Розділіть час на великі часові кроки (скажімо, 100-300 мс) та використовуйте параболічне наближення для кожного кроку часу. Сили однакові по всьому, крім опору повітря. Параболічний шлях в основному для постійного прискорення, але при опорі повітря прискорення змінюється, оскільки сила залежить від швидкості. Розумним наближенням вважається опір повітря як постійний протягом кожного кроку. Але використання квадратичного (тобто параболічного) наближення при інтеграції дозволяє обробляти значно більші часові кроки. Потім ви просто обчислюєте, поки парабола не перетне бажану точку в горизонтальному напрямку, а потім порівняйте висоти.

EDIT: Трохи докладніше про порівняння. Ви знаєте, що протягом часу (який може бути багато в ігрових кадрах), гравець перетинає ціль <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

Так! Я це зробив!

Я використовую просте моделювання, яке займає перше положення для посадки за вертикальною віссю цільової точки - звідти я беру попереднє модельоване положення і складаю відрізок. Тепер я перевіряю, чи є цільова точка нижче цього відрізка. Якщо є - ми можемо туди стрибнути.

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

Це персонаж на gif, керований гравцем. Рожевий - це передбачуваний шлях, жовті сегменти прогнозуються наступними кроковими позиціями, а кінцевий сегмент стає білим, якщо цільова точка лежить під ним, червоною в іншому випадку. Червона крива - фактичний шлях польоту. Є деякі незначні неточності через включену інтерполяцію фізичного стану.

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

Ось повний код у Lua, який використовується для вирішення початкової проблеми (код передбачає, що у вас є власний режим "debug_draw" та власний векторний клас з основними методами, такими як "length_sq" (довжина в квадраті), "нормалізація" або оператори +, * :

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

Прийміть, йде до Джейсона за те, щоб направити мене в потрібне русло! Дякую!


2

Ви можете "просто обчислити" відповідь, але я впевнений, що ви виявите її недостатньою, як тільки отримаєте її через сильно інтерактивний характер фізики "вільного падіння".

Подумайте про використання іншого підходу: Пошук. Ось як це робиться для Super Mario AI: http://aigamedev.com/open/interview/mario-ai/

Пошук можливих патчів, щоб дістатися від А до В, дозволяє здійснювати необмежену інтерактивність у повітрі, в той час як обчислювально ефективний.


1
Це практично лише для певних світів. Зокрема, Маріо обмежує розмір графіка пошуку, будучи приблизно лінійним, має обмежену кількість швидкостей і має чудову евристику. Залежно від гри це може бути неправдою. Крім того, обчислювально ефективний є відносним, оскільки цей AI, ймовірно, повинен працювати для більш ніж одного персонажа / ворога, тоді як у Маріо є лише один для управління.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.