Dinamik dalgalarla 2D suyu nasıl oluşturabilirim?


81

Yeni Süper Mario Bros'un nasıl yapılacağını öğrenmek istediğim harika bir 2D suyu var.

İşte onu gösteren bir video . Açıklayıcı bir bölüm:

Yeni Süper Mario Bros su efektleri

Suya vuran şeyler dalgalar yaratır. Ayrıca sürekli "arka plan" dalgalar da vardır. Kamera hareket etmediğinde, videodaki 00:50'den hemen sonra sabit dalgalara iyi bir şekilde bakabilirsiniz.

Sıçrama efektlerinin bu dersin ilk bölümünde olduğu gibi çalıştığını farz ediyorum .

Bununla birlikte, NSMB'de suyun yüzeyinde sabit dalgalar vardır ve sıçramalar çok farklı görünür. Diğer bir fark ise, öğreticide, eğer bir sıçrama oluşturursanız, ilk önce, sıçramanın kaynağındaki suda derin bir "delik" oluşturur. Yeni süper mario broşlarda bu delik yoktur veya çok daha küçüktür. Suya girip çıkarken oyuncunun yarattığı su sıçramalarına atıfta bulunuyorum.

Sabit dalgalar ve su sıçramalarına sahip su yüzeyini nasıl oluşturabilirim?

XNA’da programlama yapıyorum. Bunu kendim denedim, ancak arka plandaki sinüs dalgalarının dinamik dalgalarla birlikte iyi çalışmasını sağlayamadım .

New Super Mario Bros geliştiricilerinin bunu tam olarak nasıl yaptığını sormuyorum - sadece bunun gibi bir etkinin nasıl yeniden yaratılacağıyla ilgileniyor.

Yanıtlar:


147

Denedim.

Su sıçramalarına (yaylar)

Bu dersten bahsettiğimiz gibi, suyun yüzeyi bir tel gibidir: Telin bir noktasını çekerseniz, o noktanın yanındaki noktalar da aşağı çekilecektir. Tüm noktalar aynı zamanda bir temele geri çekilir.

Temelde yan yana da birbirini çeken birçok dikey yay vardır.

Bunu Lua'da LÖVE kullanarak çizdim ve şunu aldım:

bir sıçrama animasyonu

Mantıklı görünüyor. Ah Hooke yakışıklısın dahi.

Eğer onunla oynamak istiyorsan, işte Phil'in izniyle bir JavaScript limanı ! Kodum bu cevabın sonunda.

Arka plan dalgalar (yığılmış sinüsler)

Doğal arkaplan dalgaları, bir araya getirilen bir sürü sinüs dalgası (farklı genlik, faz ve dalga boyları gibi) bana benziyor. İşte yazdığımda şöyle görünüyordu:

sinüs girişiminden kaynaklanan arka plan dalgalar

Girişim kalıpları oldukça makul görünüyor.

Şimdi hep beraber

Öyleyse, sıçrama dalgalarını ve arka plan dalgalarını bir araya getirmek oldukça basit bir mesele:

sıçraması ile arka plan dalgalar

Sıçrama olduğunda, orijinal arka plan dalgasının nerede olacağını gösteren küçük gri daireler görebilirsiniz.

Bağladığınız videoya çok benziyor , bu yüzden bunu başarılı bir deney olarak kabul ediyorum.

İşte benim main.lua(tek dosya). Bence oldukça okunaklı.

-- Resolution of simulation
NUM_POINTS = 50
-- Width of simulation
WIDTH = 400
-- Spring constant for forces applied by adjacent points
SPRING_CONSTANT = 0.005
-- Sprint constant for force applied to baseline
SPRING_CONSTANT_BASELINE = 0.005
-- Vertical draw offset of simulation
Y_OFFSET = 300
-- Damping to apply to speed changes
DAMPING = 0.98
-- Number of iterations of point-influences-point to do on wave per step
-- (this makes the waves animate faster)
ITERATIONS = 5

-- Make points to go on the wave
function makeWavePoints(numPoints)
    local t = {}
    for n = 1,numPoints do
        -- This represents a point on the wave
        local newPoint = {
            x    = n / numPoints * WIDTH,
            y    = Y_OFFSET,
            spd = {y=0}, -- speed with vertical component zero
            mass = 1
        }
        t[n] = newPoint
    end
    return t
end

-- A phase difference to apply to each sine
offset = 0

NUM_BACKGROUND_WAVES = 7
BACKGROUND_WAVE_MAX_HEIGHT = 5
BACKGROUND_WAVE_COMPRESSION = 1/5
-- Amounts by which a particular sine is offset
sineOffsets = {}
-- Amounts by which a particular sine is amplified
sineAmplitudes = {}
-- Amounts by which a particular sine is stretched
sineStretches = {}
-- Amounts by which a particular sine's offset is multiplied
offsetStretches = {}
-- Set each sine's values to a reasonable random value
for i=1,NUM_BACKGROUND_WAVES do
    table.insert(sineOffsets, -1 + 2*math.random())
    table.insert(sineAmplitudes, math.random()*BACKGROUND_WAVE_MAX_HEIGHT)
    table.insert(sineStretches, math.random()*BACKGROUND_WAVE_COMPRESSION)
    table.insert(offsetStretches, math.random()*BACKGROUND_WAVE_COMPRESSION)
end
-- This function sums together the sines generated above,
-- given an input value x
function overlapSines(x)
    local result = 0
    for i=1,NUM_BACKGROUND_WAVES do
        result = result
            + sineOffsets[i]
            + sineAmplitudes[i] * math.sin(
                x * sineStretches[i] + offset * offsetStretches[i])
    end
    return result
end

wavePoints = makeWavePoints(NUM_POINTS)

-- Update the positions of each wave point
function updateWavePoints(points, dt)
    for i=1,ITERATIONS do
    for n,p in ipairs(points) do
        -- force to apply to this point
        local force = 0

        -- forces caused by the point immediately to the left or the right
        local forceFromLeft, forceFromRight

        if n == 1 then -- wrap to left-to-right
            local dy = points[# points].y - p.y
            forceFromLeft = SPRING_CONSTANT * dy
        else -- normally
            local dy = points[n-1].y - p.y
            forceFromLeft = SPRING_CONSTANT * dy
        end
        if n == # points then -- wrap to right-to-left
            local dy = points[1].y - p.y
            forceFromRight = SPRING_CONSTANT * dy
        else -- normally
            local dy = points[n+1].y - p.y
            forceFromRight = SPRING_CONSTANT * dy
        end

        -- Also apply force toward the baseline
        local dy = Y_OFFSET - p.y
        forceToBaseline = SPRING_CONSTANT_BASELINE * dy

        -- Sum up forces
        force = force + forceFromLeft
        force = force + forceFromRight
        force = force + forceToBaseline

        -- Calculate acceleration
        local acceleration = force / p.mass

        -- Apply acceleration (with damping)
        p.spd.y = DAMPING * p.spd.y + acceleration

        -- Apply speed
        p.y = p.y + p.spd.y
    end
    end
end

-- Callback when updating
function love.update(dt)
    if love.keyboard.isDown"k" then
        offset = offset + 1
    end

    -- On click: Pick nearest point to mouse position
    if love.mouse.isDown("l") then
        local mouseX, mouseY = love.mouse.getPosition()
        local closestPoint = nil
        local closestDistance = nil
        for _,p in ipairs(wavePoints) do
            local distance = math.abs(mouseX-p.x)
            if closestDistance == nil then
                closestPoint = p
                closestDistance = distance
            else
                if distance <= closestDistance then
                    closestPoint = p
                    closestDistance = distance
                end
            end
        end

        closestPoint.y = love.mouse.getY()
    end

    -- Update positions of points
    updateWavePoints(wavePoints, dt)
end

local circle = love.graphics.circle
local line   = love.graphics.line
local color  = love.graphics.setColor
love.graphics.setBackgroundColor(0xff,0xff,0xff)

-- Callback for drawing
function love.draw(dt)

    -- Draw baseline
    color(0xff,0x33,0x33)
    line(0, Y_OFFSET, WIDTH, Y_OFFSET)

    -- Draw "drop line" from cursor

    local mouseX, mouseY = love.mouse.getPosition()
    line(mouseX, 0, mouseX, Y_OFFSET)
    -- Draw click indicator
    if love.mouse.isDown"l" then
        love.graphics.circle("line", mouseX, mouseY, 20)
    end

    -- Draw overlap wave animation indicator
    if love.keyboard.isDown "k" then
        love.graphics.print("Overlap waves PLAY", 10, Y_OFFSET+50)
    else
        love.graphics.print("Overlap waves PAUSED", 10, Y_OFFSET+50)
    end


    -- Draw points and line
    for n,p in ipairs(wavePoints) do
        -- Draw little grey circles for overlap waves
        color(0xaa,0xaa,0xbb)
        circle("line", p.x, Y_OFFSET + overlapSines(p.x), 2)
        -- Draw blue circles for final wave
        color(0x00,0x33,0xbb)
        circle("line", p.x, p.y + overlapSines(p.x), 4)
        -- Draw lines between circles
        if n == 1 then
        else
            local leftPoint = wavePoints[n-1]
            line(leftPoint.x, leftPoint.y + overlapSines(leftPoint.x), p.x, p.y + overlapSines(p.x))
        end
    end
end

Mükemmel cevap! Çok teşekkür ederim. Ayrıca, sorumu gözden geçirdiğiniz için teşekkür ederim, bunun daha net olduğunu görebiliyorum. Ayrıca, gifler çok faydalıdır. Şans eseri, bir sıçrama oluştururken ortaya çıkan büyük deliği önlemenin bir yolunu biliyor musunuz? Mikael Högström bu hakkı çoktan cevaplamış olabilir ama bu soruyu yayınlamadan önce bile denedim ve sonucum deliğin üçgen şeklinde olması ve çok gerçekçi görünmemesiydi.
Berry

"Sıçrama deliğinin" derinliğini kesmek için, dalganın maksimum genliğini, yani herhangi bir noktanın taban çizgisinden sapmasına ne kadar izin verildiğini sınırlayabilirsiniz.
Anko

3
İlgilenen herkes için BTW: Suyun kenarlarını sarmak yerine, kenarları normalleştirmek için taban çizgisini kullanmayı seçtim. Aksi takdirde, suyun sağında bir sıçrama yaratırsanız, aynı zamanda gerçekçi olmayan bulduğum su solunda dalgalar yaratacaktır. Ayrıca, dalgaları sarmadığım için, arka plan dalgaları çok çabuk düzleşirdi. Bu nedenle, bunları sadece grafiksel bir etki yapmayı seçtim, Mikael Högström'ün dediği gibi, arka plan dalgaları hız ve ivme hesaplamalarına dahil edilmeyecekti.
Berry

1
Sadece bilmeni istedim. "Sıçrama deliğinin" bir if ifadesiyle kesilmesi hakkında konuştuk. İlk başta bunu yapmak için isteksizdim. Ancak, arka plan dalgaları yüzeyin düz durmasını önleyeceğinden, gerçekten mükemmel çalıştığını fark ettim.
Berry

4
Bu dalga kodunu JavaScript'e dönüştürüp buraya jsfiddle'a koydum: jsfiddle.net/phil_mcc/sXmpD/8
Phil McCullick

11

Çözüm için (matematiksel olarak konuşursak, problemi diferansiyel denklemlerin çözülmesiyle çözebilirsiniz, fakat eminim ki bu şekilde yapmazlar) dalgalar yaratma 3 seçeneğiniz vardır (ne kadar ayrıntılı olması gerektiğine bağlı olarak):

  1. Dalgaları trigonometrik fonksiyonlarla hesaplayın (en basit ve en hızlı)
  2. Anko'nun önerdiği gibi yap
  3. Diferansiyel denklemleri çözme
  4. Doku araması kullan

1. Çözüm

Gerçekten basit, her dalga için, yüzeyin her noktasından kaynağa (mutlak) mesafeyi hesaplıyoruz ve formül ile 'yüksekliği' hesaplıyoruz

1.0f/(dist*dist) * sin(dist*FactorA + Phase)

nerede

  • dist bizim mesafemiz
  • Faktör A, dalgaların ne kadar hızlı / yoğun olması gerektiği anlamına gelen bir değerdir.
  • Aşama dalganın aşamasıdır, animasyonlu bir dalga elde etmek için zamanla arttırmalıyız.

İstediğimiz kadar birlikte terim ekleyebileceğimizi unutmayın (süperpozisyon ilkesi).

profesyonel

  • Hesaplamak gerçekten çok hızlı
  • Uygulaması kolaydır

karşı

  • Bir 1d Yüzeyinde (basit) yansımalar için, yansımaları simüle etmek için "hayalet" dalga kaynakları oluşturmamız gerekir, bu 2d yüzeylerde daha karmaşıktır ve bu basit yaklaşımın sınırlamalarından biridir.

2. Çözüm

profesyonel

  • Çok basit
  • Yansımaları kolayca hesaplamayı sağlar
  • Kolayca 2d veya 3B uzaya genişletilebilir

karşı

  • Boşaltma değeri çok yüksekse sayısal olarak kararsız hale gelebilir
  • Çözüm 1'den daha fazla hesaplama gücüne ihtiyaç duyar (ancak Çözüm 3'e çok benzemiyor )

Çözüm 3

Şimdi sert bir duvara çarptım, bu en karmaşık çözüm.

Bunu uygulamadım ama bu canavarları çözmek mümkün.

Burada matematiği hakkında bir sunum bulabilirsiniz, basit değildir ve farklı tür dalgaların diferansiyel denklemleri de vardır.

İşte bazı diferansiyel denklemler daha özel durumları çözmek için bir değil tam listesi (Solitonlar, Peakons, ...)

profesyonel

  • Gerçekçi dalgalar

karşı

  • Çabaya değmeyen çoğu oyun için
  • En fazla hesaplama süresi gerekiyor

4. Çözüm

Çözüm 1'den biraz daha karmaşık ancak çözüm 3'ten bu kadar karmaşık değil.

Önceden hesaplanmış dokuları kullanıyoruz ve bunları harmanlıyoruz, daha sonra yer değiştirme eşlemesini kullanıyoruz (aslında 2d dalgalar için bir yöntemdir ancak ilke 1d dalgalar için de çalışabilir)

Oyun sturmovik bu yaklaşımı kullandı ama bu konudaki makalenin bağlantısını bulamıyorum.

profesyonel

  • 3'ten daha basittir
  • iyi görünen sonuçlar alır (2d için)
  • sanatçılar iyi bir iş çıkarsa gerçekçi görünebilir

karşı

  • canlandırması zor
  • tekrarlanan desenler ufukta görünebilir

6

Sabit dalgalar eklemek için, dinamikleri hesapladıktan sonra birkaç sinüs dalgası ekleyin. Basit olması için, bu yer değiştirmeyi yalnızca grafiksel bir etki yapacağım ve dinamiklerini kendileri etkilemesine izin vermeyeceğim, ancak her iki alternatifi deneyebilir ve hangisinin en iyi sonucu verdiğini görebilirsiniz.

"Sıçrama deliğini" daha küçük yapmak için, Splash (int index, float speed) yönteminin sadece endeksi değil aynı zamanda efekti yaymak için aynı olan bazı köşeleri de etkilemesi için değiştirilmesini öneririm " enerji". Etkilenen köşelerin sayısı, nesnenizin genişliğine bağlı olabilir. Mükemmel bir sonuç elde etmeden önce efekti çok fazla düzeltmeniz gerekebilir.

Suyun daha derin kısımlarını dokulamak için makalede anlatıldığı gibi yapabilir ve sadece daha derin kısmı "daha mavi" yapabilir veya suyun derinliğine bağlı olarak iki doku arasında enterpolasyon yapabilirsiniz.


Cevabın için teşekkürler. Aslında benden önce başka birinin bunu denediğini ve bana daha spesifik bir cevap verebileceğini umuyordum. Ancak ipuçlarınız da çok takdir edilmektedir. Aslında çok meşgulüm, ama bunun için vaktim olduğunda, bahsettiğiniz şeyleri deneyeceğim ve biraz daha kodla oynayacağım.
Berry,

1
Tamam, ama yardıma ihtiyacın olan belirli bir şey varsa, sadece söyle ve biraz daha ayrıntılı olup olamayacağımı göreyim.
Mikael Högström,

Çok teşekkür ederim! Gelecek hafta bir sınav haftasına girdiğim için sorumu çok iyi karşılamadım. Sınavlarımı tamamladıktan sonra, kod üzerinde kesinlikle daha fazla zaman harcayacağım ve büyük olasılıkla daha spesifik sorularla geri döneceğim.
Berry
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.