A'dan B'ye atlayabilir miyim?


10

Yan kaydıraçım için temel bir yapay zeka yapıyorum ve bir AI ünitesinin A noktasından B noktasına ulaşıp ulaşamayacağını bilmem gerekiyor.

Karakterlerimin uçuş yörüngesi, havada (örneğin Jazz Jackrabbit 2'de olduğu gibi) kuvvet uygulayabildikleri için biraz alışılmadık, bu yüzden yaklaşık bir merminin klasik yörüngesinin aksine ...

atılan veya fırlatılan bir merminin itme olmaksızın (...) alacağı yol.

... sanırım benim sorunum daha çok itmeli bir mermiyle ilgili (örneğin roket).

Bunu göstermek için, atlamak ve sürekli olarak "sol düğmeye" basarsam uçuş eğrisi karakterime böyle görünür (sol uçta farklı görünüyor, burası havada bazı manevralar yapıyordum): resim açıklamasını buraya girin

Uçuş sırasında uygulanan kuvvet her zaman X eksenine paraleldir, bu nedenle "sola" basarsam F = (-f, 0) ve "sağ" tutarsam F = (f, 0) olur.

Bir kayak pisti gibi hareket edebilir:

resim açıklamasını buraya girin

Bu yüzden sadece bir parabol olan klasik yörüngeden çok farklıdır (kaynak: wikipedia ):

resim açıklamasını buraya girin

Daha zor hale getirmek için, basit hava direncini simüle ediyorum, böylece karakterlerim sadece maksimum hız değerine kadar hızlanabilir.

Bu, ters yönde küçük bir kuvvet uygulanarak yapılır :

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, benim durumumda 0.1 olan bir sabittir.

Diyelim ki karakterim sonsuz küçük bir nokta.

Ve ediyorum DEĞİL sorum böyle gider, böylece göz önüne engelleri alarak ...

Başlangıç ​​hızı V verildiğinde, atlama sırasında karaktere uyguladığım bir dürtü J = (0, -j) , yerçekimi G = (0, g) , kuvvet F = (+ -f ) nasıl belirlenir (en azından güvenilir olarak tahmin edilir) , 0) uçuş sırasında sürekli olarak ve hava direncini gerçekten dikkate almaya karar verirsek (bu isteğe bağlıdır) , karakterinizin alacağı yolun çizdiği eğrinin altında bir nokta olup olmadığı?

Hesaplamalarla nereden başlayacağım konusunda hiçbir fikrim yok ve aslında kesin bir cevapla ilgilenmiyorum; AI'nın mükemmel bir şekilde hareket etmesine gerek olmadığı için iyi çalışan bir saldırı / yaklaşım harika olurdu.

edit: Jason önerdiği gibi simülasyon kullanarak bu çözmeye karar verdim, ama böyle bir dava nasıl ele alınır? resim açıklamasını buraya girin

Bir gelen segmenti çizmek gerekir C için D bu kesimin alt istenen nokta bulunup bulunmadığı ve çek?

Veya istenen noktaya yatay mesafede yeterince yakın olan noktayı aramak için C ve D arasındaki zaman aralıklarını ikili olarak aramalı mıyım ve ancak dikey farkı kontrol etmeli miyim? (bana biraz abartılı geliyor)


Hava direncini dikkate almadığımız dava için bir çözüm bulduğumu düşünüyorum: gamedev.stackexchange.com/questions/37916/…
Patryk Czachurski

Yanıtlar:


4

Belirttiğiniz gibi, en iyi seçim yaklaşık olarak bu durumda sayısal bir şema kullanmaktır. Zamanı büyük zaman aralıklarına bölün (100-300 ms diyelim) ve her zaman aralığı için parabolik yaklaşımı kullanın. Hava direnci haricinde kuvvetler aynıdır. Parabolik yol temel olarak sabit hızlanma içindir, ancak hava direnciyle hız değişir çünkü kuvvet hıza bağlıdır. Makul bir yaklaşım, hava direncini her bir zaman aralığında sabit olarak değerlendirmektir. Ancak, entegrasyon sırasında ikinci dereceden (yani parabolik) bir yaklaşım kullanmak, çok daha büyük zaman aralıklarını ele almanızı sağlar. Sonra sadece bir parabol yatay yönde istenen noktayı geçene kadar hesaplayın ve sonra yükseklikleri karşılaştırın.

EDIT: Karşılaştırma hakkında biraz daha ayrıntı. Zaman aşımı boyunca (oyun karelerinde çok sayıda olabilir), oyuncunun hedefi geçtiğini biliyorsunuz <targetx,targety>. Yolları aşağıdaki konumla tanımlanır <ax*t^2 + bx*t + cx, ay*t^2 + by*t + cy>:

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

ttimestep ( 0 <= t <= dt) ve benzer şekilde geçen zamandır y. Yani t=0karakter bir önceki konumda olduğunda t=dtve bir sonraki konumda olduğunda. Yörünge boyunca herhangi bir yerde hesaplayabilmemiz için bunun temel olarak Euler güncellemesi ile dtdeğiştirildiğini unutmayın t. Şimdi x-pozisyonunun ikinci dereceden bir fonksiyon olduğunu biliyoruz, bu yüzden karakterin doğrudan hedefin üstünde veya altında olduğu adımda iki kez çözebilir ax*t^2 + bx*t + cx = targetx ve (iki adede kadar) elde edebiliriz . O zaman aralıkta olmayan çözümleri atıyoruz [0,dt], çünkü bunlar geçerli zaman aralığında değildir. (Sağlamlık için, yuvarlama sorunlarınız olmaması için aralığın uçlarına küçük bir sabit ekleyin). Şimdi (filtrelemeden sonra) çözümümüz yoktu, bu durumda hedefe bu zaman diliminde ulaşamıyoruz. Aksi takdirde, ay*t^2 + by*t + cyçözümleri değerlendiririz ve bunu y ile karşılaştırırız targety. Yörüngenizdeki bir noktada hedefin üstünde ve daha sonra bunun altında olabileceğinizi unutmayın (ya da tam tersi). Bu gibi durumları ne yapmak istediğinize göre yorumlamanız gerekir.

Bir grup zaman aşımını düşünmek, orijinal soruna analitik bir çözüm bulmaktan çok daha kolaydır ve hareket modelini değiştirebileceğiniz için çok daha esnektir ve bu kabaca işe yarayacaktır.

Değişken adımları kullanmak için bonus puanlar, örneğin, ilk saniye için 100 ms (on puan), sonraki iki için 200 ms, (on puan daha), 4 saniyede 400 ms, vb. Aslında, karakteriniz terminal hızına yaklaştıkça direnç azalır ve yine de daha büyük zaman aralıklarına ihtiyacınız yoktur. Bu şekilde, T saniye karmaşıklığı O (T) yerine O (log T) olduğundan, çok fazla işlem yapmadan gerçekten uzun atlamalarla başa çıkabilirsiniz.

Ayrıca, karakter zıplayarak yarı yolda ilerlemeyi bıraktığında veya başka bir şekilde güçlendirmeye başladığında ne olacağını simüle edebilirsiniz. Yukarıdaki hile ile karmaşıklık O ((log T) ^ 2), ki bu çok kötü değil.


+1, Harika cevap! Gerçek simülasyonu nasıl düşünemedim. Lütfen "parabolik yaklaşım" hakkında ayrıntılı bilgi verebilir misiniz (tam olarak anlamıyorum)? Sadece RK4 ve Euler gibi hızları entegre etme yöntemini mi kastediyorsunuz? Öyleyse, açıklayabilir veya en azından nasıl yapılacağı hakkında bazı bilgilere bağlantı verebilir misiniz?
Patryk Czachurski

1
Normalde yaparsınız x'= x + v*dt. Bunun yerine kullanın x' = x + v*dt + 1/2*a*dt*dt. dtKüçük olduğunda , küçük dt^2olduğu için, genellikle oyunlarda geleneksel Euler entegrasyonunda bırakılır. İşte dtküçük değil, bu yüzden ivme terimine ihtiyacınız var. Yana dtikinci güce yükseltilir, bu bir karesel entegrasyon ve yolu bir parabol, dolayısıyla parabolik tahmindir. RK4 esas olarak daha yüksek türevleri hesaplar ve böylece kübik, kuartik, quintic vb. Yaklaşımlar verebilir. RK4 bunun için aşırı bir olasılıktır, büyük olasılıkla istikrar önemli olmadığından.

ve sanırım hızın kendisi geleneksel Euler'deki gibi entegre olmalı? v' = v + a*dt
Patryk Czachurski

1
Evet. Pisliğiniz yok, sıfır olduğunu varsayıyorsunuz.

Lütfen düzenlemeye bir göz atın.
Patryk Czachurski

4

Yaşasın! Yaptım!

Hedef noktanın dikey ekseninin arkasına inmek için ilk pozisyonu alan basit simülasyon kullanıyorum - oradan, önceki simüle edilmiş pozisyonu alıp bir segment yapıyorum. Şimdi hedef noktanın bu segmentin altında olup olmadığını kontrol ediyorum. Eğer öyleyse - oraya atlayabiliriz.

resim açıklamasını buraya girin

GIF üzerinde oyuncu kontrollü bir karakter. Pembe tahmin edilen yoldur, sarı segmentler sonraki adım pozisyonları olarak tahmin edilir ve hedef nokta bunun altında kalırsa son segment beyaza döner, aksi takdirde kırmızı olur. Kırmızı eğri gerçek uçuş yoludur. Fizik durumu enterpolasyonunun açık olması nedeniyle bazı küçük yanlışlıklar vardır .

Hesaplamalar şaşırtıcı derecede kolay çıktı, ancak ortamımı bu saf hesaplamalar gibi çalıştırıyor ... popoda büyük bir acıydı. En azından orada bazı ciddi hataları çözdüm, bu yüzden sonuçta yararlı bir egzersiz oldu.

Orijinal sorunu çözmek için kullanılan Lua'daki kodun tamamı (kod, kendi "debug_draw" rutininize ve "length_sq" (uzunluk kare), "normalize" veya operatörler +, * gibi temel yöntemlerle kendi vektör sınıfınıza sahip olduğunuzu varsayar. :

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, Jason'a doğru yolu seçtiği için gidiyor! Teşekkürler!


2

Cevabı "sadece hesaplamak" isteyebilirsiniz, ancak "serbest düşme" fiziğinizin son derece etkileşimli doğası nedeniyle, onu aldıktan sonra onu yetersiz bulacağınızdan emin olabilirsiniz.

Farklı bir yaklaşım kullanmayı düşünün: Arama. Super Mario AI için şu şekilde yapılır: http://aigamedev.com/open/interview/mario-ai/

A'dan B'ye ulaşmak için olası yolları aramak, hala hesaplama açısından verimli iken havada havada sınırsız etkileşim sağlar.


1
Bu sadece belirli dünyalar için pratiktir. Özellikle Mario, kabaca doğrusal, sınırlı sayıda hıza ve mükemmel bir sezgisel tarama yaparak arama grafiğinin boyutunu sınırlar. Oyuna bağlı olarak, bu doğru olmayabilir. Ayrıca bu yapay zeka muhtemelen birden fazla karakter / düşman için çalışmak zorunda kalacağı için hesaplama açısından etkilidir, oysa Mario'da kontrol edilecek sadece bir tane vardır.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.