Skip to content

Instantly share code, notes, and snippets.

@JVinceW
Last active October 30, 2023 02:44
Show Gist options
  • Save JVinceW/3f2cd97aab49b83f5e44da7542bcdd16 to your computer and use it in GitHub Desktop.
Save JVinceW/3f2cd97aab49b83f5e44da7542bcdd16 to your computer and use it in GitHub Desktop.
Medium article https://medium.com/p/73c5e7f228c9 Using SO in ECS by parsing to IComponentData
public class BrideAssetConfigAuthoring : MonoBehaviour
{
[SerializeField]
private BulletConfiguration _bulletConfiguration;
private class BrideAssetConfigBaker : Baker<BrideAssetConfigAuthoring>
{
public override void Bake(BrideAssetConfigAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
var playerConfiguration = new BulletConfigurationComponentData();
authoring._bulletConfiguration.ConvertToUnManaged(ref playerConfiguration, this);
// Add configuration to entity
AddComponent(entity, playerConfiguration);
}
}
}
[CreateAssetMenu(fileName = "BulletConfiguration", menuName = "Project/Config/Bullet", order = 0)]
public class BulletConfiguration : ScriptableObject
{
[SerializeField]
private GameObject _bulletPrefab;
[SerializeField]
private float _bulletSpeed;
[SerializeField]
private float _bulletMaxDistanceSq;
public void ConvertToUnManaged(ref BulletConfigurationComponentData data, IBaker baker)
{
data.BulletPrefab = baker.GetEntity(_bulletPrefab, TransformUsageFlags.Dynamic);
data.BulletSpeed = _bulletSpeed;
data.BulletMaxDistanceSq = _bulletMaxDistanceSq;
}
}
/// <summary>
/// The object of convert ScriptableObject to unmanaged data.
/// TODO: Try to use with the ISharedComponentData instead of IComponentData when have more knowledge of ISharedComponentData.
/// Now, the ISharedComponentData is not working. with error:
/// // Serializing of shared components with Entity fields is not supported as Entity references are not patched when deserializing. //
/// </summary>
[BurstCompile]
public struct BulletConfigurationComponentData : IComponentData
{
[ReadOnly] public Entity BulletPrefab;
[ReadOnly] public float BulletSpeed;
[ReadOnly] public float BulletMaxDistanceSq;
}
public partial struct BulletFireSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<PlayerFireBulletTag>();
state.RequireForUpdate<BulletConfigurationComponentData>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.Temp);
var configurationSingleton = SystemAPI.GetSingleton<BulletConfigurationComponentData>();
foreach (var transform in SystemAPI.Query<LocalTransform>().WithAll<PlayerFireBulletTag>())
{
var projectileEntity = ecb.Instantiate(configurationSingleton.BulletPrefab);
var projectileTransform =
LocalTransform.FromPositionRotationScale(transform.Position, transform.Rotation, transform.Scale);
// Set location for the entity
ecb.SetComponent(projectileEntity, projectileTransform);
// AddComponent
ecb.AddComponent(projectileEntity, new BulletSpeed {
Value = configurationSingleton.BulletSpeed
});
ecb.AddComponent(projectileEntity, new BulletMaxTravelDistanceSq {
Value = configurationSingleton.BulletMaxDistanceSq
});
}
ecb.Playback(state.EntityManager);
ecb.Dispose();
}
}
[BurstCompile]
public struct BulletSpeed : IComponentData
{
public float Value;
}
[BurstCompile]
public struct BulletMaxTravelDistanceSq : IComponentData
{
public float Value;
}

この記事の英語はMedium個人ブログにも書いてあります。

この記事では、Unity ECS(Entity Component System)で Scriptable Object(SO)を使用して、SO データを IComponent に変換する方法について説明します。 基本を思い出してください:ECSのすべてはデータに関するものですので、「オブジェクト、継承、など」OOPに関連するすべてのものは(理論上)存在しないでしょう。そして、SOはOOPのものである(ある視点では)ので、誰かはこれをECSの内部で使用するのは奇妙だと思うかもしれません。 しかし、ゲームプレイデータを設定するためには、これは不可欠な部分です。OOPの世界でもECSの世界でも、それが必要です。 この記事では、SOを使用する非常にシンプルな方法を紹介しますが、もっと良い方法がたくさんあると確信しています。後で更新します。

メモ:この記事を読むには、Unity ECS の基本知識が少なくとも必要です。

Environment Setup:

シナリオ

この記事では、SOを使用して弾丸の基本的なプロパティを設定し、ファイアボタンを押したときに弾丸を発射します。この記事はプロジェクト全体をカバーしていないため、SOがどのように動作するかの一部分のみをカバーしています。

Setup Step

  1. 弾丸の Scriptable Object(SO)の設定:
    [CreateAssetMenu(fileName = "BulletConfiguration", menuName = "Project/Config/Bullet", order = 0)]
    public class BulletConfiguration : ScriptableObject
    {
      // Prefab that use for spawn bullet
      [SerializeField]
      private GameObject _bulletPrefab;
    
      // Speed of the bullet
      [SerializeField]
      private float _bulletSpeed;
    
      // the max distance square that bullet can move before removed
      [SerializeField]
      private float _bulletMaxDistanceSq;
    
      // This is a util method use for baking process.
      public void ConvertToUnManaged(ref BulletConfigurationComponentData data, IBaker baker)
      {
       data.BulletPrefab = baker.GetEntity(_bulletPrefab, TransformUsageFlags.Dynamic);
       data.BulletSpeed = _bulletSpeed;
       data.BulletMaxDistanceSq = _bulletMaxDistanceSq;
      }
    }
  1. IComponentData から派生した BulletConfigurationComponentData 構造体を作成します。この構造体は BulletConfiguration SO のミラーとして機能します。
    [BurstCompile]
    public struct BulletConfigurationComponentData : IComponentData
    {
      [ReadOnly] public Entity BulletPrefab;
      [ReadOnly] public float BulletSpeed;
      [ReadOnly] public float BulletMaxDistanceSq;
    }
  1. 実際のSOへの参照を保持し、データをECSワールドに組み込むブリッジ MonoBehaviorを作成します。
public class BrideAssetConfigAuthoring : MonoBehaviour
{
 [SerializeField]
 private BulletConfiguration _bulletConfiguration;

 private class BrideAssetConfigBaker : Baker<BrideAssetConfigAuthoring>
 {
  public override void Bake(BrideAssetConfigAuthoring authoring)
  {
   var entity = GetEntity(TransformUsageFlags.None);
   var playerConfiguration = new BulletConfigurationComponentData();

   // we will use the method that we already create before to parsing data into the component
   authoring._bulletConfiguration.ConvertToUnManaged(ref playerConfiguration, this);

   // Add configuration to entity
   AddComponent(entity, playerConfiguration);
  }
 }
}

ここまでで、基本的な設定は完了し、SOデータは使用準備が整いました。これをスポーン弾丸システム(別名FireBulletSystem)で使用します。SOはデータのみを保持すべきであると仮定するため、1つのインスタンスのみを持つべきだと考えています。そのため、エンティティを取得するために GetSingleton を使用します。

public partial struct BulletFireSystem : ISystem
{
  [BurstCompile]
  public void OnCreate(ref SystemState state)
  {
   state.RequireForUpdate<PlayerFireBulletTag>();
   state.RequireForUpdate<BulletConfigurationComponentData>();
  }

  [BurstCompile]
  public void OnUpdate(ref SystemState state)
  {
   var ecb = new EntityCommandBuffer(Allocator.Temp);
   var configurationSingleton = SystemAPI.GetSingleton<BulletConfigurationComponentData>();
   foreach (var transform in SystemAPI.Query<LocalTransform>().WithAll<PlayerFireBulletTag>())
   {
    var projectileEntity = ecb.Instantiate(configurationSingleton.BulletPrefab);
    var projectileTransform =
     LocalTransform.FromPositionRotationScale(transform.Position, transform.Rotation, transform.Scale);
    
    // Set location for the entity
    ecb.SetComponent(projectileEntity, projectileTransform);
    
    // AddComponent
    ecb.AddComponent(projectileEntity, new BulletSpeed {
     Value = configurationSingleton.BulletSpeed
    });
    ecb.AddComponent(projectileEntity, new BulletMaxTravelDistanceSq {
     Value = configurationSingleton.BulletMaxDistanceSq
    });
   }
   ecb.Playback(state.EntityManager);
   ecb.Dispose();
  }
}

最後に

この方法を使えば、私の目標を達成できますが、試して解決したいことはまだたくさんあります。

  • 制約: SOのデータは常に読み取り専用である必要があり、1つ作成して永遠に使用するべきです。このアプローチでは、データはルールが固定されていない場合に実行時に簡単に変更される可能性があります。実行時にBlobAssetにパースして読み取り専用にできるかどうかを検討しています。

  • IComponentData は使えるか?それとも ISharedComponentData がより良い選択か?(まだ検討中)

しかし、個人的な感触では、SOをECSと組み合わせるためには、たくさんのものとたくさんのルールを作成する必要があります。プロセスを簡素化する方法を見つけるべきです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment