12.05 - TIL

김혁·2025년 12월 5일

TIL

목록 보기
71/84

오늘의 코드카타

  • 숫자 타자 대회
    • 숫자 자판을 가장 효율적으로 입력하는 방식을 시뮬레이션해보는 문제
    • 현재의 최적 선택이 미래의 최적 선택을 보장할 수 없기 때문에 DP를 통해 문제를 해결했고, 왼손, 오른손의 위치를 인덱스로 가지는 배열을 통해 구현했다.
    • https://school.programmers.co.kr/learn/courses/30/lessons/136797

팀 프로젝트 진행 내용

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되게끔 지정해줬다.
PlayerAI

오늘의 CS

std::deque

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

0개의 댓글