使用javascript渲染瓷砖地图

时间:2014-02-18 04:33:45

标签: javascript html5-canvas render tile createjs

我正在寻找关于采用如下瓷砖地图的示例实施思路的逻辑理解:

http://thorsummoner.github.io/old-html-tabletop-test/pallete/tilesets/fullmap/scbw_tiles.png

以合理的方式渲染,例如:

http://thorsummoner.github.io/old-html-tabletop-test/

我看到所有的瓷砖都在那里,但我不明白它们是如何以形成形状的方式放置的。

到目前为止,我对渲染图块的理解很简单,而且非常手动。循环遍历地图数组,其中有数字(1,2,3,无论如何),渲染指定的图块。

var mapArray = [
    [0, 0, 0, 0 ,0],
    [0, 1, 0, 0 ,0],
    [0, 0, 0, 0 ,0],
    [0, 0, 0, 0 ,0],
    [0, 0, 1, 1 ,0]
];

function drawMap() {
    background = new createjs.Container();      
    for (var y = 0; y < mapArray.length; y++) {
        for (var x = 0; x < mapArray[y].length; x++) {
            if (parseInt(mapArray[y][x]) == 0) {
                var tile = new createjs.Bitmap('images/tile.png');
            }
            if (parseInt(mapArray[y][x]) == 1) {
                var tile = new createjs.Bitmap('images/tile2.png'); 
            }
            tile.x = x * 28;
            tile.y = y * 28;
            background.addChild(tile);
        }
    }
    stage.addChild(background);     
}   

得到我:

enter image description here

但这意味着我必须手动确定每个瓷砖在阵列中的位置,以便形成逻辑形状(岩层,草地等)

显然,上面制作github代码的人使用了不同的方法。理解逻辑(简单的伪代码)的任何指导都会非常有用

1 个答案:

答案 0 :(得分:31)

那里没有任何逻辑。

如果您检查页面的来源,您会看到正文中的最后一个脚本标记有一个巨大的图块坐标数组。

在这个例子中没有任何魔术可以证明一个聪明的&#34;用于确定如何形成形状的系统。

现在,那就是说,有这样的事情......但他们并不简单。

更简单,更易于管理,是地图编辑器。


平铺编辑

开箱即用:

有很多方法可以做到这一点...有免费或廉价的程序可以让你绘制瓷砖,然后吐出XML或JSON或CSV或任何给定的程序支持/导出。

Tiled http://mapeditor.org)就是这样一个例子 还有其他人,但是Tiled是我能想到的第一个,是免费的,而且实际上相当不错。

<强>的优点:
直接的好处是,您可以获得一个应用程序,可以加载图像切片,并将其绘制到地图中 这些应用程序甚至可能支持添加碰撞层和实体层(放置[2,1]的敌人,[3,5]的加电和#34;伤害玩家&#34;触发器,岩浆)。

<强>缺点: ...缺点是您需要确切知道这些文件的格式,以便您可以将它们读入您的游戏引擎 现在,这些系统的输出是相对标准化的...这样你就可以将地图数据插入到不同的游戏引擎中(不管怎么说?),而游戏引擎并不是全部使用完全相同的tile文件,大多数优秀的tile-editors允许导出为多种格式(有些格式可以让你定义自己的格式)。

...所以说,替代(或者真的,相同的解决方案,只是手工制作),将是创建自己的拼贴编辑器。

<强> DIY
您可以在Canvas中创建它,就像创建引擎来绘制图块一样容易 关键的区别在于你有你的瓷砖地图(比如StarCr中的tilemap .png ......呃...&#34;发现艺术&#34;来自示例,那里)。
找到瓷砖的坐标并在与该指数匹配的世界坐标上绘制它们,而不是循环遍历数组,你要做的是从地图中选择一个瓷砖(比如在MS Paint中选择颜色),然后在哪里单击(或拖动),找出与之相关的数组点,并将该索引设置为等于该图块。

<强>的优点:
天空才是极限;你可以做任何你想要的东西,使它适合你想要使用的任何文件格式,并让它处理你想要扔的任何疯狂的东西......
缺点:
......当然,这意味着你必须自己制作它,并定义你想要使用的文件格式,并编写逻辑来处理所有这些想法......

基本实施
虽然我通常会尝试使这个整洁,并且JS范例友好,但这会产生大量代码。
因此,我将尝试表示它应该分解为单独的模块。

// assuming images are already loaded properly
// and have fired onload events, which you've listened for
// so that there are no surprises, when your engine tries to
// paint something that isn't there, yet


// this should all be wrapped in a module that deals with
// loading tile-maps, selecting the tile to "paint" with,
// and generating the data-format for the tile, for you to put into the array
// (or accepting plug-in data-formatters, to do so)
var selected_tile = null,
    selected_tile_map = get_tile_map(), // this would be an image with your tiles
    tile_width  = 64, // in image-pixels, not canvas/screen-pixels
    tile_height = 64, // in image-pixels, not canvas/screen-pixels

    num_tiles_x = selected_tile_map.width  / tile_width,
    num_tiles_y = selected_tile_map.height / tile_height,

    select_tile_num_from_map = function (map_px_X, map_px_Y) {
        // there are *lots* of ways to do this, but keeping it simple
        var tile_y = Math.floor(map_px_Y / tile_height), // 4 = floor(280/64)
            tile_x = Math.floor(map_px_X / tile_width ),

            tile_num = tile_y * num_tiles_x + tile_x;
            // 23 = 4 down * 5 per row + 3 over

        return tile_num;
    };

    // won't go into event-handling and coordinate-normalization
    selected_tile_map.onclick = function (evt) {
        // these are the coordinates of the click,
        //as they relate to the actual image at full scale
        map_x, map_y;
        selected_tile = select_tile_num_from_map(map_x, map_y);
    };

现在你有一个简单的系统来确定点击了哪个瓷砖 同样,有很多方法可以建立这个,你可以使它更多OO,
并做一个适当的&#34;瓷砖&#34;数据结构,您希望在整个引擎中读取和使用。

现在,我只是返回从零到零的数字,从左到右,从上到下阅读。
如果每行有5个图块,并且有人选择第二行的第一个图块,那就是#3图块。

然后,对于&#34;绘画&#34;,你只需要听一下画布点击,弄清楚X和Y是什么, 找出世界上的位置,以及等于的阵列点 从那里,您只需转储selected_tile的价值,以及它的相关信息。

// this might be one long array, like I did with the tile-map and the number of the tile
// or it might be an array of arrays: each inner-array would be a "row",
// and the outer array would keep track of how many rows down you are,
// from the top of the world
var world_map = [],

    selected_coordinate = 0,

    world_tile_width  = 64, // these might be in *canvas* pixels, or "world" pixels
    world_tile_height = 64, // this is so you can scale the size of tiles,
                            // or zoom in and out of the map, etc

    world_width  = 320,
    world_height = 320,


    num_world_tiles_x = world_width  / world_tile_width,
    num_world_tiles_y = world_height / world_tile_height,

    get_map_coordinates_from_click = function (world_x, world_y) {
        var coord_x = Math.floor(world_px_x / num_world_tiles_x),
            coord_y = Math.floor(world_px_y / num_world_tiles_y),

            array_coord = coord_y * num_world_tiles_x + coord_x;

        return array_coord;
    },

    set_map_tile = function (index, tile) {
        world_map[index] = tile;
    };

    canvas.onclick = function (evt) {
        // convert screen x/y to canvas, and canvas to world
        world_px_x, world_px_y;
        selected_coordinate = get_map_coordinates_from_click(world_px_x, world_px_y);

        set_map_tile(selected_coordinate, selected_tile);
    };

正如您所看到的,执行另一个的过程与执行另一个的过程非常相似(因为它是 - 在一个坐标集中给定x和y,将其转换为另一个比例/集)

然后,绘制瓷砖的过程几乎完全相反 给定world-index和tile-number,反向查找world-x / y和tilemap-x / y。 您也可以在示例代码中看到该部分。

这张瓷砖画是制作2D地图的传统方式,无论我们是在谈论星际争霸,塞尔达还是马里奥兄弟。 并非所有人都拥有瓷砖涂料的奢侈品和#34;编辑器(有些是手工制作文本文件,甚至是电子表格,以获得正确的间距),但是如果加载星际争霸甚至是WarCraft III(这是3D),并进入他们的编辑,那么瓷砖画家就是你得到了什么,这正是暴雪制作这些地图的方式。

添加

在基本前提下,您现在可以使用其他地图&#34;地图&#34;这也是必需的: 你需要一张碰撞图来知道你可以/不能走的那些瓷砖,一个实体图,以显示有门的地方,或者电源或矿物,或敌人产卵,或过场动画的事件触发器......

并非所有这些都需要在与世界地图相同的坐标空间中运行,但它可能有所帮助。

此外,您可能想要一个更聪明的世界&#34; 能够在一个级别中使用多个平铺贴图,例如...
并在tile-editor中下拉以交换tile-maps。

...一种方法可以保存两个图块信息(不仅仅是X / Y,还有其他关于图块的信息),并保存完成的&#34;地图&#34;数组,填充瓷砖。

即使只是复制JSON,并将其粘贴到自己的文件中......


程序生成

另一种方式,你之前建议的方式(&#34;知道如何连接岩石,草等等#34;)被称为程序生成
这是一个更难,更多涉及 像暗黑破坏神这样的游戏使用它,这样你每次玩游戏时都会处于不同的随机生成环境中。 Warframe是一个使用程序生成来做同样事情的FPS。

<强>前提:
基本上,你从瓷砖开始,而不是只是一个瓷砖是一个图像,瓷砖必须是一个有图像和位置的对象,但也有一个可能在它周围的东西列表。
当你放下一片草时,那草就有可能在它旁边产生更多的草 草可能会说,在它周围的四个方向中的任何一个方向,水的概率为10%,岩石几率为20%,污垢几率为30%,草的几率为40%。

当然,它真的不是那么简单(如果你错了,也可能是这样)。

虽然这是一个想法,但程序生成的棘手部分实际上是确保一切正常而不会破坏。
约束
你不可能,例如在这个例子中,悬崖墙出现在高地的内侧。它只能出现在上方和右方的高地,低地和左下方的地方(星际争霸编辑器会自动执行此操作,如您所绘)。斜坡只能连接有意义的瓷砖。你可以不在门外,或将世界包裹在河流/湖泊中,阻止你移动(或者更糟糕的是,阻止你完成一个级别)。

<强>优点
对于长寿来说真的很棒,如果你可以让你的所有寻路和约束工作 - 不仅是伪随机生成地形和布局,还有敌人放置,战利品放置等等。 近14年后,人们仍在玩暗黑破坏神II。

<强>缺点
当你是一个单人团队(在业余时间他们没有碰巧成为数学家/数据科学家)时,真的很难做对。 确保地图有趣/平衡/竞争真的很糟糕...
星际争霸永远不会使用100%随机生成公平的游戏玩法 程序生成可以用作种子&#34; 你可以点击&#34;随机化&#34;按钮,看看你得到了什么,然后从那里进行调整和修复,但是为了约束传播而编写的平衡&#34;或许多游戏规则,你和# 39;最终花费更多的时间修理发电机,而不仅仅是自己画地图。

有一些教程,学习遗传算法,寻路等等,都是很好的技能......为了学习制作2D自上而下的瓷砖游戏,buuuut是在你获得一两个游戏/引擎之后,你需要考虑一下这个问题。