오늘의 코드카타
억억단을 외우자
- 배수를 통한 약수 찾기
- DP를 통해 최대값 원소 찾기
- LV3 - 23%
-> 문제 풀이
오늘의 공부
GAS 공부
Ability API
- UAbilityTask_ApplyRootMotionConstantForce
- Ability : Applies force to Character's movement
- 대쉬할 때 사용 (캐릭터한테 지정한만큼 힘과 시간 적용)
- UAbilityTask_WaitDelay
- Ability : Wait for Duration
- 끝나는 타이밍을 명확히 할 때 사용 (지정한 시간만큼 기다린 후에 EndAbility 발동)
void UDashAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData){
...
const FVector Direction = Character->GetActorForwardVector().GetSafeNormal();
const float Strength = (DashDuration > 0.f) ? (DashDistance / DashDuration) : DashDistance;
UAbilityTask_ApplyRootMotionConstantForce* RMTask = UAbilityTask_ApplyRootMotionConstantForce::ApplyRootMotionConstantForce(
this, NAME_None, Direction, Strength,
DashDuration, false, nullptr,
ERootMotionFinishVelocityMode::MaintainLastRootMotionVelocity,
FVector::ZeroVector, !bIgnoreGravity, true);
if (RMTask)
{
RMTask->ReadyForActivation();
}
if (UAbilityTask_WaitDelay* Delay = UAbilityTask_WaitDelay::WaitDelay(this, DashDuration))
{
Delay->OnFinish.AddDynamic(this, &ThisClass::OnDashFinished);
Delay->ReadyForActivation();
}
}
Effect API
- Cost Effect
- 필요한 자원을 Attribute Set에서 검사해서 차감시킴 (차감 불가능한 경우 Effect 불가능)
UHealCostEffect::UHealCostEffect()
{
DurationPolicy = EGameplayEffectDurationType::Instant;
FGameplayModifierInfo ManaMod;
ManaMod.Attribute = UMyAttributeSet::GetManaAttribute();
ManaMod.ModifierOp = EGameplayModOp::Additive;
ManaMod.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(-40.f));
Modifiers.Add(ManaMod);
}
- Cooldown Effect
- Tag를 이용해서 Effect의 쿨타임 계산 (Tag를 가지고 있을 시에 Effect 불가능)
- UTargetTagsGameplayEffectComponent
- Effect : GameplayEffect의 Target에게 Tag를 부여하는 컴포넌트
UE_DEFINE_GAMEPLAY_TAG(TAG_Cooldown_Heal, "Cooldown.Heal");
UHealCooldownEffect::UHealCooldownEffect()
{
DurationPolicy = EGameplayEffectDurationType::HasDuration;
DurationMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(5.f));
UTargetTagsGameplayEffectComponent* TargetTags = CreateDefaultSubobject<UTargetTagsGameplayEffectComponent>(TEXT("TargetTagsComp"));
FInheritedTagContainer TagChanges = TargetTags->GetConfiguredTargetTagChanges();
TagChanges.Added.AddTag(TAG_Cooldown_Heal);
TargetTags->SetAndApplyTargetTagChanges(TagChanges);
}
- Heal Effect (예시)
UHealEffect::UHealEffect()
{
DurationPolicy = EGameplayEffectDurationType::Instant;
FGameplayModifierInfo HealthMod;
HealthMod.Attribute = UMyAttributeSet::GetHealthAttribute();
HealthMod.ModifierOp = EGameplayModOp::Additive;
HealthMod.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(80.f));
Modifiers.Add(HealthMod);
}
- Ability에 Cost & Cooldown Effect 설정
UHealAbility::UHealAbility()
{
// 인스턴싱 설정
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
// Cost와 Cooldown Effect 연결 - GameplayAbility 내부에 이미 선언
CostGameplayEffectClass = UHealCostEffect::StaticClass();
CooldownGameplayEffectClass = UHealCooldownEffect::StaticClass();
// 실제 힐링 Effect 설정 - UHealAbility.h 파일에서 선언
HealEffectClass = UHealEffect::StaticClass();
}
- Effect 중첩 설정
UStrengthBuffEffect::UStrengthBuffEffect()
{
...
StackingType = EGameplayEffectStackingType::AggregateByTarget;
StackLimitCount = 5;
StackDurationRefreshPolicy = EGameplayEffectStackingDurationPolicy::RefreshOnSuccessfulApplication;
StackPeriodResetPolicy = EGameplayEffectStackingPeriodPolicy::ResetOnSuccessfulApplication;
}
- Effect 적용
void UBuffAbility::ActivateAbility(...)
{
...
if (BuffEffectClass && ActorInfo->AbilitySystemComponent.IsValid())
{
FGameplayEffectContextHandle ContextHandle =
ActorInfo->AbilitySystemComponent->MakeEffectContext();
ContextHandle.AddSourceObject(this);
FGameplayEffectSpecHandle SpecHandle =
ActorInfo->AbilitySystemComponent->MakeOutgoingSpec(
BuffEffectClass, 1, ContextHandle);
if (SpecHandle.IsValid())
{
FActiveGameplayEffectHandle ActiveHandle =
ActorInfo->AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(
*SpecHandle.Data.Get());
if (ActiveHandle.WasSuccessfullyApplied())
{
UE_LOG(LogTemp, Log, TEXT("Strength Buff applied!"));
}
}
}
}
오늘의 CS
스택 오버플로우
- 정의
- 스택에 할당된 공간을 초과하여 데이터를 저장하려 할 때 발생하는 오류
- 원인
- 무한 재귀 호출 : 함수가 자기 자신을 끝없이 호출하는 무한 재귀에 빠질 때 발생
- 지나치게 큰 지역 변수 : 함수 내에서 매우 큰 배열 같은 지역 변수를 선언하면, 스택 공간을 너무 많이 차지하여 오버플로우 발생 가능
- 해결법
- 함수 종료 조건을 명확하게 함
- 재귀 대신 반복문을 사용
- 과도한 스택 사이즈보다는 동적 메모리 할당을 사용
- 컴파일러 옵션을 통한 스택 사이즈 조절
댕글링 포인터 (Dangling Pointer)
- 유효하지 않은(이미 해제되었거나 존재하지 않는) 메모리를 가리키는 포인터
- 보통 어떤 리소스를 가리키는 포인터를 2개 만들었을 때, 이미 다른 포인터가 해당 리소스를 해제한지 모르고 포인터를 참조했을 때 발생
- 스코프를 벗어난 지역 변수 참조, 잘못된 포인터 연산
포인터 vs 레퍼런스의 성능 차이
- 단순히 언어 차원에서의 성능보다는, CPU 메모리 계층 구조와 메모리 접근 패턴에서 차이가 나게 됨
- 레퍼런스
- 이미 존재하는 객체의 별칭으로, 가리키는 대상이 확정적 정보이기 때문에 주소가 이미 확정적이라서, 컴파일러는 보통 레지스터로 최적화해서 접근
- 포인터
- 런타임에 가리키는 대상이 달라질 수 있기 때문에 최적화를 제한적으로 적용하게 되고, 불필요한 메모리 접근이 발생할 수 있음
- 포인터가 랜덤한 위치를 가리킨다면, CPU 캐시에 데이터가 없을 확률이 커져서 캐시 미스 발생
캐시 히트와 메모리 접근 패턴
- CPU 캐시는 공간 지역성과 시간 지역성을 이용
- 공간 지역성 : 가까운 메모리는 같이 쓰일 확률이 높음
- 시간 지역성 : 최근에 쓴 메모리는 또 쓸 확률이 높음
- 캐시 히트 : CPU가 어떤 데이터를 읽으려 할 때, 이미 캐시에 그 데이터가 존재하는 경우
- 캐시 미스 : CPU가 요청한 데이터가 캐시에 없어서 RAM에서 가져와야 하는 경우
- 캐시 친화적인 구조
- 연속된 컨테이너(배열) 사용
- AoS (Array of Structures) 보다는 SoA(Structure of Arrays) 사용
AoS vs SoA
- AoS (Array of Structures)
- 메모리 구조 : [x,y,z,vx,vy,vz][x,y,z,vx,vy,vz][x,y,z,vx,vy,vz]...
- 장점 : 직관적, 객체지향적
- 단점 : x만 필요해도 (x,y,z,vx,vy,vz)를 한꺼번에 캐시로 가져옴
struct Particle {
float x, y, z;
float vx, vy, vz;
};
std::vector<Particle> particles;
- SoA (Structure of Arrays)
- 메모리 구조 : [x0,x1,x2...][y0,y1,y2...]...
- 장점 : x 좌표만 연산할 때, 캐시에 x 값들만 맞게 올라옴 -> 캐시 히트율 증가
- 단점 : 구조가 복잡해지고 관리가 힘듦
struct Particles {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
};