simplestarの技術ブログ

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

Unity:C#で共用体ちっくなビットフィールドの利用:SimpleHexWorld17.8:適切にマテリアルを切り替えたい

前回、パフォーマンスを落とさないために、テクスチャ座標で異なるブロックの種類を表現できる仕組みを作ったわけです

f:id:simplestar_tech:20180121185357j:plain
テクスチャ座標でブロックの種類を表現

が、それでもマテリアルを切り替えたいときだってあります。

そもそも四大元素や陰陽五行はマテリアルが違うからこそ、人類は歴史的に別の要素として認識してこれたわけです。(ですよね?)

1.透明な風
2.半透明な水
3.不透明な木、土
4.光を反射する金
5.光を発する火

実装としては、512種類ごとにマテリアルを切り替えるIDのルールを決めてほしいという欲求があります。
また、現在の Block の情報は int 値の 32 bit で、シリアライズするときは int としてシステムに認識させておいて
アプリ側での利用でコストの小さい変換により、コードとしては堅牢かつ可読性の高い情報アクセスというものを望んでいます。
解決方法として、以下のように共用体チックな ビットフィールド指定を行い、プロパティとしてビットフィールドの値を取り出せるようにしてみました。
int でファイルから読み込んだ値を変換する時のこと考えたら _raw を外から設定できるようにしておくと良いかもしれません。

public struct BlockDataSt
{
    public static BlockDataSt Identity { get { return new BlockDataSt() { _raw = 0 }; } }

    public MaterialID materialId
    {
        get { return (MaterialID)(_raw & mask0); }
        set { _raw = (uint)(_raw & ~mask0 | (uint)value & mask0); }
    }

    public byte stress
    {
        get { return (byte)((_raw & mask1) >> loc1); }
        set { _raw = (uint)(_raw & ~mask1 | ((uint)value << loc1) & mask1); }
    }

    public byte heat
    {
        get { return (byte)((_raw & mask2) >> loc2); }
        set { _raw = (uint)(_raw & ~mask2 | ((uint)value << loc2) & mask2); }
    }

    public byte impuritiesId
    {
        get { return (byte)((_raw & mask3) >> loc3); }
        set { _raw = (uint)(_raw & ~mask3 | ((uint)value << loc3) & mask3); }
    }

    public byte impuritiesRatio
    {
        get { return (byte)((_raw & mask4) >> loc4); }
        set { _raw = (uint)(_raw & ~mask4 | ((uint)value << loc4) & mask4); }
    }

    private uint _raw;
    private const int sz0 = 10, loc0 = 0, mask0 = ((1 << sz0) - 1) << loc0;
    private const int sz1 = 7, loc1 = loc0 + sz0, mask1 = ((1 << sz1) - 1) << loc1;
    private const int sz2 = 7, loc2 = loc1 + sz1, mask2 = ((1 << sz2) - 1) << loc2;
    private const int sz3 = 4, loc3 = loc2 + sz2, mask3 = ((1 << sz3) - 1) << loc3;
    private const int sz4 = 4, loc4 = loc2 + sz2, mask4 = ((1 << sz3) - 1) << loc4;
}

public enum MaterialID
{
    Air = 0,
    Earth = 1,
    Gold = 2,
    Water = 3,
    Wood = 4,
    Fire = 5,
}

上記実装は
stackoverflow.com
の掲示板の kvc さんの返信内容を参考に作っています。

ほかにも

1.ビット フラグとしての列挙型
列挙型 (C# プログラミング ガイド) | Microsoft Docs

2.[StructLayout(LayoutKind.Explicit)] 複数のフィールドの位置を重ねる
ufcpp.net

3.BitVector32 構造体
BitVector32 構造体 (System.Collections.Specialized)

を参考にしましたが、1.はコードを書く時の認識負荷が高いので却下で、2.はそもそもビットフィールドではなく byte 単位のオフセットだったのと、3.は良かったかもしれませんが、32bit の制約を付けたくなかったという理由でやめています。

さて、アプリ側の話ですが、マテリアルごとに分類し、512種類ごとに振り分けるとこんな感じでしょうか

public enum MaterialID
{
    Air = 0,
    Earth = 1,
    Wood,
    Water = 512,
    Glass,
    Crystal,
    Gold = 1024,
    Silver,
    Copper,
    Iron,
    Fire = 1536,
}

これで、数字を見るだけでどのマテリアルを振ればよいか判定できますし、そのマテリアル切り替えのオフセット量を ID から引けば、マテリアル内でのインデックスが取り出せそうです。
さっそく、マテリアルを切り替える処理を書いてみましょう。

以下は実装メモなので、後程記載