Instantly share code, notes, and snippets.
Forked from JohnnyTurbo/CameraTargetSingleton.cs
Created
March 13, 2025 05:18
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save dezashibi/b8ceb0c6b38a1f0372e536f5ce282047 to your computer and use it in GitHub Desktop.
Scripts from tutorial: Tutorial: SURVIVORS-LIKE w/ Unity DOTS & ECS - https://youtu.be/cc5l66FwpQ4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
namespace TMG.Survivors | |
{ | |
public class CameraTargetSingleton : MonoBehaviour | |
{ | |
public static CameraTargetSingleton Instance; | |
public void Awake() | |
{ | |
if (Instance != null) | |
{ | |
Debug.LogWarning("Warning multiple instances of CameraTargetSingleton detected. Destroying new instance.", Instance); | |
Destroy(gameObject); | |
return; | |
} | |
Instance = this; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using Unity.Entities; | |
using Unity.Mathematics; | |
using Unity.Physics; | |
using Unity.Burst; | |
using Unity.Rendering; | |
namespace TMG.Survivors | |
{ | |
public struct InitializeCharacterFlag : IComponentData, IEnableableComponent {} | |
public struct CharacterMoveDirection : IComponentData | |
{ | |
public float2 Value; | |
} | |
public struct CharacterMoveSpeed : IComponentData | |
{ | |
public float Value; | |
} | |
[MaterialProperty("_FacingDirection")] | |
public struct FacingDirectionOverride : IComponentData | |
{ | |
public float Value; | |
} | |
public struct CharacterMaxHitPoints : IComponentData | |
{ | |
public int Value; | |
} | |
public struct CharacterCurrentHitPoints : IComponentData | |
{ | |
public int Value; | |
} | |
public struct DamageThisFrame : IBufferElementData | |
{ | |
public int Value; | |
} | |
public class CharacterAuthoring : MonoBehaviour | |
{ | |
public float MoveSpeed; | |
public int HitPoints; | |
private class Baker : Baker<CharacterAuthoring> | |
{ | |
public override void Bake(CharacterAuthoring authoring) | |
{ | |
var entity = GetEntity(TransformUsageFlags.Dynamic); | |
AddComponent<InitializeCharacterFlag>(entity); | |
AddComponent<CharacterMoveDirection>(entity); | |
AddComponent(entity, new CharacterMoveSpeed | |
{ | |
Value = authoring.MoveSpeed | |
}); | |
AddComponent(entity, new FacingDirectionOverride | |
{ | |
Value = 1 | |
}); | |
AddComponent(entity, new CharacterMaxHitPoints | |
{ | |
Value = authoring.HitPoints | |
}); | |
AddComponent(entity, new CharacterCurrentHitPoints | |
{ | |
Value = authoring.HitPoints | |
}); | |
AddBuffer<DamageThisFrame>(entity); | |
AddComponent<DestroyEntityFlag>(entity); | |
SetComponentEnabled<DestroyEntityFlag>(entity, false); | |
} | |
} | |
} | |
[UpdateInGroup(typeof(InitializationSystemGroup))] | |
public partial struct CharacterInitializationSystem : ISystem | |
{ | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
foreach (var (mass, shouldInitialize) in SystemAPI.Query<RefRW<PhysicsMass>, EnabledRefRW<InitializeCharacterFlag>>()) | |
{ | |
mass.ValueRW.InverseInertia = float3.zero; | |
shouldInitialize.ValueRW = false; | |
} | |
} | |
} | |
public partial struct CharacterMoveSystem : ISystem | |
{ | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
foreach (var (velocity, facingDirection, direction, speed, entity) in SystemAPI.Query<RefRW<PhysicsVelocity>, RefRW<FacingDirectionOverride>, CharacterMoveDirection, CharacterMoveSpeed>().WithEntityAccess()) | |
{ | |
var moveStep2d = direction.Value * speed.Value; | |
velocity.ValueRW.Linear = new float3(moveStep2d, 0f); | |
if (math.abs(moveStep2d.x) > 0.15f) | |
{ | |
facingDirection.ValueRW.Value = math.sign(moveStep2d.x); | |
} | |
if (SystemAPI.HasComponent<PlayerTag>(entity)) | |
{ | |
var animationOverride = SystemAPI.GetComponentRW<AnimationIndexOverride>(entity); | |
var animationType = math.lengthsq(moveStep2d) > float.Epsilon ? PlayerAnimationIndex.Movement : PlayerAnimationIndex.Idle; | |
animationOverride.ValueRW.Value = (float)animationType; | |
} | |
} | |
} | |
} | |
public partial struct GlobalTimeUpdateSystem : ISystem | |
{ | |
private static int _globalTimeShaderPropertyID; | |
public void OnCreate(ref SystemState state) | |
{ | |
_globalTimeShaderPropertyID = Shader.PropertyToID("_GlobalTime"); | |
} | |
public void OnUpdate(ref SystemState state) | |
{ | |
Shader.SetGlobalFloat(_globalTimeShaderPropertyID, (float)SystemAPI.Time.ElapsedTime); | |
} | |
} | |
public partial struct ProcessDamageThisFrameSystem : ISystem | |
{ | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
foreach (var (hitPoints, damageThisFrame, entity) in SystemAPI.Query<RefRW<CharacterCurrentHitPoints>, DynamicBuffer<DamageThisFrame>>().WithPresent<DestroyEntityFlag>().WithEntityAccess()) | |
{ | |
if (damageThisFrame.IsEmpty) continue; | |
foreach (var damage in damageThisFrame) | |
{ | |
hitPoints.ValueRW.Value -= damage.Value; | |
} | |
damageThisFrame.Clear(); | |
if (hitPoints.ValueRO.Value <= 0) | |
{ | |
SystemAPI.SetComponentEnabled<DestroyEntityFlag>(entity, true); | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Entities; | |
using Unity.Transforms; | |
namespace TMG.Survivors | |
{ | |
public struct DestroyEntityFlag : IComponentData, IEnableableComponent {} | |
[UpdateInGroup(typeof(SimulationSystemGroup), OrderLast = true)] | |
[UpdateBefore(typeof(EndSimulationEntityCommandBufferSystem))] | |
public partial struct DestroyEntitySystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<BeginInitializationEntityCommandBufferSystem.Singleton>(); | |
state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>(); | |
} | |
public void OnUpdate(ref SystemState state) | |
{ | |
var endEcbSystem = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>(); | |
var endEcb = endEcbSystem.CreateCommandBuffer(state.WorldUnmanaged); | |
var beginEcbSystem = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>(); | |
var beginEcb = beginEcbSystem.CreateCommandBuffer(state.WorldUnmanaged); | |
foreach (var (_, entity) in SystemAPI.Query<DestroyEntityFlag>().WithEntityAccess()) | |
{ | |
if (SystemAPI.HasComponent<PlayerTag>(entity)) | |
{ | |
GameUIController.Instance.ShowGameOverUI(); | |
} | |
if (SystemAPI.HasComponent<GemPrefab>(entity)) | |
{ | |
var gemPrefab = SystemAPI.GetComponent<GemPrefab>(entity).Value; | |
var newGem = beginEcb.Instantiate(gemPrefab); | |
var spawnPosition = SystemAPI.GetComponent<LocalToWorld>(entity).Position; | |
beginEcb.SetComponent(newGem, LocalTransform.FromPosition(spawnPosition)); | |
} | |
endEcb.DestroyEntity(entity); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Burst; | |
using Unity.Collections; | |
using UnityEngine; | |
using Unity.Entities; | |
using Unity.Mathematics; | |
using Unity.Physics; | |
using Unity.Physics.Systems; | |
using Unity.Transforms; | |
namespace TMG.Survivors | |
{ | |
public struct EnemyTag : IComponentData {} | |
public struct EnemyAttackData : IComponentData | |
{ | |
public int HitPoints; | |
public float CooldownTime; | |
} | |
public struct EnemyCooldownExpirationTimestamp : IComponentData, IEnableableComponent | |
{ | |
public double Value; | |
} | |
public struct GemPrefab : IComponentData | |
{ | |
public Entity Value; | |
} | |
[RequireComponent(typeof(CharacterAuthoring))] | |
public class EnemyAuthoring : MonoBehaviour | |
{ | |
public int AttackDamage; | |
public float CooldownTime; | |
public GameObject GemPrefab; | |
private class Baker : Baker<EnemyAuthoring> | |
{ | |
public override void Bake(EnemyAuthoring authoring) | |
{ | |
var entity = GetEntity(TransformUsageFlags.Dynamic); | |
AddComponent<EnemyTag>(entity); | |
AddComponent(entity, new EnemyAttackData | |
{ | |
HitPoints = authoring.AttackDamage, | |
CooldownTime = authoring.CooldownTime | |
}); | |
AddComponent<EnemyCooldownExpirationTimestamp>(entity); | |
SetComponentEnabled<EnemyCooldownExpirationTimestamp>(entity, false); | |
AddComponent(entity, new GemPrefab | |
{ | |
Value = GetEntity(authoring.GemPrefab, TransformUsageFlags.Dynamic) | |
}); | |
} | |
} | |
} | |
public partial struct EnemyMoveToPlayerSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<PlayerTag>(); | |
} | |
public void OnUpdate(ref SystemState state) | |
{ | |
var playerEntity = SystemAPI.GetSingletonEntity<PlayerTag>(); | |
var playerPosition = SystemAPI.GetComponent<LocalTransform>(playerEntity).Position.xy; | |
var moveToPlayerJob = new EnemyMoveToPlayerJob | |
{ | |
PlayerPosition = playerPosition | |
}; | |
state.Dependency = moveToPlayerJob.ScheduleParallel(state.Dependency); | |
} | |
} | |
[BurstCompile] | |
[WithAll(typeof(EnemyTag))] | |
public partial struct EnemyMoveToPlayerJob : IJobEntity | |
{ | |
public float2 PlayerPosition; | |
private void Execute(ref CharacterMoveDirection direction, in LocalTransform transform) | |
{ | |
var vectorToPlayer = PlayerPosition - transform.Position.xy; | |
direction.Value = math.normalize(vectorToPlayer); | |
} | |
} | |
[UpdateInGroup(typeof(PhysicsSystemGroup))] | |
[UpdateAfter(typeof(PhysicsSimulationGroup))] | |
[UpdateBefore(typeof(AfterPhysicsSystemGroup))] | |
public partial struct EnemyAttackSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<SimulationSingleton>(); | |
} | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
var elapsedTime = SystemAPI.Time.ElapsedTime; | |
foreach (var (expirationTimestamp, cooldownEnabled) in SystemAPI.Query<EnemyCooldownExpirationTimestamp, EnabledRefRW<EnemyCooldownExpirationTimestamp>>()) | |
{ | |
if (expirationTimestamp.Value > elapsedTime) continue; | |
cooldownEnabled.ValueRW = false; | |
} | |
var attackJob = new EnemyAttackJob | |
{ | |
PlayerLookup = SystemAPI.GetComponentLookup<PlayerTag>(true), | |
AttackDataLookup = SystemAPI.GetComponentLookup<EnemyAttackData>(true), | |
CooldownLookup = SystemAPI.GetComponentLookup<EnemyCooldownExpirationTimestamp>(), | |
DamageBufferLookup = SystemAPI.GetBufferLookup<DamageThisFrame>(), | |
ElapsedTime = elapsedTime | |
}; | |
var simulationSingleton = SystemAPI.GetSingleton<SimulationSingleton>(); | |
state.Dependency = attackJob.Schedule(simulationSingleton, state.Dependency); | |
} | |
} | |
[BurstCompile] | |
public struct EnemyAttackJob : ICollisionEventsJob | |
{ | |
[ReadOnly] public ComponentLookup<PlayerTag> PlayerLookup; | |
[ReadOnly] public ComponentLookup<EnemyAttackData> AttackDataLookup; | |
public ComponentLookup<EnemyCooldownExpirationTimestamp> CooldownLookup; | |
public BufferLookup<DamageThisFrame> DamageBufferLookup; | |
public double ElapsedTime; | |
public void Execute(CollisionEvent collisionEvent) | |
{ | |
Entity playerEntity; | |
Entity enemyEntity; | |
if (PlayerLookup.HasComponent(collisionEvent.EntityA) && AttackDataLookup.HasComponent(collisionEvent.EntityB)) | |
{ | |
playerEntity = collisionEvent.EntityA; | |
enemyEntity = collisionEvent.EntityB; | |
} | |
else if (PlayerLookup.HasComponent(collisionEvent.EntityB) && AttackDataLookup.HasComponent(collisionEvent.EntityA)) | |
{ | |
playerEntity = collisionEvent.EntityB; | |
enemyEntity = collisionEvent.EntityA; | |
} | |
else | |
{ | |
return; | |
} | |
if (CooldownLookup.IsComponentEnabled(enemyEntity)) return; | |
var attackData = AttackDataLookup[enemyEntity]; | |
CooldownLookup[enemyEntity] = new EnemyCooldownExpirationTimestamp { Value = ElapsedTime + attackData.CooldownTime }; | |
CooldownLookup.SetComponentEnabled(enemyEntity, true); | |
var playerDamageBuffer = DamageBufferLookup[playerEntity]; | |
playerDamageBuffer.Add(new DamageThisFrame | |
{ | |
Value = attackData.HitPoints | |
}); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Burst; | |
using UnityEngine; | |
using Unity.Entities; | |
using Unity.Mathematics; | |
using Unity.Transforms; | |
using Random = Unity.Mathematics.Random; | |
namespace TMG.Survivors | |
{ | |
public struct EnemySpawnData : IComponentData | |
{ | |
public Entity EnemyPrefab; | |
public Entity ReaperPrefab; | |
public float SpawnInterval; | |
public float SpawnDistance; | |
} | |
public struct EnemySpawnState : IComponentData | |
{ | |
public float SpawnTimer; | |
public float ReaperSpawnTimer; | |
public Random Random; | |
} | |
public class EnemySpawnerAuthoring : MonoBehaviour | |
{ | |
public GameObject EnemyPrefab; | |
public GameObject ReaperPrefab; | |
public float ReaperSpawnTime; | |
public float SpawnInterval; | |
public float SpawnDistance; | |
public uint RandomSeed; | |
private class Baker : Baker<EnemySpawnerAuthoring> | |
{ | |
public override void Bake(EnemySpawnerAuthoring authoring) | |
{ | |
var entity = GetEntity(TransformUsageFlags.None); | |
AddComponent(entity, new EnemySpawnData | |
{ | |
EnemyPrefab = GetEntity(authoring.EnemyPrefab, TransformUsageFlags.Dynamic), | |
ReaperPrefab = GetEntity(authoring.ReaperPrefab, TransformUsageFlags.Dynamic), | |
SpawnInterval = authoring.SpawnInterval, | |
SpawnDistance = authoring.SpawnDistance | |
}); | |
AddComponent(entity, new EnemySpawnState | |
{ | |
SpawnTimer = 0f, | |
ReaperSpawnTimer = authoring.ReaperSpawnTime, | |
Random = Random.CreateFromIndex(authoring.RandomSeed) | |
}); | |
} | |
} | |
} | |
public partial struct EnemySpawnSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<PlayerTag>(); | |
state.RequireForUpdate<BeginInitializationEntityCommandBufferSystem.Singleton>(); | |
} | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
var deltaTime = SystemAPI.Time.DeltaTime; | |
var ecbSystem = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>(); | |
var ecb = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged); | |
var playerEntity = SystemAPI.GetSingletonEntity<PlayerTag>(); | |
var playerPosition = SystemAPI.GetComponent<LocalTransform>(playerEntity).Position; | |
foreach (var (spawnState, spawnData, entity) in SystemAPI.Query<RefRW<EnemySpawnState>, EnemySpawnData>().WithEntityAccess()) | |
{ | |
spawnState.ValueRW.ReaperSpawnTimer -= deltaTime; | |
if (spawnState.ValueRO.ReaperSpawnTimer <= 0f) | |
{ | |
var reaper = ecb.Instantiate(spawnData.ReaperPrefab); | |
var reaperSpawnPoint = playerPosition + new float3(15f, 10f, 0f); | |
ecb.SetComponent(reaper, LocalTransform.FromPositionRotationScale(reaperSpawnPoint, quaternion.identity, 4f)); | |
var enemyQuery = SystemAPI.QueryBuilder().WithAll<EnemyTag>().Build(); | |
var enemies = enemyQuery.ToEntityArray(state.WorldUpdateAllocator); | |
ecb.DestroyEntity(enemies); | |
ecb.DestroyEntity(entity); | |
continue; | |
} | |
spawnState.ValueRW.SpawnTimer -= deltaTime; | |
if (spawnState.ValueRO.SpawnTimer > 0f) continue; | |
spawnState.ValueRW.SpawnTimer = spawnData.SpawnInterval; | |
var newEnemy = ecb.Instantiate(spawnData.EnemyPrefab); | |
var spawnAngle = spawnState.ValueRW.Random.NextFloat(0f, math.TAU); | |
var spawnPoint = new float3 | |
{ | |
x = math.sin(spawnAngle), | |
y = math.cos(spawnAngle), | |
z = 0f | |
}; | |
spawnPoint *= spawnData.SpawnDistance; | |
spawnPoint += playerPosition; | |
ecb.SetComponent(newEnemy, LocalTransform.FromPosition(spawnPoint)); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Burst; | |
using UnityEngine; | |
using Unity.Entities; | |
using Unity.Physics; | |
using Unity.Collections; | |
namespace TMG.Survivors | |
{ | |
public struct GemTag : IComponentData {} | |
public class GemAuthoring : MonoBehaviour | |
{ | |
private class Baker : Baker<GemAuthoring> | |
{ | |
public override void Bake(GemAuthoring authoring) | |
{ | |
var entity = GetEntity(TransformUsageFlags.Dynamic); | |
AddComponent<GemTag>(entity); | |
AddComponent<DestroyEntityFlag>(entity); | |
SetComponentEnabled<DestroyEntityFlag>(entity, false); | |
} | |
} | |
} | |
public partial struct CollectGemSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<SimulationSingleton>(); | |
} | |
[BurstCompile] | |
public void OnUpdate(ref SystemState state) | |
{ | |
var newCollectJob = new CollectGemJob | |
{ | |
GemLookup = SystemAPI.GetComponentLookup<GemTag>(true), | |
GemsCollectedLookup = SystemAPI.GetComponentLookup<GemsCollectedCount>(), | |
DestroyEntityLookup = SystemAPI.GetComponentLookup<DestroyEntityFlag>(), | |
UpdateGemUILookup = SystemAPI.GetComponentLookup<UpdateGemUIFlag>() | |
}; | |
var simulationSingleton = SystemAPI.GetSingleton<SimulationSingleton>(); | |
state.Dependency = newCollectJob.Schedule(simulationSingleton, state.Dependency); | |
} | |
} | |
[BurstCompile] | |
public struct CollectGemJob : ITriggerEventsJob | |
{ | |
[ReadOnly] public ComponentLookup<GemTag> GemLookup; | |
public ComponentLookup<GemsCollectedCount> GemsCollectedLookup; | |
public ComponentLookup<DestroyEntityFlag> DestroyEntityLookup; | |
public ComponentLookup<UpdateGemUIFlag> UpdateGemUILookup; | |
public void Execute(TriggerEvent triggerEvent) | |
{ | |
Entity gemEntity; | |
Entity playerEntity; | |
if (GemLookup.HasComponent(triggerEvent.EntityA) && GemsCollectedLookup.HasComponent(triggerEvent.EntityB)) | |
{ | |
gemEntity = triggerEvent.EntityA; | |
playerEntity = triggerEvent.EntityB; | |
} | |
else if (GemLookup.HasComponent(triggerEvent.EntityB) && GemsCollectedLookup.HasComponent(triggerEvent.EntityA)) | |
{ | |
gemEntity = triggerEvent.EntityB; | |
playerEntity = triggerEvent.EntityA; | |
} | |
else | |
{ | |
return; | |
} | |
var gemsCollected = GemsCollectedLookup[playerEntity]; | |
gemsCollected.Value += 1; | |
GemsCollectedLookup[playerEntity] = gemsCollected; | |
UpdateGemUILookup.SetComponentEnabled(playerEntity, true); | |
DestroyEntityLookup.SetComponentEnabled(gemEntity, true); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Collections; | |
using UnityEngine; | |
using Unity.Entities; | |
using Unity.Physics; | |
using Unity.Physics.Systems; | |
using Unity.Transforms; | |
namespace TMG.Survivors | |
{ | |
public struct PlasmaBlastData : IComponentData | |
{ | |
public float MoveSpeed; | |
public int AttackDamage; | |
} | |
public struct PlasmaBlastExpirationTimer : IComponentData | |
{ | |
public float Value; | |
} | |
public class PlasmaBlastAuthoring : MonoBehaviour | |
{ | |
public float MoveSpeed; | |
public int AttackDamage; | |
public float DestroyAfterTime; | |
private class Baker : Baker<PlasmaBlastAuthoring> | |
{ | |
public override void Bake(PlasmaBlastAuthoring authoring) | |
{ | |
var entity = GetEntity(TransformUsageFlags.Dynamic); | |
AddComponent(entity, new PlasmaBlastData | |
{ | |
MoveSpeed = authoring.MoveSpeed, | |
AttackDamage = authoring.AttackDamage | |
}); | |
AddComponent(entity, new PlasmaBlastExpirationTimer | |
{ | |
Value = authoring.DestroyAfterTime | |
}); | |
AddComponent<DestroyEntityFlag>(entity); | |
SetComponentEnabled<DestroyEntityFlag>(entity, false); | |
} | |
} | |
} | |
public partial struct MovePlasmaBlastSystem : ISystem | |
{ | |
public void OnUpdate(ref SystemState state) | |
{ | |
var deltaTime = SystemAPI.Time.DeltaTime; | |
foreach (var (transform, data) in SystemAPI.Query<RefRW<LocalTransform>, PlasmaBlastData>()) | |
{ | |
transform.ValueRW.Position += transform.ValueRO.Right() * data.MoveSpeed * deltaTime; | |
} | |
// Destroy Plasma Blast After Time | |
foreach (var (timer, entity) in SystemAPI.Query<RefRW<PlasmaBlastExpirationTimer>>().WithPresent<DestroyEntityFlag>().WithEntityAccess()) | |
{ | |
timer.ValueRW.Value -= deltaTime; | |
if (timer.ValueRO.Value > 0) continue; | |
SystemAPI.SetComponentEnabled<DestroyEntityFlag>(entity, true); | |
} | |
} | |
} | |
[UpdateInGroup(typeof(PhysicsSystemGroup))] | |
[UpdateAfter(typeof(PhysicsSimulationGroup))] | |
[UpdateBefore(typeof(AfterPhysicsSystemGroup))] | |
public partial struct PlasmaBlastAttackSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<SimulationSingleton>(); | |
} | |
public void OnUpdate(ref SystemState state) | |
{ | |
var attackJob = new PlasmaBlastAttackJob | |
{ | |
PlasmaBlastLookup = SystemAPI.GetComponentLookup<PlasmaBlastData>(true), | |
EnemyLookup = SystemAPI.GetComponentLookup<EnemyTag>(true), | |
DamageBufferLookup = SystemAPI.GetBufferLookup<DamageThisFrame>(), | |
DestroyEntityLookup = SystemAPI.GetComponentLookup<DestroyEntityFlag>() | |
}; | |
var simulationSingleton = SystemAPI.GetSingleton<SimulationSingleton>(); | |
state.Dependency = attackJob.Schedule(simulationSingleton, state.Dependency); | |
} | |
} | |
public struct PlasmaBlastAttackJob : ITriggerEventsJob | |
{ | |
[ReadOnly] public ComponentLookup<PlasmaBlastData> PlasmaBlastLookup; | |
[ReadOnly] public ComponentLookup<EnemyTag> EnemyLookup; | |
public BufferLookup<DamageThisFrame> DamageBufferLookup; | |
public ComponentLookup<DestroyEntityFlag> DestroyEntityLookup; | |
public void Execute(TriggerEvent triggerEvent) | |
{ | |
Entity plasmaBlastEntity; | |
Entity enemyEntity; | |
if (PlasmaBlastLookup.HasComponent(triggerEvent.EntityA) && EnemyLookup.HasComponent(triggerEvent.EntityB)) | |
{ | |
plasmaBlastEntity = triggerEvent.EntityA; | |
enemyEntity = triggerEvent.EntityB; | |
} | |
else if (PlasmaBlastLookup.HasComponent(triggerEvent.EntityB) && EnemyLookup.HasComponent(triggerEvent.EntityA)) | |
{ | |
plasmaBlastEntity = triggerEvent.EntityB; | |
enemyEntity = triggerEvent.EntityA; | |
} | |
else | |
{ | |
return; | |
} | |
var attackDamage = PlasmaBlastLookup[plasmaBlastEntity].AttackDamage; | |
var enemyDamageBuffer = DamageBufferLookup[enemyEntity]; | |
enemyDamageBuffer.Add(new DamageThisFrame { Value = attackDamage }); | |
DestroyEntityLookup.SetComponentEnabled(plasmaBlastEntity, true); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Collections; | |
using UnityEngine; | |
using Unity.Entities; | |
using Unity.Mathematics; | |
using Unity.Physics; | |
using Unity.Rendering; | |
using Unity.Transforms; | |
using UnityEngine.UI; | |
namespace TMG.Survivors | |
{ | |
public struct PlayerTag : IComponentData {} | |
public struct CameraTarget : IComponentData | |
{ | |
public UnityObjectRef<Transform> CameraTransform; | |
} | |
public struct InitializeCameraTargetTag : IComponentData {} | |
[MaterialProperty("_AnimationIndex")] | |
public struct AnimationIndexOverride : IComponentData | |
{ | |
public float Value; | |
} | |
public enum PlayerAnimationIndex : byte | |
{ | |
Movement = 0, | |
Idle = 1, | |
None = byte.MaxValue | |
} | |
public struct PlayerAttackData : IComponentData | |
{ | |
public Entity AttackPrefab; | |
public float CooldownTime; | |
public float3 DetectionSize; | |
public CollisionFilter CollisionFilter; | |
} | |
public struct PlayerCooldownExpirationTimestamp : IComponentData | |
{ | |
public double Value; | |
} | |
public struct GemsCollectedCount : IComponentData | |
{ | |
public int Value; | |
} | |
public struct UpdateGemUIFlag : IComponentData, IEnableableComponent {} | |
public struct PlayerWorldUI : ICleanupComponentData | |
{ | |
public UnityObjectRef<Transform> CanvasTransform; | |
public UnityObjectRef<Slider> HealthBarSlider; | |
} | |
public struct PlayerWorldUIPrefab : IComponentData | |
{ | |
public UnityObjectRef<GameObject> Value; | |
} | |
public class PlayerAuthoring : MonoBehaviour | |
{ | |
public GameObject AttackPrefab; | |
public float CooldownTime; | |
public float DetectionSize; | |
public GameObject WorldUIPrefab; | |
private class Baker : Baker<PlayerAuthoring> | |
{ | |
public override void Bake(PlayerAuthoring authoring) | |
{ | |
var entity = GetEntity(TransformUsageFlags.Dynamic); | |
AddComponent<PlayerTag>(entity); | |
AddComponent<InitializeCameraTargetTag>(entity); | |
AddComponent<CameraTarget>(entity); | |
AddComponent<AnimationIndexOverride>(entity); | |
var enemyLayer = LayerMask.NameToLayer("Enemy"); | |
var enemyLayerMask = (uint)math.pow(2, enemyLayer); | |
var attackCollisionFilter = new CollisionFilter | |
{ | |
BelongsTo = uint.MaxValue, | |
CollidesWith = enemyLayerMask | |
}; | |
AddComponent(entity, new PlayerAttackData | |
{ | |
AttackPrefab = GetEntity(authoring.AttackPrefab, TransformUsageFlags.Dynamic), | |
CooldownTime = authoring.CooldownTime, | |
DetectionSize = new float3(authoring.DetectionSize), | |
CollisionFilter = attackCollisionFilter | |
}); | |
AddComponent<PlayerCooldownExpirationTimestamp>(entity); | |
AddComponent<GemsCollectedCount>(entity); | |
AddComponent<UpdateGemUIFlag>(entity); | |
AddComponent(entity, new PlayerWorldUIPrefab | |
{ | |
Value = authoring.WorldUIPrefab | |
}); | |
} | |
} | |
} | |
[UpdateInGroup(typeof(InitializationSystemGroup))] | |
public partial struct CameraInitializationSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<InitializeCameraTargetTag>(); | |
} | |
public void OnUpdate(ref SystemState state) | |
{ | |
if (CameraTargetSingleton.Instance == null) return; | |
var cameraTargetTransform = CameraTargetSingleton.Instance.transform; | |
var ecb = new EntityCommandBuffer(state.WorldUpdateAllocator); | |
foreach (var (cameraTarget, entity) in SystemAPI.Query<RefRW<CameraTarget>>().WithAll<InitializeCameraTargetTag, PlayerTag>().WithEntityAccess()) | |
{ | |
cameraTarget.ValueRW.CameraTransform = cameraTargetTransform; | |
ecb.RemoveComponent<InitializeCameraTargetTag>(entity); | |
} | |
ecb.Playback(state.EntityManager); | |
} | |
} | |
[UpdateAfter(typeof(TransformSystemGroup))] | |
public partial struct MoveCameraSystem : ISystem | |
{ | |
public void OnUpdate(ref SystemState state) | |
{ | |
foreach (var (transform, cameraTarget) in SystemAPI.Query<LocalToWorld, CameraTarget>().WithAll<PlayerTag>().WithNone<InitializeCameraTargetTag>()) | |
{ | |
cameraTarget.CameraTransform.Value.position = transform.Position; | |
} | |
} | |
} | |
public partial class PlayerInputSystem : SystemBase | |
{ | |
private SurvivorsInput _input; | |
protected override void OnCreate() | |
{ | |
_input = new SurvivorsInput(); | |
_input.Enable(); | |
} | |
protected override void OnUpdate() | |
{ | |
var currentInput = (float2)_input.Player.Move.ReadValue<Vector2>(); | |
foreach (var direction in SystemAPI.Query<RefRW<CharacterMoveDirection>>().WithAll<PlayerTag>()) | |
{ | |
direction.ValueRW.Value = currentInput; | |
} | |
} | |
} | |
public partial struct PlayerAttackSystem : ISystem | |
{ | |
public void OnCreate(ref SystemState state) | |
{ | |
state.RequireForUpdate<PhysicsWorldSingleton>(); | |
state.RequireForUpdate<BeginInitializationEntityCommandBufferSystem.Singleton>(); | |
} | |
public void OnUpdate(ref SystemState state) | |
{ | |
var elapsedTime = SystemAPI.Time.ElapsedTime; | |
var ecbSystem = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>(); | |
var ecb = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged); | |
var physicsWorldSingleton = SystemAPI.GetSingleton<PhysicsWorldSingleton>(); | |
foreach (var (expirationTimestamp, attackData, transform) in SystemAPI.Query<RefRW<PlayerCooldownExpirationTimestamp>, PlayerAttackData, LocalTransform>()) | |
{ | |
if (expirationTimestamp.ValueRO.Value > elapsedTime) continue; | |
var spawnPosition = transform.Position; | |
var minDetectPosition = spawnPosition - attackData.DetectionSize; | |
var maxDetectPosition = spawnPosition + attackData.DetectionSize; | |
var aabbInput = new OverlapAabbInput | |
{ | |
Aabb = new Aabb | |
{ | |
Min = minDetectPosition, | |
Max = maxDetectPosition | |
}, | |
Filter = attackData.CollisionFilter | |
}; | |
var overlapHits = new NativeList<int>(state.WorldUpdateAllocator); | |
if (!physicsWorldSingleton.OverlapAabb(aabbInput, ref overlapHits)) | |
{ | |
continue; | |
} | |
var maxDistanceSq = float.MaxValue; | |
var closestEnemyPosition = float3.zero; | |
foreach (var overlapHit in overlapHits) | |
{ | |
var curEnemyPosition = physicsWorldSingleton.Bodies[overlapHit].WorldFromBody.pos; | |
var distanceToPlayerSq = math.distancesq(spawnPosition.xy, curEnemyPosition.xy); | |
if (distanceToPlayerSq < maxDistanceSq) | |
{ | |
maxDistanceSq = distanceToPlayerSq; | |
closestEnemyPosition = curEnemyPosition; | |
} | |
} | |
var vectorToClosestEnemy = closestEnemyPosition - spawnPosition; | |
var angleToClosestEnemy = math.atan2(vectorToClosestEnemy.y, vectorToClosestEnemy.x); | |
var spawnOrientation = quaternion.Euler(0f, 0f, angleToClosestEnemy); | |
var newAttack = ecb.Instantiate(attackData.AttackPrefab); | |
ecb.SetComponent(newAttack, LocalTransform.FromPositionRotation(spawnPosition, spawnOrientation)); | |
expirationTimestamp.ValueRW.Value = elapsedTime + attackData.CooldownTime; | |
} | |
} | |
} | |
public partial struct UpdateGemUISystem : ISystem | |
{ | |
public void OnUpdate(ref SystemState state) | |
{ | |
foreach (var (gemCount, shouldUpdateUI) in SystemAPI.Query<GemsCollectedCount, EnabledRefRW<UpdateGemUIFlag>>()) | |
{ | |
GameUIController.Instance.UpdateGemsCollectedText(gemCount.Value); | |
shouldUpdateUI.ValueRW = false; | |
} | |
} | |
} | |
public partial struct PlayerWorldUISystem : ISystem | |
{ | |
public void OnUpdate(ref SystemState state) | |
{ | |
var ecb = new EntityCommandBuffer(state.WorldUpdateAllocator); | |
foreach (var (uiPrefab, entity) in SystemAPI.Query<PlayerWorldUIPrefab>().WithNone<PlayerWorldUI>().WithEntityAccess()) | |
{ | |
var newWorldUI = Object.Instantiate(uiPrefab.Value.Value); | |
ecb.AddComponent(entity, new PlayerWorldUI | |
{ | |
CanvasTransform = newWorldUI.transform, | |
HealthBarSlider = newWorldUI.GetComponentInChildren<Slider>() | |
}); | |
} | |
foreach (var (transform, worldUI, currentHitPoints, maxHitPoints) in SystemAPI.Query<LocalToWorld, PlayerWorldUI, CharacterCurrentHitPoints, CharacterMaxHitPoints>()) | |
{ | |
worldUI.CanvasTransform.Value.position = transform.Position; | |
var healthValue = (float)currentHitPoints.Value / maxHitPoints.Value; | |
worldUI.HealthBarSlider.Value.value = healthValue; | |
} | |
foreach (var (worldUI, entity) in SystemAPI.Query<PlayerWorldUI>().WithNone<LocalToWorld>().WithEntityAccess()) | |
{ | |
if (worldUI.CanvasTransform.Value != null) | |
{ | |
Object.Destroy(worldUI.CanvasTransform.Value.gameObject); | |
} | |
ecb.RemoveComponent<PlayerWorldUI>(entity); | |
} | |
ecb.Playback(state.EntityManager); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment