馃寠 Tutorial Interaktywnych Metaballs

Naucz si臋 kodowa膰 organiczne animacje przypominaj膮ce krople

Czym s膮 Metaballs?

Metaballs (a mo偶e po polsku meta-kulki?) to organicznie wygl膮daj膮ce kszta艂ty, kt贸re 艂膮cz膮 si臋 i mieszaj膮 p艂ynnie, gdy zbli偶aj膮 si臋 do siebie. S膮 tworzone za pomoc膮 r贸wna艅 pola matematycznego i cz臋sto pojawiaj膮 si臋 w lampach lawowych, symulacjach p艂yn贸w i efektach w grach.

馃 Podstawowa Koncepcja

Ka偶dy metaball tworzy niewidzialn膮 "stref臋 wp艂ywu" wok贸艂 siebie. Gdy wiele stref si臋 nak艂ada, ich wp艂ywy si臋 sumuj膮. Tam, gdzie 艂膮czna si艂a pola przekracza pr贸g, rysujemy piksel. To tworzy p艂ynny efekt mieszania.

Krok 1: Konfiguracja Canvas

1 Stw贸rz struktur臋 HTML

Pobierz wersj臋 pe艂noekranow? HTML+JS tutaj: metaballs.htm lub przeanalizuj poni偶sze kroki.

Najpierw potrzebujemy elementu canvas do rysowania:

<canvas id="canvas"></canvas>

<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    
    // Dostosuj canvas do rozmiaru okna
    function resize() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
    }
    
    resize();
    window.addEventListener('resize', resize);
</script>

Krok 2: Tworzenie Klasy Metaball

2 Zdefiniuj w艂a艣ciwo艣ci i zachowanie metaball

Ka偶dy metaball potrzebuje pozycji, pr臋dko艣ci i rozmiaru:

class Metaball {
    constructor(x, y, vx, vy, radius) {
        this.x = x;        // Pozycja X
        this.y = y;        // Pozycja Y
        this.vx = vx;      // Pr臋dko艣膰 X
        this.vy = vy;      // Pr臋dko艣膰 Y
        this.radius = radius;  // Rozmiar
    }
    
    update() {
        // Przesu艅 kul臋
        this.x += this.vx;
        this.y += this.vy;
        
        // Odbij si臋 od 艣cian
        if (this.x - this.radius < 0 || this.x + this.radius > canvas.width) {
            this.vx *= -1;
        }
        if (this.y - this.radius < 0 || this.y + this.radius > canvas.height) {
            this.vy *= -1;
        }
    }
}

Krok 3: Formu艂a Metaball

3 Oblicz si艂臋 pola w dowolnym punkcie

To jest matematyczne serce metaballs. Dla ka偶dego piksela obliczamy, jak bardzo wp艂ywa na niego ka偶da kula:

si艂a_pola = (promie艅虏) / (odleg艂o艣膰虏)

Im bli偶ej punkt jest do centrum kuli, tym silniejszy wp艂yw. Sumujemy wszystkie wp艂ywy:

function getMetaballValue(x, y) {
    let sum = 0;
    
    // Dodaj wp艂yw ka偶dej kuli
    for (const ball of balls) {
        // Oblicz kwadrat odleg艂o艣ci (szybsze ni偶 sqrt)
        const dx = x - ball.x;
        const dy = y - ball.y;
        const distSq = dx * dx + dy * dy;
        
        // Dodaj wp艂yw tej kuli
        sum += (ball.radius * ball.radius) / distSq;
    }
    
    return sum;
}

馃挕 Dlaczego Kwadrat Odleg艂o艣ci?

U偶ywamy kwadratu odleg艂o艣ci zamiast rzeczywistej odleg艂o艣ci, poniewa偶 jest szybszy do obliczenia (nie wymaga pierwiastka kwadratowego) i nadal daje nam p艂ynne zanikanie, kt贸rego chcemy. Odwrotna relacja kwadratowa tworzy naturalnie wygl膮daj膮ce pole.

Krok 4: Renderowanie Metaballs

4 Konwertuj warto艣ci pola na piksele

Sprawdzamy ka偶dy piksel i kolorujemy go na podstawie si艂y pola:

function render() {
    const imageData = ctx.createImageData(canvas.width, canvas.height);
    const data = imageData.data;
    const step = 3; // Sprawd藕 co 3 piksele dla wydajno艣ci
    
    for (let y = 0; y < canvas.height; y += step) {
        for (let x = 0; x < canvas.width; x += step) {
            const value = getMetaballValue(x, y);
            
            // Je艣li pole jest wystarczaj膮co silne, narysuj piksel
            if (value > 1) {
                // Kolor na podstawie intensywno艣ci
                const intensity = Math.min(value / 3, 1);
                const r = Math.floor(100 + intensity * 155);
                const g = Math.floor(50 + intensity * 100);
                const b = Math.floor(200 + intensity * 55);
                
                // Wype艂nij blok step x step
                for (let dy = 0; dy < step; dy++) {
                    for (let dx = 0; dx < step; dx++) {
                        const idx = ((y + dy) * canvas.width + (x + dx)) * 4;
                        data[idx] = r;
                        data[idx + 1] = g;
                        data[idx + 2] = b;
                        data[idx + 3] = 255; // Kana艂 alfa
                    }
                }
            }
        }
    }
    
    ctx.putImageData(imageData, 0, 0);
}
Wskaz贸wka Wydajno艣ci: U偶ywamy zmiennej step do pomijania pikseli. Sprawdzanie ka偶dego piksela (step=1) wygl膮da p艂ynniej, ale jest wolniejsze. Krok 2-4 r贸wnowa偶y jako艣膰 i wydajno艣膰.

Krok 5: Dodawanie Interaktywno艣ci

5 艢led藕 pozycj臋 myszy/dotyku

Stw贸rz niewidzialny metaball, kt贸ry pod膮偶a za kursorem:

const mouseInfluence = {
    x: -1000,
    y: -1000,
    radius: 80,
    active: false
};

canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    mouseInfluence.x = e.clientX - rect.left;
    mouseInfluence.y = e.clientY - rect.top;
    mouseInfluence.active = true;
});

canvas.addEventListener('mouseleave', () => {
    mouseInfluence.active = false;
});

Nast臋pnie uwzgl臋dnij to w obliczeniach pola:

function getMetaballValue(x, y) {
    let sum = 0;
    
    for (const ball of balls) {
        const dx = x - ball.x;
        const dy = y - ball.y;
        const distSq = dx * dx + dy * dy;
        sum += (ball.radius * ball.radius) / distSq;
    }
    
    // Dodaj wp艂yw myszy
    if (mouseInfluence.active) {
        const dx = x - mouseInfluence.x;
        const dy = y - mouseInfluence.y;
        const distSq = dx * dx + dy * dy;
        sum += (mouseInfluence.radius * mouseInfluence.radius) / distSq;
    }
    
    return sum;
}

Krok 6: P臋tla Animacji

6 Po艂膮cz wszystko razem
function animate() {
    // Wyczy艣膰 canvas
    ctx.fillStyle = '#0a0a0a';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // Zaktualizuj wszystkie kule
    for (const ball of balls) {
        ball.update();
    }
    
    // Renderuj metaballs
    render();
    
    // P臋tla
    requestAnimationFrame(animate);
}

animate();

Obs艂uga Dotyku

Dodaj obs艂ug臋 zdarze艅 dotykowych dla urz膮dze艅 mobilnych:

canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    const touch = e.touches[0];
    const rect = canvas.getBoundingClientRect();
    mouseInfluence.x = touch.clientX - rect.left;
    mouseInfluence.y = touch.clientY - rect.top;
    mouseInfluence.active = true;
});

canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect();
    balls.push(new Metaball(
        e.clientX - rect.left,
        e.clientY - rect.top,
        (Math.random() - 0.5) * 3,
        (Math.random() - 0.5) * 3,
        30 + Math.random() * 40
    ));
});

Wskaz贸wki Optymalizacyjne

Usprawnienia Wydajno艣ci:

Eksperymentuj i Rozwijaj

Teraz, gdy rozumiesz podstawy, wypr贸buj te warianty:

馃帗 Kluczowe Wnioski

Udanego kodowania! 馃殌

Wi臋cej samouczk贸w JS

Gra "Wie偶a" - 84 linie JavaScript

Fraktale - 25 linii

Sinus scroll - 30 linii

Gra "Angry Chickens"

Animowane krzywe kwadratowe - 40 linii

Animowane konstelacje cz膮steczek - 42 linie

Eksperyment z enginem fizyki