Kurs HTML5 - SVG - Animacja zaawansowana  Udostępnij na: Facebook

Autor: Marcin Dembowski

Opublikowano: 2011-11-29

Obiektami SVG możesz zarządzać za pomocą języka Java Script. Dzięki temu, w czasie działania programu, będziesz miał możliwość zmiany rozmiaru, kształtu i położenia poszczególnych obiektów. Obliczając odległość od poszczególnych obiektów, możesz wykrywać pomiędzy nimi kolizje, a także określać oddziaływania. Funkcjonalność tą wykorzystuje się podczas tworzenia interaktywnych prezentacji, czy gier.

Przed wykonaniem zadań powinieneś wiedzieć:

Po wykonaniu zadań:

  • dowiesz się, jak wykrywać proste kolizje obiektów SVG,
  • nauczysz się modyfikować położenie obiektów SVG w czasie,
  • dowiesz się, jak sterować obiektami za pomocą klawiatury.

Implementacja

Twoim zadaniem będzie utworzenie prostej gry, w której za pomocą odbijającej się piłki od paletki, sterowanej za pomocą klawiatury, będzie można zbijać przeszkody, znajdujące się na górze.. Grę przygotujesz w SVG, z wykorzystaniem języka Java Script. W rezultacie przeglądarka wyświetli stronę, przedstawioną na Rys. 1.

Rys. 1. Wynik końcowy ćwiczenia.

Tworzenie zarysu gry

W kroku tym przygotujesz szablon pliku SVG, który wyświetli obszar gry (wypełniony siatką), wraz z obiektem piłki i paletki. Dodatkowo szablon zawierać będzie kilka funkcji Java Script, które uzupełnisz w dalszych krokach .

1.    Utwórz plik SVG.

  • W dowolnym edytorze utwórz nowy dokument SVG i wpisz poniższy kod:
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 400 300" version="1.1" 
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>Gra</title>
    <pattern id="rulerPattern" patternUnits="userSpaceOnUse"
    x="0" y="0" width="10" height="10"
    viewBox="0 0 10 10" >
    <line x1="0" y1="0" x2="10" y2="0" stroke="lightgray" fill="none" />
    <line x1="0" y1="0" x2="0" y2="10" stroke="lightgray" fill="none" />
    </pattern>
    <rect x="0" y="0" width="100%" height="100%" fill="url(#rulerPattern)" stroke="gray" /><circle cx="200" cy="250" r="10" stroke="black" fill="yellow" id="ball" />
    <rect x="0" y="0" height="100%" width="100%" stroke="black" stroke-width="5" fill="transparent" />
    <rect x="160" y="270" height="10" width="80" id="paddle" />
    <g id="wall" />
    <script type="text/ecmascript">
    <![CDATA[
        var paddle = document.getElementById("paddle");
        var ball = document.getElementById("ball");
        var wall = document.getElementById("wall");
        var items = [];
        var speed = 2;
        ball.vh = speed;
        ball.vv = speed;
        function randInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }
        function doAnimation() {
            <!-- RUCH -->
            checkArea();
            collidesWithPaddle();
            collidesWithItem();
        }
        function collidesWithPaddle() {
            <!-- KOLIZJE Z PALETKA -->
        }
        function collidesWithItem() {
            <!-- KOLIZJE Z ELEMENTAMI -->
        }
        function createWall() {
            <!-- TWORZENIE ELEMENTOW -->
        }
        function checkArea() {
            <!-- KOLIZJE ZE SCIANA -->
        }
        <!-- STEROWANIE -->
        createWall();
        setInterval('doAnimation()',16.7);
    // ] ]>
    </script>
</svg>
  • Zapisz powyższy plik pod nazwą brick.svg i otwórz go w przeglądarce. Powinieneś otrzymać grafikę zgodną z Rys. 2.

Rys. 2. Podstawowy szablon strony, wyświetlany w przeglądarce.

Informacja
Przyjrzyj się skryptowi strony. Umieszczono w nim kilka funkcji oraz zmiennych pomocniczych. Zmienne paddle, ball oraz wall zawierają uchwyty do obiektów SVG. Odpowiadają one odpowiednio za paletkę, piłkę oraz ścianę, którą później utworzysz w funkcji createWall. Zauważ, że wall wskazuje na kontener g, który nie jest widoczny dla użytkownika w przeglądarce (więcej na ten temat kontenerów możesz znaleźć w części Biblioteka elementów w SVG.). Zmienna items, która jest tablicą, będzie przechowywać elementy przeszkód dla piłki, które znajdują się w kontenerze wall. Dodatkowo utworzona została zmienna speed, określająca stałą prędkość ruchu piłki. Zauważ także, że po załadowaniu strony, wywoływana jest funkcja setInterval, odpowiadająca za cykliczne wywołanie funkcji gry doAnimation.

Dodanie ruchu piłki oraz detekcja jej kolizji ze ścianami

W tym kroku wprawisz piłkę w ruch oraz sprawdzisz, czy piłka koliduje ze ścianą. Ścianę definiuje granica całej strony SVG – przy zetknięciu się piłki ze ścianą, piłka zmieni kierunek lotu.

  1. Wprawienie piłki w ruch.

Dla uproszczenia, nasza piłka będzie się poruszać jedynie w czterech skośnych kierunkach. Każdy kierunek określany jest przez wartości dwóch punktów (vv oraz vh), informujących o ruchu w pionie i poziomie. Dla zilustrowania tego przyjrzyj się rysunkowi pomocniczemu, tzn. Rys. 3.

Rys. 3. Rysunek pomocniczy, ilustrujący kierunki lotu piłki.

  • Zamień linię z komentarzem RUCH za pomocą poniższego kodu:
ball.cx.baseVal.value += ball.vh;
ball.cy.baseVal.value += ball.vv;
  1. Detekcja kolizji piłki ze ścianą.
  • Zamień linię z komentarzem KOLIZJE ZE SCIANA za pomocą poniższego kodu:
var r = ball.r.baseVal.value;
if (ball.cx.baseVal.value + r >= 400) {
        ball.vh = -speed;
    } else if (ball.cx.baseVal.value -r <= 0) {
        ball.vh = speed;
    }
    if (ball.cy.baseVal.value + r >= 300) {
        ball.vv = -speed; // ZATRZYMAJ
    } else if (ball.cy.baseVal.value -r  <= 0) {
        ball.vv = speed;
}
  • Zapisz plik i otwórz go w przeglądarce. Nasza piłka powinna się poruszać oraz odbijać od ścian.
Informacja

Przy zetknięciu się piłki z krawędzią obrazka, piłka zmienia kierunek lotu. Przyjrzyj się rysunkowi pomocniczemu, tzn. Rys. 4. W lewej części przedstawiony został tor lotu piłki. Piłka kieruje się od dołu, ku lewej krawędzi obrazka, następnie styka się z lewą krawędzią i zmienia kierunek lotu. Zauważ, że po tym zetknięciu zmieniła się wartość zmiennej vh na przeciwną. Podobna sytuacja zajdzie dla prawej krawędzi. Dla górnej i dolnej krawędzi wartość na przeciwną zmieni zmienna vv.

Zwróć uwagę także na sposób detekcji kolizji. Należy sprawdzić, czy krawędź piłki styka się lub przekracza krańce obrazka. Obiektcircle informuje o swoim położeniu poprzez wartości parametrów cx oraz cy, które są punktami środka koła. Wobec tego musimy sprawdzić, czy środek piłki nie przekracza obszaru, którego krawędzie są przesunięte o wielkość promienia r piłki. Na rysunku pomocniczym, czyli Rys. 4., faktyczny dozwolony obszar oznaczony został czerwoną, przerywaną linią, a przykład kolizji został przedstawiony w prawej części rysunku.

Rys. 4. Ilustracja kolizji piłki ze ścianą.

Sterowanie paletką

  • Zamień linię z komentarzem STEROWANIE za pomocą poniższego kodu:
function processKeys(e) {
    switch (e.keyCode) {
        case 39 : // <-
            var x = paddle.x.baseVal.value;
            x = x + 20;
            if (x > 310) x = 310;
            paddle.x.baseVal.value = x;
        break;
        case 37 : // ->
            var x = paddle.x.baseVal.value;
            x = x - 20;
            if (x < 10) x = 10;
            paddle.x.baseVal.value = x;
        break;
    }
}
window.addEventListener('keydown', processKeys, false);
  • Zapisz plik i otwórz go w przeglądarce. Wciskając klawisze strzałek (lewa, prawa) paletka u dołu ekranu powinna przesuwać się w określonym kierunku.
  1. Detekcja kolizji piłki z paletką.
  • Zamień komentarz KOLIZJE Z PALETKA za pomocą poniższego kodu:
var pX = paddle.x.baseVal.value;
var pY = paddle.y.baseVal.value;
var pW = paddle.width.baseVal.value;
var cX = ball.cx.baseVal.value;
var cY = ball.cy.baseVal.value;
var cR = ball.r.baseVal.value;
if (cY + cR > pY && cY < pY) {
    if (cX > pX && cX < pX + pW) {
        ball.vv = -speed;
    }
}
  • Zapisz plik i otwórz go w przeglądarce. Piłka powinna odbijać się teraz od paletki.

Tworzenie elementów SVG na przykładzie przeszkód

  • Zamień komentarz TWORZENIE ELEMENTOW za pomocą poniższego kodu:
while (wall.hasChildNodes()) {
    wall.removeChild(wall.lastChild);
}
for (var i = 0; i < 13; i++) {
    for (var j = 0; j < 3; j++) {
        var item = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        item.r.baseVal.value = 10;
        item.cx.baseVal.value = i*30+20;
        item.cy.baseVal.value = j*25+25;
        item.style.fill = 'rgb(' + randInt(0,255) + ',' + randInt(0,255) + ',' + randInt(0,255)+')';
        item.style.stroke = 'rgb(' + randInt(0,255) + ',' + randInt(0,255) + ',' + randInt(0,255)+')';
        items.push(item);
        wall.appendChild(item);
    }
}
  • Zapisz plik i otwórz go w przeglądarce. Na planszy powinna pojawić się ściana zbudowana z kół wypełnionych losowymi kolorami tak, jak na Rys. 5.

Rys. 5. Plansza gry wypełniona przeszkodami.

Detekcja kolizji piłki z przeszkodami

  • Zamień komentarz KOLIZJE Z ELEMENTAMI za pomocą poniższego kodu:
var isEmpty = true;
for (var i = items.length - 1; i >= 0; i--) {
    var item = items[i];
    if (item == null) continue;
    isEmpty = false;
    var deltaX = item.cx.baseVal.value - ball.cx.baseVal.value;
    var deltaY = item.cy.baseVal.value - ball.cy.baseVal.value;
    var d = Math.sqrt( (deltaX*deltaX) + (deltaY*deltaY) );
    if (d <= ( item.r.baseVal.value + ball.r.baseVal.value )) {
        wall.removeChild(item);
        items[i] = null;
        ball.vh = deltaX < 0 ? speed : -speed;
        ball.vv = deltaY < 0 ? speed : -speed;
        return;
    }
}
if (isEmpty) createWall();
  • Zamień linię z komentarzem ZATRZYMAJ za pomocą poniższego kodu:
ball.vv = 0;
ball.vh = 0;
  • Zapisz plik i otwórz go w przeglądarce. Kolizja piłki z przeszkodą powinna usunąć przeszkodę i zmienić kierunek lotu piłki. Dodatkowo nie przechwycenie przez paletkę piłki powinno zatrzymać ruch piłki.

Podsumowanie

W tym ćwiczeniu utworzyłeś prostą grę w SVG. Nauczyłeś się podstaw detekcji kolizji obiektów oraz dowiedziałeś się, w jaki sposób dodawać elementy do obrazka z poziomu Java Script.

W kolejnym artykule dowiesz się, jakie są różnice pomiędzy elementem SVG, elementem CANVAS oraz gdzie lepiej sprawdza się SVG.

Zadanie

Aby sprawdzić swoje umiejętności, spróbuj utworzyć prostą animację, której wygląd przedstawiony został na Rys. 6. Grafika składa się z czterech ścian (oznaczonych czarnym kolorem), które na początku animacji znajdują się w pozycjach oznaczonych na szaro. Odbijająca się piłka, po zetknięciu się ze ścianą zmienia kierunek lotu, a także przesuwa ścianę o jeden punkt w kierunku brzegu grafiki. Dla uproszczenia nie badamy wyjścia piłki i ściany poza obszar grafiki.

Rys. 6. Zadanie – kolizje w SVG.

  • Ściany są prostokątami, utwórz zmienne globalne w Java Script, które będą wskazywały na te prostokąty.
  • Funkcja badająca kolizję piłki ze ścianami jest analogiczna do funkcji checkArea z omawianej w tym artykule gry – za wyjątkiem, że sprawdzisz, czy piłka nie wychodzi poza granice ścian, a nie grafiki.
  • Dla poprawienia dokładności odbicia od lewej i górnej ściany, weź pod uwagę także grubość ściany.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 400 300" version="1.1" 
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>Gra - zadanie</title>
    <rect x="100" y="0" width="10" height="300" fill="black" id="leftWall" />
    <rect x="300" y="0" width="10" height="300" fill="black" id="rightWall" />
    <rect x="0" y="100" width="400" height="10" fill="black" id="topWall" />
    <rect x="0" y="200" width="400" height="10" fill="black" id="bottomWall" />
    <circle cx="200" cy="150" r="10" stroke="black" fill="yellow" id="ball" />
<script type="text/ecmascript">
    <![CDATA[
    var ball = document.getElementById("ball");
    var topWall = document.getElementById("topWall");
    var bottomWall = document.getElementById("bottomWall");
    var leftWall = document.getElementById("leftWall");
    var rightWall = document.getElementById("rightWall");
    var speed = 2;
    ball.vh = speed;
    ball.vv = speed;
    function doAnimation() {
        ball.cx.baseVal.value += ball.vh;
        ball.cy.baseVal.value += ball.vv;
        checkArea();
    }
    function checkArea() {
        var r = ball.r.baseVal.value;
        if (ball.cx.baseVal.value + r >= rightWall.x.baseVal.value) {
            rightWall.x.baseVal.value++;
            ball.vh = -speed;
        }
        if (ball.cx.baseVal.value - r <= leftWall.x.baseVal.value + 10) {
            leftWall.x.baseVal.value--;
            ball.vh = speed;
        }
        if (ball.cy.baseVal.value + r >= bottomWall.y.baseVal.value) {
            bottomWall.y.baseVal.value++;
            ball.vv = -speed;
        }
        if (ball.cy.baseVal.value - r  <= topWall.y.baseVal.value + 10) {
            topWall.y.baseVal.value--;
            ball.vv = speed;
        }
    }
    setInterval('doAnimation()',16.7);
// ] ]>
    </script>