09.30 - TIL

김혁·2025년 9월 30일

TIL

목록 보기
29/84

오늘의 코드카타

  • 최댓값과 최솟값
  • JadenCase 문자열 만들기
  • 이진 변환 반복하기
    • 이진 변환하기 : bitset 활용 혹은 직접 계산을 통해 이진 변환
    • 0, 1의 개수 세기 : count() 활용 혹은 직접 순회하면서 세기 -> 둘 다 O(N)
    • https://school.programmers.co.kr/learn/courses/30/lessons/70129
      vector<int> solution(string s) {
          int transform = 0;
          int zero_count = 0;
          
          while(s != "1") {
              transform++;
              zero_count += count(s.begin(), s.end(), '0');
              int one_count = count(s.begin(), s.end(), '1');
              
              // bitset 변환
              bitset<32> temp = one_count;
              s = temp.to_string();
              s = s.substr(s.find('1'));	// 32비트로 변환했기 때문에 1이 처음에 나오는 위치 찾아서 자르기
              
              // 직접 변환
              //	s = "";
              //	while(one_count >= 1){
              //	    s = to_string(one_count % 2) + s;
              //	    one_count /= 2;
              //	}
          }
          
          return {transform, zero_count};
      }

오늘의 공부

팀 프로젝트 진행 내용

1. AI 화내는 애니메이션 5초로 늘이기

  • 캐릭터가 스턴 걸리는 시간이 5초인데, AI가 화내는 시간이 다소 짧음

  • 현재 GameplayAbility의 UAbilityTask_PlayMontageAndWait를 통해 화내는 애니메이션 몽타주 재생

  • 화내는 애니메이션 몽타주를 5초로 늘리고자 하는데, 기존의 애니메이션 시퀀스는 34프레임으로 약 1.13초임

    • 화내는 애니메이션을 (Start, Loop, End)로 분리를 해서 Loop를 계속 반복재생을 통해 애니메이션 몽타주 길이를 조절하고자 함

    • 기존의 애니메이션을 프레임 단위로 자른 결과, Loop 애니메이션 시퀀스의 처음과 끝의 위치가 달라서 반복재생할 때, 입이나 팔이 텔포되는 현상이 발생

      처음끝 (수정 전)

      -> 처음과 끝의 위치를 최대한 맞추고자, Loop 애니메이션의 끝에 키를 추가해서 본의 위치를 수정 (본을 클릭하고, +Key를 누르면 Curve Editor를 통해 수정 가능)

      처음끝 (수정 후)

  • 몽타주에서 Loop Count를 통해 Loop 시퀀스를 반복재생시켜 몽타주의 길이를 약 5초 정도로 수정

2. AI의 BTTask_ActivateAbility 수정

  • 기존의 Task는 GameplayAbility를 받아서 실행하고 바로 EBTNodeResult::Succeeded 반환
    -> 화내는 애니메이션을 재생하면서 움직이거나 다른 동작을 수행하게 됨
  • GameplayAbility의 AbilityEndedCallbacks의 어빌리티 종료 시 발생하는 콜백 함수에 델리게이트 바인딩
EBTNodeResult::Type UBTTask_ActivateAbility::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	CachedOwnerComp = &OwnerComp;
	
	...
	if (AbilityToActivate)
	{
		FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromClass(AbilityToActivate);
		if (Spec)
		{
        	// Delegate calls when ability ends
			ASC->AbilityEndedCallbacks.AddUObject(this, &ThisClass::OnAbilityEnded);
			
			bool bActivated = ASC->TryActivateAbilityByClass(AbilityToActivate);
			if (bActivated)
			{
				return EBTNodeResult::InProgress;
			}
		}
	}
	
	
	return EBTNodeResult::Failed;
}

void UBTTask_ActivateAbility::OnAbilityEnded(UGameplayAbility* Ability)
{
	if (Ability && Ability->GetClass() == AbilityToActivate)
	{
		if (CachedOwnerComp.IsValid())
		{
			FinishLatentTask(*CachedOwnerComp.Get(), EBTNodeResult::Succeeded);
		}
	}
}

3. AI 화내는 애니메이션 재생할 때, 공격받은 쪽 바라보게 수정

  • 공격받은 위치를 매개변수로 받아와서 UKismetMathLibrary::FindLookAtRotation() 함수를 통해 현재 위치를 기반으로 바라봐야 할 회전 값 받아오기
void AEGAICharacter::OnAngryMode(FVector AttackLocation)
{
	if (HasAuthority())
	{
		...
        
		FVector curLocation = GetActorLocation();
		FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(curLocation, AttackLocation);
        
		LookAtRotation.Pitch = 0.f;
		LookAtRotation.Roll = 0.f
        
		SetActorRotation(LookAtRotation);
	}
}


4. 동적으로 NavMesh가 변경되는 문을 레벨에 배치

  • 내비게이션 모디파이어 컴포넌트를 활용
문을 닫은 상태문을 연 상태

5. 델리게이트 두 번 Add해서 발생하는 에러 수정

Ensure condition failed: InvocationList[CurFunctionIndex] != InDelegate
File: ScriptDelegates.h Line: 1009
  • 상황
    • 테스트 플레이를 진행하는데, 위와 같은 에러가 서버에서 발생하면서 렉이 걸렸음
    • 해당 에러는 같은 델리게이트를 두 번 Add하려고 해서 에디터가 ensure()로 걸어준 상황이라고 함
  • 에러 원인
    • Item_Spawner에서 Item을 먹을 시에 30초 후에 새로 아이템이 스폰되게 하기 위해 델리게이트를 연결해줬는데, 이를 DelegateManager를 만들어서 싱글톤으로 관리를 하고 있었다.
    • 그 결과, 모든 아이템이 DelegateManager를 통해 같은 델리게이트에 브로드캐스트하고, 아이템 스포너가 같은 델리게이트에 바인딩해서 호출받고 있었음
    • 같은 델리게이트에 여러 개의 동일한 바인딩을 호출하니 해당 오류가 발생함
    • 맵에 종종 아이템이 여러 개가 중복되는 경우가 있었는데, 레벨이 열리는 과정에서 생기는 오류라고 생각했는데, 위와 같은 문제 때문에 생긴 것으로 보임
  • 해결
    • Item 클래스의 별도로 Delegate를 선언해서 독립적인 Delegate 관리가 필요했음
    • DelegateManager는 전역적인 Delegate 관리만 하는 것이 올바른 동작으로 보임

오늘의 CS

UCLASS

  • 언리얼 엔진에서 클래스를 정의하고 관리하기 위해 사용됨 (엔진이 관리하는 객체)
  • UObject 기반 클래스여서 언리얼 엔진의 핵심 기능을 모두 사용 가능
    • 가비지 컬렉션 (GC) 관리
    • 리플리케이션 가능
    • RTTI 지원 (Cast<>)
  • 상태 + 동작을 가지는 오브젝트 구현에 적합

USTRUCT

  • 데이터 구조체를 정의하기 위해 사용됨
  • UObject 기반이 아니여서 상대적으로 가벼움
    • 단순 데이터 구조이기 때문에 GC 대상이 아님
    • 리플렉션 시스템도 지원함 (블루프린트 노출)
    • 리플리케이션 불가능, RTTI 불가능
  • 데이터만 담는 타입으로 사용

-> C++와는 다르게 교차상속 불가능

UObject vs UCLASS

  • UObject
    • 언리얼 엔진의 모든 리플렉션 객체의 기반 클래스
    • UCLASS 매크로로 정의된 모든 클래스는 UObject를 상속받게 됨
  • UClass
    • UObject의 메타클래스로 클래스 자체를 설명하는 객체
    • 특정 클래스의 타입 정보를 담는 객체
    • 언리얼에서는 리플렉션 시스템을 위해 클래스 정의도 객체화했는데, 이것이 UClass 객체
profile
게임 개발자를 향해..

0개의 댓글