Unityで開発する際、構造体(struct)の適切な使用はパフォーマンスとコード品質に大きな影響を与えます。この記事では、Unity C#での構造体活用に関する実践的なアプローチを紹介します。
// 例: 3次元の座標を表す小さな構造体
public struct Position
{
public float x;
public float y;
public float z;
}
// 例: 複雑なゲームオブジェクトデータ
public class EnemyData
{
public string name;
public List<Ability> abilities;
public Dictionary<string, float> stats;
}
// 良い例: 必要最小限のフィールドを持つ構造体
public struct PlayerInput
{
public float horizontal;
public float vertical;
public bool jumpPressed;
}
// 避けるべき例: 大きすぎる構造体
public struct GameState
{
public string levelName; // 文字列は参照型
public List<Enemy> enemies; // コレクションは参照型
public Dictionary<int, PlayerData> players;
// 他多数のデータ...
}
// イミュータブルな構造体の例
public readonly struct GameTime
{
private readonly float elapsedTime;
public GameTime(float time)
{
elapsedTime = time;
}
public float ElapsedTime => elapsedTime;
// 変更する代わりに新しいインスタンスを返す
public GameTime AddTime(float deltaTime) => new GameTime(elapsedTime + deltaTime);
}
// 小さな構造体は値渡し
public void MoveCharacter(Vector3 direction)
{
transform.position += direction * speed * Time.deltaTime;
}
// 大きめの構造体は参照渡し
public void ProcessPhysics(ref PhysicsData physicsData)
{
// データを処理・更新
}
// 読み取り専用の場合はinキーワード
public float CalculateTrajectory(in ProjectileData data)
{
// データを読み取るだけの処理
return /* 計算結果 */;
}
using Unity.Entities;
using Unity.Mathematics;
// DOTSでのコンポーネントとして使用する構造体
public struct TransformComponent : IComponentData
{
public float3 position;
public quaternion rotation;
public float3 scale;
}
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
[BurstCompile]
public struct ParticleUpdateJob : IJobParallelFor
{
public NativeArray<float3> positions;
public NativeArray<float3> velocities;
public float deltaTime;
public void Execute(int index)
{
positions[index] += velocities[index] * deltaTime;
}
}
// ゲーム内のタイルデータを表す構造体
public struct TileData
{
public byte type;
public byte elevation;
public ushort flags;
// 32ビットに収まるようパッキング
}
// 使用例
public class ChunkManager : MonoBehaviour
{
private NativeArray<TileData> tiles;
private const int ChunkSize = 16 * 16;
void Awake()
{
tiles = new NativeArray<TileData>(ChunkSize, Allocator.Persistent);
}
void OnDestroy()
{
if (tiles.IsCreated)
tiles.Dispose();
}
}
// 問題: ボックス化が発生するコード
private List<Vector3> positions = new List<Vector3>();
Dictionary<Vector3, int> positionMap = new Dictionary<Vector3, int>();
// 解決策: ジェネリック型やNativeCollectionの活用
private NativeList<Vector3> positions;
private NativeParallelHashMap<Vector3, int> positionMap;
// 問題: 構造体のフィールドを直接変更できない
public struct Enemy
{
public Vector3 position;
public int health;
}
void UpdateEnemy()
{
Enemy enemy = enemies[0];
enemy.position += Vector3.forward; // これだけではenemies[0]は更新されない
enemies[0] = enemy; // 正しい方法: 変更後に再代入する
}
// 問題: 大きな構造体の過剰コピー
public struct LargeData
{
public Vector3[] points; // 配列は参照なのでOK
public float[,] heightMap; // 2次元配列も参照
}
// メソッド呼び出しでのコピー問題を解決
public void ProcessData(ref LargeData data)
{
// refで参照渡しすることでコピーを防止
}
// 構造体とクラスの性能比較例
public class PerformanceTest : MonoBehaviour
{
private const int Count = 100000;
void Start()
{
TestStructs();
TestClasses();
}
void TestStructs()
{
Profiler.BeginSample("Struct Creation");
var positions = new Vector3[Count];
for (int i = 0; i < Count; i++)
positions[i] = new Vector3(i, i, i);
Profiler.EndSample();
}
void TestClasses()
{
Profiler.BeginSample("Class Creation");
var positions = new Vector3Wrapper[Count];
for (int i = 0; i < Count; i++)
positions[i] = new Vector3Wrapper(i, i, i);
Profiler.EndSample();
}
}
public class Vector3Wrapper
{
public float x, y, z;
public Vector3Wrapper(float x, float y, float z)
{
this.x = x; this.y = y; this.z = z;
}
}
Unity 2022.3LTS以降およびC# 9.0以上を想定しています。バージョンによって一部挙動が異なる場合があります。