Spis treści:

Kategoria:HTMLJavaScriptCanvas


Generowanie losowego terenu 2D

HTML5 i Javascript znowu w akcji

Wiele prostych i skomplikowanych gier musi mieć jakieś środowisko, w którym dzieje się akcja. Plansze, poziomy, etapy, mapy - wszystko to można zdefiniować na etapie projektowania. Problem w tym, że takich map mamy tylko tyle, ile zostało utworzonych przez twórcę gry. Dość powszechną praktyką, nadającą się do niektórych gier jest losowe tworzenie obszarów. Mamy wtedy pewność, że map nam nigdy nie zabraknie. Każda będzie na swój sposób unikatowa.

Istnieje wiele różnych sposobów generowania terenu. Zajmę się dzisiaj jedną z nich. Aplikacja zostanie napisana w języku JavaScript, a sam teren zostanie narysowany na obiekcie płótna HTML5. Jeżeli dla kogoś obiekt płótna sprawia trudności, zachęcam do zapoznania się z kilkoma wpisami na ten temat: Przejdź do kategorii Canvas.

Ci, którym dodatkowa wiedza nie jest potrzebna, mogą przejść dalej.

Przykładowy teren 2D

Zanim przejdę do kodu i wyjaśnień pokażę, co można osiągnąć. Wiem, że większość zerka na obrazek i na jego podstawie rozstrzyga, czy warto czytać i analizować dalej. To dla nich, ale nie tylkko, pokażę gotowy wynik. Popatrzmy zatem na dwa przykłady:

Rys. 1 - Losowo wygenerowany teren.
Rys. 2 - Inny losowo wygenerowany teren.

Losowość przykładu jest dość mocno zubożona. Krawędzie są zawsze na tym samym poziomie, a pofałdowanie dość delikatne. O tym jednak za chwilę.

Kod przykładowej strony HTML

Poniżej zamieszczam pełny kod strony HTML, wraz z kodem JavaScript. Można ten kod skopiować, wkleić do notatnika, zapisać jako plik HTML i uruchomić - wszystko powinno działać. Nie ma tu żadnych zewnętrznych bibliotek, gotowych obrazków - sam kod.

<!DOCTYPE html>
<html>
<head><title>Generowanie terenu</title></head>
<body>
<h2>Generowanie terenu w HTML5 i Javascript</h2>
<figure>
<canvas id="paint1width="640height="320"></canvas>
<figcaption>Rys. 1 - Losowo wygenerowany teren.</figcaption>
</figure>
<figure>
<canvas id="paint2width="640height="320"></canvas>
<figcaption>Rys. 2 - Inny losowo wygenerowany teren.</figcaption>
</figure>
<script type="text/javascript">
function Paint(c){
  var e = document.getElementById(c);
  this.Canvas = e;
  this.Ctx = e.getContext("2d");
  this.X = e.offsetLeft;
  this.Y = e.offsetTop;
  this.Width = e.width;
  this.Height = e.height;
  
  this.Suppression = 0.5;
  this.Density = 5;
}

Paint.prototype.Draw = function(){
  var sky = this.Ctx.createLinearGradient(0,0,0,this.Height);
  sky.addColorStop(0, '#6666DD');
  sky.addColorStop(0.5, '#DAFEFC');
  var grass = this.Ctx.createLinearGradient(0,0,0,this.Height);
  grass.addColorStop(0.5, '#0F4005');
  grass.addColorStop(1, '#1A6A24');
  this.Ctx.fillStyle = sky;
  this.Ctx.fillRect(0,0,this.Width,this.Height);
  
  var array = new Array();
  var length = this.Width/this.Density;
  var step = length;
  var amplitude = this.Height/2;
  array[0] = 0;
  array[length]=0;
  this.GetRecursiveValues(array, 0, length, amplitude);
  
  var horiz = 2*this.Height/3;
  this.Ctx.fillStyle = grass;
  this.Ctx.beginPath();
  this.Ctx.moveTo(0,horiz+array[0]);
  for (var i=1; i<=length; i++)
    this.Ctx.lineTo(i*this.Density,horiz+array[i]);
  this.Ctx.lineTo(this.Width, this.Height);
  this.Ctx.lineTo(0, this.Height);
  this.Ctx.closePath();
  this.Ctx.fill();
}
Paint.prototype.GetRecursiveValues = function(array, left, right, ampl){
  if (right-left<2)
    return;

  var mid = (right+left)/2;
  array[mid] = (array[left]+array[right]+2*Math.random()*ampl-ampl)/2.0;
  this.GetRecursiveValues(array, left, mid, ampl*this.Suppression);
  this.GetRecursiveValues(array, mid, right, ampl*this.Suppression);
}

//Dopiero po załadowaniu całej strony elementy będą poukładane
window.addEventListener("load"function(){
  new Paint("paint1").Draw();
  new Paint("paint2").Draw();
  }, false);
</script>
</body>
</html>

Jak na takie efekty, kodu nie jest dużo. Oprócz kodu odpowiedzialnego za generowanie terenu, na listingu znajdują się znaczniki HTML oraz kod odpowiedzialny za upiększenie rezultatu (między innymi gradient nieba i terenu zielonego).

Metoda przesuwania środka odcinka

Metoda ta jest jedną z wielu, które mogą być wykorzystane do generowania losowych map. Nazywa się ją czasem fraktalem losowego przesunięcia środka. Jak działa algorytm?

  1. Dobieramy dwa punkty końcowe. W pokazanym przykładzie znajdują się one na wysokości 1/3 całego obszaru. W ciekawszych rozwiązaniach punkty te mogą być rozmieszczone losowo.
  2. Pomiędzy każdą itniejącą parą punktów wstawiamy dodatkowy dodatkowy punkt. Przyjmuje on wartość równą średniej z dwóch sąsiednich punktów zmienioną o wartość losową wychylenia (ang. Amplitude).
  3. Wartość losowa z każdą kolejną iteracją jest zmniejszana - mniejsze odległości to mniejsze odchylenia. Wartość zmniejszającą nazwałem tłumieniem (ang. Suppression).

Wartości wyliczone dla poszczególnych punktów umieszczane są w tablicy. Tablica zawiera informacje o punktach znajdujących się w odległości 5 pikseli (reprezentuje to zmienna Density). Wartości punktów środkowych wyliczane są rekurencyjnie.

Teran gładki, teren poszarpany

Na sposób generownia terenu wpływ mają dwa parametry:

  • Zmienna amplitude - Określa wartość wychylenia, maksymalną odległość przesunięcia środka odcinka. Trzeba pamiętać, że przesunięcie jest losowe i może się wahać w granicach -amplituda, +amplituda. Może też wynieść 0.
  • Pole Suppression - Współczynnik tłumienia. Im mniejsza odległość między punktami, tym mniejsze powinno być wychylenie (amplituda). To dlatego w każdym kolejnym kroku iteracji amplituda mnożona jest przez współczynnik tłumienia. Niski współczynnik, mniejszy niż 0.5, powoduje powstawanie gładkich terenów. Współczynnik większy niż 0.5 powoduje powstawanie ostrych krawędzi.

Warto z tymi współczynnikami poeksperymentować i przekonać się doświadczalnie o ich wpływie na końcowy rezultat.

Co można zrobić z takim terenem?

Już sam taki dwuwymiarowy teren daje wiele możliwości. Wystarczy ustawić dwa czołgi, kąt lufy i siłę strzału i mamy prostą grę. Można na takim terenie umieścić samochód terenowy i zrobić prostą grę wyścigową.

Stosując prostą metodę otrzymaliśmy teren dwuwymiarowy. A gdybyśmy tak zechcieli rozszerzyć nasz teren na 3D? W najprostszej postaci wystarczyłoby, zamiast wyznaczać przesunięcie środka odcinka, wyznaczyć środek kwadratu. Być może zechcielibyśmy wyznaczać przesunięcie środka trójkąta lub sześciokąta? Każde z rozwiązań ma swoje wady i zalety i każde jest w pewien sposób interesujące. Powiem nawet więcej! Dość powszechne są techniki wygenerowania kilku map terenu, każda z innymi współczynnikami tłumienia i dodanie ich do siebie (uśrednienie). Można w ten sposób otrzymać górzysty teren jako plan ogólny i jednolitą chropowatość jako plan szczegółowy. Postaram się w najbliższym czasie nieco rozwinąć ten temat.

Kategoria:HTMLJavaScriptCanvas

, 2013-12-20

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !
a z innej strony - co gdybym ciąg znaków chciał mieć rozbity nie na wiersze a na kolumny? Czyli ciąg ABCD: 1. kolumna: A, 2. kolumna: B, 3. kolumna: C, 4 kolumna: D?
Ciekawy artykuł.
Czy można za pomocą EF wysłać swoje zapytanie?
Czy lepiej do tego użyć ADO.net i DataTable?