클로드 커넥터 활용과 기본적인 참고사항

1. 클로드 코드가 아닌 클로드를 사용한 이유

  • 클로드 코드는 터미널에서 사용함으로써 개발자 편의사항은 높지만 클로드에게 너무 많은 권한을 줄 수 있음
    → AI에 의존하기 보다는 적당한 수준의 활용과 공부가 필요한 시점
  • 클로드 코드에서도 노션과 깃허브 사용 클로드 PC 앱을 사용한 PC 파일 접근이 가능
    → 위 기능이면 충분한 편의성과 클로드에 대한 자료 제공이 충분하다는 판단

2. 클로드를 통한 노션과 PC 파일 접근

  • 노션은 의외로 커넥터 접근을 통한 개인 자료 확인 및 페이지 수정기능 수준이 높지만 생각보다 오류가 자주나서 방법을 찾는중
  • PC 앱을 통한 로컬 파일 접근 커넥터는 생각보다 유용하고 빠른 반응 속도를 보여줌
    • 단순 참고의경우 대략 30개의 소스코드 파일의 3000줄이 넘는 코드를 금방 분석하여 답변해주는 성능을 보여주었음
    • 심지어 그 파일을 각 폴더별 분류 아티팩트를 만들어 그 안에 소스코드를 전부 붙여넣는 작업도 생각보다 금방 완료했음
    • 처음에 Opus4.1을 사용해 작업을 모두 완료하기 전에 사용량을 다 써버렸지만 그 이후에 Sonnet4를 사용했을땐 전부 완료하고 사용량이 어느정도 남았음
    • 그뒤 노션과 로컬 파일 접근을 통한 도움을 받아 작업을 진행

3. 깃허브 이슈

  • 깃허브 커넥트 연동 시 프로젝트 파일업로드로 깃허브 레포지토리의 선택한 폴더가 업로드 가능하며 변동 시 동기화로 최신화 까지 가능
    • 그러나 어째서인지 깃허브에 올라가있는 파일을 업로드하면 대부분의 파일은 인식이 안되고 파일 자체를 클로드에 업로드하여도 에러가 발생함
  • 문제 해결을 위해 다시 깃허브 업로드 동기화, 깃허브에서 파일 첨부, 로컬파일을 클로드에 업로드 등 다양한 방법시도
    • 여러번의 시도 끝에 업로드가 안되는 파일은 한글 주석이 달려있다는 공통점을 발견
    • 구글링과 AI 검색에서도 Visual Studio 기본 저장이 UTF-8 이 아니기 때문에 깃허브 업로드시 문자가 깨지거나 AI가 읽지 못하는 문제가 생길 수 있다는 것을 확인
    • 여러 방법을 써서 UTF-8 로 변환하려 했지만 파일들을 수동으로 하나씩 변환하기에는 너무 비효율 적이고 언리얼 프로젝트 특성 때문인지 여러 방법들이 실패하였고 마지막으로 PoewerShell을 이용한 변환 시도
      1. 언리얼 프로젝트 폴더에 XXX.uproject 파일이 있는 폴더에서 PowerShell 열기
      2. PowerShell에 아래의 명령어 복사 및 실행
Get-ChildItem -Path "Source" -Recurse -Include *.cpp,*.h | ForEach-Object {
Write-Host "Converting: $($_.Name)" 
$content = Get-Content $_.FullName -Raw -Encoding Default 
$utf8 = New-Object System.Text.UTF8Encoding $true 
[System.IO.File]::WriteAllText($_.FullName, $content, $utf8) } 
Write-Host "Conversion completed!"
  • 이후 깃허브 커밋 → 푸시를 통한 깃허브 업데이트를 진행하여 클로드에서도 정상적으로 읽고 한글 폰트가 깨지는 문제 해결

' > 공부' 카테고리의 다른 글

AI와 기술 부채  (3) 2025.08.10
Unreala CollisionChannel Issue 정리  (1) 2025.08.05

GPT-5와 기술 부채에 대한 경험과 생각

1. 이전 AI 활용 방식과 변화

  • 과거에는 AI를 작은 단위 작업에 활용
    → 내가 직접 설계한 틀을 AI에게 검토시킨 뒤, 피드백을 반영해 점진적으로 구현
  • GPT-5 업데이트 후 코딩 능력 강화가 두드러짐
    → 이번에는 3개 이상의 클래스를 한 번에 생성·수정하도록 요청

2. 이번 사례

  • TurnLevel에서 전투를 위해 CombatManager 구현
  • GPT-5 제안 구조 중, Character와 전투 상태 관련 로직을 COEGameInstance에 병합
  • CharacterCombatManager 연결을 위해 TurnCombatBridgeComponent를 상속한 블루프린트를 할당
  • 결과: 오타·실수 외에는 첫 컴파일에서 성공
    → 기존 방식 대비 작업 시간 6시간 → 3시간 단축

3. 느낀 점

  • AI가 대부분의 코드를 작성해도 사람이 전체 구조와 로직을 이해·검토해야 함
  • 구현된 함수와 변수를 확인하는 과정 때문에 시간 단축 효과는 제한적
  • 이후 기능 추가·유지보수 시 기술 부채 가능성
    • AI가 기존 코드 일부만 수정하지 않고 전체를 재작성하는 경우가 잦아 위험
    • 불필요한 흐름 변경 및 예기치 못한 문제 발생 위험
    • 문제가 생겨도 AI는 책임지지 않음

4. 결론

  • AI에게 모든 것을 맡기는 것은 위험 → 최종 검토와 책임은 반드시 사람의 몫
  • AI를 그대로 쓰면 개발자의 실력이 정체될 수 있음
    • AI를 활용하는 능력도 개발자의 필요 능력으로 자리잡고 있기 때문에 개발자 본인의 개발 능력에 AI 활용능력도 습득이 필요
  • 기업 입장에서는
    • 잘하는 중·고급 개발자가 주니어 업무를 AI로 보조
    • 단순 작업은 비전문 인력 + AI 조합이 더 효율적일 수 있음

' > 공부' 카테고리의 다른 글

Claude 사용기  (3) 2025.08.22
Unreala CollisionChannel Issue 정리  (1) 2025.08.05

DECLARE_MULTICAST_DELEGATE관련 이슈

  • 최초에는 FOnTurnEnemyDead OnDead를 UPROPERTY를 통해 BlueprintAssignable 속성을 부여했으나DECLARE_MULTICAST_DELEGATE는 정적(C++) 타입이어서 등록되지 않음 이를 해결하기위해서는 동적(DYNAMIC) 델리케이트를 사용하거나 C++전용으로 UPROPERTY를 제거해야함 결국 아래와 같이 수정
// 완전 C++ 전용으로, UPROPERTY 제거
DECLARE_MULTICAST_DELEGATE(FOnTurnEnemyDead);
FOnTurnEnemyDead OnDead;
  • 오류 해결 이후에도 브로드캐스트 호출 시그니처 관련 이슈로 델리게이트 선언과 Broadcast 호출의 매개변수가 일치하지 않아 아래와 같이 수정
//수정 전
OnDead.Broadcast(DamageCauser);

//수정 후
OnDead.Broadcast();
  • 리스너(바인딩) 방식에서도 AddDynamic이 아닌 AddUObject로 수정
//수정 전
Enemy->OnDead.AddDynamic(this, &ATurnGameMode::OnEnemyDied);

//수정 후
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDied);
  • 만일 델리게이트에서 파라미터로 AActor* 같은 인자를 넘기고 싶다면 아래와 같이 사용해야함
// 헤더
DECLARE_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*);
FOnTurnEnemyDead OnDead;

// CPP
OnDead.Broadcast(DamageCauser);

// 바인딩
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDiedWithParam);
void OnEnemyDiedWithParam(AActor* Causer) { … }
  • 블루프린트 바인딩이 필요할때는 DYNAMIC 버전 필요
// 헤더
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*, DamageCauser);

UPROPERTY(BlueprintAssignable, Category="Combat")
FOnTurnEnemyDead OnDead;
  • 순수 C++전용 타입이라면 델리게이트선언에서 DYNAMIC 제거
  • Broadcast호출과 시그니처(매개변수 개수)는 일치해야함
    ex) DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*, DamageCauser);
  • 리스너등록(바인딩)은 순수 C++타입은 AddUObject 사용 블루프린트 바인딩 시 AddDynamic 사용
  • 필요시 파라미터 버전은 OneParam등 사용
//순수 C++ 델리게이트 (파라미터 없음)

//헤더
DECLARE_MULTICAST_DELEGATE(FOnTurnEnemyDead);

FOnTurnEnemyDead OnDead;

//Broadcast
OnDead.Broadcast();

//바인딩
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDied);

void ATurnGameMode::OnEnemyDied()
{
    // 파라미터 없는 콜백
}

//순수 C++ 델리게이트 (파라미터 1개)

//헤더
DECLARE_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*);

FOnTurnEnemyDead OnDead;

//Broadcast
OnDead.Broadcast(DamageCauser);

//바인딩
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDiedWithParam);

// 콜백
void ATurnGameMode::OnEnemyDiedWithParam(AActor* Causer)
{
    // Causer 처리
}

//동적(Dynamic) 델리게이트 (블루프린트 바인딩, 파라미터 없음)

//헤더
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTurnEnemyDead);

UPROPERTY(BlueprintAssignable, Category="Combat")
FOnTurnEnemyDead OnDead;

//Broadcast
OnDead.Broadcast();

//바인딩
Enemy->OnDead.AddDynamic(this, &ATurnGameMode::OnEnemyDied);

// 콜백
UFUNCTION()  // 반드시 UFUNCTION() 필요
void OnEnemyDied();

void ATurnGameMode::OnEnemyDied()
{
    // 파라미터 없는 블루프린트 바인딩 콜백
}

//동적(Dynamic) 델리게이트 (블루프린트 바인딩, 파라미터 1개)

//헤더
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*, DamageCauser);

UPROPERTY(BlueprintAssignable, Category="Combat")
FOnTurnEnemyDead OnDead;

//Broadcast
OnDead.Broadcast(DamageCauser);

//바인딩
Enemy->OnDead.AddDynamic(this, &ATurnGameMode::OnEnemyDiedWithParam);

// 콜백
UFUNCTION()  // 반드시 UFUNCTION() 필요
void OnEnemyDiedWithParam(AActor* Causer);

void ATurnGameMode::OnEnemyDiedWithParam(AActor* Causer)
{
    // 파라미터로 전달된 Causer 처리
}

용도 선언 매크로 바인딩 Broadcast
순수 C++, 파라미터 없음 DECLARE_MULTICAST_DELEGATE AddUObject Broadcast()
순수 C++, 파라미터 있음 DECLARE_MULTICAST_DELEGATE_OneParam AddUObject Broadcast(param)
블루프린트, 파라미터 없음 DECLARE_DYNAMIC_MULTICAST_DELEGATE AddDynamic Broadcast()
블루프린트, 파라미터 있음 DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam AddDynamic Broadcast(param)

이슈 해결 후 추가로 찾아본 언리얼 엔진에서의 델리게이트 이론정리

  1. 델리게이트란?

    • 함수 포인터의 안전한 버전 == 함수를 저장해두었다가 나중에 호출할 수 있는 객체
  2. 왜 사용하는지?

    • 의존성 제거: Player가 UI 나 Sound 클래스를 몰라도 됨

    • 독립적 개발: 각 시스템을 따로 개발 가능

    • 테스트 용이: Player만 단독으로 테스트 가능

      // 직접 호출 방식 
      class APlayer : public APqwn 
      { 
      private: 
      AUIManager* UIManager; //직접 참조 필요 
      ASoundManager* SoundManager; //직접 참조 필요 
      AGameMode* GameMode; // 직접 참조 필요 
      public: 
      void TakeDamage(float Damage) 
      { 
          CurrentHealth -= Damage; 
          // 모든 시스템을 직접 호출 
          if (UIManager) UIManager->UpdateHealthBar(CurrentHealth); 
          if (SoundManager) SoundManager->PlayDamageSound(); 
          if (GameMode) GameMode->CheckGameOver(); 
      
           //새로운 시스템 추가시 이어서 수정 
      } 
      }; 
      // 델리게이트 방식 
      class APlayer : public APawn 
      { 
      public: 
      DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChaged, float); 
      FOnHealthChange OnHealthChanged; 
      void TakeDamage(float Damge) 
      { 
          CurrentHealth -= Damage; 
      
          // 누가 듣고 있는지 몰라도 됨 
          OnHealthChanaged.Broadcast(CurrentHealth); 
      } 
      };
  3. 확장성

    • 이벤트 기반 아키텍처
      • Observer 패턴: 하나의 이벤트에 여러 관찰자
      • 발행 구독 모델: 이벤트 발생자와 처리자 분리
      • 비동기 처리: 이벤트 발생과 처리의 분리
    • 유지보수성
      • 단일 수정: 이벤트 로직만바꾸면 모든 곳에 적용
      • 버그 격리: 한 시스템 오류가 다른 시스템에는 영향 X
      • 코드 재사용: 같은 이벤트를 여러 곳에서 활용
    //새로운 시스템 추가가 쉬움 
    class AParticleManager : public AActor 
    { 
        void BeginPlay() override 
        { 
            //기존 Player 클래스 수정 없이 새 기능 추가 
            Player->OnHealthChanged.AddUObject(this,&AParticleManager::PlayBloodEffect); 
        } 
    };

' > 포트폴리오' 카테고리의 다른 글

포트폴리오 log #5  (2) 2025.07.31
포트폴리오 log #4  (4) 2025.07.17
포트폴리오 log #3  (0) 2025.07.16
포트폴리오 log #2  (0) 2025.07.15
포트폴리오 log #1  (0) 2025.07.11

프로젝트 세팅에서 Object Channel 추가로인한 오류 발생

  • 기존에 아무것도 생성하지않은 상태에서 Item Object Channel 추가로 인해 비헤비어트리 서비스 노드에 문제가 발생하여 Enemy가 Player를 타겟으로 서치하는 BTService_SearchTarget 클래스가 제대로 작동하지않음

  • 위와같이Object Channel을 추가하게 되면 DefaultEngine.ini파일에 아래와 같은 코드가 추가되어 ECC_GameTraceChannel1 에 Item이 할당되게 된다.
DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False,Name="Item")
  • 위와 같이 ECC_GameTraceChannel1 에 Item할당되게 되면 기존의 BTService_SearchTarget에서 Player를 찾기위해 OverlapMultiByChannel을 사용할때 기본 설정으로 사용했던 ECC_GameTraceChannel1가 새로 추가한 Item 오브젝트의 설정을 따라가게되고 기본 반응이 무시로 덮어쓰이게됨.
  • 따라서 이를 해결하기 위해서는 새로운 Object Channel을 설정하고 그 채널을 사용하거나 아무것도 할당되지 않은 ECC_GameTraceChannel2 채널을 사용하도록 수정할 필요가있어 ECC_GameTraceChannel2 채널을 사용하도록 수정하였음
//수정 전
void UBTService_SearchTarget::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

    auto Pawn = OwnerComp.GetAIOwner()->GetPawn();
    if (Pawn != nullptr)
    {
        FVector Center = Pawn->GetActorLocation();
        float SearchDistance = 500.f;
        TArray<FOverlapResult> OverlapResults;
        FCollisionQueryParams QueryParams(NAME_Name, false, Pawn);

        bool Result = GetWorld()->OverlapMultiByChannel
        (
            OverlapResults,
            Center,
            FQuat::Identity,
            ECollisionChannel::ECC_GameTraceChannel1, // Item Object Channel이 할당되어 수정이 필요해진 부분
            FCollisionShape::MakeSphere(SearchDistance),
            QueryParams
        );

        if (Result)
        {
            for (auto& OverlapResult : OverlapResults)
            {
                auto Player = Cast<AMyPlayer>(OverlapResult.GetActor());
                if (Player)
                {
                    DrawDebugSphere(GetWorld(), Center, SearchDistance, 10, FColor::Green, false, 0.5f);
                    OwnerComp.GetBlackboardComponent()->SetValueAsObject(FName("Target"), Player);
                    return;
                }

            }

        }


        OwnerComp.GetBlackboardComponent()->SetValueAsObject(FName("Target"), nullptr);
        DrawDebugSphere(GetWorld(), Center, SearchDistance, 10, FColor::Red, false, 0.5f);

    }


}

//수정 후
void UBTService_SearchTarget::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);

    auto Pawn = OwnerComp.GetAIOwner()->GetPawn();
    if (Pawn != nullptr)
    {
        FVector Center = Pawn->GetActorLocation();
        float SearchDistance = 500.f;
        TArray<FOverlapResult> OverlapResults;
        FCollisionQueryParams QueryParams(NAME_Name, false, Pawn);

        bool Result = GetWorld()->OverlapMultiByChanne2 // 수정 된 부분
        (
            OverlapResults,
            Center,
            FQuat::Identity,
            ECollisionChannel::ECC_GameTraceChannel1,
            FCollisionShape::MakeSphere(SearchDistance),
            QueryParams
        );

        if (Result)
        {
            for (auto& OverlapResult : OverlapResults)
            {
                auto Player = Cast<AMyPlayer>(OverlapResult.GetActor());
                if (Player)
                {
                    DrawDebugSphere(GetWorld(), Center, SearchDistance, 10, FColor::Green, false, 0.5f);
                    OwnerComp.GetBlackboardComponent()->SetValueAsObject(FName("Target"), Player);
                    return;
                }

            }

        }


        OwnerComp.GetBlackboardComponent()->SetValueAsObject(FName("Target"), nullptr);
        DrawDebugSphere(GetWorld(), Center, SearchDistance, 10, FColor::Red, false, 0.5f);

    }


}

' > 공부' 카테고리의 다른 글

Claude 사용기  (3) 2025.08.22
AI와 기술 부채  (3) 2025.08.10

DoDefaultAttack() 함수에서 실제 공격 판정 Sphere와 DrawDebugCapsule에서 그리는 Capsule의 형태가 일치 X

  • 실제 공격판정인 SweepSingleByChannel에서는 FQuat::Identity를 사용해 스피어가 Z축 기준으로 수직으로 서있는 형태이고  DrawDebugCapsule에서는 FQuat Rotation = FRotationMatrix::MakeFromZ(Vec).ToQuat() 변수를 선언한뒤 Rotaion을 적용하여 캐릭터의 정면 방향으로 그리게 되는 것을 FQuat Rot = FRotationMatrix::MakeFromZ(EndPos - StartPos).ToQuat(); 를 공통으로 사용하여 일치하게 수정
void ACOECharacter::DoDefaultAttack()
{
	FHitResult HitResult;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this);
	Params.bTraceComplex = false;
	Params.bReturnPhysicalMaterial = false;

	float AttackRange = 250.f;
	float AttackRadius = 50.f;
	FVector StartPos = GetActorLocation();
	FVector EndPos = GetActorLocation() + GetActorForwardVector() * AttackRange;
	FQuat Rot = FRotationMatrix::MakeFromZ(EndPos - StartPos).ToQuat(); // 수정을 위해 추가된 부분
	bool Result = GetWorld()->SweepSingleByChannel
	(
		HitResult,														
		StartPos,									
		EndPos,										
		Rot,							//FQuat::Identity에서 미리 선언한 Rot으로 수정
		ECC_GameTraceChannel3,						
		FCollisionShape::MakeSphere(AttackRadius),	
		Params										
	);


	
	FVector Center = (StartPos + EndPos) * 0.5f;
	float HalfHeight = AttackRange * 0.5f;
    //FQuat Rotation = FRotationMatrix::MakeFromZ(Vec).ToQuat(); -> 같은 FQuat Rot을 사용하기 위해 삭제
	FColor DrawColor;

	DrawColor = Result ? FColor::Green : FColor::Red;
	//실제 공격에 사용한 FQuat값을 사용하기위해 기존의 Rotation 대신 Rot을 사용
	DrawDebugCapsule(GetWorld(), Center, HalfHeight, AttackRadius, Rot, DrawColor, false, 2.f);

	if (Result && HitResult.GetActor())
	{
		UE_LOG(LogTemp, Log, TEXT("Hit : %s"), *HitResult.GetActor()->GetName());
		
		UGameplayStatics::ApplyDamage(HitResult.GetActor(), 10.f, GetInstigatorController(), this, nullptr);
		
		if (AExplorationEnemy* Enemy = Cast<AExplorationEnemy>(HitResult.GetActor()))
		{	
			
			if (Enemy->PossibleBattleLevels.Num() > 0)
			{
				
				FName SelectedBattleMap = Enemy->PossibleBattleLevels[FMath::RandRange(0, Enemy->PossibleBattleLevels.Num() - 1)];
			
				if (UCOEGameInstance* GI = Cast<UCOEGameInstance>(UGameplayStatics::GetGameInstance(this)))
				{
					GI->bPlayerInitiative = true; 
					GI->bPlayerWasDetected = false;
					GI->ReturnLocation = GetActorLocation();
					GI->ReturnMapName = FName("Lvl_ThirdPerson"); 
					GI->EnemyToRemove = HitResult.GetActor();
				}

				
				UGameplayStatics::OpenLevel(this, SelectedBattleMap);
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Enemy has no PossibleBattleLevels!"));
			}
		}
	}
	UE_LOG(LogTemp, Warning, TEXT("DefaultAttack() called in TurnBattleLevel!"));
}

 

 

DetectPlayerComponent.cpp 구현 중 생성자에 스피어 반경, CollisionProfile 설정, Bind overlap Event 등을 구현하였더니 컴파일이 안되는 오류 발생

  • 생성자에서 구현 X BeginPlay에서 구현하여 CollsionProfile 시스템이 완전히 초기화 된 이후 안전하게 적용
// 초기 작성 코드
UDetectPlayerComponent::UDetectPlayerComponent()
{
    // 충돌 반경설정
    SetSphereRadius(100.f);
    SetCollisionProfileName(TEXT("OverlapAllDynamic"));

    // Bind overlap events
    OnComponentBeginOverlap.AddDynamic(this, &UDetectPlayerComponent::HandleBeginOverlap);
    OnComponentEndOverlap.AddDynamic(this, &UDetectPlayerComponent::HandleEndOverlap);
}

void UDetectPlayerComponent::BeginPlay()
{
    Super::BeginPlay();
}

// 수정 코드
UDetectPlayerComponent::UDetectPlayerComponent()
{
   
}

void UDetectPlayerComponent::BeginPlay()
{
    Super::BeginPlay();
    // 충돌 반경설정
    SetSphereRadius(100.f);
    SetCollisionProfileName(TEXT("OverlapAllDynamic"));

    // Bind overlap events
    OnComponentBeginOverlap.AddDynamic(this, &UDetectPlayerComponent::HandleBeginOverlap);
    OnComponentEndOverlap.AddDynamic(this, &UDetectPlayerComponent::HandleEndOverlap);
}

 

' > 포트폴리오' 카테고리의 다른 글

포트폴리오 log #6  (0) 2025.08.05
포트폴리오 log #4  (4) 2025.07.17
포트폴리오 log #3  (0) 2025.07.16
포트폴리오 log #2  (0) 2025.07.15
포트폴리오 log #1  (0) 2025.07.11

캐릭터 Stats 구조체 구현

  • 처음에는 가장 베이스가 될 COECharacter클래스에 CharacterStats에 관련된 변수를 선언하려 하였으나 구초체로 구현하는 것이 더 적합할 것이라는 판단에 구현하던중 여러 Character 및 Enemy 클래스에서 활용하기에는 구조체 클래스를 하나 만드는 것이 적합하다는 판단에 FCharacterStats 클래스를 만들기로 결정 앞의 F는 언리얼 명명체계임

FCharacterStats 클래스 생성을위해 빈 C++클래스 생성

  • 생성된 FCharacterStats.h에서 #include "CoreMinimal.h" #include "FCharacterStats.generated.h" 두개 만 남기고 다 삭제한다음 아래코드로 작성 #include "클래스명.generated.h" 가 가장 마지막에 선언되지않으면 빌드에 문제가 생김 
    → 몰라서 다른 문제인지 찾아보다가 GPT에서 알려줘서 고침
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "FCharacterStats.generated.h"

/**
 * 
 */
USTRUCT(BlueprintType)
struct FCharacterStats
{
	GENERATED_BODY()

public:
	
	/** HPMAX */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float MAXHP;

	/** HP */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float CurrentHP;

	/** Vitality */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float Vitality;

	/** AttackPower */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float AttackPower;

	/** Defense */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float Defense;

	/** Agility */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float Agility;

	/** Luck */
	UPROPERTY(BlueprintReadOnly, Category = "Status")
	float Luck;
};

//COECharacter 클래스에서 사용하기 위해 COECharacter.h에 #include "FCharacterStats.h" 추가
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Logging/LogMacros.h"
#include "FCharacterStats.h"
#include "COECharacter.generated.h" //각 클래스의 generate.h가 가장 마지막에 선언 되어야함

 

' > 포트폴리오' 카테고리의 다른 글

포트폴리오 log #6  (0) 2025.08.05
포트폴리오 log #5  (2) 2025.07.31
포트폴리오 log #3  (0) 2025.07.16
포트폴리오 log #2  (0) 2025.07.15
포트폴리오 log #1  (0) 2025.07.11

SetAinming상태 전환중 불안정한 카메라 전환 해결

  • 아래와 같이 단순 CameraBoom의 좌표변경으로는 전환이 매끄럽지않은 문제가 발생하여 구글링과 GPT를 비교해본 결과
void ACOECharacter::SetAiming(bool bNewAiming)
{

	if (bIsAiming)
		{
			// 조준 카메라로 전환
			// 속도 감소 등
			UE_LOG(LogTemp, Log, TEXT("IsAiming"));
			CameraBoom->SocketOffset = FVector(300.f, 80.f, 50.f);
		
		}
		else
		{
			// 기본 상태 복귀
			UE_LOG(LogTemp, Log, TEXT("IsNotAiming"));
			CameraBoom->SocketOffset = FVector(200.f, 80.f, 50.f);
		}
}
  • 아래와 같은 공통된 방법이 있어 코드를 수정하였으나 조준상태에서 벗어나지 못하는 문제가 발생
void ACOECharacter::SetAiming(bool bNewAiming)
{
	bIsAiming = bNewAiming;
	GetWorldTimerManager().ClearTimer(AimingInterpTimerHandle); // 이전 타이머 정리

	StartSocketOffset = CameraBoom->SocketOffset;
	TargetSocketOffset = bIsAiming
		? FVector(300.f, 80.f, 50.f)
		: FVector(200.f, 80.f, 50.f);


	InterpAlpha = 0.f;
	bInterpToAiming = bIsAiming;

	GetWorldTimerManager().SetTimer
	(
		AimingInterpTimerHandle,
		this,
		&ACOECharacter::UpdateAimingInterp,
		0.01f, // 10ms 간격
		true   // 반복 실행
	);
}

void ACOECharacter::UpdateAimingInterp()
{
	InterpAlpha +=  0.05f; // 0~1로 점점 증가 (속도 조절 가능)

	FVector NewOffset = FMath::Lerp(StartSocketOffset, TargetSocketOffset, InterpAlpha);
	CameraBoom->SocketOffset = NewOffset;

	if (InterpAlpha >= 1.0f)
	{
		CameraBoom->SocketOffset = TargetSocketOffset;
		GetWorldTimerManager().ClearTimer(AimingInterpTimerHandle);
	}
}
  • 아래의 사진에 표시한 한계치들을 짧게 하여 우클릭을 누르자마자 길게누르기 판정을 줘 해결함

' > 포트폴리오' 카테고리의 다른 글

포트폴리오 log #6  (0) 2025.08.05
포트폴리오 log #5  (2) 2025.07.31
포트폴리오 log #4  (4) 2025.07.17
포트폴리오 log #2  (0) 2025.07.15
포트폴리오 log #1  (0) 2025.07.11

언리얼 C++ 근접 공격 Input 중복 문제 해결

  • 공격 Mongtage 작업 완료 후 공격 모션중 충돌 판정이 필요한 부분에 Collision 작업과 공격당한 상대에게 Damage 판정을 구현 중 이전의 코드 상태면 AnimMontage는 공격 모션이 다 끝나야 다시 시작할 수 있지만 Controller 입력과 Character 의 함수는 계속 호출 됨으로써 COECharacter에 구현할 Collision과 Damage판정에 오류가 생길 가능성을 예방하기 위해 bIsAttacking 값을 만들어 false일때만 공격하게 만들 필요성이 생김
  • 처음에는 COECharacter에서 bool bIsAttacking = false인 변수를 public으로 만들고 COEPlayerController에서 인풋이 들어오면 true로 바꾼다음 AnimMontage가 끝날때 OnAttackMontageEnded 함수를 통해 bIsAttacking = fasle로 만들어주려 했으나 계속 true값이 나오는 문제가 생겨 AnimNotify를 이용하는 방법을 찾아 아래와 같이 적용함

//COEPlayerController

// COEChar nullptr 검사와 COEChar->GetCharacter()->IsFalling()으로 공중에있는지 이미 공격 중인지 체크
if (!IsValid(COEChar) || COEChar->GetCharacterMovement()->IsFalling())
{
	UE_LOG(LogTemp, Log, TEXT("COEChar == nullptr && bIsFalling == true"));
	return;
}
// 공격중이라면 입력 X
if (COEChar->bIsAttacking)
{
	UE_LOG(LogTemp, Log, TEXT("bIsAttacking == true"));
	return;
}
// 문제없을 시 COECharacter의 DefaultAttack() 실행
COEChar->DefaultAttack();
COEChar->bIsAttacking = true;

//COEAnimInstance
	
//공격이 끝나면 bIsAttacking = false
UFUNCTION(BlueprintCallable, Category = "Animation")
void AnimNotify_End();
//공격모션 중 타격 타이밍에 Collision 생성
UFUNCTION(BlueprintCallable, Category = "Animation")
void AnimNotify_DoDefaultAttack();
	
void UCOEAnimInstance::AnimNotify_End()
{
	Character->bIsAttacking = false;
	UE_LOG(LogTemp, Log, TEXT("bIsAttacking == false"));
}

void UCOEAnimInstance::AnimNotify_DoDefaultAttack()
{
	Character->DoDefaultAttck();
	UE_LOG(LogTemp, Log, TEXT("DoDefaultAttack"));
}

→ 위와 같은 수정으로 AttackMontage가 끝나야만 공격 Input을 받을 수 있게 변경

' > 포트폴리오' 카테고리의 다른 글

포트폴리오 log #6  (0) 2025.08.05
포트폴리오 log #5  (2) 2025.07.31
포트폴리오 log #4  (4) 2025.07.17
포트폴리오 log #3  (0) 2025.07.16
포트폴리오 log #1  (0) 2025.07.11

언리얼 C++  Character 변수명 오류 및 Cast<자료형>(GetCharacter())오류 해결과 Build 시간 단축

  • 언리얼 포트폴리오 작성 중 샘플C++파일을 수정하면서 모든 Input은 PlayerController쪽 클래스에 몰아넣고 Character클래스 쪽에는 카메라 및 AnimInstance 캐스트 정도만 생성자에 몰아 넣어둔 상태로 개발하였고 매번 빌드때마다 오래걸리는 이슈가 발생하였으나 원인을 찾지못한 상태로 진행
  • PlayerController쪽에 키보드 F클릭시 기본공격(근접)을 받아오고 거기서 Character의 DefaultAttakc() 을 호출해서 Character클래스의 DefaultAttack()을 실행 그 뒤 AnimInstance의 DefaultAttackAnim()으로 AttackAnimMontage를 실행 시켜주는 방식으로 구성
  • 그 상태에서 ACOECharacter* Character 같은 변수선언을 COEPlayerController클래스에서 사용 시 오류가 발생하는것을 발견 이전에도 COEPlayerController에서 GetPawn()과 GetCharacter()를 받을때 Character, Pawn등을 변수명으로 사용하면 문제가 되었기에 혹시나 하는 마음으로 Character는 COEChar 으로 아래와 같이 수정하고 매번 Cast해주던 것을 BeginPlay()에서 한번만 Cast해줬더니 빌드가 오래걸리는 문제와 수정전의 불필요해보이고 지저분했던 코드 문제 해결
if (ACOECharacter* Character = Cast<ACOECharacter>(GetCharacter()))
//처음엔 위 코드로 작성하여 사용할때는 Character변수명에 오류 발생

ACharacter* ControllerCharacter = this->GetCharacter(); //수정전
    if (ControllerCharacter != nullptr) 
//처럼 매번 ControllerCharacter에 this->GetCharacter()를 할당해서 사용하던 코드를  아래처럼 BeginPlay()에서 한번만 Cast해주고
void ACOEPlayerController::BeginPlay()
{
	Super::BeginPlay();

	COEChar = Cast<ACOECharacter>(GetCharacter());
}
	if (IsValid(COEChar)) //수정후
//위 코드로 수정하였더니 코드도 간결해 지고 빌드도 빨라짐
  • 추가로 오늘 작업목표중 근접공격 구현에서 아래와 같이 코드를 작성했을때 AnimInstance가 null이 되는 문제가 생겨 찾아보니 생성자에서 AnimInstance를 캐스트하면 매시가 생성전이라 제대로 작동하지 않을 수 있다고 하여 아래와 같이 BeginPlay() 에서 캐스트 해주도록 수정
void ACOECharacter::BeginPlay()
{
    Super::BeginPlay();
    //AnimInstance 캐스트
    AnimInstance = Cast<UCOEAnimInstance>(GetMesh()->GetAnimInstance());
}

void ACOECharacter::DefaultAttack()
{
    //AnimInstance가 nullptr이 아니라면 DefaultAttackAnim 실행
    if (IsValid(AnimInstance))
    {
        AnimInstance->DefaultAttackAnim();
    }
    else
    {
        UE_LOG(LogTemp, Log, TEXT("Null"));
    }
    UE_LOG(LogTemp, Log, TEXT("DefaultAttack"));
}

' > 포트폴리오' 카테고리의 다른 글

포트폴리오 log #6  (0) 2025.08.05
포트폴리오 log #5  (2) 2025.07.31
포트폴리오 log #4  (4) 2025.07.17
포트폴리오 log #3  (0) 2025.07.16
포트폴리오 log #2  (0) 2025.07.15

+ Recent posts