simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

CubeWalk:水平な世界データから始める

はじめに

PlayFab ログイン成功後、CDNから世界圧縮データを取得し
Unity で展開してから、それぞれのチャンク配列を確保し、データを詰めていく
詰め終わったら、ゲームシーンを開始する

世界圧縮データを最初は作らなければならない

世界データは16,777,216のint配列

ゲーム内のチャンクインデックスの振り方をおさらい

チャンクにはキーがあり、3次元であらわされたものを一次元に直すと次の通り

        /// <summary>
        /// チャンクキーから世界で一意のチャンクインデックスを取得
        /// </summary>
        internal int ChunkKeyToChunkIndex(Vector3Int chunkKeyXYZ)
        {
            var byteMax = (byte.MaxValue + 1);
            return chunkKeyXYZ.x * byteMax * byteMax + chunkKeyXYZ.z * byteMax + chunkKeyXYZ.y;
        }

つまり、y方向にインクリメントで、z方向に 256 単位オフセット、x方向は 256x256 オフセットが足されて特定されます。

同じようなやり方なら y 方向にインクリメントで、z方向に 16 単位オフセット、x方向は 16x16オフセットが足される形でチャンクのインデックスが得られるとする
一つのチャンクが 16 x 16 x 16 の 4096 キューブ情報を持つので、常にチャンクインデックス x 4096 がチャンクのデータポインタとなります。

ゲーム内のキューブインデックスの振り方をおさらい

        /// <summary>
        /// キューブの3次元位置表現から一次元の配列インデックスを取得
        /// </summary>
        int CubeInt3ToCubeIndex(Vector3Int cubeInt3)
        {
            return cubeInt3.x * ChunkConst.ChunkSizeZ * ChunkConst.ChunkSizeY + cubeInt3.z * ChunkConst.ChunkSizeY + cubeInt3.y;
        }

つまり、 y 方向にインクリメントで、z方向に 16 単位オフセット、x方向は 16x16オフセットが足される形でキューブのインデックスが得られる
チャンクの時と同じですね。
これでキューブのチャンク内の3次元位置から 0~4095 のインデックスになる

世界を y 方向に二分するインデックス範囲は?

y軸上半分を 0 とし
y 軸下半分を 1 とする値埋めをしたいとしたら

チャンクインデックスを各軸 0~15でループさせる 3重ループを作ったとして
その時にチャンクインデックスをy 方向にインクリメントで、z方向に 16 単位オフセット、x方向は 16x16オフセットが足される形でチャンクのインデックスを求め
その時にチャンクインデックス x 4096 がチャンクのデータポインタとなり
そこから連続して 4096 個を処理するとして
条件はチャンクインデックスが y < 8 とするときに 1 を入れれば良い

では golang で作ってみます。

golang で水平な世界埋め

まずはおさらい
前回のコードに
simplestar-tech.hatenablog.com

simplestar-tech.hatenablog.com

次の main を書けば下準備がそろう

func main() {

	// s3 init
	err := PersistentS3Store.Init(s3bucket, "ap-northeast-1")
	if err != nil {
		log.Fatal("s3 Init error.", err)
		return
	}
	// s3 download backup
	gzdata, err := PersistentS3Store.Get(cubedataObjectKey)
	if err != nil {
		log.Fatal("s3 Get error.", err)
		return
	}
	// unzip backup
	cubedata, err := gUnzipData(gzdata)
	if err != nil {
		log.Fatal("gUnzipData error.", err)
	}

	// zip data
	compressedData, err := gZipData(cubedata)
	if err != nil {
		log.Fatal("gZipData error.", err)
	}

	// local save
	writeFileData(compressedData, cubedataObjectKey)
	// s3 upload
	// err = PersistentS3Store.Set(cubedataObjectKey, compressedData)
	// if err != nil {
	// 	log.Fatal("s3 Set error.", err)
	// }
}

肝心の for 文を次のように書きます。
乱数生成あり

	random := rand.New(rand.NewSource(1))
	deight := 16
	for x := 0; x < deight; x++ {
		chunkX := x * deight * deight
		for z := 0; z < deight; z++ {
			chunkZ := z * deight
			// random blocks
			for y := 0; y < 8; y++ {
				chunkOffset := (chunkX + chunkZ + y) * deight * deight * deight
				for index := 0; index < deight*deight*deight; index++ {
					dataIndex := (chunkOffset + index) * 4
					cubedata[dataIndex+0] = 1
					cubedata[dataIndex+1] = (byte)(random.Intn(24))
					side := (byte)(random.Intn(67))
					cubedata[dataIndex+2] = side
					cubedata[dataIndex+3] = side
				}
			}
			// sky
			for y := 8; y < deight; y++ {
				chunkOffset := (chunkX + chunkZ + y) * deight * deight * deight
				for index := 0; index < deight*deight*deight; index++ {
					dataIndex := (chunkOffset + index) * 4
					cubedata[dataIndex+0] = 0
					cubedata[dataIndex+1] = 0
					cubedata[dataIndex+2] = 0
					cubedata[dataIndex+3] = 0
				}
			}
		}
	}

出力した gz ファイルのサイズは 16.7 MB
さすがに乱数で作ったファイルはそこまで小さくたためなかった

これを s3 に上げて、そのあとキャッシュサーバーを再起動すれば PlayFab の CDN が 16MB ほどのファイルになるはずです。
やってみましょう。

動作確認

PlayFab のスケジュールタスクはタイムアウトエラーが出ているけど
CDNには 17MB のデータがアップロードされていました。
go には何かそういうの回避するのあるのかな?

go側はタイムアウト設定した方がリークがなくなってよいのだとか
christina04.hatenablog.com


あと
>http.request呼び出しタイムアウトは2.5秒です。
解決方法は今のところないのだとさ まぁタイムアウトが起きても、呼び出されたキャッシュサーバーは仕事を完遂するので今は良しとします。
community.playfab.com