このようなカードの描き方は説明しません。言語、グラフィックスライブラリ、プラットフォームなどに依存します。 配列にマップデータを入力する方法を説明します。
ノイズ
2Dマップを生成する標準的な方法は、Perlinノイズやシンプレックスノイズなど、制限された周波数帯域のノイズをビルディングブロックとして使用することです。 ノイズ関数は次のようになります。
マップ上の各ポイントに0.0〜1.0の数値を割り当てます。 この画像では、0.0は黒で、1.0は白です。 Cに似た言語の構文で各グリッドポイントの色を設定する方法は次のとおりです。
for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double nx = x/width - 0.5, ny = y/height - 0.5; value[y][x] = noise(nx, ny); } }
ループは、Javascript、Python、Haxe、C ++、C#、Java、および他のほとんどの一般的な言語で同じように機能するため、必要な言語に変換できるようにCのような構文で示します。 チュートリアルの残りの部分では、新しい関数を追加するときにループの本体がどのように変化するか(行の
value[y][x]=…
)を示します。 デモでは完全な例を示します。
一部のライブラリでは、0.0から1.0の範囲で値を返すために、結果の値をシフトまたは乗算する必要があります。
身長
ノイズ自体は単なる数字の集まりです。 私たちはそれに意味を与える必要があります。 最初に考えられることは、ノイズ値を高さにバインドすることです(これは「高さマップ」と呼ばれます)。 上記のノイズを取り、高さとして描画しましょう:
内側のループを除いて、コードはほぼ同じままでした。 これは次のようになります。
elevation[y][x] = noise(nx, ny);
はい、それだけです。 マップデータは同じままでしたが、ここでは
value
ではなく、
elevation
(高さ)と呼び
value
。
たくさんの丘がありましたが、それ以上はありません。 何が悪いの?
頻度
ノイズはどの周波数でも生成できます 。 これまでのところ、周波数を1つだけ選択しました。 それがどのように影響するか見てみましょう。
スライダー (元の記事)で値を変更して、さまざまな周波数で何が起こるかを確認してください。
スケールを変更するだけです。 最初はこれはあまり役に立たないようですが、そうではありません。 理論を説明するもう1つのチュートリアル (Habréでの翻訳 )があります:周波数、振幅、オクターブ、ピンクノイズ、ブルーノイズなどの概念。
elevation[y][x] = noise(freq * nx, freq * ny);
周波数の逆数である波長を思い出すことも時々役立ちます。 周波数を2倍にすると、サイズは半分になります。 波長を2倍にすると、すべて倍になります。 波長は、ピクセル/タイル/メートル、またはマップ用に選択したその他の単位で測定される距離です。 周波数に関連しています:
wavelength = map_size / frequency
。
オクターブ
高さマップをより面白くするために、 異なる周波数のノイズを追加します 。
elevation[y][x] = 1 * noise(1 * nx, 1 * ny); + 0.5 * noise(2 * nx, 2 * ny); + 0.25 * noise(4 * nx, 2 * ny);
1つのマップで、大きな低周波丘と小さな高周波丘を混在させましょう。 元の記事のスライダーを動かして、小さな丘をミックスに追加します。
今では、私たちが必要とするフラクタル救済のようです! 丘や不均一な山を手に入れることはできますが、まだ平野はありません。 これを行うには、別のものが必要です。
再配布
ノイズ関数は、0〜1(または、ライブラリに応じて-1〜+1)の値を提供します。 フラットプレーンを作成するには、高さを累乗します。 (元の記事の) スライダーを動かして、異なる角度を取得します。
e = 1 * noise(1 * nx, 1 * ny); + 0.5 * noise(2 * nx, 2 * ny); + 0.25 * noise(4 * nx, 4 * ny); elevation[y][x] = Math.pow(e, exponent);
高い値は平野までの平均の高さを低くし、低い値は山のピークに向かって平均の高さを上げます。 それらを省略する必要があります。 べき関数は単純なので使用しますが、任意の曲線を使用できます。 もっと複雑なデモがあります。
現実的な標高マップができたので、バイオームを追加しましょう!
バイオーム
ノイズは数値を与えますが、森林、砂漠、海の地図が必要です。 最初にできることは、小さな高さを水に変えることです。
function biome(e) { if (e < waterlevel) return WATER; else return LAND; }
うわー、これはすでに手続き的に生成された世界のようになっています! 水、草、雪があります。 しかし、さらに必要な場合はどうでしょうか? 水、砂、草、森林、サバンナ、砂漠、雪のシーケンスを作成しましょう。
高さに基づく救済
function biome(e) { if (e < 0.1) return WATER; else if (e < 0.2) return BEACH; else if (e < 0.3) return FOREST; else if (e < 0.5) return JUNGLE; else if (e < 0.7) return SAVANNAH; else if (e < 0.9) return DESERT; else return SNOW; }
うわー、それは素晴らしいですね! ゲームでは、値とバイオームを変更できます。 Crysisにはさらに多くのジャングルがあります。 Skyrimにはもっと多くの氷と雪があります。 しかし、どのように数値を変更しても、このアプローチはかなり制限されます。 地形タイプは高さに対応するため、ストリップを形成します。 それらをより面白くするためには、何か他のものに基づいてバイオームを選択する必要があります。 湿度の2番目のノイズマップを作成しましょう。
上記は高さのノイズです。 下-湿度ノイズ
次に、高さと湿度を一緒に使用しましょう。 以下に示す最初の画像では、y軸は高さ(上の画像から取得)、x軸は湿度(2番目の画像は高い)です。 これにより、魅力的なマップが得られます。
2つのノイズ値に基づく緩和
小さな高さは海と海岸です。 高所は岩が多く雪が多い。 その間に、幅広いバイオームを取得します。 コードは次のようになります。
function biome(e, m) { if (e < 0.1) return OCEAN; if (e < 0.12) return BEACH; if (e > 0.8) { if (m < 0.1) return SCORCHED; if (m < 0.2) return BARE; if (m < 0.5) return TUNDRA; return SNOW; } if (e > 0.6) { if (m < 0.33) return TEMPERATE_DESERT; if (m < 0.66) return SHRUBLAND; return TAIGA; } if (e > 0.3) { if (m < 0.16) return TEMPERATE_DESERT; if (m < 0.50) return GRASSLAND; if (m < 0.83) return TEMPERATE_DECIDUOUS_FOREST; return TEMPERATE_RAIN_FOREST; } if (m < 0.16) return SUBTROPICAL_DESERT; if (m < 0.33) return GRASSLAND; if (m < 0.66) return TROPICAL_SEASONAL_FOREST; return TROPICAL_RAIN_FOREST; }
必要に応じて、ゲームの要件に応じてこれらすべての値を変更できます。
バイオームが必要ない場合、滑らかなグラデーション( この記事を参照 )で色を作成できます。
バイオームと勾配の両方について、単一のノイズ値では十分な変動性が得られませんが、2つで十分です。
気候
前のセクションでは、 温度の代わりに高度を使用しました。 高さが高いほど、温度は低くなります。 ただし、地理的な緯度も温度に影響します。 高さと緯度の両方を使用して温度を制御しましょう:
極(緯度が大きい)の近くの気候は寒く、山の頂上(高さが高い)の気候も寒いです。 これまでのところ、私はそれをあまり一生懸命に解決していません。これらのパラメーターに適切なアプローチをするには、多くの微調整が必要です。
季節的な気候変動もあります。 夏と冬には、北半球と南半球が暖かくなり寒くなりますが、赤道では状況はあまり変わりません。 ここでも多くのことができます。たとえば、卓越風と海流、気候に対するバイオームの影響、温度に対する海洋の平均化効果をシミュレートできます。
島々
いくつかのプロジェクトでは、地図の境界線が水である必要がありました。 これにより、世界は1つ以上の島になります。 これを行うには多くの方法がありますが、ポリゴンマップジェネレーターでかなり単純なソリューションを使用しました。高さを
e = e + a - b*d^c
として変更しました(
d
は中心からの距離(0-1のスケール))。 別のオプションは、
e = (e + a) * (1 - b*d^c)
です。 定数
a
はすべてを上げ、
b
は端を下げ、
c
は低下率を制御します。
私はこれに完全に満足しているわけではなく 、まだまだ調査が必要です。 マンハッタンまたはユークリッド距離ですか? 中心までの距離に依存するのか、端までの距離に依存するのか? 距離を2乗するか、線形にするか、または他の程度にする必要がありますか? それは加算/減算、または乗算/除算、または他の何かでしょうか? 元の記事では、Add、a = 0.1、b = 0.3、c = 2.0を試すか、Multiply、a = 0.05、b = 1.00、c = 1.5を試します。 最適なオプションは、プロジェクトによって異なります。
なぜ標準的な数学関数に固執するのですか? 私の記事でRPGの損傷 (Habréでの翻訳)について述べたように、誰もが(私を含む)多項式、指数分布などの数学関数を使用しますが、コンピューター上ではそれらに限定することはできません。 ルックアップテーブル
e = e + height_adjust[d]
を使用して、 任意のフォーメーション関数を使用してここで使用できます。 これまでのところ、私はこの問題を研究していません。
スパイクノイズ
高さを累乗する代わりに、絶対値を使用して鋭いピークを作成できます。
function ridgenoise(nx, ny) { return 2 * (0.5 - abs(0.5 - noise(nx, ny))); }
オクターブを追加するには、大きな周波数の振幅を変化させて、追加されたノイズを山だけが受信できるようにします。
e0 = 1 * ridgenoise(1 * nx, 1 * ny); e1 = 0.5 * ridgenoise(2 * nx, 2 * ny) * e0; e2 = 0.25 * ridgenoise(4 * nx, 4 * ny) * (e0+e1); e = e0 + e1 + e2; elevation[y][x] = Math.pow(e, exponent);
私はこのテクニックの経験があまりないので、うまく使う方法を学ぶために実験する必要があります。 スパイクのある低周波ノイズとスパイクのない高周波ノイズを混ぜることも興味深いかもしれません。
テラス
高さを次のnレベルに丸めると、テラスが得られます。
これは、高さ再分布関数を
e = f(e)
形式で適用した結果です。 上記では、
e = Math.pow(e, exponent)
を使用して山のピークをシャープにしました。 ここでは、
e = Math.round(e * n) / n
を使用してテラスを作成します。 非ステップ関数を使用する場合、テラスを丸くしたり、特定の高さでのみ発生させることができます。
ツリーの配置
通常、高さと湿度にフラクタルノイズを使用しましたが、木や石などの不等間隔のオブジェクトを配置するためにも使用できます。 高さには、低周波数の高振幅(「レッドノイズ」)を使用します。 オブジェクトを配置するには、高周波数(「ブルーノイズ」)で高振幅を使用する必要があります。 ブルーノイズパターンは左側に表示されます。 右側は、ノイズが隣接する値よりも大きい場所です。
for (int yc = 0; yc < height; yc++) { for (int xc = 0; xc < width; xc++) { double max = 0; // for (int yn = yc - R; yn <= yc + R; yn++) { for (int xn = xc - R; xn <= xc + R; xn++) { double e = value[yn][xn]; if (e > max) { max = e; } } } if (value[yc][xc] == max) { // xc,yc } } }
バイオームごとに異なるRを選択すると、樹木の可変密度を取得できます。
このようなノイズを使用してツリーを配置できるのは素晴らしいことですが、他のアルゴリズムがより効果的であり、より均一な分布を作成することがよくあります。ポアソンスポット、ヴァンタイル、またはグラフィックディザリングです。
無限とその先へ
位置(x、y)でのバイオームの計算は、他のすべての位置の計算とは無関係です。 このローカル計算には、2つの便利なプロパティがあります。並列で計算でき、無限の地形で使用できます。 左側のミニマップ (元の記事)にマウスカーソルを置き、右側にマップを生成します。 カード全体を生成することなく(さらに保存することなく)、カードの任意の部分を生成できます。
実装
ノイズを使用して地形を生成することは一般的なソリューションであり、インターネットでは多くの異なる言語とプラットフォームのチュートリアルを見つけることができます。 異なる言語でカードを生成するためのコードはほぼ同じです。 以下に、3つの異なる言語での最も単純なループを示します。
- Javascript:
let gen = new SimplexNoise(); function noise(nx, ny) { // Rescale from -1.0:+1.0 to 0.0:1.0 return gen.noise2D(nx, ny) / 2 + 0.5; } let value = []; for (let y = 0; y < height; y++) { value[y] = []; for (let x = 0; x < width; x++) { let nx = x/width - 0.5, ny = y/height - 0.5; value[y][x] = noise(nx, ny); } }
- C ++:
module::Perlin gen; double noise(double nx, double ny) { // Rescale from -1.0:+1.0 to 0.0:1.0 return gen.GetValue(nx, ny, 0) / 2.0 + 0.5; } double value[height][width]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double nx = x/width - 0.5, ny = y/height - 0.5; value[y][x] = noise(nx, ny); } }
- Python:
from opensimplex import OpenSimplex gen = OpenSimplex() def noise(nx, ny): # Rescale from -1.0:+1.0 to 0.0:1.0 return gen.noise2d(nx, ny) / 2.0 + 0.5 value = [] for y in range(height): value.append([0] * width) for x in range(width): nx = x/width - 0.5 ny = y/height - 0.5 value[y][x] = noise(nx, ny)
すべてのノイズライブラリはほとんど同じです。 Pythonの opensimplex、C ++のlibnoise 、またはJavaScriptのsimplex-noiseを試してください。 最も一般的な言語には、多くのノイズライブラリがあります。 または、Perlinノイズがどのように機能するかを学ぶことも、自分でノイズを認識することもできます。 しませんでした
ご使用の言語のさまざまなノイズライブラリでは、アプリケーションの詳細はわずかに異なる場合があります(0.0〜1.0の範囲の戻り値、-1.0〜+1.0の範囲の戻り値など)が、基本的な考え方は同じです。 実際のプロジェクトでは、クラスで
noise
関数と
gen
オブジェクトをラップする必要がありますが、これらの詳細は無関係なので、それらをグローバルにしました。
このような単純なプロジェクトでは、どのようなノイズを使用してもかまいません。パーリンノイズ、シンプレックスノイズ、OpenSimplexノイズ、値ノイズ、ミッドポイントオフセット、ダイヤモンドアルゴリズム、逆フーリエ変換などです。 それぞれに長所と短所がありますが、同様のカードジェネレーターでは、ほぼ同じ出力値を作成します。
マップのレンダリングはプラットフォームとゲームに依存するため、実装しませんでした。 このコードは、高さとバイオームを生成するためにのみ必要であり、そのレンダリングはゲームで使用されるスタイルに依存します。 プロジェクトでコピー、移植、使用できます。
実験
オクターブを混合し、パワーをパワーに上げ、高さと湿度を組み合わせてバイオームを作成することを検討しました。 ここでは、これらのパラメーターをすべて試すことができるインタラクティブなグラフを調べて、コードの構成を示します。
サンプルコードを次に示します。
var rng1 = PM_PRNG.create(seed1); var rng2 = PM_PRNG.create(seed2); var gen1 = new SimplexNoise(rng1.nextDouble.bind(rng1)); var gen2 = new SimplexNoise(rng2.nextDouble.bind(rng2)); function noise1(nx, ny) { return gen1.noise2D(nx, ny)/2 + 0.5; } function noise2(nx, ny) { return gen2.noise2D(nx, ny)/2 + 0.5; } for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var nx = x/width - 0.5, ny = y/height - 0.5; var e = (1.00 * noise1( 1 * nx, 1 * ny) + 0.50 * noise1( 2 * nx, 2 * ny) + 0.25 * noise1( 4 * nx, 4 * ny) + 0.13 * noise1( 8 * nx, 8 * ny) + 0.06 * noise1(16 * nx, 16 * ny) + 0.03 * noise1(32 * nx, 32 * ny)); e /= (1.00+0.50+0.25+0.13+0.06+0.03); e = Math.pow(e, 5.00); var m = (1.00 * noise2( 1 * nx, 1 * ny) + 0.75 * noise2( 2 * nx, 2 * ny) + 0.33 * noise2( 4 * nx, 4 * ny) + 0.33 * noise2( 8 * nx, 8 * ny) + 0.33 * noise2(16 * nx, 16 * ny) + 0.50 * noise2(32 * nx, 32 * ny)); m /= (1.00+0.75+0.33+0.33+0.33+0.50); /* draw biome(e, m) at x,y */ } }
難点があります。高さと湿度のノイズのために、異なるシードを使用する必要があります。そうでなければ、それらは同じであることが判明し、カードはそれほど面白く見えません。 Javascriptでは、prng-parkmillerライブラリを使用します 。 C ++では、2つの個別のlinear_congruential_engineオブジェクトを使用できます 。 Pythonでは、 random.Randomクラスの 2つの個別のインスタンスを作成できます。
考え
マップ生成の単純さのためにこのアプローチが好きです。 それは高速であり、まともな結果を生成するために非常に少ないコードが必要です。
このアプローチにおける彼の制限は好きではありません。 ローカル計算では、各ポイントは他のすべてのポイントから独立しています。 マップの異なる領域は互いに接続されていません 。 マップ上の各場所は同じようです。 たとえば、「地図上に3〜5つの湖がある」などのグローバルな制限や、最高峰の頂上から海に流れる川などのグローバルな機能はありません。 また、良い画像を取得するためには、長時間パラメータを設定する必要があるという事実も好きではありません。
なぜ推奨するのですか? これは、特にインディーズゲームやゲームジャムにとっては良い出発点だと思います。 私の友達の2人がゲームコンテストのためにわずか30日で狂気の領域の初期バージョンを書いた。 地図の作成を手伝ってほしいと頼まれました。 私はこの手法(およびあまり役に立たないことが判明した他のいくつかの関数)を使用し、それらのマップを作成しました。 数か月後、プレイヤーからのフィードバックを受け取り、ゲームの設計を慎重に研究した後、 ここで説明するボロノイポリゴンに基づいたより高度なマップジェネレーターを作成しました(Habréでの翻訳 )。 このカードジェネレーターは、この記事で説明する手法を使用しません。 ノイズを使用して、まったく異なる方法でマップを作成します。
追加情報
ノイズ関数でできることはたくさんあります。 インターネットを検索すると、乱気流、大波、うねのあるマルチフラクタル、振幅減衰、テラス、ボロノイノイズ、解析的微分、ドメインワーピングなどのオプションを見つけることができます。 このページをインスピレーションの源として使用できます。 ここではそれらを考慮せず、私の記事は単純さに焦点を当てています。
このプロジェクトは、以前のマップ生成プロジェクトの影響を受けました。
- 私は最初のReal of the Mad Godカードジェネレーターに Perlinの全体的なノイズを使用しました。 アルファテストの最初の6か月間使用してから、アルファテスト中に決定したゲームプレイ要件のために特別に作成されたボロノイポリゴンのマップジェネレーターに置き換えました。 記事のバイオームとその色は、これらのプロジェクトから取られています。
- オーディオ信号の処理を研究するとき、周波数、振幅、オクターブ、ノイズの「色」などの概念を説明するノイズチュートリアルを書きました。 サウンドで機能する同じ概念は、ノイズベースのカード生成にも適用されます。 そのとき、私は生のデモ救済生成を作成しましたが、まだ完成していません。
- 境界を見つけるために実験することもあります。 魅力的なマップを作成するために最低限必要なコードの量を知りたいと思いました。 このミニプロジェクトでは、コードのゼロ行に到達しました-すべては画像フィルター(乱流、しきい値、色のグラデーション)で行われます。 これは私を幸せで悲しくさせました。 画像フィルターによってマップ生成をどの程度実行できますか? 十分に大きい。 「滑らかな色のグラデーションのスキーム」について上で説明したすべては、この実験から取られています。 ノイズレイヤーは乱流イメージフィルターです。 オクターブは互いに重ね合わされた画像です。 度ツールは、Photoshopでは「曲線補正」と呼ばれます。
少し気になるのは、ゲーム開発者がノイズベースの地形(中点変位を含む)を生成するために記述するコードのほとんどが、サウンドフィルターとイメージフィルターのコードと同じであるということです。 一方で、わずか数行のコードでかなりまともな結果が得られるため、この記事を執筆しました。 これは、 すばやく簡単に参照できるポイントです。 通常、このようなカードは長い間使用しませんが、ゲームデザインに適したタイプのカードが見つかったらすぐに、より複雑なマップジェネレーターに置き換えます。 これは私にとって標準的なパターンです。非常に単純なものから始めて、使用しているシステムをよりよく理解した後にのみ置き換えます。
ノイズを使ってできることは他にもたくさんありますが、私が言及した記事ではほんのわずかです。 Noise Studioを試して、さまざまな機能をインタラクティブにテストしてください。