使用javascript渲染瓷砖地图 [英] render a tile map using javascript

查看:143
本文介绍了使用javascript渲染瓷砖地图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找关于采用这样的平铺图的示例实现思路的逻辑理解:



)就是这样一个例子。

还有其他的,但Tiled是我能想到的第一个,是免费的,实际上相当不错。



专业人士


直接的好处是,您可以获得一个应用程序,可以加载图像切片并绘制它们这些应用程序甚至可能支持添加碰撞层和实体层(将敌人放在[2,1],加电到[3,5]和伤害玩家) 触发,越过熔岩。



缺点:
...缺点是您需要确切知道这些文件的格式,以便您可以将它们读入您的游戏引擎。

现在,这些系统的输出是相对标准化的......这样您就可以将地图数据插入到不同的游戏引擎中(不管是什么意思?),以及虽然游戏引擎并不都使用完全相同的平铺文件,但大多数优秀的平铺编辑器允许导出为多种格式(有些格式可以让您定义自己的格式)。



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



DIY

您可以在Canvas中创建它,就像创建引擎来绘制图块一样容易。< br>
关键的区别在于你有你的瓷砖地图(比如StarCr中的tilemap .png ......呃...这个例子中的找到艺术)。

而不是循环遍历数组,找到图块的坐标并在与该索引匹配的世界坐标处绘制它们,您要做的是从地图中选择一个图块(如在MS Paint中选择颜色),以及然后无论你在哪里点击(或拖动),找出与之相关的数组点,并将该索引设置为等于该图块。



pros:

天空是极限;你可以做任何你想要的东西,使它适合你想要使用的任何文件格式,并让它处理你想要扔的任何疯狂的东西......

缺点:

...当然,这意味着你必须自己制作它,并定义你想要使用的文件格式,并编写逻辑来处理所有这些想法...



基本实现

虽然我通常会尝试使这个整洁,并且JS-paradigm友好,但这会导致在这里有很多代码。

所以我会尝试表示它应该分解成单独的模块。

  //假设图像已经正确加载
//并且已经触发了onload事件,你已经听了
//这样当你的引擎尝试时没有任何意外到
//画一些不存在的东西,但


//这应该都包含在一个处理
//加载瓷砖的模块中地图,选择要绘制的图块,
//并为tile生成数据格式,让你放入数组
//(或接受插件数据格式化器,这样做)
var selected_tile = null,
selected_tile_map = get_tile_map(),//这将是你的瓷砖的图像
tile_width = 64,//图像像素,而不是画布/屏幕像素
tile_height = 64 ,//在图像像素中,而不是画布/屏幕像素

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){
//有很多方法可以做到这一点,但保持简单
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 + +

$ b返回tile_num;
};

//不会进入事件处理和坐标规范化
selected_tile_map.onclick = function(evt){
//这些是点击的坐标,
//因为它们与全尺寸的实际图像有关
map_x,map_y;
selected_tile = select_tile_num_from_map(map_x,map_y);
};

现在你有一个简单的系统来确定点击了哪个瓷砖。

同样,有很多方法可以构建它,你可以使它更多OO,

并制作一个合适的tile数据结构,你希望在整个引擎中读取和使用它。



现在,我只是返回从零到零的数字,从左到右,从上到下阅读。

如果有的话每行5个图块,有人选择第二行的第一个图块,即图块#5。



然后,对于绘画,你只需要听一下画布点击,弄清楚X和Y是什么,
弄清楚在哪里那个世界,以及那个等于的数组点。

从那里,你只需要转储 selected_tile 的值,这就是它。 / p>

  //这可能是一个长数组,就像我使用了tile-map和tile 
一样//或者它可能是一个数组数组:每个内部数组都是一个行,
//外部数组将跟踪你的行数,
//来自世界之巅
var world_map = [],

selected_coordinate = 0,

world_tile_width = 64,//这些可能是* canvas *像素,或世界像素
world_tile_height = 64,//这样你可以缩放瓷砖的大小,
//或放大和缩小地图等等。

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;

返回array_coord;
},

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

canvas.onclick = function(evt){
//将屏幕x / y转换为画布,将画布转换为世界
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地图的传统方式,无论我们是在谈论星际争霸,塞尔达,或者Mario Bros.

并非所有人都拥有使用瓷砖绘画编辑器的奢侈品(有些是手工制作文本文件,甚至是电子表格,以获得正确的间距),但如果你加载了星际争霸甚至是魔兽争霸III(这是3D),并进入他们的编辑,一个瓦片画家正是你得到的,而且正是暴雪制作这些地图的方式。



添加



在基本前提下,您现在还需要其他地图:
您需要一张碰撞图来了解哪些瓷砖你可以/不能走路,实体地图,显示有门的地方,或者电源或矿物,敌人产生,或过场动作的事件触发器......



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



此外,您可能需要一个更智能的世界。

能够在一个级别中使用多个平铺贴图,例如...

并在平铺编辑器中下拉以交换瓦图。



...一种省略瓷砖信息的方法(不仅仅是X / Y,还有关于瓷砖的其他信息),并保存完成的映射数组,填充瓷砖。



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






程序生成



这样做的另一种方式,就是你之前建议的方式(知道如何连接岩石,草等)被称为程序生成

这是一个更难和更多参与。

像暗黑破坏神这样的游戏使用这个,所以你在不同的随机每次你玩的时候都会产生环境。 Warframe是一个FPS,它使用程序生成来做同样的事情。



前提:


基本上,你从瓷砖开始,而不只是瓷砖是图像,瓷砖必须是一个具有图像和位置的对象,但也有一个可能在它周围的东西的列表。

当你放下一片草时,那草将有一个在它旁边产生更多草的可能性。

草可能会说有10%的几率水,20%的岩石几率,30%的污垢几率,以及40%的草几率,在它周围的四个方向中的任何一个方向。



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



虽然这是理念,但程序生成的棘手部分实际上是确保一切正常而不会破坏。

约束

你不能,例如在这个例子中,悬崖墙出现在高地的内侧。它只能出现在上方和右方的高地,低地和左下方的地方(星际争霸编辑器会自动执行此操作,如您所绘)。斜坡只能连接有意义的瓷砖。你不能将门挡住,或将世界包裹在阻止你移动的河流/湖泊中(或者更糟糕的是,阻止你完成一个级别)。



专业

如果你可以获得所有的寻路和约束来工作,这对于长寿来说真的很棒 - 不仅是伪随机生成地形和布局,还有敌人的位置,赃物,等等。

人们仍在玩暗黑破坏神II,差不多14年后。



缺点

当你是一个单人团队(在业余时间他们不是数学家/数据科学家)时,真的很难做对。)
真的不能保证地图有趣/平衡/有竞争力...

星际争霸永远不会使用100%随机生成公平游戏。

程序生成可以用作种子。

您可以点击随机化按钮,看看你得到了什么,然后从那里进行调整和修复,但是会有这么多修复平衡,或者为了限制传播而编写的许多游戏规则,你最终会花费更多的时间修复生成器,而不仅仅是自己绘制地图。



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


I'm looking for a logical understanding with sample implementation ideas on taking a tilemap such as this:

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/

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.

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

Tiled ( http://mapeditor.org ) is one such example.
There are others, but Tiled is the first I could think of, is free, and is actually quite decent.

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).

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
You could create it in Canvas, just as easily as creating the engine to paint the tiles.
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).
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.

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.
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,
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.

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.
From there, you just dump in the value of selected_tile, and that's about it.

// 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).

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

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.

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

Even just copying JSON, and pasting it into its own file...


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.

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.

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.

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

这篇关于使用javascript渲染瓷砖地图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆