오늘의 코드카타
팀 프로젝트 진행 내용
1. 피격 시에 다른 동작 불가능하게 추가 구현
- 기존에는 피격 리액션 중에 점프나 다른 어빌리티가 실행이 가능했다. 또한 피격 리액션 중에 공격 받으면 데미지가 2번 들어가는 현상이 있어서 이를 해결하고자 했다.
피격 시에 이동 제어
- 먼저 피격 리액션 중에 점프나 이동이 불가능하게 하고자, 캐릭터의 무브먼트를 통해 움직임이 불가능하게끔 하였다. AI의 경우에는 Pawn으로 구현되어 있다면, BT에서 관리를 할 것으로 보여서 추가적인 작업은 필요하지 않을 것으로 보였다.
// ActivateAbility
if (HasAuthority(&ActivationInfo))
{
if (TObjectPtr<ACharacter> Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get()))
{
Character->GetCharacterMovement()->DisableMovement();
}
}
// EndAbility
if (HasAuthority(&ActivationInfo))
{
if (TObjectPtr<ACharacter> Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get()))
{
Character->GetCharacterMovement()->SetMovementMode(MOVE_Walking);
}
}
피격 시에 다른 어빌리티 및 데미지 중복 적용 불가능
- 추후에 캐릭터에 무적 효과를 부여할 수도 있지 않을까해서 상처입지 않는 GameplayEffect와 다른 어빌리티를 제한하는 GameplayEffect를 받아서 사용하고자 했다.
// AO_GameplayAbility_HitReact.h
UPROPERTY(EditDefaultsOnly, Category = "HitReact")
TSubclassOf<UGameplayEffect> InvulnerableEffectClass;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "HitReact")
TSubclassOf<UGameplayEffect> BlockAbilitiesEffectClass;
FActiveGameplayEffectHandle InvulnerableEffectHandle;
FActiveGameplayEffectHandle BlockAbilitiesEffectHandle;
- 두 가지의 Effect를 부여하고 해제하는 코드를
ActivateAbility()와 EndAbility()에 추가했다.
// ActivateAbility
if (HasAuthority(&ActivationInfo))
{
if (InvulnerableEffectClass)
{
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, InvulnerableEffectClass, 1.f);
InvulnerableEffectHandle = ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, SpecHandle);
}
if (BlockAbilitiesEffectClass)
{
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, BlockAbilitiesEffectClass, 1.f);
BlockAbilitiesEffectHandle = ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, SpecHandle);
}
}
// EndAbility
if (HasAuthority(&ActivationInfo))
{
if (UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get())
{
if (InvulnerableEffectHandle.IsValid())
{
ASC->RemoveActiveGameplayEffect(InvulnerableEffectHandle);
}
InvulnerableEffectHandle.Invalidate();
if (BlockAbilitiesEffectHandle.IsValid())
{
ASC->RemoveActiveGameplayEffect(BlockAbilitiesEffectHandle);
}
BlockAbilitiesEffectHandle.Invalidate();
}
}
GE_Invulnerable은 간단하게 Actor에게 Status.Invulnerable이라는 태그를 부여하는 이펙트다.

GE_BlockAbilities은 Actor에게 Status.Debuff.BlockAbilities라는 태그를 부여하고, Ability 태그를 가진 모든 어빌리티를 막는 이펙트다.

- 데미지를 가하는 부분에서 액터에게
Status.Invulnerable 태그가 부여되어있다면, 데미지가 들어가지 않도록 추가했다.
void UAO_GameplayAbility_MeleeHitConfirm::ApplyDamageToActor(AActor* TargetActor, const FAO_MeleeHitTraceParams& Params,
const FGameplayAbilityActorInfo* ActorInfo)
{
...
// 무적 상태인 경우 적용하지 않음
const FGameplayTag InvulnerableTag = FGameplayTag::RequestGameplayTag(FName("Status.Invulnerable"));
if (TargetASC->HasMatchingGameplayTag(InvulnerableTag))
{
return;
}
...
}
2. 플레이어와 AI를 구분을 위한 콜리전 설계
- 데미지를 가하는 로직에서 트레이스 채널을 지정해서 Trace하고, 이에 따라 데미지를 가하게끔 구현했었다.
- 이를 위해 Player와 AI를 구분하기 위한 콜리전 설계를 했다.
- Player가 공격 -> Player는 안 맞고, AI만 맞음
- AI가 공격 -> Player만 맞고, AI는 안 맞음
- 먼저 트레이스에서의 검사가 필요하기 때문에 Trace Channels에 Player와 AI 채널을 추가했다.

- 다음으로 콜리전 프리셋을 추가해서 채널을 일일이 관리하는 것보다 프리셋만 지정하면 되게끔 Player와 AI 프리셋을 추가했다.
- ObjectType은 기존의 Pawn을 유지했고, 서로의 Trace만 Block되게끔 지정해줬다.
| Player | AI |
|---|
 |  |
오늘의 CS
std::deque
- 여러 개의 고정 크기 메모리 블록에 요소들을 저장하고, 이 블록들은 비연속적임
- 고정 크기 메모리 블록은 연속된 배열 구조를 가지게 되어서, 캐시 효율성이
std::vector보다는 떨어지지만, std::list보다는 좋음
- 내부적으로 데이터는 메모리 블록에 저장을 하고, 메모리 블록의 위치를 관리하기 위해 내부 맵 배열을 통해 인덱스 접근을 가능하게 함
std::vector와 비교했을 때 재할당 없이 앞에나 뒤에 새로운 블록을 연결할 수 있어, 양 끝에서의 삽입/삭제에 효율적임
- 시간복잡도
- 요소 접근 :
O(1) -> 내부 맵 배열을 통해 접근, 인덱스 계산이 필요함
- 삽입/삭제(앞, 뒤) :
O(1) -> 새로운 블록을 연결하거나 제거하는 방식으로 처리됨
- 삽입/삭제(중간) :
O(n) -> 많은 요소 이동이 필요하기 때문에 비용이 발생함
- 청크(메모리 블록)의 크기는 보통 일정한 크기를 가지게 되는데, 구현 환경(컴파일러, 아키텍처)에 따라 다르다.
- 맵 배열을 통해 논리적인 인덱스를 저장하고 실제 청크를 통해 물리적인 인덱스를 저장하고, 이 둘을 매핑함으로써
std::vector처럼 어느 정도의 연속적인 메모리 구조를 통해 높은 캐시 효율성을 가져가고, std::list처럼 앞에서도 삽입/삭제를 효율적으로 하기 위해 개발된 컨테이너다.