티스토리 뷰

🟩 오늘의 목표

  • 언리얼 엔진의 콜리전 규칙과 종류를 이해하고 프로젝트 상황에 맞는 설정을 익힌다.
  • 트레이스(Trace) 기능을 활용하여 보이지 않는 물리 판정(사격, 상호작용)을 구현하는 법을 배운다.
  • C++에서 Enhanced Input과 애니메이션 몽타주를 결합한 실전 발사 및 상호작용 로직을 구축한다.

 

🟧 1. 콜리전(Collision) 시스템의 기초


🟦 콜리전의 목적과 Enabled 4종류

콜리전은 무엇이 어떻게 부딪히는지 정의하는 규칙이며, 질의(Query)와 물리(Physics) 두 가지 목적으로 나뉜다.

  • NoCollision: 완전 무시.
  • Query Only: 트레이스나 오버랩 등 조회만 가능하며 가장 자주 쓰임.
  • Physics Only: 물리 충돌만 처리.
  • Query and Physics: 조회와 물리 둘 다 처리.

🟦 Response 구조와 프리셋

내가 무엇인지 정의하는 Object Type과 무엇을 검사할지 결정하는 Trace Channel의 구조를 이해했다. 상대가 왔을 때 Block(막음), Overlap(겹침), Ignore(무시)로 반응하며, 이 조합을 저장해둔 것이 프리셋(Preset)이다. 팀 작업에서는 프리셋을 통일해야 디버깅 시간을 줄일 수 있다.

🟦 Simple vs Complex 콜리전

박스나 캡슐 형태의 단순한 Simple 콜리전은 빠르고 효율적이지만, 메시 삼각형 기준인 Complex 콜리전은 정확한 대신 비용이 비싸다. bTraceComplex 옵션을 통해 이를 제어할 수 있다.

 

🟧 2. 트레이스(Trace) 기초 및 활용


🟦 트레이스의 종류와 방식

보이지 않는 선이나 형상을 쏴서 충돌을 질의하는 기능인 트레이스의 다양한 방식을 배웠다.

  • Single vs Multi: 첫 번째로 막힌 것만 필요한지(사격), 여러 개를 관통해야 하는지(레이저)에 따라 선택한다.
  • Shape Trace: 라인은 정밀하지만 빗나가기 쉬우므로, 아이템 줍기나 근접 상호작용처럼 UX가 중요한 곳에는 스피어(구) 형태의 트레이스가 유리하다.

🟦 Sweep 판정의 응용

Start와 End 지점을 동일하게 설정하여 Sweep을 실행하면, 이동하는 구체가 아니라 특정 지점에 딱 고정된 원(구) 판정을 만들 수 있다. 이는 캐릭터 주변의 상호작용 범위를 설정할 때 유용하다.

 

🟧 3. C++ 실전 구현 및 API 활용


🟦 라인트레이스 사격 로직 (Fire)

UKismetSystemLibrary::LineTraceSingle 함수를 사용하여 사격 로직을 구현했다. 오른손(hand_r) 소켓에서 시작하여 카메라의 전방 벡터 방향으로 트레이스를 수행하며, 자기 자신을 무시 액터에 추가하여 오류를 방지했다.

🟦 스피어 스윕 상호작용 (Interact)

GetWorld()->SweepSingleByChannel 함수를 활용해 왼손(hand_l) 위치에서 일정 범위 안의 액터를 검출하는 법을 배웠다. DrawDebugSphere를 통해 상호작용 범위를 시각화하고 히트 여부에 따라 색상을 다르게 표시했다.

 

🟧 핵심 요약 및 주의사항

  • Generate Overlap Events: 오버랩 이벤트를 사용하려면 이 옵션이 체크되어 있는지 반드시 확인해야 한다.
  • ActorsToIgnore: 발사나 상호작용 시 자기 자신의 캡슐이나 메시가 먼저 맞지 않도록 무시 리스트에 자신(this)을 꼭 추가해야 한다.
  • 성능 최적화: 정확도가 크게 중요하지 않은 경우 Complex 대신 Simple 콜리전을 사용하여 연산 비용을 아껴야 한다.
  • 디버그 시각화: Kismet Trace의 Debug 옵션이나 DrawDebugHelpers를 활용해 판정 범위를 눈으로 확인하며 작업하는 것이 효율적이다.

 

🔗 관련 리소스 및 API 

 

💻 오늘 학습한 C++ 코드 스니펫

📄 TestProjectChracter.cpp (펼치기 / 접기)

// Copyright Epic Games, Inc. All Rights Reserved. 

#include "TestProjectCharacter.h" // 이 cpp가 구현하는 캐릭터 클래스 헤더 포함
#include "Camera/CameraComponent.h" // UCameraComponent 사용(팔로우 카메라)
#include "Components/CapsuleComponent.h" // 캡슐 콜리전 컴포넌트 사용
#include "Components/SkeletalMeshComponent.h" // 스켈레탈 메쉬(손 소켓/본 위치) 사용
#include "Engine/Engine.h" // GEngine(화면 디버그 메시지) 사용
#include "GameFramework/CharacterMovementComponent.h" // 캐릭터 이동 컴포넌트 설정
#include "GameFramework/Controller.h" // 컨트롤러(회전/입력) 접근
#include "GameFramework/SpringArmComponent.h" // 스프링암(카메라 붐) 사용
#include "EnhancedInputComponent.h" // Enhanced Input 바인딩에 필요
#include "InputActionValue.h" // FInputActionValue(Value 타입) 사용
#include "Kismet/KismetSystemLibrary.h" // UKismetSystemLibrary::LineTraceSingle 사용
#include "Engine/EngineTypes.h" // UEngineTypes::ConvertToTraceType 사용(채널->TraceType 변환)
#include "CollisionShape.h" // FCollisionShape::MakeSphere 사용
#include "DrawDebugHelpers.h" // 디버그 드로잉 함수들

DEFINE_LOG_CATEGORY(LogTemplateCharacter); 

ATestProjectCharacter::ATestProjectCharacter() // 캐릭터 생성자 시작
{ // 생성자 본문 시작
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); 

	bUseControllerRotationPitch = false; // 컨트롤러 Pitch가 캐릭터 회전에 직접 적용되지 않게 함
	bUseControllerRotationYaw = false; // 컨트롤러 Yaw가 캐릭터 회전에 직접 적용되지 않게 함
	bUseControllerRotationRoll = false; // 컨트롤러 Roll이 캐릭터 회전에 직접 적용되지 않게 함

	GetCharacterMovement()->bOrientRotationToMovement = true; // 이동 방향으로 캐릭터가 회전하도록 설정
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // 회전 속도(초당 회전량)

	GetCharacterMovement()->JumpZVelocity = 500.f; // 점프 초기 수직 속도
	GetCharacterMovement()->AirControl = 0.35f; // 공중 제어(조작감)
	GetCharacterMovement()->MaxWalkSpeed = 500.f; // 최대 이동 속도
	GetCharacterMovement()->MinAnalogWalkSpeed = 20.f; // 아날로그 입력 최소 속도
	GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; // 지상 감속(브레이크)
	GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f; // 공중 감속(브레이크)

	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom")); // 카메라 붐(스프링암) 컴포넌트 생성
	CameraBoom->SetupAttachment(RootComponent); // 루트(캡슐)에 스프링암 부착
	CameraBoom->TargetArmLength = 400.0f; // 카메라와 캐릭터 사이 거리
	CameraBoom->bUsePawnControlRotation = true; // 컨트롤러 회전(시선)을 스프링암에 반영

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera")); // 실제 카메라 컴포넌트 생성
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 스프링암 끝 소켓에 카메라 부착
	FollowCamera->bUsePawnControlRotation = false; // 카메라 자체는 컨트롤 회전을 직접 쓰지 않음(스프링암이 회전)
} // 생성자 끝

void ATestProjectCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) // 입력 바인딩 함수(엔진이 호출)
{ // 함수 본문 시작
	Super::SetupPlayerInputComponent(PlayerInputComponent); // 부모(기본 캐릭터) 입력 설정도 수행

	if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) // EnhancedInputComponent인지 캐스팅
	{ // Enhanced Input 사용 가능할 때
		// 이동/카메라회전/점프 바인딩은 요청에 따라 주석 없이 유지 // 원문 코멘트(요청사항)
		if (JumpAction) // JumpAction이 BP에서 지정되어 있는지 확인
		{ // 점프 바인딩 시작
			EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ATestProjectCharacter::DoJumpStart); // 점프 시작(눌림)
			EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ATestProjectCharacter::DoJumpEnd); // 점프 종료(뗌)
		} // 점프 바인딩 끝
		if (MoveAction) // MoveAction이 지정되어 있는지 확인
		{ // 이동 바인딩 시작
			EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ATestProjectCharacter::Move); // 이동 입력(축값) 지속 처리
		} // 이동 바인딩 끝
		if (LookAction) // LookAction이 지정되어 있는지 확인
		{ // 시야 회전 바인딩 시작
			EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ATestProjectCharacter::Look); // 룩 입력 처리
		} // 시야 회전 바인딩 끝
		if (MouseLookAction) // MouseLookAction이 지정되어 있는지 확인
		{ // 마우스 룩 바인딩 시작
			EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ATestProjectCharacter::Look); // 룩 입력 처리(같은 함수 재사용)
		} // 마우스 룩 바인딩 끝

		// 조준(Aim): 우클릭 홀드 구현을 위해 Started/Completed 사용 // Aim 입력 처리 방식 설명
		// Started  : 버튼 눌렀을 때 1회 호출 -> 조준 시작 // 눌렀을 때
		// Completed: 버튼 뗐을 때 1회 호출 -> 조준 종료 // 뗐을 때
		if (AimAction) // AimAction이 지정되어 있는지 확인
		{ // 조준 바인딩 시작
			EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &ATestProjectCharacter::AimStarted); // 우클릭 눌림 이벤트
			EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &ATestProjectCharacter::AimCompleted); // 우클릭 뗌 이벤트
		} // 조준 바인딩 끝

		// 발사(Fire): 클릭 1회 발사 -> Started 사용 // 발사 입력 처리 설명
		if (FireAction) // FireAction이 지정되어 있는지 확인
		{ // 발사 바인딩 시작
			EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &ATestProjectCharacter::Fire); // 좌클릭 눌림 시 발사
		} // 발사 바인딩 끝

		// 상호작용(Interact): E키 1회 -> Started 사용 // 상호작용 입력 처리 설명
		if (InteractAction) // InteractAction이 지정되어 있는지 확인
		{ // 상호작용 바인딩 시작
			EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ATestProjectCharacter::Interact); // E키 눌림 시 상호작용
		} // 상호작용 바인딩 끝
	} // EnhancedInputComponent 성공 분기 끝
	else // EnhancedInputComponent가 아닐 때
	{ // 오류 처리 시작
		// 인코딩 문제 방지: 메시지는 영어(ASCII)로 유지 // 문자열 깨짐 방지 목적
		UE_LOG(LogTemplateCharacter, Error, TEXT("'%s': Failed to find EnhancedInputComponent."), *GetNameSafe(this)); // 로그 출력
	} // 오류 처리 끝
} // SetupPlayerInputComponent 끝

void ATestProjectCharacter::Move(const FInputActionValue& Value) // 이동 입력 콜백
{ // 함수 본문 시작
	const FVector2D MovementVector = Value.Get<FVector2D>(); // 입력 값을 Vector2D로 꺼냄(X=Right, Y=Forward)
	DoMove(MovementVector.X, MovementVector.Y); // 실제 이동 로직 함수 호출
} // Move 끝

void ATestProjectCharacter::Look(const FInputActionValue& Value) // 시야 회전 입력 콜백
{ // 함수 본문 시작
	const FVector2D LookAxisVector = Value.Get<FVector2D>(); // 입력 값을 Vector2D로 꺼냄(X=Yaw, Y=Pitch)
	DoLook(LookAxisVector.X, LookAxisVector.Y); // 실제 룩 로직 함수 호출
} // Look 끝

void ATestProjectCharacter::AimStarted(const FInputActionValue& Value) // 조준 시작 입력 콜백(우클릭 눌림)
{ // 함수 본문 시작
	DoAimStart(); // 조준 시작 로직 실행
} // AimStarted 끝

void ATestProjectCharacter::AimCompleted(const FInputActionValue& Value) // 조준 종료 입력 콜백(우클릭 뗌)
{ // 함수 본문 시작
	DoAimEnd(); // 조준 종료 로직 실행
} // AimCompleted 끝

void ATestProjectCharacter::Fire(const FInputActionValue& Value) // 발사 입력 콜백(좌클릭)
{ // 함수 본문 시작
	DoFire(); // 발사 로직 실행
} // Fire 끝

void ATestProjectCharacter::Interact(const FInputActionValue& Value) // 상호작용 입력 콜백(E키)
{ // 함수 본문 시작
	DoInteract(); // 상호작용 로직 실행
} // Interact 끝

void ATestProjectCharacter::DoMove(float Right, float Forward) // 실제 이동 로직
{ // 함수 본문 시작
	if (!GetController()) // 컨트롤러가 없으면 이동 불가(안전)
	{ // 예외 처리 시작
		return; // 함수 종료
	} // 예외 처리 끝

	const FRotator Rotation = GetController()->GetControlRotation(); // 현재 컨트롤러 회전(카메라 기준)
	const FRotator YawRotation(0.f, Rotation.Yaw, 0.f); // Yaw만 사용(수평 이동)

	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); // 전방 벡터
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); // 우측 벡터

	AddMovementInput(ForwardDirection, Forward); // 전/후 입력 반영
	AddMovementInput(RightDirection, Right); // 좌/우 입력 반영
} // DoMove 끝

void ATestProjectCharacter::DoLook(float Yaw, float Pitch) // 실제 시야 회전 로직
{ // 함수 본문 시작
	if (!GetController()) // 컨트롤러가 없으면 회전 불가(안전)
	{ // 예외 처리 시작
		return; // 함수 종료
	} // 예외 처리 끝

	AddControllerYawInput(Yaw); // Yaw(좌/우) 회전 입력 누적
	AddControllerPitchInput(Pitch); // Pitch(상/하) 회전 입력 누적
} // DoLook 끝

void ATestProjectCharacter::DoAimStart() // 조준 시작 로직(몽타지 포함)
{ // 함수 본문 시작
	// 조준 상태 ON // 조준 플래그 켬
	// bIsAiming은 이후에 FOV 축소, 이동속도 감소, 탄퍼짐 감소 같은 로직의 기준값으로 사용 가능 // 확장 포인트 설명
	bIsAiming = true; // 조준 상태 저장

	// 조준 몽타지 재생(캐릭터 BP에서 AimMontage를 할당해야 실제 재생됨) // 에셋 할당 필요
	if (AimMontage) // 조준 몽타지가 지정되어 있는지 확인
	{ 
		PlayAnimMontage(AimMontage); // 조준 몽타지 재생
	} 
} // DoAimStart 끝

void ATestProjectCharacter::DoAimEnd() // 조준 종료 로직(몽타지 포함)
{ // 함수 본문 시작
	// 조준 상태 OFF // 조준 플래그 끔
	bIsAiming = false; // 조준 상태 저장

	if (AimMontage) // 조준 몽타지가 지정되어 있는지 확인
	{ 
		StopAnimMontage(AimMontage); // 해당 몽타지만 정지
	} 
} // DoAimEnd 끝

void ATestProjectCharacter::DoFire() // 발사 로직(몽타지 + 라인트레이스)
{ // 함수 본문 시작
	// 1) 발사 몽타지 재생(연출) // 발사 애니메이션 연출
	if (FireMontage) // 발사 몽타지가 지정되어 있는지 확인
	{ // 몽타지 재생 시작
		PlayAnimMontage(FireMontage); // 발사 몽타지 재생
	} // 몽타지 재생 끝

	// 2) 발사 Start 지점: 오른손(hand_r) 소켓/본에서 가져오기 // 총구(손)에서 발사 시작점 확보
	USkeletalMeshComponent* MeshComp = GetMesh(); // 캐릭터의 스켈레탈 메쉬 가져오기
	if (!MeshComp || !GetWorld()) // 메쉬 또는 월드가 없으면 진행 불가
	{ 
		return; 
	} 

	FVector Start = FVector::ZeroVector; // Start 초기값(안전하게 0으로 초기화)

	if (MeshComp->DoesSocketExist(RightHandSocketName)) // RightHandSocketName 이름의 소켓이 존재하는지 확인
	{ 
		Start = MeshComp->GetSocketLocation(RightHandSocketName); // 소켓 월드 위치를 Start로 사용
	} 
	else // 소켓이 없으면 본 이름으로 시도
	{ 
		const int32 BoneIndex = MeshComp->GetBoneIndex(RightHandSocketName); // 본 인덱스 조회
		if (BoneIndex != INDEX_NONE) // 본이 존재하면
		{ 
			Start = MeshComp->GetBoneLocation(RightHandSocketName); // 본 월드 위치를 Start로 사용
		} 
		else // 소켓/본 모두 못 찾은 경우
		{ 
			Start = GetActorLocation(); // 캐릭터 위치를 Start로 사용
		} 
	} 

	FVector Dir = GetActorForwardVector(); // 기본값: 캐릭터 전방
	if (FollowCamera) // 카메라가 있으면
	{ 
		Dir = FollowCamera->GetForwardVector(); // 카메라 전방 벡터를 발사 방향으로 사용
	} 
	else if (Controller) // 카메라가 없고 컨트롤러가 있으면
	{ 
		Dir = Controller->GetControlRotation().Vector(); // 컨트롤러 시선 방향 벡터를 사용
	} 

	const FVector End = Start + Dir * FireRange; // Start에서 FireRange 만큼 전방으로 쏜 End 계산

	const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(WeaponTraceChannel); // 채널을 TraceTypeQuery로 변환(Kismet 함수 요구)

	TArray<AActor*> ActorsToIgnore; // 무시할 액터 배열 생성
	ActorsToIgnore.Add(this); // 자기 자신 무시(손/캡슐 먼저 맞는 문제 방지)

	const EDrawDebugTrace::Type DebugType = bDrawFireDebug ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::None; // 디버그 라인 표시 여부 선택

	FHitResult Hit; // 히트 결과를 받을 구조체

	const bool bHit = UKismetSystemLibrary::LineTraceSingle( // 라인트레이스 수행 결과(bool) 저장
		GetWorld(), // 월드 컨텍스트
		Start, // 시작점
		End, // 끝점
		TraceType, // 트레이스 타입(채널 변환값)
		true,                   // bTraceComplex: 정확한 메시 판정이 필요하면 true, 성능 우선이면 false도 고려
		ActorsToIgnore, // 무시할 액터 리스트
		DebugType,            // 디버그 라인(선) 표시를 엔진이 자동으로 처리
		Hit, // 히트 결과 출력
		true,                   // bIgnoreSelf: true 권장(ActorsToIgnore와 함께 안전)
		FLinearColor::Red,    // TraceColor(디버그용)
		FLinearColor::Green,  // TraceHitColor(디버그용)
		FireDebugDuration // 디버그 표시 시간
	); // 라인트레이스 호출 끝

	if (bHit && Hit.GetActor()) // 히트했고 액터가 존재하면
	{ // 히트 처리 시작
		const FString HitName = Hit.GetActor()->GetName(); // 맞은 액터 이름 가져오기

		UE_LOG(LogTemplateCharacter, Log, TEXT("Fire Hit: %s"), *HitName); // 로그로 히트 이름 출력

		if (GEngine) // 화면 디버그 메시지를 사용할 수 있으면
		{ 
			GEngine->AddOnScreenDebugMessage( // 화면에 디버그 메시지 출력
				-1, 1.5f, FColor::Cyan, // Key=-1(새 메시지), Duration=1.5초, 색상=Cyan
				FString::Printf(TEXT("Fire Hit: %s"), *HitName) // 표시할 문자열(히트 이름)
			); 
		} // 화면 출력 끝

	} // 히트 처리 끝
} // DoFire 끝

void ATestProjectCharacter::DoInteract() // 상호작용 로직(몽타지 + “딱 원” 판정)
{ // 함수 본문 시작
	if (InteractMontage) // 몽타지가 지정되어 있는지 확인
	{ 
		PlayAnimMontage(InteractMontage); // 상호작용 몽타지 재생
	} 

	USkeletalMeshComponent* MeshComp = GetMesh(); // 스켈레탈 메쉬 가져오기(왼손 위치 필요)
	if (!MeshComp || !GetWorld()) // 메쉬 또는 월드가 없으면 진행 불가
	{ // 예외 처리 시작
		return; // 함수 종료
	} // 예외 처리 끝

	FVector HandStart = FVector::ZeroVector; // 왼손 시작 위치 초기화

	if (MeshComp->DoesSocketExist(LeftHandSocketName)) // 소켓이 있으면 소켓을 우선 사용
	{ // 소켓 사용 분기
		HandStart = MeshComp->GetSocketLocation(LeftHandSocketName); // 왼손 소켓 위치
	} // 소켓 분기 끝
	else // 소켓이 없으면 본으로 시도
	{ // 본 사용 분기 시작
		const int32 BoneIndex = MeshComp->GetBoneIndex(LeftHandSocketName); // 본 인덱스 조회
		if (BoneIndex != INDEX_NONE) // 본이 존재하면
		{ // 본 위치 사용
			HandStart = MeshComp->GetBoneLocation(LeftHandSocketName); // 왼손 본 위치
		} // 본 위치 사용 끝
		else // 소켓/본 모두 못 찾으면
		{ // fallback 시작
			HandStart = GetActorLocation(); // 캐릭터 위치로 대체(디버그 목적)
		} // fallback 끝
	} // 본 사용 분기 끝

	// 3) 방향: 카메라 전방 우선(바라보는 곳과 상호작용 일치) // 시선과 상호작용을 맞추기 위함
	FVector Dir = GetActorForwardVector(); // 기본값: 캐릭터 전방
	if (FollowCamera) // 카메라가 있으면
	{ // 카메라 전방 사용
		Dir = FollowCamera->GetForwardVector(); // 카메라 전방을 상호작용 방향으로 사용
	} // 카메라 분기 끝
	else if (Controller) // 카메라가 없고 컨트롤러가 있으면
	{ // 컨트롤러 회전 사용
		Dir = Controller->GetControlRotation().Vector(); // 컨트롤러 시선 방향 사용
	} // 컨트롤러 분기 끝

	const FVector Center = HandStart + Dir * InteractRange; // 현재는 손에서 InteractRange만큼 앞으로 떨어진 곳에 원(구) 판정(멀리 나가는 원인)

	FCollisionQueryParams Params(SCENE_QUERY_STAT(TestProject_InteractSpherePoint), true, this); // 스윕 쿼리 파라미터 생성(bTraceComplex=true)
	Params.AddIgnoredActor(this); // 자기 자신 무시(캡슐/자기 메시 히트 방지)

	const FCollisionShape Sphere = FCollisionShape::MakeSphere(InteractSphereRadius); // 반지름 InteractSphereRadius의 구 충돌 모양 생성

	FHitResult Hit; // 히트 결과 구조체

	const bool bHit = GetWorld()->SweepSingleByChannel( // “구를 굴리는 스윕”을 단일 히트로 수행
		Hit, // 결과(히트 정보)
		Center,                  // Start
		Center,                  // End를 동일하게(스윕 길이 0 -> “딱 원/구” 판정)
		FQuat::Identity, // 구는 회전 영향 없으므로 Identity
		InteractTraceChannel, // 상호작용 트레이스 채널
		Sphere, // 구 모양
		Params // 쿼리 파라미터(무시 액터/복잡도 등)
	); // 스윕 호출 끝

	// 5) 디버그: 선을 안 그리고 구만 표시해서 “원(구)”만 보이게 한다. // 디버그 시각화
	if (bDrawInteractDebug) // 디버그 표시 옵션이 켜져 있으면
	{ // 디버그 드로우 시작
		DrawDebugSphere( // 월드에 구를 그린다
			GetWorld(), // 월드
			Center, // 구 중심
			InteractSphereRadius, // 반지름
			16, // 세그먼트 수(클수록 매끈하지만 비용 증가)
			bHit ? FColor::Green : FColor::Red, // 맞았으면 초록, 아니면 빨강
			false, // PersistentLines=false(영구 표시 X)
			InteractDebugDuration // 표시 시간(초)
		); // 구 디버그 드로우 끝

		if (bHit) // 맞았으면
		{ // 히트 포인트 표시 시작
			DrawDebugPoint(GetWorld(), Hit.ImpactPoint, 12.0f, FColor::Yellow, false, InteractDebugDuration); // 충돌 지점에 점 표시
		} // 히트 포인트 표시 끝
	} // 디버그 드로우 끝

	if (bHit && Hit.GetActor()) // 히트했고 액터가 존재하면
	{ // 히트 처리 시작
		AActor* HitActor = Hit.GetActor(); // 맞은 액터 포인터 얻기
		if (IsValid(HitActor) && HitActor != this) // 유효한 액터이고 자기 자신이 아니면
		{ // 액터 처리 시작
			const FString HitName = HitActor->GetName(); // 맞은 액터 이름 얻기

			UE_LOG(LogTemplateCharacter, Log, TEXT("Interact Hit: %s"), *HitName); // 로그 출력

			if (GEngine) // 화면 메시지 가능하면
			{ // 화면 출력 시작
				GEngine->AddOnScreenDebugMessage( // 화면 메시지 출력
					-1, 2.0f, FColor::Cyan, // Key=-1(새 메시지), 2초, 청록색
					FString::Printf(TEXT("Interact Hit: %s"), *HitName) // 출력 문자열
				); // 화면 출력 호출 끝
			} // 화면 출력 끝

			//HitActor->Destroy(); // 현재 주석 처리: 실행하면 맞은 액터를 즉시 삭제
		} // 액터 처리 끝
	} // 히트 처리 끝
} // DoInteract 끝

void ATestProjectCharacter::DoJumpStart() // 점프 시작 로직
{ // 함수 본문 시작
	Jump(); // ACharacter의 Jump 호출
} // DoJumpStart 끝

void ATestProjectCharacter::DoJumpEnd() // 점프 종료 로직
{ // 함수 본문 시작
	StopJumping(); // ACharacter의 StopJumping 호출
} // DoJumpEnd 끝
        
📑 TestProjectChracter.h (펼치기 / 접기)

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Logging/LogMacros.h"
#include "Engine/EngineTypes.h" // ECollisionChannel
#include "TestProjectCharacter.generated.h"

class USpringArmComponent;
class UCameraComponent;
class UInputAction;
class UAnimMontage;
struct FInputActionValue;

DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);

UCLASS(abstract)
class ATestProjectCharacter : public ACharacter
{
	GENERATED_BODY()

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
	USpringArmComponent* CameraBoom;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
	UCameraComponent* FollowCamera;

protected:
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* JumpAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* MoveAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* LookAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* MouseLookAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* AimAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* FireAction;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* InteractAction;

	UPROPERTY(EditAnywhere, Category = "Montage")
	UAnimMontage* AimMontage;

	UPROPERTY(EditAnywhere, Category = "Montage")
	UAnimMontage* FireMontage;

	UPROPERTY(EditAnywhere, Category = "Montage")
	UAnimMontage* InteractMontage;

	UPROPERTY(VisibleInstanceOnly, Category = "Aim")
	bool bIsAiming = false;

	UPROPERTY(EditAnywhere, Category = "Weapon")
	float FireRange = 10000.0f;

	UPROPERTY(EditAnywhere, Category = "Weapon")
	TEnumAsByte<ECollisionChannel> WeaponTraceChannel = ECC_Visibility;

	UPROPERTY(EditAnywhere, Category = "Weapon")
	FName RightHandSocketName = TEXT("hand_r");

	UPROPERTY(EditAnywhere, Category = "Weapon")
	bool bDrawFireDebug = true;

	UPROPERTY(EditAnywhere, Category = "Weapon")
	float FireDebugDuration = 1.0f;

	UPROPERTY(EditAnywhere, Category = "Interact")
	float InteractRange = 5.0f;

	UPROPERTY(EditAnywhere, Category = "Interact")
	FName LeftHandSocketName = TEXT("hand_l");

	UPROPERTY(EditAnywhere, Category = "Interact")
	TEnumAsByte<ECollisionChannel> InteractTraceChannel = ECC_Visibility;

	UPROPERTY(EditAnywhere, Category = "Interact")
	float InteractSphereRadius = 25.0f;

	UPROPERTY(EditAnywhere, Category = "Interact")
	bool bDrawInteractDebug = true;

	UPROPERTY(EditAnywhere, Category = "Interact")
	float InteractDebugDuration = 1.0f;

public:
	ATestProjectCharacter();

protected:
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
	void AimStarted(const FInputActionValue& Value);
	void AimCompleted(const FInputActionValue& Value);
	void Fire(const FInputActionValue& Value);
	void Interact(const FInputActionValue& Value);

public:
	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoMove(float Right, float Forward);

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoLook(float Yaw, float Pitch);

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoAimStart();

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoAimEnd();

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoFire();

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoInteract();

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoJumpStart();

	UFUNCTION(BlueprintCallable, Category = "Input")
	virtual void DoJumpEnd();

public:
	FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
	FORCEINLINE UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
        

'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글

TIL - 40일차  (0) 2026.01.23
TIL - 39일차  (0) 2026.01.22
TIL - 37일차  (1) 2026.01.20
TIL - 36일차  (1) 2026.01.19
TIL - 35일차  (1) 2026.01.16