Minecraft用のワールドジェネレーターの作成

はじめに



ほぼすべてのHabrの読者がMinecraftについて聞いたと思います。誰かがシングルでプレイし、誰かが多数のサーバーの1つで、誰かがhabrayuzerの1人でも小さなサーバーを持っていました。 2か月プレイした後、私は疑問に思いました-自分のマップジェネレーターを書くことは可能ですか 判明したように、これは数日間のゆるやかなグーグルとコーディングで可能です。



少し技術的な部分


鉱山wikiプロジェクトによると、マップは地域ファイル(16x16x128キューブの32x32ブロック、地域ごとに合計262144平方ブロック)に保存され、次の構造を持ちます。

  1. チャンクオフセット(いわゆる16x16x128ブロック)を含む4096バイト、および4kbのブロック単位のサイズ、切り上げ、3バイトオフセット、1-サイズ
  2. 4096バイトのタイムスタンプチャンク、各4バイト
  3. ファイルの最後までの残りのスペースは、実際には、Zlibで圧縮されたチャンクデータです。 4バイト-圧縮データのサイズ、1-圧縮方法(デフォルト2、Zlib(RFC1950))、zlibで圧縮されたサイズ1のNBT構造 、つまりキューブコンテナー自体

    パックされたデータが4 kbの整数のセクター数より少ない場合、各チャンクは4096バイトの整数のセクター数で表されるオフセットで始まるため、セクターの残りはゼロで埋められます。


言語選択


Delphi 7を停止しました。第一に、これが私が知っている唯一の言語であり、第二に、ギャンブルのマニュアルにノートを書き始めたのは4年前のバージョン7でした。



コード


データは圧縮形式で保存されるため、 zlibモジュールが必要です。

ZlibExを使用しました



最初に、後でデータを書き込むチャンククラスを作成します

Tchunk = class(TObject) private public Data: tmemorystream; c_data: tmemorystream; c_stream: tzcompressionstream; constructor Create; procedure writeblock(x, y, z, block: integer); overload; procedure writeblock(x, y, z, block, color: integer); overload; procedure compress; end;
      
      







このクラスのコードは次のとおりです。


 constructor tchunk.Create; begin Data := TMemoryStream.Create; Data.size := 82360; Data.LoadFromFile('data.bin'); c_data := TMemoryStream.Create; c_stream := tzcompressionstream.Create(c_data, zcdefault, 15, 8, zsdefault); end; procedure tchunk.writeblock(x, y, z, block: integer); begin Data.Seek(form1.getoffset(x, y, z) + 16487, 0); Data.Write(block, 1); end; procedure tchunk.compress; var buffer: array [0..82360] of byte; begin c_data.Position := 0; Data.Position := 0; Data.Read(buffer, 82360); c_stream.writebuffer(buffer, 82360); c_stream.Free; c_data.SaveToFile('file' + IntToStr(n)); end;
      
      







getoffset関数は、y + ( z *128 + ( x * 128 * 16 ) )



によって目的のオフセットを返します
 function tform1.getoffset(x, y, z: integer): integer; begin Result := y + (z * 128 + (x * 128 * 16)); end;
      
      







varにいくつかの変数を追加します。

 chunks:array[0..32] of array[0..32] of tchunk; n: integer=0;
      
      







すべてのチャンクを完成したファイルに組み立てる手順:

 procedure tform1.SwapEndiannessOfBytes(var Value: cardinal); var tmp: cardinal; i: integer; begin tmp := 0; for i := 0 to sizeof(Value) - 1 do Inc(tmp, ((Value shr (8 * i)) and $FF) shl (8 * (sizeof(Value) - i - 1))); Value := tmp; end; procedure tform1.generatefile; var fileoffset: integer; time, compressiontype, counter: integer; filename: string; regionfile: tfilestream; tmp: cardinal; size: integer; n_x, n_z: integer; bu: array[0..99999] of byte; n: integer; roundedsize: integer; neededsize: integer; d: byte; begin fileoffset := 2; time := $d8de2f4e; compressiontype := $02; filename := GetVar('Appdata') + '\.minecraft\saves\NewWorld\region\r.0.0.mcr'; regionfile := tfilestream.Create(filename, fmcreate); n := 0; for n_x := 0 to 31 do for n_z := 0 to 31 do begin chunks[n_x][n_z].compress; roundedsize := ((chunks[n_x][n_z].c_data.Size) div 4096); if (((chunks[n_x][n_z].c_data.Size) mod 4096) > 0) then Inc(roundedsize); regionfile.seek((4 * ((n_x mod 32) + (n_z mod 32) * 32)), 0); tmp := fileoffset; SwapEndiannessOfBytes(tmp); tmp := tmp shr 8; regionfile.Write(tmp, 4); regionfile.seek(4 * ((n_x mod 32) + (n_z mod 32) * 32) + 3, 0); regionfile.Write(roundedsize, 1); size := chunks[n_x][n_z].c_data.Size + 1; regionfile.seek(fileoffset * 4096, 0); tmp := size; SwapEndiannessOfBytes(tmp); regionfile.Write(tmp, 4); regionfile.Write(compressiontype, 1); chunks[n_x][n_z].c_data.Position := 0; chunks[n_x][n_z].c_data.readbuffer(bu, chunks[n_x][n_z].c_data.size); regionfile.Writebuffer(bu, chunks[n_x][n_z].c_data.size); regionfile.seek((n) * 4 + 4096, 0); regionfile.Write(time, 4); fileoffset := fileoffset + ((chunks[n_x][n_z].c_data.Size) div 4096); if (((chunks[n_x][n_z].c_data.Size) mod 4096) > 0) then fileoffset := fileoffset + 1; Inc(n); end; neededsize := 4096 * fileoffset - regionfile.Size - 1; regionfile.Seek(regionfile.Size, 0); d := 00; for n := 0 to neededsize do regionfile.Write(d, 1); regionfile.Free; end;
      
      







これで、領域内の任意の座標で任意のブロックを記録する方法ができました。 必要に応じて、残りの領域で同じことを簡単に繰り返すことができます。コードの10行目が必要です。



書き込みブロックのラップ:

 procedure tform1.writeworld(x, y, z, block: integer); var xw, zw: integer; begin xw := (x div 16); zw := (z div 16); chunks[xw][zw].writeblock(x mod 16, y, z mod 16, block); end;
      
      







世界の世代、その圧縮と保存。

 procedure TForm1.Button4Click(Sender: TObject); var x, y, z: integer; xx, zz: integer; image: tbitmap; begin for xx := 0 to 31 do for zz := 0 to 31 do begin chunks[xx][zz] := tchunk.Create; end; image := tbitmap.Create; image.LoadFromFile('image.bmp'); for x:=0 to 127 do for y:=0 to 116 do begin if image.Canvas.Pixels[x,y]=clblack then form1.writeworld(x,117-y,0,49); if image.Canvas.Pixels[x,117-y]=clwhite then form1.writeworld(x,y,0,80); end; form1.generatefile;
      
      







結果:

画像

画像



ピクセルアートだけでなく、任意の形状を生成できます。これらはすべて、任意の式で設定できます。 たとえば、正弦波としての性別:

画像



プロジェクトはこちらからダウンロードできます



既知のバグ:



ToDo:





更新:



参照:




All Articles