Pyxel 地形の自動生成

パーリンノイズでタイルマップを設定する

 Python向けレトロゲームエンジン「Pyxel(ピクセル)」で地形の自動生成(タイルマップにタイルを自動で配置させる)の実験をしました。

 Pyxelのnoise()命令を使うと,座標ごとのパーリンノイズの値を取得できるので,このノイズ値をタイルに置き換えます。
 
 
 ランダムな配置ではなく,起伏があるような変化になります。

 ・パーリンノイズの公式サンプル pyxel/12_perlin_noise.py at main · kitao/pyxel · GitHub

 ・noise()命令コード例 game/api_math.md at main · benkyoubox/game · GitHub


範囲ごとのタイルの割り当て

実験A 水辺と陸地のイメージ

 以下のタイルを配置する例。
 

genmap01.pygame/genmap01.py at main · benkyoubox/game · GitHub
抜粋

def genmap(self):
    scale = 0.035     # この値が大きいほど変化が多くなる
    z = 0
    fieldtile_y = 0
    for y in range(self.width//8):
        for x in range(self.height//8):
            n = pyxel.noise(x*scale, y*scale, z*scale)    # -1.0 から 1.0 の間の値が返る

            if n < -0.3:
                tileNo = 1
            elif n < -0.2:
                tileNo = 2
            elif n < 0.3:
                tileNo = 3
            else:
                tileNo = 4

            pyxel.tilemap(TM).pset(x,y,(tileNo,fieldtile_y))
    return

実行結果

 曲線がはっきりしているので,ノイズ値に変化をつけてみます。(正しくはオクターブという考え方で合成するようですが,今回は無理やり値を変えます)

<2023-04-16追記>
 複雑な地形にする場合のノイズ値の合成について訂正記事を書きました。

 kinutani.hateblo.jp

 
※古いコード例は削除

 丸みを帯びた境界を少しでこぼこさせることができました。

 

実験B 高所と低所

 以下のタイルを配置する例。(0,1:草原,2,3:岩場,4,5」雪原のイメージ)
 
・1回目のマップ全マスループで,ノイズ値により低い所のタイルと高い所のタイルを配置します。(ノイズ値はabs()で絶対値にして中央部分が低地になるようにする。高所のタイルは壁にしてゲーム時は通行不可にする想定)
・その次のループで,高所タイルの下方向2マスが低所タイルならば崖タイルに置き換えます。
・草原の場合は,スケールを変えたノイズで花の分布を重ねます。

game/genmap02.py at main · benkyoubox/game · GitHub
抜粋

def genmap(self,width,height,scale,z,key):
    ''' w,h pixelsize  scale=0.05-0.07 z=1- '''
    self.width = width
    self.height = height
    fieldtile_low = stagetiles[key][0]
    fieldtile_high = stagetiles[key][1]
    lidx = 0
    for y in range(self.width//8):
        for x in range(self.height//8):
            n = abs(pyxel.noise(x*scale, y*scale, z*scale))

            if n < 0.15:
                pyxel.tilemap(TM).pset(x,y,fieldtile_low[1])
            elif n < 0.5:
                pyxel.tilemap(TM).pset(x,y,fieldtile_low[0])
            else:
                pyxel.tilemap(TM).pset(x,y,fieldtile_high[0])

    clifftile = stagetiles[key][2]
    for y in range(self.width//8 - 1):
        for x in range(self.height//8):
            if fieldtile_high[0] == pyxel.tilemap(TM).pget(x,y) :
                if fieldtile_low[0][0] == pyxel.tilemap(TM).pget(x,y+1)[0] :
                    pyxel.tilemap(TM).pset(x,y+1,clifftile[0])
                    if y+2 < self.height//8:
                        pyxel.tilemap(TM).pset(x,y+2,clifftile[1])
    if 'grassy' == key:
        for y in range(self.width//8):
            for x in range(self.height//8):
                if fieldtile_low[0] == pyxel.tilemap(TM).pget(x,y) :
                    n = pyxel.noise(x*0.08, y*0.08, z*scale)*pyxel.rndf(-1.11,1.19)
                    if n < -0.4:
                        pyxel.tilemap(0).pset(x,y,fieldtile_low[2])
                    elif 0.4 < n:
                        pyxel.tilemap(0).pset(x,y,fieldtile_low[3])
                
    return

実行結果



リソースファイルの保存

 自動生成した地形のタイルマップをリソースファイルとして出力する場合は,save()命令を使います。(上級者向けAPI

save(filename, [image], [tilemap], [sound], [music])

filename 出力ファイル名
[image], [tilemap], [sound], [music] 保存フラグ(bool デフォルト値はTrue)

pyxel.save("newmap.pyxres")

 

ゲームへの利用

 アクションアドベンチャーゲームのマップに利用する例です。
 

 【ソースコード
 
 game/pyxel/generator_map/prot at main · benkyoubox/game · GitHub
 ※ stages.py の genmap() が地形の自動生成処理です。
 ※ ゲームとしては機能しません。

 【Webブラウザでのデモ】
 http://benkyoubox.starfree.jp/100_game/10_pyxel/actadv/actadv01_map.html
 (Cキーで表示マップ切り替え。壁で動けないときは再度Cキーを押してください)


 

関連記事

kinutani.hateblo.jp