Jak stworzyć sześciokątną mapę świata w PHP z bazy danych dla gry strategicznej opartej na przeglądarce

28

Próbuję stworzyć sześciokątną mapę świata dla mojej strategii opartej na przeglądarce PHP. W mojej bazie danych utworzyłem tabelę z następującymi danymi dla wiersza: id, type, x, y i busy. Gdzie typ to rodzaj płytek, które są zdefiniowane liczbowo. Na przykład 1 oznacza trawę. Sama mapa ma wymiary 25 x 25.

Chcę narysować mapę z bazy danych z klikalnymi kafelkami i możliwością nawigacji po mapie za pomocą strzałek. Naprawdę nie mam pojęcia, jak zacząć od tego i każda pomoc byłaby mile widziana.

fabianPas
źródło

Odpowiedzi:

38

* Edycja: Naprawiono błąd w javascript, który powodował błąd w firefoxie *

Edycja: właśnie dodano możliwość skalowania heksów do kodu źródłowego PHP. Małe rozmiary 1/2 lub 2x duże, wszystko zależy od ciebie :)

Nie byłem do końca pewien, jak to wszystko napisać, ale odkryłem, że łatwiej jest po prostu napisać kod dla pełnego przykładu na żywo. Strona (link i źródło poniżej) dynamicznie generuje mapę heksadecypalną z PHP i używa Javascript do obsługi kliknięć mapy. Kliknięcie heksa powoduje podświetlenie heksa.

Mapa jest generowana losowo, ale zamiast tego należy wypełnić własny kod, aby wypełnić mapę. Jest reprezentowany przez prostą tablicę 2d, w której każdy element tablicy zawiera rodzaj terenu występujący na tym heksie.

Kliknij mnie, aby wypróbować przykład mapy heksadecymalnej

Aby użyć, kliknij dowolny hex, aby go podświetlić.

Obecnie generuje mapę 10x10, ale możesz zmienić rozmiar mapy w PHP na dowolny rozmiar. Na przykład używam również zestawu płytek z gry Wesnoth. Ich wysokość to 72 x 72 piksele, ale źródło pozwala również ustawić rozmiar kafelków heksadecymalnych.

Heksy są reprezentowane przez obrazy PNG z obszarami „poza heksami” ustawionymi jako przezroczyste. Do pozycjonowania każdego heksa używam CSS do ustawienia bezwzględnej pozycji każdej płytki, obliczonej na podstawie współrzędnej siatki heksadecymalnej. Mapa jest zamknięta w jednym DIV, co powinno ułatwić modyfikację przykładu.

Oto pełny kod strony. Możesz także pobrać źródło demonstracyjne (w tym wszystkie obrazy heksadecymalne).

<?php
// ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// :: HEX.PHP
// ::
// :: Author:  
// ::    Tim Holt, [email protected]
// :: Description:  
// ::    Generates a random hex map from a set of terrain types, then
// ::    outputs HTML to display the map.  Also outputs Javascript
// ::    to handle mouse clicks on the map.  When a mouse click is
// ::    detected, the hex cell clicked is determined, and then the
// ::    cell is highlighted.
// :: Usage Restrictions:  
// ::    Available for any use.
// :: Notes:
// ::    Some content (where noted) copied and/or derived from other 
// ::    sources.
// ::    Images used in this example are from the game Wesnoth.
// ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

// --- Turn up error reporting in PHP
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// --- Define some constants
$MAP_WIDTH = 10;
$MAP_HEIGHT = 10;
$HEX_HEIGHT = 72;

// --- Use this to scale the hexes smaller or larger than the actual graphics
$HEX_SCALED_HEIGHT = $HEX_HEIGHT * 1.0;
$HEX_SIDE = $HEX_SCALED_HEIGHT / 2;
?>
<html>
    <head>
        <title>Hex Map Demo</title>
        <!-- Stylesheet to define map boundary area and hex style -->
        <style type="text/css">
        body {
            /* 
            margin: 0;
            padding: 0;
            */
        }

        .hexmap {
            width: <?php echo $MAP_WIDTH * $HEX_SIDE * 1.5 + $HEX_SIDE/2; ?>px;
            height: <?php echo $MAP_HEIGHT * $HEX_SCALED_HEIGHT + $HEX_SIDE; ?>px;
            position: relative;
            background: #000;
        }

        .hex-key-element {
            width: <?php echo $HEX_HEIGHT * 1.5; ?>px;
            height: <?php echo $HEX_HEIGHT * 1.5; ?>px;
            border: 1px solid #fff;
            float: left;
            text-align: center;
        }

        .hex {
            position: absolute;
            width: <?php echo $HEX_SCALED_HEIGHT ?>;
            height: <?php echo $HEX_SCALED_HEIGHT ?>;
        }
        </style>
    </head>
    <body>
    <script>

function handle_map_click(event) {
    // ----------------------------------------------------------------------
    // --- This function gets a mouse click on the map, converts the click to
    // --- hex map coordinates, then moves the highlight image to be over the
    // --- clicked on hex.
    // ----------------------------------------------------------------------

    // ----------------------------------------------------------------------
    // --- Determine coordinate of map div as we want the click coordinate as
    // --- we want the mouse click relative to this div.
    // ----------------------------------------------------------------------

    // ----------------------------------------------------------------------
    // --- Code based on http://www.quirksmode.org/js/events_properties.html
    // ----------------------------------------------------------------------
    var posx = 0;
    var posy = 0;
    if (event.pageX || event.pageY) {
        posx = event.pageX;
        posy = event.pageY;
    } else if (event.clientX || e.clientY) {
        posx = event.clientX + document.body.scrollLeft
            + document.documentElement.scrollLeft;
        posy = event.clientY + document.body.scrollTop
            + document.documentElement.scrollTop;
    }
    // --- Apply offset for the map div
    var map = document.getElementById('hexmap');
    posx = posx - map.offsetLeft;
    posy = posy - map.offsetTop;
    //console.log ("posx = " + posx + ", posy = " + posy);

    // ----------------------------------------------------------------------
    // --- Convert mouse click to hex grid coordinate
    // --- Code is from http://www-cs-students.stanford.edu/~amitp/Articles/GridToHex.html
    // ----------------------------------------------------------------------
    var hex_height = <?php echo $HEX_SCALED_HEIGHT; ?>;
    x = (posx - (hex_height/2)) / (hex_height * 0.75);
    y = (posy - (hex_height/2)) / hex_height;
    z = -0.5 * x - y;
    y = -0.5 * x + y;

    ix = Math.floor(x+0.5);
    iy = Math.floor(y+0.5);
    iz = Math.floor(z+0.5);
    s = ix + iy + iz;
    if (s) {
        abs_dx = Math.abs(ix-x);
        abs_dy = Math.abs(iy-y);
        abs_dz = Math.abs(iz-z);
        if (abs_dx >= abs_dy && abs_dx >= abs_dz) {
            ix -= s;
        } else if (abs_dy >= abs_dx && abs_dy >= abs_dz) {
            iy -= s;
        } else {
            iz -= s;
        }
    }

    // ----------------------------------------------------------------------
    // --- map_x and map_y are the map coordinates of the click
    // ----------------------------------------------------------------------
    map_x = ix;
    map_y = (iy - iz + (1 - ix %2 ) ) / 2 - 0.5;

    // ----------------------------------------------------------------------
    // --- Calculate coordinates of this hex.  We will use this
    // --- to place the highlight image.
    // ----------------------------------------------------------------------
    tx = map_x * <?php echo $HEX_SIDE ?> * 1.5;
    ty = map_y * <?php echo $HEX_SCALED_HEIGHT ?> + (map_x % 2) * (<?php echo $HEX_SCALED_HEIGHT ?> / 2);

    // ----------------------------------------------------------------------
    // --- Get the highlight image by ID
    // ----------------------------------------------------------------------
    var highlight = document.getElementById('highlight');

    // ----------------------------------------------------------------------
    // --- Set position to be over the clicked on hex
    // ----------------------------------------------------------------------
    highlight.style.left = tx + 'px';
    highlight.style.top = ty + 'px';
}
</script>
<?php

// ----------------------------------------------------------------------
// --- This is a list of possible terrain types and the
// --- image to use to render the hex.
// ----------------------------------------------------------------------
    $terrain_images = array("grass"    => "grass-r1.png",
                            "dirt"     => "dirt.png",
                            "water"    => "coast.png",
                            "path"     => "stone-path.png",
                            "swamp"    => "water-tile.png",
                            "desert"   => "desert.png",
                            "oasis"    => "desert-oasis-tile.png",
                            "forest"   => "forested-mixed-summer-hills-tile.png",
                            "hills"    => "hills-variation3.png",
                            "mountain" => "mountain-tile.png");

    // ==================================================================

    function generate_map_data() {
        // -------------------------------------------------------------
        // --- Fill the $map array with values identifying the terrain
        // --- type in each hex.  This example simply randomizes the
        // --- contents of each hex.  Your code could actually load the
        // --- values from a file or from a database.
        // -------------------------------------------------------------
        global $MAP_WIDTH, $MAP_HEIGHT;
        global $map, $terrain_images;
        for ($x=0; $x<$MAP_WIDTH; $x++) {
            for ($y=0; $y<$MAP_HEIGHT; $y++) {
                // --- Randomly choose a terrain type from the terrain
                // --- images array and assign to this coordinate.
                $map[$x][$y] = array_rand($terrain_images);
            }
        }
    }

    // ==================================================================

    function render_map_to_html() {
        // -------------------------------------------------------------
        // --- This function renders the map to HTML.  It uses the $map
        // --- array to determine what is in each hex, and the 
        // --- $terrain_images array to determine what type of image to
        // --- draw in each cell.
        // -------------------------------------------------------------
        global $MAP_WIDTH, $MAP_HEIGHT;
        global $HEX_HEIGHT, $HEX_SCALED_HEIGHT, $HEX_SIDE;
        global $map, $terrain_images;

        // -------------------------------------------------------------
        // --- Draw each hex in the map
        // -------------------------------------------------------------
        for ($x=0; $x<$MAP_WIDTH; $x++) {
            for ($y=0; $y<$MAP_HEIGHT; $y++) {
                // --- Terrain type in this hex
                $terrain = $map[$x][$y];

                // --- Image to draw
                $img = $terrain_images[$terrain];

                // --- Coordinates to place hex on the screen
                $tx = $x * $HEX_SIDE * 1.5;
                $ty = $y * $HEX_SCALED_HEIGHT + ($x % 2) * $HEX_SCALED_HEIGHT / 2;

                // --- Style values to position hex image in the right location
                $style = sprintf("left:%dpx;top:%dpx", $tx, $ty);

                // --- Output the image tag for this hex
                print "<img src='$img' alt='$terrain' class='hex' style='zindex:99;$style'>\n";
            }
        }
    }

    // -----------------------------------------------------------------
    // --- Generate the map data
    // -----------------------------------------------------------------
    generate_map_data();
    ?>

    <h1>Hex Map Example</h1>
    <a href='index.phps'>View page source</a><br/>
    <a href='hexmap.zip'>Download source and all images</a>

    <!-- Render the hex map inside of a div block -->
    <div id='hexmap' class='hexmap' onclick='handle_map_click(event);'>
        <?php render_map_to_html(); ?>
        <img id='highlight' class='hex' src='hex-highlight.png' style='zindex:100;'>
    </div>

    <!--- output a list of all terrain types -->
    <br/>
    <?php 
        reset ($terrain_images);
        while (list($type, $img) = each($terrain_images)) {
            print "<div class='hex-key-element'><img src='$img' alt='$type'><br/>$type</div>";
        }
    ?>
    </body>
</html>

Oto zrzut ekranu z przykładu ...

Zrzut ekranu przykładowej mapy heksadecymalnej

Zdecydowanie przydałoby się kilka ulepszeń. W poprzednim komentarzu zauważyłem, że znasz jQuery, co jest dobre. Nie użyłem go tutaj, aby uprościć sprawę, ale byłoby całkiem użyteczne w użyciu.

Tim Holt
źródło
1
Uznanie dla ciebie :)
Fuu,
1
Zdecydowanie spójrz na przykład Fuu. Być może będziesz mógł użyć mojej metody pozycjonowania obrazów szesnastkowych i określania kliknięć w połączeniu z jego sugestią dotyczącą jQuery i JSON. Och, możesz spojrzeć na to, jak nakładam wyróżnienie na mapę. To tylko obraz, ale ustawiłem właściwość stylu z-index na wyższą liczbę niż płytki - co oznacza, że ​​zostanie narysowany później. Możesz użyć tego samego pomysłu do nałożenia gracza, znaczników, przepływających chmur, na wszystko, co chcesz zrobić.
Tim Holt,
Erk - nie testowałem tego na Firefoxie. Zaktualizowałem kod o nowy bit, aby określić lokalizację kliknięcia i teraz działa w przeglądarce Firefox. Dlatego używasz jQuery, więc nie musisz się tym martwić :)
Tim Holt
1
żebyś wiedział, że w wersji demo używasz zindex: 99 na każdym divie. Powinien to być indeks Z: 99, ale go nie potrzebujesz.
corymathews,
@corymathews Właściwie wygląda to na początek implementacji uwzględniającej rzeczy, które „wychodzą” z kafelków, takie jak drzewo po prawej stronie kafelka lasu. Potrzebuje zmiany indeksu, aby inne kafelki nie zachodziły na drzewo (co jest bieżącym zachowaniem).
Jonathan Connell
11

Powinieneś napisać mały silnik układu kafelków javascript, który odwzorowuje współrzędne kafelków bazy danych na widok na stronie internetowej, ponieważ pozwala to na outsourcing czasu przetwarzania procesora na komputer gracza. Nie jest to trudne i możesz to zrobić na kilku stronach kodu.

Zasadniczo będziesz pisać cienką warstwę PHP, której jedynym celem jest dostarczanie klientowi współrzędnych z bazy danych, najlepiej w odpowiedzi na wywołanie AJAX ze strony internetowej. Prawdopodobnie użyłbyś formatu danych JSON do łatwego parsowania, a następnie część generująca i wyświetlająca mapę byłaby zapisywana w javascript i wykonywana na kliencie przy użyciu biblioteki takiej jak jQuery, jak sugeruje numo16. Ta część jest stosunkowo łatwa do wykonania i obowiązują te same koncepcje, co w prawdziwych aplikacjach do gier, więc lista artykułów komunistycznych kaczek wyjaśni ci część wyświetlającą heks.

Do wyświetlania grafiki mapy na ekranie graczy polecam użycie techniki CSS Sprites , która pozwala przechowywać wszystkie kafelki mapy w jednym pliku. Do pozycjonowania użyłbyś współrzędnych bezwzględnych dla obrazu kafelka owiniętego w div, które ponownie znajdują się w stosunkowo pozycjonowanym div div kontenera.

Jeśli zastosujesz zdarzenia kliknięcia jQuery do tych div zawijania obrazu, możesz łatwo klikać mapę bez konieczności ręcznego śledzenia pozycji myszy, zgodnie z sugestią. Nadaj styl div kontenera z wycięciem przelewu, aby przyciąć krawędzie mapy tak, aby były kwadratowe zamiast poszarpanych linii sześciokątnych, aby mapa wyglądała ładnie. :)

Fuu
źródło
Dziękuję Ci bardzo. Znam już jQuery, ponieważ jest to niesamowita biblioteka! Jeszcze raz dziękuję!
fabianPas,
Zdecydowanie użyj jQuery - niesamowitego języka. Fuu, twoja odpowiedź jest zdecydowanie bardziej elegancka niż moja, i pójdę, gdybym dał przykład więcej czasu. jQuery + JSON, aby uzyskać dane mapy, byłoby dobrym rozwiązaniem.
Tim Holt,
1

Myślę, że w miarę wczytywania danych z bazy danych każdy kafelek zostanie utworzony jako kwadratowy obraz z sześciokątną mapą obrazu w dowolnej pozycji określonej przez punkt (x, y). Oznacza to, że będziesz musiał utworzyć obrazy kafelków jako sześciokąty z otaczającym pustym kanałem alfa, abyś mógł trochę nakładać się na kafelki, aby wyglądały na pasujące do siebie. Możesz zajrzeć do jQuery, aby pomóc dopracować grafikę i elementy interfejsu użytkownika (animacja, szybsze i łatwiejsze ajax, łatwa obsługa zdarzeń itp.).

numo16
źródło
1

Obawiam się, że nie mówię w PHP, więc nie mogę tworzyć przykładów kodu. Oto jednak ładna lista zasobów, które mogą ci pomóc. :)

Oto ładna lista artykułów z siatki izometrycznej / heksagonalnej na Gamedevie; począwszy od tego, jak radzić sobie z sześciokątnymi sznurkami, po płytki do buforowania . (Oczywiście niektóre rzeczy nie będą istotne, ponieważ są to głównie ... jak to jest? Na komputerze, a nie w przeglądarce internetowej.)

Jeśli chodzi o wyświetlacz graficzny, po prostu dodaj przezroczystość do kwadratowego obrazu sześciokątnej płytki.

„Klikalne” byłoby czymś w rodzaju:

if mouse button down on app:  
take screen coordinates of mouse  
Compare to screen coordinates of tiles

Nie mam pojęcia, ile ma przeszkód w zdarzeniach użytkowników i podłączaniu bazy danych do PHP, więc być może będziesz musiał zajrzeć do innych języków i struktur.

Życzę szczęścia. :)

Kaczka komunistyczna
źródło
Nawet w grach przeglądarkowych mile widziane są sztuczki programowania na niskim poziomie, jeśli nie są potrzebne jeszcze bardziej.
Tor Valamo
1

Kontynuując podejście Fuu, mam działającą wersję, która opiera się wyłącznie na javascript i jQuery w przeglądarce do renderowania mapy heksadecymalnej. W tej chwili istnieje funkcja, która generuje losową strukturę mapy w JSON (z dwóch możliwych płytek) mniej więcej tak:

var map = [[„ocean,„ pustynia ”,„ pustynia ”], [„ pustynia ”,„ pustynia ”,„ ocean ”], [„ ocean, „pustynia”, „ocean”]]

... ale łatwo sobie wyobrazić, że strona internetowa wywołuje wywołanie Ajax w celu uzyskania takiej struktury mapy z serwera zamiast generowania samego kodu.

Kod znajduje się na stronie jsfiddle , skąd można również znaleźć link do objaśniającego go postu na blogu oraz link do github, jeśli jesteś zainteresowany.

Tim Gilbert
źródło