屋根裏

コードを書いたりします

ドット絵の惑星を生成するプログラムをp5.jsで作る

以前、Planetariumという惑星をランダムに生成してくれるソフトに触れ、とても印象に残っていたので自分でも作ってみました。

デモ

f:id:yurkth:20200511021415p:plain

このページで実際に惑星を生成できます。

テキストボックスに適当な英数字を入れてExploreボタンを押すと、その惑星が到着されます。何も入力しないと未知の惑星にたどり着きます。

環境

p5.js

今回もSlay the Spire風のマップを作るときに利用したp5.jsを使っていきます。

実装

ドット絵の表示

p5.jsは初めからドット絵を表示できるわけではありません。今回は数行追加してピクセルパーフェクトなドット絵を描画できるようにしました。

普通にcanvasを作成した後、styleで拡大させています。以下のコード中で使われているpwは幅、phは高さ、scalingは倍率です。今回はscalingをウィンドウのサイズに合わせて設定していますが、適当な定数でも大丈夫です。ただし、整数の方が良いと思います。

pixelDensityはデフォルトで1に設定されているはずなのですが、スマートフォンからアクセスした場合の初期値が2になっていたので、あらかじめ設定することにしました。

  const canvas = createCanvas(pw, ph)
  pixelDensity(1) // for smartphone
  canvas.parent("canvas")
  canvas.elt.style.cssText += `width: ${width * scaling}px; height: ${height * scaling}px;`

また、普通に拡大しただけだとぼやけてしまうので、cssからimage-renderingpixelatedを設定しています。

canvas {
  margin: auto;
  display: block;
  image-rendering: pixelated;
}

ピクセルの描画にはset関数を使います。直接pixels[]に入れたほうが速いようなのですが、スマートフォンで描画が崩れたため今回はset関数を使っています。

他にも、background関数やtext関数はそのまま使うことができます。テキストを表示する際はフォントとフォントサイズに注意してください。図形の描画は、四角形ならばnoStrokeしてから使うことでちゃんと描画できるのですが、丸などはnoSmoothを使ってもアンチエイリアスが勝手に入ってしまい無理でした。

球体の描画

今回は2Dのマップを球体に見せかけています。

2Dのマップは2:1のサイズにし、そのうちの半分が球体の見える部分になります。

f:id:yurkth:20200511020317p:plain

これを球面に見えるようにしていきたいのですが、まずはドット絵で円を描けるようになる必要があります。

TIC-80というオープンソースのファンタジーコンソールで利用していたアルゴリズムを参考にし、下の画像のように円の各行の幅を求められるようにしました(画像は直径が19pxの円の場合です)。TIC-80のプログラムは直径が奇数の円しか描けなかったのですが、今回は偶数でも描けるようにしています。

f:id:yurkth:20200511020257p:plain

ここで、2Dのマップから切り抜いた正方形の各行を円の幅に圧縮することで平面を球面に見せていきます。各行から円に必要な数だけのピクセルを均等に抜き出しました。

f:id:yurkth:20200511020333p:plain

これを中心に寄せ集めることで球体っぽく見せることができました。

f:id:yurkth:20200511020341p:plain

マップ生成

マップの生成にはノイズ関数を使っていきます。p5.jsのnoise関数はあまり速くないので、simplex-noiseというライブラリを使いました。通常のSimplex noiseだけだとあまり面白くないので、fBmやドメインワーピングを使っているほか、リッジノイズや三角関数を利用した縞模様にも対応しています。

2Dのノイズをそのまま貼り付けると、球体に描画したときに極地に近づくほど圧縮されてしまいます。そのため、3Dのノイズに2Dの座標からアクセスできるように座標変換を行っています。

f:id:yurkth:20200511020244p:plain

ただノイズを使っているだけなのであまりきれいなマップにはなりませんが、今回は色数も少なくするので気にならないと思いこれだけにしておきます。より細かいマップを作りたい場合はRed Blob Gameさんの記事を見てみるといいでしょう。

パレット生成

今回はパレットも生成していきたいと思います。

色はHSV(HSB)色空間で指定します。基準となる色相をランダムに用意し、それに180を足すと補色が手に入るといった要領で色を作ります。統一感を持たせるため、彩度と明度はあらかじめある程度指定しておき、ランダムに上下させるのがいいでしょう。色相を青に近づけると色が暗くなるので、それも活用しています。

こちらを参考にしながら、類似色や補色など、いくつかのパレットのパターンを用意しました。

正直、微妙なパレットになることも多いので、もう少し条件をつけた方がいいように思います。色数制限について考えないなら、こちらのパレット生成がいいかもしれません。

仕上げ

雲や衛星、背景の星を追加したら完成です。

一度訪れた惑星に再び行けるように、テキストボックスからシード値を設定できるようにもしました。

f:id:yurkth:20200511020232p:plain

まとめ

結構いい雰囲気になったのではないでしょうか。まだパレットや惑星の模様など、手を加えられるところは多いと思うのでたまに改良していきたいです。

このプログラムで生成された惑星の画像をつぶやくTwitterbotも作ってみたので、そちらについても別の記事で紹介しています。

yurkth/astraea - GitHub