Cómo crear un mapa del mundo hexagonal en PHP desde una base de datos para un juego de estrategia basado en navegador

28

Estoy tratando de crear un mapa mundial hexagonal para mi juego de estrategia basado en el navegador PHP. He creado una tabla en mi base de datos con los siguientes datos por fila: id, tipo, x, y y ocupada. Donde tipo es el tipo de mosaicos, que se definen en números. Por ejemplo, 1 es hierba. El mapa en sí es 25 x 25.

Quiero dibujar el mapa de la base de datos con mosaicos en los que se pueda hacer clic y la posibilidad de navegar por el mapa con flechas. Realmente no tengo ni idea de cómo comenzar con esto y agradecería cualquier ayuda.

fabianPas
fuente

Respuestas:

38

* Editar: se corrigió el error en JavaScript que causaba un error en Firefox *

Editar: solo se agregó la capacidad de escalar hexágonos al código fuente PHP. Pequeños de 1/2 tamaño o 2x jumbo, todo depende de ti :)

No estaba muy seguro de cómo poner todo esto por escrito, pero descubrí que era más fácil escribir el código para un ejemplo completo en vivo. La página (enlace y fuente a continuación) genera dinámicamente un mapa hexadecimal con PHP y usa Javascript para manejar los clics del mapa. Al hacer clic en un hex, resalta el hex.

El mapa se genera aleatoriamente, pero debería poder usar su propio código para rellenar el mapa. Está representado por una simple matriz 2d, con cada elemento de la matriz que contiene el tipo de terreno presente en ese hex.

Haga clic en mí para probar el ejemplo de mapa hexadecimal

Para usar, haga clic en cualquier hex para resaltarlo.

En este momento está generando un mapa de 10x10, pero puede cambiar el tamaño del mapa en PHP para que sea del tamaño que desee. También estoy usando un conjunto de fichas del juego Wesnoth para el ejemplo. Tienen 72x72 píxeles de altura, pero la fuente también le permite establecer el tamaño de sus mosaicos hexagonales.

Los hexes están representados por imágenes PNG con áreas "fuera del hex" establecidas como transparentes. Para colocar cada hexágono, estoy usando CSS para establecer la posición absoluta de cada mosaico, calculada por la coordenada de la cuadrícula hexadecimal. El mapa está encerrado en un solo DIV, lo que debería facilitarle la modificación del ejemplo.

Aquí está el código de la página completa. También puede descargar la fuente de demostración (incluidas todas las imágenes hexadecimales).

<?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>

Aquí hay una captura de pantalla del ejemplo ...

Captura de pantalla de ejemplo de mapa hexadecimal

Definitivamente podría usar algunas mejoras. Noté en un comentario anterior que dijiste que estabas familiarizado con jQuery, lo cual es bueno. No lo usé aquí para simplificar las cosas, pero sería bastante útil de usar.

Tim Holt
fuente
1
felicitaciones :)
Fuu
1
Definitivamente mira el ejemplo de Fuu. Es posible que pueda usar mi método de posicionar imágenes hexadecimales y determinar los clics combinados con su sugerencia de jQuery y JSON. Oh, podrías ver cómo superpongo el resaltado en el mapa. Es solo una imagen, pero he establecido la propiedad de estilo de índice z en un número mayor que los mosaicos, lo que significa que se dibuja más tarde. Podrías usar la misma idea para superponer a un jugador, marcadores, nubes a la deriva, sobre cualquier cosa que quieras hacer.
Tim Holt
Erk: no lo probé en Firefox. He actualizado el código con un nuevo código para determinar la ubicación del clic y ahora funciona en Firefox. Es por eso que usas jQuery, para que no tengas que preocuparte por estas cosas :)
Tim Holt
1
solo para que sepas en la demostración que usas zindex: 99 en cada div. Esto debería ser z-index: 99, pero no lo necesita.
corymathews
@corymathews En realidad, parece el comienzo de una implementación para tener en cuenta las cosas que "salen" de los mosaicos, como el árbol a la derecha del mosaico del bosque. Necesita que se cambie su índice para que otros mosaicos no se superpongan al árbol (que es el comportamiento actual).
Jonathan Connell
11

Debería escribir un pequeño motor de diseño de mosaico de JavaScript que mapee las coordenadas del mosaico de la base de datos en una vista en la página web, ya que esto le permite externalizar el tiempo de procesamiento de la CPU a la computadora del jugador. No es difícil de hacer y puedes hacerlo en pocas páginas de código.

Esencialmente, estará escribiendo una capa delgada de PHP cuyo único propósito es entregar datos de coordenadas al cliente desde su base de datos, preferiblemente en respuesta a una llamada AJAX desde su página web. Probablemente usaría un formato de datos JSON para un análisis fácil, y luego el mapa que genera y muestra la parte se escribiría en javascript y se ejecutaría en el cliente usando una biblioteca como jQuery como lo sugiere numo16. Esta parte es relativamente fácil de hacer y se aplican los mismos conceptos que en las aplicaciones de juegos reales, por lo que la lista de artículos de los patos comunistas le explicará la parte de visualización hexadecimal.

Para la visualización de gráficos de mapas en la pantalla de los jugadores, recomendaría que utilice la técnica CSS Sprites que le permite almacenar todos sus mosaicos de mapas en un solo archivo. Para el posicionamiento, usaría coordenadas absolutas para la imagen de mosaico envuelta en un div, que nuevamente está en un div de contenedor relativamente posicionado.

Si aplica eventos de clic jQuery a estos divs de ajuste de imagen, puede hacer que se pueda hacer clic en el mapa fácilmente sin tener que rastrear manualmente las posiciones del mouse como se sugiere. Diseñe el contenedor div con un recorte de desbordamiento para recortar los bordes del mapa para que sean cuadrados en lugar de los mosaicos hexagonales de líneas irregulares para que el mapa se vea bien. :)

Fuu
fuente
Muchas gracias. ¡Ya estoy familiarizado con jQuery ya que es una biblioteca increíble! ¡Gracias de nuevo!
fabianPas
Definitivamente use jQuery, un lenguaje increíble. Fuu, tu respuesta es definitivamente más elegante que la mía y el camino que seguiría si tuviera que dar el ejemplo más tiempo. jQuery + JSON para obtener datos del mapa sería el camino a seguir.
Tim Holt
1

Creo que a medida que los datos se leen desde la base de datos, cada mosaico se creará como una imagen cuadrada con un mapa de imagen hexagonal en cualquier posición especificada por su punto (x, y). Lo que significa que tendrá que crear sus imágenes de mosaico como hexágonos con un canal alfa vacío circundante, para que pueda superponer sus mosaicos un poco y hacer que parezcan encajar. Es posible que desee consultar jQuery para ayudar a pulir los gráficos y el lado de la interfaz de usuario (animación, ajax más rápido y fácil, manejo fácil de eventos, etc.).

numo16
fuente
1

Me temo que no hablo PHP, así que no puedo hacer ejemplos de código. Sin embargo, aquí hay una buena lista de recursos que pueden ayudarlo. :)

Aquí hay una buena lista de artículos de cuadrícula isométrica / hexagonal en Gamedev; que van desde cómo lidiar con los cordones hexagonales hasta el almacenamiento en caché de fichas . (Por supuesto, algunas de las cosas no serán relevantes ya que son principalmente ... ¿cuál es la palabra? En una PC, no en un navegador web).

En cuanto a la visualización gráfica, simplemente agregue transparencia a una imagen cuadrada de un mosaico hexagonal.

'Clickable' sería algo así como:

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

No tengo idea de cuánto tienen los eventos de usuario y la conexión de bases de datos a PHP, por lo que es posible que tenga que buscar otros lenguajes y marcos para eso.

Te deseo suerte. :)

El pato comunista
fuente
Incluso en juegos basados ​​en navegador, se aprecian trucos de programación de bajo nivel, si no se necesitan aún más.
Tor Valamo
1

Siguiendo el enfoque de Fuu, tengo una versión funcionando que se basa exclusivamente en javascript y jQuery en el navegador para representar el mapa hexadecimal. En este momento hay una función que genera una estructura de mapa aleatorio en JSON (de dos mosaicos posibles) más o menos así:

mapa var = [["océano," desierto "," desierto "], [" desierto, "desierto", "océano"], ["océano," desierto "," océano "]]

... pero es fácil imaginar que la página web emita una llamada Ajax para obtener dicha estructura de mapa de un servidor en lugar de generar el código en sí.

El código está disponible en jsfiddle , desde donde también puede encontrar un enlace a una publicación de blog que lo explica, y un enlace de github si está interesado.

Tim Gilbert
fuente