如何计算相邻的图块以生成2D图块地图阵列的边缘和角?

时间:2019-04-14 21:19:00

标签: javascript procedural-generation

我有一个简单的方法,可以从SimpleX Noise生成2D岛图,但是这还存在一个更大的问题。它运作良好,但留下了尖角。

Sharp corners on 2D Tile Map

我想做的就是计算并计算邻居以添加正确的边和角瓷砖,但是我不确定如何添加它。什么是最好的计算方法?

generateMap()
{
    let outputMap = [];

    for(let y = 0; y < this.tileCount; y++)
    {
        outputMap[y] = [];
        for(let x = 0; x < this.tileCount; x++)
        {
            let nx = x / this.tileCount - 0.5, ny = y / this.tileCount - 0.5;
            let e = 1 + +Math.abs(this.heightMapGen.noise2D(1 * nx, 1 * ny));
            e += 0.5 + +Math.abs(this.heightMapGen.noise2D(2 * nx, 2 * ny));  
            e += 0.25 + +Math.abs(this.heightMapGen.noise2D(4 * nx, 4 * ny));  


            let output = (Math.pow(e, 0.21) % 1).toFixed(2).split(".");
            outputMap[y][x] = parseFloat(output[1]);

            if (outputMap[y][x] <= 25)
            {
                outputMap[y][x] = 0; // Water //
            } else {
                // Terrain //
                switch(outputMap[y][x])
                {
                    case 28:
                    case 29:
                        outputMap[y][x] = 2;
                    break;                        
                    case 27:
                        outputMap[y][x] = 1;
                    break;
                    case 26:
                        outputMap[y][x] = 4;
                    break;
                    default:
                        outputMap[y][x] = 3;
                }                
            }  
        }
    }

    return outputMap;
}

2 个答案:

答案 0 :(得分:0)

确定那些黄色(沙色)边缘砖的位置,外角有4种情况,内角有4种情况。

+-->  +-----------+  <---+ corner tile (one case)
      |         |_|  
      |    land   |  sea here
      |    here   |
      |           |
      |           |
+-->  +-----------+  <----+

4 external corners (sea shore, beach)


      +-----------+
      | land here |
+-------> +---+ <-------+
      |   |wat|   |
      |   |er |   |
+-------> +---+ <-------+
      |           |
      +-----------+

4 potential internal corners (lake shore or pond edge)

要确定在哪种情况下,您要查看单元格邻居(是水吗?是朝哪个方向...)。 您可以针对这些情况使用8个特定的图块(有关说明,请参见classic tilesets),也可以在alpha通道上使用透明效果。 正如您在图块示例中看到的那样,可能还有更多的情况:只有一个单元格的水,只有两个单元格的水...您可以从生成的地图中消除它们以避免它们。

祝你好运。

答案 1 :(得分:0)

(据我所知)关于如何完成此操作的最佳解释是Enhancing Procedural Maps - Tiles Generation

我已将本文用作改善我正在制作的游戏的地图生成的起点(距离预期结果还很遥远,但可以作为起点):

这是当前的地图生成代码,尽管它现在存在一些故障:

'use strict';

import {range, sample} from 'lodash';
import SimplexNoise from 'simplex-noise';

enum TileEdge {
    A, B, L, R,
    BOTTOM, TOP,
    ITL, ITR, IBL, IBR,
    OTL, OTR, OBL, OBR
};

const tileEdgeNames = (() => {
    const names = [];
    for (let item in TileEdge) {
        if (isNaN(Number(item))) {
            names.push(item.toLowerCase());
        }
    }
    return names;
})();
console.log(tileEdgeNames);

export const desertTileIndexes = {
    a: [16, 46],
    b: [50, 51, 60, 61,62, 63],
    l: [15],
    r: [17],
    top: [6],
    bottom: [26],

    itl: [5],
    itr: [7],
    ibl: [25],
    ibr: [27],

    otl: [8],
    otr: [9],
    obl: [18],
    obr: [19]
};

export const cloudTileIndexes = {
    a: [13, 20, 21],
    b: [-1],
    l: [12],
    r: [14],
    top: [3],
    bottom: [23],

    itl: [2],
    itr: [4],
    ibl: [22],
    ibr: [24],

    otl: [0],
    otr: [1],
    obl: [10],
    obr: [11]
};

export const rockTileIndexes = {
    a: [76, 70, 71, 72, 73, 74],
    b: [-1],
    l: [75],
    r: [77],
    top: [66],
    bottom: [86],

    itl: [68],
    itr: [69],
    ibl: [78],
    ibr: [79],

    otl: [65],
    otr: [67],
    obl: [85],
    obr: [87]
};

const generateMainPlanes = (position, {tileCount = 10, threshold = 0, noiseFunction} = {}): TileEdge[] => {
    return range(tileCount).map((j) => noiseFunction(position, j) > threshold ? TileEdge.B : TileEdge.A;
};


const generateTileIndexes = ([bottom, current, top], {tileTypeIndexes} = {}) => {
    return current.map((idx, col) => sample(tileTypeIndexes[tileEdgeNames[idx]]));
};

const createStripGenerator = ({tileCount = 10, threshold = 0} = {}) => {    
    const simplex = new SimplexNoise();
    const noiseFunction = (x, y) => simplex.noise2D(x, y);

    const options = {tileCount, threshold, noiseFunction};

    let position = 0;

    return () => {
        return generateMainPlanes(position++, options);
    }
}

const holePatchingAutomata = [
    {
        pattern: [
            '???',
            '*.*',
            '???',
        ],
        result: TileEdge.A
    },
    {
        pattern: [
            '?*?',
            '?.?',
            '?*?',
        ],
        result: TileEdge.A
    },
    {
        pattern: [
            '??*',
            '?.?',
            '*??',
        ],
        result: TileEdge.A
    },
    {
        pattern: [
            '*??',
            '?.?',
            '??*',
        ],
        result: TileEdge.A
    },
];

const cornerAutomata = [

    {
        pattern: [
            '?.?',
            '?.*',
            '?.?',
        ],
        result: TileEdge.L
    },
    {
        pattern: [
            '?.?',
            '*.?',
            '?.?',
        ],
        result: TileEdge.R
    },
    {
        pattern: [
            '???',
            '...',
            '?*?',
        ],
        result: TileEdge.TOP
    },
    {
        pattern: [
            '?*?',
            '...',
            '???',
        ],
        result: TileEdge.BOTTOM
    },


    // Inner corner
    {
        pattern: [
            '?*?',
            '*.?',
            '???',
        ],
        result: TileEdge.ITL
    },
    {
        pattern: [
            '?*?',
            '?.*',
            '???',
        ],
        result: TileEdge.ITR
    },
    {
        pattern: [
            '???',
            '*.?',
            '?*?',
        ],
        result: TileEdge.IBL
    },
    {
        pattern: [
            '???',
            '?.*',
            '?*?',
        ],
        result: TileEdge.IBR
    },

    // Outer corners
    {
        pattern: [
            '???',
            '?..',
            '?.*',
        ],
        result: TileEdge.OTL
    },
    {
        pattern: [
            '???',
            '..?',
            '*.?',
        ],
        result: TileEdge.OTR
    },
    {
        pattern: [
            '?.*',
            '?..',
            '???',
        ],
        result: TileEdge.OBL
    },
    {
        pattern: [
            '*.?',
            '..?',
            '???',
        ],
        result: TileEdge.OBR
    },

];

const patternTokenHandlers = {
    '?': () => true,
    '.': x => x === TileEdge.B,
    '*': x => x === TileEdge.A
};

const patternMatches = (expected, actual, offset) => {
    for (let i = 0, j = offset - 1; i < expected.length; i++, j++) {
        if (!patternTokenHandlers[expected[i]](actual[j])) {
            return false;
        }           
    }

    return true;
}

const applyAutomata = (automata, [bottom, current, top]) => {
    const currentModified = current.map((idx, col) => {
        return automata.reduce((val, {pattern, result}) => {
            if (col > 0 && col < current.length - 1) {
                if (val !== TileEdge.A && val !== TileEdge.B) {
                    return val;
                }

                const [patTop, patCurrent, patBottom] = pattern;

                if (patternMatches(patBottom, bottom, col) &&
                   patternMatches(patCurrent, current, col) &&
                   patternMatches(patTop, top, col)) {              
                    return result;
                }
            }

            return val;
        }, idx);
    });

    return [bottom, currentModified, top];
}

export const mapGenerator = ({tileCount = 10, threshold = 0, tileTypeIndexes} = {}) => {
    const generateStrip = createStripGenerator({tileCount, threshold});

    let bottom;
    let current = generateStrip();
    let top = generateStrip();

    return () => {
        const currentStrips = [bottom, current, top] = [current, top, generateStrip()];     
        const withHolesPatched = applyAutomata(holePatchingAutomata, currentStrips);
        const withCorners = applyAutomata(cornerAutomata, withHolesPatched);
        return generateTileIndexes(withCorners, {tileTypeIndexes});
    }
};