오늘 알아볼 내용
Guard가 목적지에 도착하면 Idle 상태가 되고 CoolTime 동안 대기를 한 뒤에 다음 목적지로 이동하는 순찰을 구현할 예정입니다.
Idle 상태 만들기
1. ComponentData 추가
Idle 상태가 되면 Guard에게 얼마동안 휴식을 취했는지 저장하는 IdleTimer Data를 추가합니다. GuardAuthoring.cs에 아래의 코드를 추가합니다.
public struct IdleTimer : IComponentData
{
public float Value;
}
2. GuardAIUtility
Idle 상태가 되면 IdleTimer를 Guard에 추가하는 기능을 만듭니다. 아래의 코드를 GuardAIUtility.cs에 추가합니다.
/// <summary>
/// Idle 상태로 변경
/// </summary>
public static void TransitionToIdle(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index)
{
ecb.AddComponent(index, e, new IdleTimer { Value = 0.0f });
}
Idle 상태가 해제되면 Guard의 Idletimer를 제거하는 코드를 GuardAIUtility.cs에 추가합니다.
/// <summary>
/// Idle 상태 해제
/// </summary>
public static void TransitionFromIdle(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index)
{
ecb.RemoveComponent<IdleTimer>(index, e);
}
3. System
목적지에 도착하면 GuardAIUtility.TransitionFromIdle를 호출하여 Guard에게 IdleTimer를 추가하는 기능을 구현합니다. CheckedReachedWaypointSystem.cs를 만들고 아래의 코드를 작성합니다.
public partial class CheckedReachedWaypointSystem : SystemBase
{
private EndSimulationEntityCommandBufferSystem endSimECBSystem;
protected override void OnCreate()
{
endSimECBSystem = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
}
[BurstCompile]
protected override void OnUpdate()
{
// 멀티스레드 병렬처리에서 EntityCommandBuffer에 명령을 기록할 수 있도록 한다.
var ecb = endSimECBSystem.CreateCommandBuffer().AsParallelWriter();
Entities
// Idle 상태를 테스트하려면 .WithNone<IdleTimer>() 필요.
.ForEach((
Entity e,
int entityInQueryIndex,
in Translation currentPosition,
in TargetPosition targetPosition) =>
{
// 남은 거리 측정
var distanceSq = math.lengthsq(targetPosition.Value - currentPosition.Value);
// 도착 여부 확인
if (distanceSq < GuardAIUtility.kStopDistanceSq)
{
// 상태 변경
GuardAIUtility.TransitionToIdle(ecb, e, entityInQueryIndex);
}
}).ScheduleParallel();
// EntityCommandBuffer 명령 실행 예약
endSimECBSystem.AddJobHandleForProducer(Dependency);
}
}
#코드 설명
멀티스레드에서는 쓰기 작업의 경우 여러 스레드가 하나의 데이터에 동시에 접근할 수 있으므로 경쟁 문제가 발생합니다. 그래서 멀티스레드에서는 EntityCommandBuffer를 이용해서 쓰기 작업을 예약한 뒤에 현재 프레임이 종료되면 쓰기 작업을 진행합니다. EntityCommandBuffer의 메뉴얼은 아래의 링크를 참고합니다.
[Unity DOTS/Dots Custom Manual] - UNITY DOTS - Entity Command Buffers 에 대해 알아보자.
math.lengthsq 는 대략적인 거리를 계산할 때 사용합니다. 정밀한 거리 계산은 안되지만 성능상의 이점이 있어 대략적인 거리 계산으로도 충분할때 사용합니다. Idle상태를 테스트하고 싶다면 .WithNone<IdleTimer>() 를 추가해야 합니다.
endSimECBSystem.AddJobHandleForProducer(Dependency);는 쓰기 준비가 완료되면 endSimECBSystem에 기록된 쓰기 기록을 실행하라는 의미입니다. 매개변수로 종속성을 전달하는데 종속성은 시스템이 의존하는 모든 작업이 완료되었는지를 확인합니다.
순찰 구현하기
1. ComponentData
아래의 두개의 Data를 GuardAuthoring.cs에 추가합니다.
// 목적지 Index
public struct NextWaypointIndex : IComponentData
{
public int Value;
}
// 휴식 시간 체크
public struct CooldownTime : IComponentData
{
public float Value;
}
2. Authoring
GuardAuthoring.cs 의 GuardAuthoring Class에 전역변수를 추가합니다.
public float IdleCooldownTime = 3.0f;
GuardAuthoring.Convert ()에 dstManager.AddComponents 에 위에서 작성한 NextWaypointIndex와 CooldownTime을 추가하는 코드를 작성합니다.
dstManager.AddComponents(entity, new ComponentTypes(
new ComponentType[]
{
typeof(CooldownTime), // 추가
typeof(NextWaypointIndex), // 추가
typeof(TargetPosition),
typeof(MovementSpeed),
typeof(WaypointPosition)
}));
GuardAuthoring.Convert()에 NextWaypointIndex와 CooldownTime의 초기값을 설정하는 코드를 추가합니다.
dstManager.SetComponentData(entity, new CooldownTime { Value = IdleCooldownTime });
dstManager.SetComponentData(entity, new NextWaypointIndex { Value = 0 });
3. GuardAIUtility
순찰 상태로 변경하거나, 해제하는 코드를 GuardAIUtility에 추가합니다.
/// <summary>
/// 순찰 상태 해제
/// </summary>
public static void TransitionFromPatrolling(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index)
{
ecb.RemoveComponent<TargetPosition>(index, e);
}
/// <summary>
/// 순찰 상태로 변경
/// </summary>
/// <param name="waypointPosition">다음 순찰지</param>
public static void TransitionToPatrolling(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index, float3 waypointPosition)
{
ecb.AddComponent(index, e, new TargetPosition { Value = waypointPosition });
}
4. System
Idle 상태가 CooldownTime 동안 휴식을 취한 뒤에 순찰 상태로 변경하는 기능을 구현합니다. UpdateIdleTimerSystem.cs를 만들고 아래의 코드를 작성합니다.
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
public partial class UpdateIdleTimerSystem : SystemBase
{
private EndSimulationEntityCommandBufferSystem endSimECBSystem;
protected override void OnCreate()
{
endSimECBSystem = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
}
[BurstCompile]
protected override void OnUpdate()
{
var ecb = endSimECBSystem.CreateCommandBuffer().AsParallelWriter();
var deltaTime = Time.DeltaTime;
Entities
.ForEach((
Entity e,
int entityInQueryIndex,
DynamicBuffer<WaypointPosition> waypoints,
ref IdleTimer idleTimer,
ref NextWaypointIndex index,
in CooldownTime cooldownTime) =>
{
idleTimer.Value += deltaTime;
if (idleTimer.Value >= cooldownTime.Value)
{
// Idle 상태 해제
GuardAIUtility.TransitionFromIdle(ecb, e, entityInQueryIndex);
// 이동해야 할 목적지의 index 값 변경
index.Value = (index.Value + 1) % waypoints.Length;
// 순찰 상태로 변경
GuardAIUtility
.TransitionToPatrolling
(ecb, e, entityInQueryIndex, waypoints[index.Value].Value);
}
}).ScheduleParallel();
endSimECBSystem.AddJobHandleForProducer(Dependency);
}
}
#코드 설명
Idle 상태가 되면 몇초 동안 휴식을 취했는지 체크합니다. CooldownTime만큼 휴식을 취했다면 Idle 상태를 해제하고 목적지 Index를 변경해서 순찰 상태로 변경하는 코드입니다.
이번에는 목적지에 도착하면 순찰 상태를 해제하겠습니다.
CheckedReachedWaypointSystem의 if (distanceSq < GuardAIUtility.kStopDistanceSq) 안에 아래의 코드를 추가합니다.
[BurstCompile]
protected override void OnUpdate()
{
// 멀티스레드 병렬처리에서 EntityCommandBuffer에 명령을 기록할 수 있도록 한다.
var ecb = endSimECBSystem.CreateCommandBuffer().AsParallelWriter();
Entities
.ForEach((
Entity e,
int entityInQueryIndex,
in Translation currentPosition,
in TargetPosition targetPosition) =>
{
// 남은 거리 측정
var distanceSq = math.lengthsq(targetPosition.Value - currentPosition.Value);
// 도착 여부 확인
if (distanceSq < GuardAIUtility.kStopDistanceSq)
{
// 상태 변경
GuardAIUtility.TransitionFromPatrolling(ecb, e, entityInQueryIndex);
GuardAIUtility.TransitionToIdle(ecb, e, entityInQueryIndex);
}
}).ScheduleParallel();
// EntityCommandBuffer 명령 실행 예약
endSimECBSystem.AddJobHandleForProducer(Dependency);
}
#코드 설명
목적지에 도착하면 GuardAIUtility.TransitionFromPatrolling를 통해 순찰 상태를 제거하는 코드입니다. 이때, TargetPosition도 제거됩니다.
재생 버튼을 눌러 Guard가 목적지를 순찰하는지 테스트를 합니다.
'Unity DOTS > 따라하며 배우기' 카테고리의 다른 글
UNITY DOTS - State Machine(상태 머신) 만들기 #6 색상 변경 (0) | 2022.08.08 |
---|---|
UNITY DOTS - State Machine(상태 머신) 만들기 #5 Guard 추격 (0) | 2022.08.07 |
UNITY DOTS - State Machine(상태 머신) 만들기 #3 Guard 이동 (0) | 2022.08.06 |
UNITY DOTS - State Machine(상태 머신) 만들기 #2 Player (0) | 2022.08.05 |
댓글