概要
csvファイル(マスターデータ)を読み込み、ゲームに反映させる、ということをUnity DOTSで行う方法をまとめました。
本記事の執筆の際に、特に次のサイトを参考にさせて頂きました。
環境
- Unity 2019.3.0f5
- Entities 0.5.0 preview.17
実装
BlobAssetを使います。
次の手順に従います。
- アセットデータを表す構造体を定義する
- アロケータでアセットデータを組み立てる
- アセットデータの参照を取得してコンポーネントに記憶する
- 参照を利用してアセットデータにアクセスする
例
複数のCubeを次の表に従って配置してみます。
Position |
---|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
マスターデータの準備
マスターデータとなるcsvファイルをResourcesフォルダ内に置きます。
アセットデータを表す構造体を定義する
using Unity.Entities; using Unity.Mathematics; public struct PositionBlobAsset { public BlobArray<float3> PositionBlobArray; }
SpawnerData
using Unity.Entities; /// <summary> /// CubeをSpawnする際に使うデータ /// </summary> public struct CubeSpawnerData : IComponentData { /// <summary> /// 生成するPrefabのEntity /// </summary> public Entity CubeEntity; /// <summary> /// 読み込むデータの行数 /// </summary> public int DataLength; public BlobAssetReference<PositionBlobAsset> PosBlobAssetReference; }
Authoring
CubeSpawnerオブジェクトを作成し、ConvertToEntity
とCubeSpawnerAuthoring
をアタッチします。
using System.Collections.Generic; using System.Linq; using Unity.Collections; using Unity.Entities; using UnityEngine; public class CubeSpawnerAuthoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs { [SerializeField] private GameObject cubePrefab; // csvファイルの(Resources以下の)パス。拡張子の`.csv`は省略する private const string FilePath = "MasterData"; // データを格納する配列のリスト private List<string[]> _dataArrayList; // 全データの行数 private int _dataListLength; // ラベル名 private const string PosLabel = "Position"; private BlobAssetReference<PositionBlobAsset> _posAssetReference; private void Awake() { // csvファイルを読みこんでArrayListにコピー _dataArrayList = ReadCsv.GetDataArray(FilePath).ToList(); _dataListLength = _dataArrayList.Count; // Builderを作成 var posBlobBuilder = new BlobBuilder(Allocator.TempJob); // Rootの参照を取得 ref var posRoot = ref posBlobBuilder.ConstructRoot<PositionBlobAsset>(); var posBuilderArray = posBlobBuilder.Allocate(ref posRoot.PositionBlobArray, _dataListLength); // 取得したい列が何列目かを示すIndexを取得 var posLabelIndex = ReadCsv.GetLabelIndex(FilePath, PosLabel); for (var i = 0; i < _dataListLength; i++) { posBuilderArray[i] = float.Parse(_dataArrayList[i][posLabelIndex]); } _posAssetReference = posBlobBuilder.CreateBlobAssetReference<PositionBlobAsset>(Allocator.Persistent); posBlobBuilder.Dispose(); } public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { dstManager.AddComponentData(entity, new CubeSpawnerData() { CubeEntity = conversionSystem.GetPrimaryEntity(cubePrefab), DataLength = _dataListLength, PosBlobAssetReference = _posAssetReference }); } public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs) { referencedPrefabs.Add(cubePrefab); } }
関連 : https://alberto-hojo.hatenablog.com/entry/2019/12/21/141011
ReadCsvクラスのコードを見る
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
/// <summary>
/// CSVファイルからデータを読みこむクラス
/// </summary>
public static class ReadCsv
{
/// <summary>
/// CSVファイルからデータを読み込み、データが格納されたリストを返す
/// </summary>
/// <param name="filePath">CSVファイルの拡張子を除いたファイル名</param>
public static IEnumerable<string[]> GetDataArray(string filePath)
{
var dataArrayList = new List<string[]>();
var csvFile = Resources.Load(filePath) as TextAsset;
Debug.Assert(csvFile != null, nameof(csvFile) + " != null");
using (var reader = new StringReader(csvFile.text))
{
// 1行目はラベルなので何もしない
reader.ReadLine();
while (reader.Peek() > -1)
{
var line = reader.ReadLine();
Debug.Assert(line != null, nameof(line) + " != null");
var elements = line.Split(',').ToArray();
dataArrayList.Add(elements);
}
}
return dataArrayList;
}
/// <summary>
/// ラベル名から、そのラベルに対応する列のindexを返す
/// </summary>
/// <param name="filePath">CSVファイルの拡張子を除いたファイル名</param>
/// <param name="labelName">ラベル名</param>
public static int GetLabelIndex(string filePath, string labelName)
{
var csvFile = Resources.Load(filePath) as TextAsset;
Debug.Assert(csvFile != null, nameof(csvFile) + " != null");
using (var reader = new StringReader(csvFile.text))
{
var labelLine = reader.ReadLine();
Debug.Assert(labelLine != null, nameof(labelLine) + " != null");
var index = labelLine.Split(',').ToList().IndexOf(labelName);
return index;
}
}
}
SpawnSystem
using Unity.Entities; /// <summary> /// CubeをSpawnするタイミングで用いるタグコンポーネント /// </summary> public struct SpawnCubeTag : IComponentData { }
using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms; /// <summary> /// CubeをSpawnするSystem /// </summary> public class SpawnCubeSystem : JobComponentSystem { private EntityCommandBufferSystem _entityCommandBufferSystem; protected override void OnCreate() { _entityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>(); } [BurstCompile] private struct SpawnCubeJob : IJobForEachWithEntity<CubeSpawnerData, SpawnCubeTag> { public EntityCommandBuffer.Concurrent Concurrent; public void Execute(Entity entity, int index, ref CubeSpawnerData data, [ReadOnly] ref SpawnCubeTag tag) { ref var positionBlobArray = ref data.PosBlobAssetReference.Value.PositionBlobArray; for (var i = 0; i < data.DataLength; i++) { var cubeEntity = Concurrent.Instantiate(index, data.CubeEntity); Concurrent.SetComponent(index, cubeEntity, new Translation() {Value = math.float3(0.0f, 0.0f, positionBlobArray[i])}); } Concurrent.RemoveComponent<SpawnCubeTag>(index, entity); } } protected override JobHandle OnUpdate(JobHandle inputDeps) { var jobHandle = new SpawnCubeJob() { Concurrent = _entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent() }.Schedule(this, inputDeps); _entityCommandBufferSystem.AddJobHandleForProducer(jobHandle); return jobHandle; } }