简体   繁体   English

使用javascript渲染瓷砖地图

[英]render a tile map using javascript

I'm looking for a logical understanding with sample implementation ideas on taking a tilemap such as this: 我正在寻找一个逻辑理解与样本实现的想法,采取如下这样的tilemap:

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

And rendering in a logical way such as this: 并以这样的逻辑方式呈现:

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

I see all of the tiles are there, but I don't understand how they are placed in a way that forms shapes. 我看到所有的瓷砖都在那里,但我不明白它们是如何以形成形状的方式放置的。

My understanding of rendering tiles so far is simple, and very manual. 到目前为止,我对渲染图块的理解很简单,而且非常手动。 Loop through map array, where there are numbers (1, 2, 3, whatever), render that specified tile. 循环遍历地图数组,其中有数字(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);     
}   

Gets me: 得到我:

在此输入图像描述

But this means I have to manually figure out where each tile goes in the array so that logical shapes are made (rock formations, grass patches, etc) 但这意味着我必须手动确定每个瓷砖在阵列中的位置,以便形成逻辑形状(岩层,草地补丁等)

Clearly, the guy who made the github code above used a different method. 很明显,上面制作github代码的人使用了不同的方法。 Any guidance on understanding the logic (with simply pseudo code) would be very helpful 理解逻辑 (简单的伪代码)的任何指导都会非常有用

There isn't any logic there. 那里没有任何逻辑。

If you inspect the page's source, you'll see that the last script tag, in the body, has a huge array of tile coordinates. 如果您检查页面的源,您将看到正文中的最后一个脚本标记具有大量的图块坐标。

There is no magic in that example which demonstrates an "intelligent" system for figuring out how to form shapes. 在这个例子中没有任何魔术可以展示一个“智能”系统来确定如何形成形状。

Now, that said, there are such things... ...but they're not remotely simple. 现在,那就是说,有这样的事情......但是它们并不简单。

What is more simple, and more manageable, is a map-editor. 更重要的简单,更易于管理,是一个地图编辑器。


Tile Editors 平铺编辑

out of the box: 盒子外面:

There are lots of ways of doing this... There are free or cheap programs which will allow you to paint tiles, and will then spit out XML or JSON or CSV or whatever the given program supports/exports. 有很多方法可以做到这一点...有免费或廉价的程序可以让你绘制瓷砖,然后吐出XML或JSON或CSV或任何给定的程序支持/导出。

Tiled ( http://mapeditor.org ) is one such example. Tiledhttp://mapeditor.org )就是这样一个例子。
There are others, but Tiled is the first I could think of, is free, and is actually quite decent. 还有其他人,但是Tiled是我能想到的第一个,是免费的,而且实际上相当不错。

pros: 优点:
The immediate upside is that you get an app that lets you load image tiles, and paint them into maps. 直接的好处是,您可以获得一个应用程序,可以加载图像切片,并将它们绘制到地图中。
These apps might even support adding collision-layers and entity-layers (put an enemy at [2,1], a power-up at [3,5] and a "hurt-player" trigger, over the lava). 这些应用程序甚至可能支持添加碰撞层和实体层(在[2,1]处放置一个敌人,在[3,5]处加电,在熔岩上放置一个“伤害玩家”触发器)。

cons: ...the downside is that you need to know exactly how these files are formatted, so that you can read them into your game engines. 缺点: ...缺点是您需要确切地知道这些文件的格式,以便您可以将它们读入您的游戏引擎。
Now, the outputs of these systems are relatively-standardized... so that you can plug that map data into different game engines (what's the point, otherwise?), and while game-engines don't all use tile files that are exactly the same, most good tile-editors allow for export into several formats (some will let you define your own format). 现在,这些系统的输出是相对标准化的......所以你可以将地图数据插入不同的游戏引擎(重点是什么,否则?),而游戏引擎并不都使用完全正确的平铺文件同样,大多数优秀的平铺编辑器允许导出为多种格式(有些可以让您定义自己的格式)。

...so that said, the alternative (or really, the same solution, just hand-crafted), would be to create your own tile-editor. ...所以说,替代(或真正,相同的解决方案,只是手工制作),将创建自己的瓷砖编辑器。

DIY DIY
You could create it in Canvas, just as easily as creating the engine to paint the tiles. 你可以在Canvas中创建它,就像创建引擎来绘制tile一样容易。
The key difference is that you have your map of tiles (like the tilemap .png from StarCr... erm... the "found-art" from the example, there). 关键的区别在于你有你的瓷砖地图(比如来自StarCr的tilemap .png ......呃......来自这个例子的“发现艺术”)。
Instead of looping through an array, finding the coordinates of the tile and painting them at the world-coordinates which match that index, what you would do is choose a tile from the map (like choosing a colour in MS Paint), and then wherever you click (or drag), figure out which array point that relates to, and set that index to be equal to that tile. 找到瓷砖的坐标并在与该指数匹配的世界坐标上绘制它们,而不是循环遍历数组,你要做的是从地图中选择一个瓷砖(比如在MS Paint中选择颜色),然后在哪里单击(或拖动),找出与之相关的数组点,并将该索引设置为等于该图块。

pros: 优点:
The sky is the limit; 天空才是极限; you can make whatever you want, make it fit any file-format you want to use, and make it handle any crazy stuff you want to throw at it... 你可以制作任何你想要的东西,使它适合你想要使用的任何文件格式,并让它处理你想要抛出的任何疯狂的东西......
cons: 缺点:
...this of course, means you have to make it, yourself, and define the file-format you want to use, and write the logic to handle all of those zany ideas... ...当然,这意味着你必须自己制作它,并定义你想要使用的文件格式,并编写逻辑来处理所有这些想法......

basic implementation 基本实施
While I'd normally try to make this tidy, and JS-paradigm friendly, that would result in a LOT of code, here. 虽然我通常会尝试使这个整洁,并且JS-paradigm友好,这将导致大量的代码,在这里。
So I'll try to denote where it should probably be broken up into separate modules. 因此,我将尝试表示它应该分解为单独的模块。

// 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);
    };

Now you have a simple system for figuring out which tile was clicked. 现在你有一个简单的系统来确定点击了哪个瓷砖。
Again, there are lots of ways of building this, and you can make it more OO, 同样,有很多方法可以构建它,你可以使它更多OO,
and make a proper "tile" data-structure, that you expect to read and use throughout your engine. 并制作一个适当的“平铺”数据结构,您希望在整个引擎中阅读和使用。

Right now, I'm just returning the zero-based number of the tile, reading left to right, top to bottom. 现在,我只是返回从零开始的数字,从左到右,从上到下阅读。
If there are 5 tiles per row, and someone picks the first tile of the second row, that's tile #5. 如果每行有5个图块,并且有人选择第二行的第一个图块,那就是图块#5。

Then, for "painting", you just need to listen to a canvas click, figure out what the X and Y were, figure out where in the world that is, and what array spot that's equal to. 然后,对于“绘画”,你只需要听一下画布点击,弄清楚X和Y是什么,弄清楚世界的位置,以及等于的阵列点。
From there, you just dump in the value of selected_tile , and that's about it. 从那里,你只需要转储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);
    };

As you can see, the procedure for doing one is pretty much the same as the procedure for doing the other (because it is -- given an x and y in one coordinate-set, convert it to another scale/set). 正如您所看到的,执行另一个的过程与执行另一个过程非常相似(因为它是 - 在一个坐标集中给定x和y,将其转换为另一个比例/集)。

The procedure for drawing the tiles, then, is nearly the exact opposite. 那么,绘制瓷砖的过程几乎完全相反。
Given the world-index and tile-number, work in reverse to find the world-x/y and tilemap-x/y. 给定world-index和tile-number,反向查找world-x / y和tilemap-x / y。
You can see that part in your example code, as well. 您也可以在示例代码中看到该部分。

This tile-painting is the traditional way of making 2d maps, whether we're talking about StarCraft, Zelda, or Mario Bros. 这种瓷砖绘画是制作2D地图的传统方式,无论我们是在谈论星际争霸,塞尔达还是马里奥兄弟。
Not all of them had the luxury of having a "paint with tiles" editor (some were by hand in text-files, or even spreadsheets, to get the spacing right), but if you load up StarCraft or even WarCraft III (which is 3D), and go into their editors, a tile-painter is exactly what you get, and is exactly how Blizzard made those maps. 并非所有人都拥有“使用瓷砖绘画”编辑器的奢侈品(有些是手工制作文本文件,甚至是电子表格,以获得正确的间距),但是如果加载星际争霸甚至是魔兽争霸III(这是3D),并进入他们的编辑,瓷砖画家正是你得到的,这正是暴雪制作这些地图的方式。

additions 增加

With the basic premise out of the way, you now have other "maps" which are also required: you'd need a collision-map to know which of those tiles you could/couldn't walk on, an entity-map, to show where there are doors, or power-ups or minerals, or enemy-spawns, or event-triggers for cutscenes... 在基本前提下,您现在还需要其他“地图”:您需要一个碰撞图来了解哪些瓷砖可以/不能走,实体图,到显示哪里有门,或者是电源或矿物,或者敌人产生的,或者是过场动物的事件触发器......

Not all of these need to operate in the same coordinate-space as the world map, but it might help. 并非所有这些都需要在与世界地图相同的坐标空间中运行,但它可能会有所帮助。

Also, you might want a more intelligent "world". 此外,您可能想要一个更智能的“世界”。
The ability to use multiple tile-maps in one level, for instance... 能够在一个级别中使用多个平铺贴图,例如......
And a drop-down in a tile-editor to swap tile-maps. 并在tile-editor中下拉以交换tile-maps。

...a way to save out both tile-information (not just X/Y, but also other info about a tile), and to save out the finished "map" array, filled with tiles. ...一种省略瓷砖信息的方法(不仅仅是X / Y,还有关于瓷砖的其他信息),并保存完成的“地图”数组,填充了瓷砖。

Even just copying JSON, and pasting it into its own file... 即使只是复制JSON,并将其粘贴到自己的文件中......


Procedural Generation 程序生成

The other way of doing this, the way you suggested earlier ("knowing how to connect rocks, grass, etc") is called Procedural Generation . 另一种方式,你之前建议的方式(“知道如何连接岩石,草等”)称为程序生成
This is a LOT harder and a LOT more involved. 这是一个更难和更多参与。
Games like Diablo use this, so that you're in a different randomly-generated environment, every time you play. 像暗黑破坏神这样的游戏会使用它,这样你每次玩游戏时都会处于不同的随机生成环境中。 Warframe is an FPS which uses procedural generation to do the same thing. Warframe是一个使用程序生成来做同样事情的FPS。

premise: 前提:
Basically, you start with tiles, and instead of just a tile being an image, a tile has to be an object that has an image and a position, but ALSO has a list of things that are likely to be around it. 基本上,你从瓦片开始,而不是仅仅是作为图像的瓦片,瓦片必须是具有图像和位置的对象,但是还具有可能围绕它的事物的列表。
When you put down a patch of grass, that grass will then have a likelihood of generating more grass beside it. 当你放下一片草时,那草就有可能在它旁边产生更多的草。
The grass might say that there's a 10% chance of water, a 20% chance of rocks, a 30% chance of dirt, and a 40% chance of more grass, in any of the four directions around it. 草可能会说,周围的四个方向中的任何一个都有10%的几率水,20%的岩石几率,30%的污垢几率和40%的草。

Of course, it's really not that simple (or it could be, if you're wrong). 当然,它真的不是那么简单(或者如果你错了的话)。

While that's the idea, the tricky part of procedural generation is actually in making sure everything works without breaking. 虽然这就是这个想法,但程序生成的棘手部分实际上是确保一切正常运行而不会破坏。
constraints 限制
You couldn't, for example have the cliff wall, in that example, appear on the inside of the high-ground. 你不能,例如在这个例子中,悬崖墙出现在高地的内侧。 It can only appear where there's high ground above and to the right, and low-ground below and to the left (and the StarCraft editor did this automatically, as you painted). 它只能出现在上方和右上方的高地,以及左下方的低地(并且星际争霸编辑器会自动执行此操作,如您所绘)。 Ramps can only connect tiles that make sense. 斜坡只能连接有意义的瓷砖。 You can't wall off doors, or wrap the world in a river/lake that prevents you from moving (or worse, prevents you from finishing a level). 你不能将门挡住,也不能将世界包裹在阻止你移动的河流/湖泊中(或者更糟糕的是,阻止你完成一个级别)。

pros 利弊
Really great for longevity, if you can get all of your pathfinding and constraints to work -- not only for pseudo-randomly generating the terrain and layout, but also enemy-placement, loot-placement, et cetera. 对于长寿来说真的很棒,如果你可以让你的所有寻路和限制工作 - 不仅是伪随机生成地形和布局,还有敌人放置,战利品放置等等。
People are still playing Diablo II, nearly 14 years later. 近14年后,人们仍在玩暗黑破坏神II。

cons 缺点
Really difficult to get right, when you're a one-man team (who doesn't happen to be a mathematician/data-scientist in their spare time). 当你是一个单人团队时(在业余时间他们不是数学家/数据科学家),真的很难做对。
Really bad for guaranteeing that maps are fun/balanced/competitive... 确保地图有趣/平衡/竞争真的很糟糕......
StarCraft could never have used 100% random-generation for fair gameplay. 星际争霸永远不会使用100%随机生成公平的游戏玩法。
Procedural-generation can be used as a "seed". 程序生成可以用作“种子”。
You can hit the "randomize" button, see what you get, and then tweak and fix from there, but there'll be so much fixing for "balance", or so many game-rules written to constrain the propagation, that you'll end up spending more time fixing the generator than just painting a map, yourself. 你可以点击“随机化”按钮,看看你得到了什么,然后从那里进行调整和修复,但是对于“平衡”,或者为了限制传播而编写的游戏规则,你将会有很多修复,最终会花费更多的时间修理发电机,而不仅仅是自己绘制地图。

There are some tutorials out there, and learning genetic-algorithms, pathfinding, et cetera, are all great skills to have... ...buuuut, for purposes of learning to make 2D top-down tile-games, are way-overkill, and rather, are something to look into after you get a game/engine or two under your belt. 有一些教程,并且学习遗传算法,寻路等等,都是很好的技能......为了学习制作2D自上而下的瓷砖游戏,buuuut有点过头了而且,在您获得一两个游戏/引擎后,需要考虑的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM