using System.Collections.Generic; using ProjectM.Simulation; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using UnityEngine; namespace ProjectM.Authoring { /// /// Bakes the designer-authored ability + character definitions into a single AbilityDatabase blob /// singleton (immutable, shared, Burst-fast) plus a companion AbilityPrefabElement buffer holding the /// per-ability projectile ghost prefab entity refs (entity refs cannot live in a blob). Place ONE of /// these in the gameplay subscene; it streams identically into the client and server worlds (config, /// not replicated). DependsOn each definition so a value change in an SO re-bakes the blob. /// public class AbilityDatabaseAuthoring : MonoBehaviour { [Tooltip("All ability definitions available in the game. Indexed at runtime by AbilityId.")] public List Abilities = new List(); [Tooltip("All character-stats definitions. Indexed at runtime by CharacterId.")] public List Characters = new List(); private class DatabaseBaker : Baker { public override void Bake(AbilityDatabaseAuthoring authoring) { var entity = GetEntity(TransformUsageFlags.None); int abilityCount = authoring.Abilities != null ? authoring.Abilities.Count : 0; int charCount = authoring.Characters != null ? authoring.Characters.Count : 0; var builder = new BlobBuilder(Allocator.Temp); ref var root = ref builder.ConstructRoot(); var abilityArray = builder.Allocate(ref root.Abilities, abilityCount); for (int i = 0; i < abilityCount; i++) { var def = authoring.Abilities[i]; if (def == null) { abilityArray[i] = default; continue; } DependsOn(def); abilityArray[i] = new AbilityDefBlob { Id = (byte)def.Id, Damage = def.Damage, ProjectileSpeed = def.ProjectileSpeed, Range = def.Range, AutoTargetRange = def.AutoTargetRange, Archetype = (byte)def.Archetype, AutoTargetConeRadians = math.radians(def.AutoTargetConeDegrees), CooldownTicks = def.CooldownTicks, Name = def.DisplayName, }; } var charArray = builder.Allocate(ref root.Characters, charCount); for (int i = 0; i < charCount; i++) { var def = authoring.Characters[i]; if (def == null) { charArray[i] = default; continue; } DependsOn(def); charArray[i] = new CharacterStatsBlob { Id = (byte)def.Id, MoveSpeed = def.MoveSpeed, TurnRateRadiansPerSec = math.radians(def.TurnRateDegreesPerSec), MaxHealth = def.MaxHealth, Name = def.DisplayName, }; } var blob = builder.CreateBlobAssetReference(Allocator.Persistent); builder.Dispose(); AddBlobAsset(ref blob, out _); AddComponent(entity, new AbilityDatabase { Value = blob }); // Companion entity-ref buffer: per-ability projectile ghost prefab (resolved via GetEntity). var prefabBuffer = AddBuffer(entity); for (int i = 0; i < abilityCount; i++) { var def = authoring.Abilities[i]; if (def == null || def.ProjectilePrefab == null) continue; prefabBuffer.Add(new AbilityPrefabElement { Id = (byte)def.Id, Prefab = GetEntity(def.ProjectilePrefab, TransformUsageFlags.Dynamic), }); } } } } }