Cách tạo bản đồ thế giới hình lục giác trong PHP từ cơ sở dữ liệu cho trò chơi chiến lược dựa trên trình duyệt


28

Tôi đang cố gắng tạo một bản đồ thế giới hình lục giác cho trò chơi chiến lược dựa trên trình duyệt PHP của mình. Tôi đã tạo một bảng trong cơ sở dữ liệu của mình với dữ liệu sau mỗi hàng: id, type, x, y và chiếm. Trong đó loại là loại gạch, được xác định bằng số. Ví dụ: 1 là cỏ. Bản đồ là 25 x 25.

Tôi muốn vẽ bản đồ từ cơ sở dữ liệu bằng các ô có thể nhấp và khả năng điều hướng qua bản đồ bằng mũi tên. Tôi thực sự không có manh mối về cách bắt đầu với điều này và bất kỳ trợ giúp sẽ được đánh giá cao.

Câu trả lời:


38

* Chỉnh sửa: Đã sửa lỗi javascript gây ra lỗi trên firefox *

Chỉnh sửa: chỉ cần thêm khả năng chia tỷ lệ các hình lục giác cho mã nguồn PHP. Những cái nhỏ cỡ 1/2 hoặc gấp đôi jumbo, tất cả tùy thuộc vào bạn :)

Tôi không chắc chắn làm thế nào để viết tất cả những điều này vào văn bản, nhưng thấy việc viết mã cho một ví dụ trực tiếp hoàn toàn dễ dàng hơn. Trang (liên kết và nguồn bên dưới) tự động tạo một hexmap với PHP và sử dụng Javascript để xử lý các nhấp chuột trên bản đồ. Nhấp vào một hình lục giác làm nổi bật hình lục giác.

Bản đồ được tạo ngẫu nhiên, nhưng bạn sẽ có thể sử dụng mã của riêng mình để thay vào đó để điền vào bản đồ. Nó được biểu diễn bằng một mảng 2d đơn giản, với mỗi phần tử mảng chứa kiểu địa hình có trong hex đó.

Nhấp vào tôi để thử Ví dụ về Bản đồ Hex

Để sử dụng, nhấp vào bất kỳ hex để làm nổi bật nó.

Ngay bây giờ, nó đang tạo bản đồ 10x10, nhưng bạn có thể thay đổi kích thước bản đồ trong PHP thành bất kỳ kích thước nào bạn muốn. Tôi cũng đang sử dụng một bộ gạch từ trò chơi Wesnoth làm ví dụ. Chúng có chiều cao 72x72 pixel, nhưng nguồn cũng cho phép bạn đặt kích thước của các ô lục giác.

Các hình lục giác được thể hiện bằng hình ảnh PNG với các khu vực "bên ngoài hình lục giác" được đặt trong suốt. Để định vị từng hex, tôi đang sử dụng CSS để đặt vị trí tuyệt đối của từng ô, được tính theo tọa độ lưới hex. Bản đồ được đặt trong một DIV duy nhất, điều này sẽ giúp bạn dễ dàng sửa đổi ví dụ hơn.

Đây là mã trang đầy đủ. Bạn cũng có thể tải xuống nguồn demo (bao gồm tất cả các hình ảnh hex).

<?php
// ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// :: HEX.PHP
// ::
// :: Author:  
// ::    Tim Holt, tim.m.holt@gmail.com
// :: 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>

Đây là một ảnh chụp màn hình của ví dụ ...

Hex Map Ví dụ Ảnh chụp màn hình

Chắc chắn có thể sử dụng một số cải tiến. Tôi nhận thấy trong một bình luận trước đó bạn nói rằng bạn đã quen thuộc với jQuery, điều này rất tốt. Tôi đã không sử dụng nó ở đây để giữ cho mọi thứ đơn giản, nhưng nó sẽ khá hữu ích để sử dụng.


1
thanh danh cho bạn :)
Fuu

1
Chắc chắn nhìn vào ví dụ của Fuu. Bạn có thể sử dụng phương pháp định vị hình ảnh hex của tôi và xác định các lần nhấp kết hợp với đề xuất của anh ấy về jQuery và JSON. Ồ, bạn có thể nhìn vào cách tôi phủ lớp tô sáng lên bản đồ. Đây chỉ là một hình ảnh, nhưng tôi đã đặt thuộc tính kiểu z-index thành số cao hơn các ô - có nghĩa là nó được vẽ sau. Bạn có thể sử dụng cùng một ý tưởng để phủ lên một người chơi, điểm đánh dấu, những đám mây trôi qua, về bất cứ điều gì bạn muốn làm.
Tim Holt

Erk - đã không kiểm tra nó trên Firefox. Tôi đã cập nhật mã với một đoạn mã mới để xác định vị trí nhấp và hiện hoạt động trên Firefox. Đây là lý do tại sao bạn sử dụng jQuery, vì vậy bạn không phải lo lắng về công cụ này :)
Tim Holt

1
chỉ để bạn biết trong bản demo bạn sử dụng zindex: 99 trên mỗi div. Đây phải là chỉ số z: 99, nhưng bạn không cần nó.
corymathews

@corymathews Trên thực tế, có vẻ như bắt đầu thực hiện để tính đến những thứ 'đi ra' của gạch, giống như cái cây bên phải của ngói rừng. Nó cần chỉ số của nó thay đổi để các ô khác không chồng lên cây (đó là hành vi hiện tại).
Jonathan Connell

11

Bạn nên viết một công cụ bố cục ô javascript nhỏ để ánh xạ tọa độ ô cơ sở dữ liệu vào chế độ xem trên trang web, vì điều này cho phép bạn thuê ngoài thời gian xử lý cpu cho máy tính của người chơi. Không khó để làm và bạn có thể làm điều đó trong một vài trang mã.

Vì vậy, về cơ bản, bạn sẽ viết một lớp PHP mỏng, trong đó mục đích duy nhất là cung cấp dữ liệu tọa độ cho máy khách từ cơ sở dữ liệu của bạn, tốt nhất là đáp ứng cuộc gọi AJAX từ trang web của bạn. Bạn có thể đang sử dụng định dạng dữ liệu JSON để phân tích cú pháp dễ dàng, sau đó phần tạo và hiển thị bản đồ sẽ được viết bằng javascript và được thực thi trên máy khách bằng thư viện như jQuery như được đề xuất bởi numo16. Phần này tương đối dễ thực hiện và các khái niệm tương tự được áp dụng như trong các ứng dụng trò chơi thực tế, vì vậy danh sách các bài báo của cộng sản sẽ giải thích cho bạn phần hiển thị hex.

Để hiển thị đồ họa bản đồ trên màn hình trình phát, tôi khuyên bạn nên sử dụng kỹ thuật Sprites CSS cho phép bạn lưu trữ tất cả các ô bản đồ của mình trong một tệp. Để định vị, bạn sẽ sử dụng tọa độ tuyệt đối cho hình ảnh xếp được bọc trong div, một lần nữa nằm trong div container tương đối được định vị.

Nếu bạn áp dụng các sự kiện nhấp chuột jQuery cho các div gói hình ảnh này, bạn có thể dễ dàng tạo bản đồ có thể nhấp mà không cần phải theo dõi thủ công các vị trí chuột như đề xuất. Tạo kiểu cho div container bằng cách cắt tràn để cắt các cạnh của bản đồ thành hình vuông thay vì các ô lục giác đường răng cưa để làm cho bản đồ trông đẹp mắt. :)


Cảm ơn nhiều. Tôi đã quen thuộc với jQuery vì đây là một thư viện tuyệt vời! Cám ơn bạn một lần nữa!
fabianPas

Chắc chắn sử dụng jQuery - ngôn ngữ tuyệt vời. Fuu, câu trả lời của bạn chắc chắn thanh lịch hơn của tôi và con đường tôi sẽ đi nếu tôi muốn đưa ra ví dụ nhiều thời gian hơn. jQuery + JSON để lấy dữ liệu bản đồ sẽ là cách tốt nhất.
Tim Holt

1

Tôi nghĩ rằng khi dữ liệu được đọc từ cơ sở dữ liệu, mỗi ô sẽ được tạo dưới dạng hình vuông với bản đồ hình lục giác ở bất kỳ vị trí nào được chỉ định bởi điểm (x, y) của bạn. Điều đó có nghĩa là bạn sẽ phải tạo hình ảnh xếp của mình dưới dạng hình lục giác với kênh alpha trống xung quanh, để bạn có thể chồng lên các ô của mình một chút để làm cho chúng có vẻ khớp với nhau. Bạn có thể muốn xem xét jQuery để giúp cải thiện đồ họa và giao diện người dùng của mọi thứ (hoạt hình, ajax nhanh hơn và dễ dàng hơn, xử lý sự kiện dễ dàng, v.v.).


1

Tôi sợ rằng tôi không nói được PHP, vì vậy tôi không thể làm ví dụ về mã. Tuy nhiên, đây là một danh sách tốt đẹp các tài nguyên có thể giúp bạn. :)

Dưới đây là một danh sách tốt đẹp các bài viết lưới isometric / lục giác trên Gamedev; khác nhau, từ cách đối phó với các cuộn dây lục giác đến các ô đệm . (Tất nhiên, một số nội dung sẽ không liên quan vì chủ yếu là ... từ đó là gì? Trên PC không phải là trình duyệt web.)

Đối với màn hình đồ họa, chỉ cần thêm độ trong suốt cho hình ảnh vuông của hình lục giác.

'Clickable' sẽ là một cái gì đó như:

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

Tôi không biết về cách thức các sự kiện của người dùng và cơ sở dữ liệu kết nối với PHP có bao nhiêu, vì vậy bạn có thể phải xem xét các ngôn ngữ và khung khác cho điều đó.

Chúc các bạn may mắn. :)


Ngay cả trong các trò chơi dựa trên trình duyệt, các thủ thuật lập trình cấp thấp được đánh giá cao, nếu không cần thiết hơn nữa.
Tor Valamo

1

Theo cách tiếp cận của Fuu, tôi đã có một phiên bản hoạt động hoàn toàn dựa trên javascript và jQuery trong trình duyệt để hiển thị bản đồ hex. Ngay bây giờ có một hàm tạo cấu trúc bản đồ ngẫu nhiên trong JSON (trong số hai ô có thể) ít nhiều như thế này:

var map = [["đại dương," sa mạc "," sa mạc "], [" sa mạc, "sa mạc", "đại dương"], ["đại dương," sa mạc "," đại dương "]]

... nhưng thật dễ dàng để tưởng tượng rằng trang web đưa ra một cuộc gọi Ajax để có được cấu trúc bản đồ như vậy từ máy chủ thay vì tự tạo mã.

Mã này có trên jsfiddle , từ đó bạn cũng có thể tìm thấy một liên kết đến một bài đăng trên blog giải thích nó và một liên kết github nếu bạn quan tâm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.