#javascript #procedural-generation
#javascript #процедурная генерация
Вопрос:
У меня есть простой метод, который генерирует 2D-карты островов из симплексного шума, однако он оставляет одну большую проблему. Это работает хорошо, но оставляет острые углы.
Что я хотел бы сделать, так это взять это и вычислить соседей, чтобы добавить правильные плитки для ребер и углов, но я не уверен, как это добавить. Каков наилучший способ вычислить это?
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;
}
Ответ №1:
Определите, где находятся эти желтые (песочные?) Плитки с краями, у вас есть 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)
Чтобы определить, в каком случае вы смотрите на соседей по ячейке (это вода? в каком направлении …).
Для этих случаев либо используйте специальные плитки make 8 (см. Для этого Классические наборы плиток), либо вы можете поиграть с эффектами прозрачности в альфа-канале.
Как вы можете видеть на примере набора листов, может быть больше случаев: только одна ячейка с водой, только две ячейки с водой … вы можете исключить их из сгенерированной карты, чтобы избежать их.
Удачи.
Ответ №2:
Лучшим объяснением того, как это можно сделать (насколько я смог найти), было бы улучшение генерации процедурных карт — плиток.
Я использовал статью в качестве отправной точки для улучшения генерации карт для игры, которую я создаю (это все еще очень далеко от предполагаемых результатов, но может быть принято за отправную точку):
- Код на Github (в частности, вы могли бы взглянуть на
map-generator.ts
); - Онлайн-демонстрация
Это текущий код генерации карты, хотя сейчас он довольно глючный:
'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 amp;amp; col < current.length - 1) {
if (val !== TileEdge.A amp;amp; val !== TileEdge.B) {
return val;
}
const [patTop, patCurrent, patBottom] = pattern;
if (patternMatches(patBottom, bottom, col) amp;amp;
patternMatches(patCurrent, current, col) amp;amp;
patternMatches(patTop, top, col)) {
return resu<
}
}
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});
}
};