29
\$\begingroup\$

I'm trying to create a hexagon world map for my PHP browser based strategy game. I've created a table in my database with the following data per row: id, type, x, y and occupied. Where type is the kind of tiles, which are defined in numbers. For example, 1 is grass. The map itself is 25 x 25.

I want to draw the map from the database with clickable tiles and the possibilty to navigate through the map with arrows. I don't really have a clue on how to start with this and any help would be appreciated.

\$\endgroup\$

5 Answers 5

38
\$\begingroup\$

*Edit: Fixed error in javascript that caused error on firefox *

Edit: just added ability to scale hexes to the PHP source code. Tiny 1/2 sized ones or 2x jumbo, it's all up to you :)

I wasn't quite sure how to put this all into writing, but found it was easier to just write the code for a full live example. The page (link and source below) dynamically generates a hexmap with PHP and uses Javascript to handle map clicks. Clicking on a hex highlights the hex.

The map is randomly generated, but you should be able to use your own code instead to populate the map. It is represented by a simple 2d array, with each array element holding the type of terrain present in that hex.

Click me to try the Hex Map Example

To use, click on any hex to highlight it.

Right now it's generating a 10x10 map, but you can change the map size in the PHP to be any size you want. I'm also using a set of tiles from the game Wesnoth for the example. They are 72x72 pixels in height, but the source also lets you set the size of your hex tiles.

The hexes are represented by PNG images with "outside the hex" areas set as transparent. To position each hex, I am using CSS to set each tile's absolute position, calculated by the hex grid coordinate. The map is enclosed in a single DIV, which should make it easier for you to modify the example.

Here is the full page code. You can also download the demo source (including all hex images).

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

Here is a screenshot of the example...

Hex Map Example Screenshot

Definitely could use some improvements. I noticed in a previous comment you said you were familiar with jQuery, which is good. I didn't use it here to keep things simple, but it would be pretty useful to use.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ kudos to you :) \$\endgroup\$
    – Fuu
    Dec 12, 2010 at 22:36
  • 1
    \$\begingroup\$ Definitely look at Fuu's example. You might be able to use my method of positioning hex images and determining clicks combined with his suggestion of jQuery and JSON. Oh you might look at how I overlay the highlight onto the map. It's just an image, but I've set the z-index style property to a higher number than the tiles - meaning it is drawn later. You could use the same idea to overlay a player, markers, clouds drifting by, about anything you'd want to do. \$\endgroup\$
    – Tim Holt
    Dec 12, 2010 at 22:45
  • \$\begingroup\$ Erk - didn't test it on Firefox. I've updated the code with a new bit of code to determine the click location & now works on Firefox. This is why you use jQuery, so you don't have to worry about this stuff :) \$\endgroup\$
    – Tim Holt
    Dec 13, 2010 at 6:40
  • 1
    \$\begingroup\$ just so you know in the demo you use zindex:99 on each div. This should be z-index:99, but you don't need it. \$\endgroup\$ Jun 8, 2011 at 20:01
  • 1
    \$\begingroup\$ Pity this wasn't put on github or something, as it has now experienced link rot. \$\endgroup\$
    – Kzqai
    Aug 18, 2015 at 13:17
11
\$\begingroup\$

You should write a small javascript tile layout engine that maps the database tile coordinates into a view on the web page, because this lets you outsource the cpu processing time to the players computer. It's not hard to do and you can do it in few pages of code.

So essentially you'll be writing a thin layer of PHP of which only purpose is to deliver coordinate data to the client from your database, preferably on response to AJAX call from your web page. You would likely be using a JSON data format for easy parsing, and then the map generating and displaying part would be written in javascript, and executed on client using a library like jQuery as suggested by numo16. This part is relatively easy to do and same concepts apply as in real game applications so communist ducks list of articles will explain you the hex displaying part.

For the displaying of map graphics on the players screen I would recommend that you use the CSS Sprites technique that lets you store all of your map tiles in a single file. For positioning you would use absolute coordinates for the tile image wrapped in a div, which again are in a relatively positioned container div.

If you apply jQuery click events to these image wrapping divs you can make the map clickable easily without having to manually track mouse positions as suggested. Style the container div with a overflow clipping to trim the map edges to be square instead of the jagged line hex tiles to make the map nice looking. :)

\$\endgroup\$
2
  • \$\begingroup\$ Thank you very much. I'm already familiar with jQuery as it is an amazing library! Thank you, again! \$\endgroup\$
    – fabianPas
    Dec 10, 2010 at 17:59
  • \$\begingroup\$ Definitely use jQuery - awesome language. Fuu, your answer is definitely more elegant than mine and the way I'd go if I were to give the example more time. jQuery + JSON to get map data would be the way to go. \$\endgroup\$
    – Tim Holt
    Dec 12, 2010 at 22:40
1
\$\begingroup\$

My thoughts are that as the data is read in from the database, each tile will be created as a square image with a hexagonal image map in whatever position specified by your (x,y) point. Which means you'll have to create your tile images as hexagons with a surrounding empty alpha channel, so that you can overlap your tiles a little bit to make them appear to fit together. You may want to look into jQuery to help polish up the graphics and UI side of things (animation, quicker and easier ajax, easy event handling, etc..).

\$\endgroup\$
1
\$\begingroup\$

I'm afraid I don't speak PHP, so I can't do code examples. However, here is a nice list of resources that may help you. :)

Here is a nice list of isometric/hexagonal grid articles on Gamedev; ranging from how to deal with hexagonal coords to caching tiles. (Of course, some of the stuff won't be relevant since it's mostly...what's the word? on a PC not a web browser.)

As for the graphical display, simply add transparency to a square image of a hexagonal tile.

'Clickable' would be something like:

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

I have no idea of how much in the way of user events and database-hooking-up-to PHP has, so you might have to look into other languages and frameworks for that.

I wish you luck. :)

\$\endgroup\$
1
  • \$\begingroup\$ Even in browser based games, low level programming tricks are appreciated, if not needed even more. \$\endgroup\$
    – Tor Valamo
    Oct 13, 2011 at 19:21
1
\$\begingroup\$

Following up on Fuu's approach, I have got a version working which relies purely on javascript and jQuery in the browser to render the hex map. Right now there is a function which generates a random map structure in JSON (out of two possible tiles) more or less like this:

var map = [["ocean, "desert", "desert"], ["desert, "desert", "ocean"], ["ocean, "desert", "ocean"]]

...but it's easy to imagine having the web page issue an Ajax call to get such a map structure from a server instead of generating the code itself.

The code is up on jsfiddle, from where you can also find a link to a blog post explaining it, and a github link if you're interested.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .