Unity ECS Study - Use Scriptable Object in ECS by parsing SO data to IComponent

Vince Wang
3 min readOct 29, 2023

Recall the basic: All of ECS is about data so the “object, inheritance,etc…” all the things that are related to OOP won’t exist (in theory). And, SO is OOP thing (in some point of view) so someone will find that it’s weird to use this inside ECS.
But, It is an essential part for setup gameplay data. And even in OOP world or ECS world, we need it.
In this article, I will use a very simple way to using SO, I’m so sure there are many better ways, and I will update later.

Memo: reader must have at least basic of Unity ECS knowledge to read this article

Environment Setup:

Scenario

In this article, we will use SO to set up some basic properties of a bullet, and we will spawn the bullet when we hit fire. This article is not cover the whole project, it’s only cover part of how SO works.

Setup Step

  1. Setup the bullet 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;
}
}

2. Create a BulletConfigurationComponentData struct that derived from IComponentData. This struct will acts as a mirror of BulletConfiguration SO

[BurstCompile]
public struct BulletConfigurationComponentData : IComponentData
{
[ReadOnly] public Entity BulletPrefab;
[ReadOnly] public float BulletSpeed;
[ReadOnly] public float BulletMaxDistanceSq;
}

2. Create the bridge MonoBehavior, which hold the reference to the real SO and bake the data to the ECS world.

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);
}
}
}

Until here, basically the setup is finished and the SO data is ready to be used. I will use it in spawn bullet system (aka FireBulletSystem). Because SO should only hold data so we assume that it should only have 1 instance. that’s why I use the GetSingleton to retrieve the entity.

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();
}
}

Finally

With this way, I can achieve what I want but there still manythings that I want to try and resolve.

  • Restriction: We want data of SO always is read-only, it should be create one and use forever, with this approach, the data is easily modified on runtime if we don’t have a fixed rule across team members, thinking about parsing it into BlobAsset in runtime to see if it can be readonly.
  • IComponentData is good to go? Or ISharedComponentData is the better option? (still considering)

But, personal feeling is to only make SO working with ECS, we have to create tons of things and tons of rules. We should find out some ways to simplify the process.

--

--

Vince Wang

Game developer, technology enthusiast, Unity Developer, UE Developer