2d-extra にはRuleTile
というカスタムタイルの実装例が載っており、これが中々に便利で愛用しています。
https://github.com/Unity-Technologies/2d-extras/blob/master/Assets/Tilemap/Tiles/Rule%20Tile/Scripts/RuleTile.cs
その便利な所については今回は割愛させていただくんですが(ひどい)
機能の一つである「ランダム表示」のランダム感がイマイチだと思ったので、その改善記録を載せておきます。
例
ここに4つのスプライトがあります。どれも横が繋がるようになっているので、ランダムに並べればパターン感の少ない石壁を作れると思っていました。
これらをRuleTile
でランダム表示させると以下のようになります。
なんか‥‥なんか同じスプライトのタイル多くない?
全部同じではないけど、ほとんど同じというかなり微妙なランダム感‥‥。
RuleTile
には Perlin Scale というパラメータを指定できるのですが、調整しても大差ありませんでした。
コードを覗いてみる
どのスプライトを使うかは、以下のGetPerlinValue()
が返す0.0~1.0の値で決まるということが分かりました。
protected static float GetPerlinValue(Vector3Int position, float scale, float offset)
{
return Mathf.PerlinNoise((position.x + offset) * scale, (position.y + offset) * scale);
}
パーリンノイズは滑らかに変化するノイズなので、隣接するセルの値が同じになりやすいという仮説を立ててみます。
そこで、ホワイトノイズに変更してみました。
ホワイトノイズに変更してみる
厳密には、これはposition
のハッシュ関数になってないので良い実装ではないのですが‥‥
(保存するたびに見た目がころころ変わります)
protected static float GetPerlinValue(Vector3Int position, float scale, float offset)
{
return Random.Range(0f, 1f);
}
とりあえず、パーリンノイズよりはマシになった気がします。
でも3段目の右など、まだ繰り返しが気になりますね。当然ながら、完全なランダムなので同じスプライトが隣接するのは避けられません。同じ値が隣接しないランダムというのは、簡単に実装できないものでしょかう?
辿り着いた答えは市松模様
グリッドを市松模様とみなして、
- 黒の時は、0.0~0.5のランダム値
- 白の時は、0.5~1.0のランダム値
と計算する方法を閃いたので試してみました。
private static float GetPerlinValue(Vector3Int position, float scale, float offset)
{
if ((position.x + position.y) % 2 == 0)
{
return Random.Range(0f, 0.5f);
}
else
{
return Random.Range(0.5f, 1f);
}
}
大変良くなりました。この実装では上下左右に隣り合うスプライトは必ず異なる1ので、パターン感がとても少ないです。
ということで、RuleTile
のランダム感に不満を感じたら、上のようにカスタマイズしてみてはいかがでしょうか。
※自分は上のコードで満足していますが、保存するたびに表示がコロコロ変わるのが嫌な方はRandom.Range()
の代わりにMathf.PerlinNoise()
を使って上手いことやりましょう。
参考情報
RuleTile
ではなくRandomTile
の実装例では、ホワイトノイズっぽい雰囲気の秘伝のタレが公開されています。
https://github.com/Unity-Technologies/2d-extras/blob/master/Assets/Tilemap/Tiles/Random%20Tile/Scripts/RandomTile.cs
public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData)
{
base.GetTileData(location, tileMap, ref tileData);
if ((m_Sprites != null) && (m_Sprites.Length > 0))
{
long hash = location.x;
hash = (hash + 0xabcd1234) + (hash << 15);
hash = (hash + 0x0987efab) ^ (hash >> 11);
hash ^= location.y;
hash = (hash + 0x46ac12fd) + (hash << 7);
hash = (hash + 0xbe9730af) ^ (hash << 11);
Random.InitState((int)hash);
tileData.sprite = m_Sprites[(int) (m_Sprites.Length * Random.value)];
}
}
そこまで計算しといて結局Random
使うんかいと言いたくなりますが、とりあえずハッシュ関数的な動きにはなってそうなので、ホワイトノイズの代わりに使ってみるのもいいかもしれません。
-
ランダムの候補の数が奇数だと、候補の真ん中にあるスプライトは連続することがあり得ます ↩