본문 바로가기

인생언리얼TPS

3.1-7 스나이퍼모드 C++

 

우리는 이전에 UserWidget을 상속받은 WBP_sniperUI를 만들었습니다. 이걸 C++에서 처리하려면 build.cs에 UMG 모듈을 추가하고 리빌드 해야지만 간단한 로딩정도는 다음과 같이 피해갈수 있습니다.

Userwidget을 상속받아 MyUserWidget C++클래스를 만듭니다. 이클래스를 UserWidget 대신 상속시킬겁니다.

2개의 파일이 만들어지지만 현재는 아무것도 하지 않습니다.

UMyUserWidget 클래스가 생성되었습니다. 이걸 나중에 사용합니다.

UCLASS()
class TPSPROJECT_API UMyUserWidget : public UUserWidget
{
	GENERATED_BODY()
	
};

WBP_sniperUI를 열고 우상을 보면  Parent class가 UserWidget입니다. 이걸 방금만든 MyUserWidget으로 변경합니다.

우산 Class Settings를 클릭해보면 

Details패널에서 Parent Class풀다운메뉴에서 MyUserWidget을 선택해서 ReParent해줍니다. 저장해줍니다.

WidgetBlue프린트를 하나더 만듭니다. 이번에는 MyUserWidget을 선택합니다. 이름은 WBP_Crosshair로 고쳐줍니다.

아까와 비슷하게 하고 Image Soruce는 Crosshair입니다. Tint를 빨간색으로 변경합니다.

앞에서만든 UMyUserWidget클래스를 사용하여 변수를 만들어 줍니다.

TPSPlayer.h

	public:
    // 스나이퍼 조준
	void SniperAim();
	// 스나이퍼 조준 중인지 여부
	bool bSniperAim = false;
    
	// 스나이퍼 UI 위젯 공장
	UPROPERTY(EditDefaultsOnly, Category = Widget)
	TSubclassOf<class UMyUserWidget> sniperUIFactory;
	// 스나이퍼 UI 위젯 인스턴스
	class UMyUserWidget* _sniperUI;
	// 일반 조준 크로스헤어UI 위젯
	UPROPERTY(EditDefaultsOnly, Category = Widget)
	TSubclassOf<class UMyUserWidget> crosshairUIFactory;
	// 크로스헤어 인스턴스
	class UMyUserWidget* _crosshairUI;

	// 총알 파편 효과 공장
	UPROPERTY(EditAnywhere, Category = BulletEffect)
	class UParticleSystem* bulletEffectFactory;

CreateWidget()은 UUserWdiget 타입을 리턴하기에  교재의 코드에 Cast< UMyUserWidget >()로 캐스팅을 해줘야 합니다.

_sniperUI = Cast< UMyUserWidget>(CreateWidget(GetWorld(), sniperUIFactory));

 

TPSPlayer.cpp

void ATPSPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
    //... 추가
		EnhancedInputComponent->BindAction(IA_1, ETriggerEvent::Triggered, this, 
        	&ATPSPlayer::ChangeToGrenadeGun);
		EnhancedInputComponent->BindAction(IA_2, ETriggerEvent::Triggered, this, 
        	&ATPSPlayer::ChangeToSniperGun);
	}
}
// Called when the game starts or when spawned
void ATPSPlayer::BeginPlay()
{
	Super::BeginPlay();
	//... 생략
	// 1. 스나이퍼 UI 위젯 인스턴스 생성
	_sniperUI = Cast< UMyUserWidget>(CreateWidget(GetWorld(), sniperUIFactory));
	// 2. 일반 조준 UI 크로스헤어 인스턴스 생성
	_crosshairUI = Cast< UMyUserWidget>(CreateWidget(GetWorld(), crosshairUIFactory));
	// 3. 일반 조준 UI 등록
	_crosshairUI->AddToViewport();

	// 기본으로 스나이퍼건을 사용하도록 설정
	ChangeToSniperGun();
}
// 유탄총으로 변경
void ATPSPlayer::ChangeToGrenadeGun()
{
	// 유탄총 사용 중으로 체크
	bUsingGrenadeGun = true;
	sniperGunComp->SetVisibility(false);
	gunMeshComp->SetVisibility(true);
}
// 스나이퍼건으로 변경
void ATPSPlayer::ChangeToSniperGun()
{
	bUsingGrenadeGun = false;
	sniperGunComp->SetVisibility(true);
	gunMeshComp->SetVisibility(false);
}

void ATPSPlayer::SniperAim()
{
	// 스나이퍼건 모드가 아니라면 처리하지 않는다.
	if (bUsingGrenadeGun)
	{
		return;
	}
	// Pressed 입력 처리
	if (bSniperAim == false)
	{
		// 1. 스나이퍼 조준 모드 활성화
		bSniperAim = true;
		// 2. 스나이퍼조준 UI 등록
		//_sniperUI->AddToViewport();
		// 3. 카메라의 시야각 Field Of View 설정
		tpsCamComp->SetFieldOfView(45.0f);
		// 4. 일반 조준 UI 제거
		//_crosshairUI->RemoveFromParent();
	}
	// Released 입력 처리
	else
	{
		// 1. 스나이퍼 조준 모드 비활성화
		bSniperAim = false;
		// 2. 스나이퍼 조준 UI 화면에서 제거
		_sniperUI->RemoveFromParent();
		// 3. 카메라 시야각 원래대로 복원
		tpsCamComp->SetFieldOfView(90.0f);
		// 4. 일반 조준 UI 등록
		_crosshairUI->AddToViewport();
	}
}

이제 빌드하고 BP_TPSPlayer에서 widget을 검색해 UI를 연결해 줍니다. IA_Aim도 연결해줍니다. 이걸 안하고 플레이하면  포인트체크를 안해서 언리얼이 죽습니다. 오동작을 막기위해 아까 연결한 이벤트그래프도 다 지워주 안나타나면 블루프린트를 컴파일해줍니다.

이제 스나이퍼 모드를 켤 IA_Aim Input Action을 bool로 만들고 IMC에 추가해서 Left Ctrl을 배정해줍니다.

TPSPlayer.h

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

다른키처럼 Triggered로 설정하면 계속 Zoom을 반복합니다. 이걸 방지하기 위해 Left Ctrl이 눌렸을때 떨어졌을때만 반응해야합니다. 따라서 Started, Completed를 사용합니다.

TPSPlayer.cpp

EnhancedInputComponent->BindAction(IA_Aim, ETriggerEvent::Started, this, &ATPSPlayer::SniperAim);
EnhancedInputComponent->BindAction(IA_Aim, ETriggerEvent::Completed, this, &ATPSPlayer::SniperAim);

 

다음은 전체코드입니다.

TPSPlayer.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "TPSPlayer.generated.h"

UCLASS()
class TPSPROJECT_API ATPSPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ATPSPlayer();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
public:
	UPROPERTY(VisibleAnywhere, Category = Camera)
	class USpringArmComponent* springArmComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
	class UCameraComponent* tpsCamComp;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputMappingContext* PlayerMappingContext;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* MoveIA;
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* LookUpIA;
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* JumpIA;
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* FireIA;
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* IA_1;
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* IA_2;
	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* IA_Aim;
public:
	void Move(const FInputActionValue& Value);
	void LookUp(const FInputActionValue& Value);
	//void Turn(const FInputActionValue& Value);
	void InputJump(const FInputActionValue& Value);
	void InputFire(const FInputActionValue& Value); 	// 총알 발사 처리 함수

	// 총 스켈레탈메시
	UPROPERTY(VisibleAnywhere, Category = GunMesh)
	class USkeletalMeshComponent* gunMeshComp;

	// 총알 공장
	UPROPERTY(EditDefaultsOnly, Category = BulletFactory)
	TSubclassOf<class ABullet> bulletFactory;

	// 스나이퍼건 스태틱메시 추가
	UPROPERTY(VisibleAnywhere, Category = GunMesh)
	class UStaticMeshComponent* sniperGunComp;

	// 유탄총 사용 중인지 여부
	bool bUsingGrenadeGun = true;
	// 유탄총으로 변경
	void ChangeToGrenadeGun();
	// 스나이퍼건으로 변경
	void ChangeToSniperGun();

	// 스나이퍼 조준
	void SniperAim();
	// 스나이퍼 조준 중인지 여부
	bool bSniperAim = false;

	// 스나이퍼 UI 위젯 공장
	UPROPERTY(EditDefaultsOnly, Category = Widget)
	TSubclassOf<class UMyUserWidget> sniperUIFactory;
	// 스나이퍼 UI 위젯 인스턴스
	class UMyUserWidget* _sniperUI;
	// 일반 조준 크로스헤어UI 위젯
	UPROPERTY(EditDefaultsOnly, Category = Widget)
	TSubclassOf<class UMyUserWidget> crosshairUIFactory;
	// 크로스헤어 인스턴스
	class UMyUserWidget* _crosshairUI;

	// 총알 파편 효과 공장
	UPROPERTY(EditAnywhere, Category = BulletEffect)
	class UParticleSystem* bulletEffectFactory;


private:
	void Locomotion();

	FVector moveDirection;
	bool fireReady;
	float fireTimerTime;
};

TPSPlayr.cpp

// Fill out your copyright notice in the Description page of Project Settings.
#include "TPSPlayer.h"
#include <GameFramework/SpringArmComponent.h>
#include <Camera/CameraComponent.h>
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "Bullet.h"
#include "MyUserWidget.h"
#include <Kismet/GameplayStatics.h>
#include "TPSProject.h"

// Sets default values
ATPSPlayer::ATPSPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	// 1. 스켈레탈메시 데이터를 불러오고 싶다.
	ConstructorHelpers::FObjectFinder<USkeletalMesh> tempMesh(TEXT("SkeletalMesh'/Game/Characters/Mannequins/Meshes/SKM_Manny.SKM_Manny'"));
	if (tempMesh.Succeeded()) {
		GetMesh()->SetSkeletalMesh(tempMesh.Object);
		// 2. Mesh 컴포넌트의 위치와 회전 값을 설정하고 싶다.
		GetMesh()->SetRelativeLocationAndRotation(FVector(0, 0, -90), FRotator(0, -90, 0));
	}
	// 3. TPS 카메라를 붙이고 싶다.
// 3-1. SpringArm 컴포넌트 붙이기
	springArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
	springArmComp->SetupAttachment(RootComponent);
	springArmComp->SetRelativeLocation(FVector(0, 70, 90));
	springArmComp->TargetArmLength = 400;
	springArmComp->bUsePawnControlRotation = true;
	// 3-2. Camera 컴포넌트 붙이기
	tpsCamComp = CreateDefaultSubobject<UCameraComponent>(TEXT("TpsCamComp"));
	tpsCamComp->SetupAttachment(springArmComp);
	tpsCamComp->bUsePawnControlRotation = false;

	bUseControllerRotationYaw = true;
	// 2단 점프
	JumpMaxCount = 2;
	// 4. 총 스켈레탈메시 컴포넌트 등록
	gunMeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("GunMeshComp"));
	// 4-1. 부모 컴포넌트를 Mesh 컴포넌트로 설정
	gunMeshComp->SetupAttachment(GetMesh());
	// 4-2. 스켈레탈메시 데이터 로드
	ConstructorHelpers::FObjectFinder<USkeletalMesh> TempGunMesh(TEXT("SkeletalMesh'/Game/FPWeapon/Mesh/SK_FPGun.SK_FPGun'"));
	// 4-3. 데이터로드가 성공했다면
	if (TempGunMesh.Succeeded())
	{
		// 4-4. 스켈레탈메시 데이터 할당
		gunMeshComp->SetSkeletalMesh(TempGunMesh.Object);
		// 4-5. 위치 조정하기
		gunMeshComp->SetRelativeLocation(FVector(-14, 52, 120));
	}

	// 5. 스나이퍼건 컴포넌트 등록
	sniperGunComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SniperGunComp"));
	// 5-1. 부모 컴포넌트를 Mesh 컴포넌트로 설정
	sniperGunComp->SetupAttachment(GetMesh());
	// 5-2. 스태틱메시 데이터 로드
	ConstructorHelpers::FObjectFinder<UStaticMesh> TempSniperMesh(TEXT("StaticMesh'/Game/SniperGun/sniper1.sniper1'"));
	// 5-3. 데이터로드가 성공했다면
	if (TempSniperMesh.Succeeded())
	{
		// 5-4. 스태틱메시 데이터 할당
		sniperGunComp->SetStaticMesh(TempSniperMesh.Object);
		// 5-5. 위치 조정하기
		sniperGunComp->SetRelativeLocation(FVector(-22, 55, 120));
		// 5-6. 크기 조정하기
		sniperGunComp->SetRelativeScale3D(FVector(0.15f));
	}
}

// Called when the game starts or when spawned
void ATPSPlayer::BeginPlay()
{
	Super::BeginPlay();
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem
			= ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(PlayerMappingContext, 0);
		}
	}
	// 1. 스나이퍼 UI 위젯 인스턴스 생성, UMyWidget으로 캐스팅해줘야함
	_sniperUI = Cast< UMyUserWidget>(CreateWidget(GetWorld(), sniperUIFactory));
	// 2. 일반 조준 UI 크로스헤어 인스턴스 생성,  UMyWidget으로 캐스팅해줘야함
	_crosshairUI = Cast< UMyUserWidget>(CreateWidget(GetWorld(), crosshairUIFactory));
	// 3. 일반 조준 UI 등록
	_crosshairUI->AddToViewport();

	// 기본으로 스나이퍼건을 사용하도록 설정
	ChangeToSniperGun();
}

// Called every frame
void ATPSPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	Locomotion();
}


// Called to bind functionality to input
void ATPSPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &ATPSPlayer::Move);
		EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &ATPSPlayer::LookUp);
		EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &ATPSPlayer::InputJump);
		EnhancedInputComponent->BindAction(FireIA, ETriggerEvent::Triggered, this, &ATPSPlayer::InputFire);
		EnhancedInputComponent->BindAction(IA_1, ETriggerEvent::Triggered, this, &ATPSPlayer::ChangeToGrenadeGun);
		EnhancedInputComponent->BindAction(IA_2, ETriggerEvent::Triggered, this, &ATPSPlayer::ChangeToSniperGun);
		EnhancedInputComponent->BindAction(IA_Aim, ETriggerEvent::Started, this, &ATPSPlayer::SniperAim);
		EnhancedInputComponent->BindAction(IA_Aim, ETriggerEvent::Completed, this, &ATPSPlayer::SniperAim);
	}
}
void ATPSPlayer::Locomotion()
{
	moveDirection = FTransform(GetControlRotation()).TransformVector(moveDirection);
	AddMovementInput(moveDirection);
	moveDirection = FVector::ZeroVector; //ZeroVector; == FVector(0,0,0)
}
void ATPSPlayer::Move(const FInputActionValue& Value)
{
	const FVector _currentValue = Value.Get<FVector>();
	//PRINT_LOG(TEXT("%f %f %f"), _currentValue.X, _currentValue.Y, _currentValue.Z);
	if (Controller)
	{
		moveDirection.Y = _currentValue.X;
		moveDirection.X = _currentValue.Y;
	}
}

void ATPSPlayer::LookUp(const FInputActionValue& Value)
{
	const FVector _currentValue = Value.Get<FVector>();
	//PRINT_LOG(TEXT("%f %f %f"), _currentValue.X, _currentValue.Y, _currentValue.Z);
	AddControllerPitchInput(_currentValue.Y);
	AddControllerYawInput(_currentValue.X);
}


void ATPSPlayer::InputJump(const FInputActionValue& Value)
{
	Jump();
}

void ATPSPlayer::InputFire(const FInputActionValue& Value)
{
	if (bUsingGrenadeGun)
	{
		// 총알 발사 처리
		FTransform firePosition = gunMeshComp->GetSocketTransform(TEXT("FirePosition"));
		GetWorld()->SpawnActor<ABullet>(bulletFactory, firePosition);
	}
	// 스나이퍼건 사용 시
	else
	{
		// LineTrace 의 시작 위치
		FVector startPos = tpsCamComp->GetComponentLocation();
		// LineTrace 의 종료 위치
		FVector endPos = tpsCamComp->GetComponentLocation() + tpsCamComp->GetForwardVector() * 5000;
		// LineTrace 의 충돌 정보를 담을 변수
		FHitResult hitInfo;
		// 충돌 옵션 설정 변수
		FCollisionQueryParams params;
		// 자기 자신(플레이어) 는 충돌에서 제외
		params.AddIgnoredActor(this);
		// Channel 필터를 이용한 LineTrace 충돌 검출(충돌 정보, 시작 위치, 종료 위치, 검출 채널, 충돌 옵션)
		bool bHit = GetWorld()->LineTraceSingleByChannel(hitInfo, startPos, endPos,
			ECC_Visibility, params);
		// LineTrace가 부딪혔을 때
		if (bHit)
		{
			// 충돌 처리 -> 총알 파편 효과 재생
			// 총알 파편 효과 트랜스폼
			FTransform bulletTrans;
			// 부딪힌 위치 할당
			bulletTrans.SetLocation(hitInfo.ImpactPoint);
			// 총알 파편 효과 인스턴스 생성
			UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), bulletEffectFactory, bulletTrans);

			auto hitComp = hitInfo.GetComponent();
			// 1. 만약 컴포넌트에 물리가 적용되어 있다면
			if (hitComp && hitComp->IsSimulatingPhysics())
			{
				// 2. 날려버릴 힘과 방향이 필요
				FVector force = -hitInfo.ImpactNormal * hitComp->GetMass() * 500000;
				// 3. 그 방향으로 날려버리고 싶다.
				hitComp->AddForce(force);
			}

			// 부딪힌 대상이 적인지 판단하기
			auto enemy = hitInfo.GetActor()->GetDefaultSubobjectByName(TEXT("FSM"));
			if (enemy)
			{
				//auto enemyFSM = Cast<UEnemyFSM>(enemy);
				//enemyFSM->OnDamageProcess();
			}
		}
	}
}
// 유탄총으로 변경
void ATPSPlayer::ChangeToGrenadeGun()
{
	// 유탄총 사용 중으로 체크
	bUsingGrenadeGun = true;
	sniperGunComp->SetVisibility(false);
	gunMeshComp->SetVisibility(true);
}
// 스나이퍼건으로 변경
void ATPSPlayer::ChangeToSniperGun()
{
	bUsingGrenadeGun = false;
	sniperGunComp->SetVisibility(true);
	gunMeshComp->SetVisibility(false);
}

void ATPSPlayer::SniperAim()
{
	// 스나이퍼건 모드가 아니라면 처리하지 않는다.
	if (bUsingGrenadeGun)
	{
		return;
	}
	// Pressed 입력 처리
	if (bSniperAim == false)
	{
		// 1. 스나이퍼 조준 모드 활성화
		bSniperAim = true;
		// 2. 스나이퍼조준 UI 등록
		_sniperUI->AddToViewport();
		// 3. 카메라의 시야각 Field Of View 설정
		tpsCamComp->SetFieldOfView(45.0f);
		// 4. 일반 조준 UI 제거
		_crosshairUI->RemoveFromParent();
	}
	// Released 입력 처리
	else
	{
		// 1. 스나이퍼 조준 모드 비활성화
		bSniperAim = false;
		// 2. 스나이퍼 조준 UI 화면에서 제거
		_sniperUI->RemoveFromParent();
		// 3. 카메라 시야각 원래대로 복원
		tpsCamComp->SetFieldOfView(90.0f);
		// 4. 일반 조준 UI 등록
		_crosshairUI->AddToViewport();
	}
}

 

LineTrace를 이용한 총알 발사하기

라인트레이스기능은 HitActor가 하나냐 복수개냐에 따라 LineTraceSingByXXX, LineTraceMultyByXXX등이 있습니다.

필터의 종류는 Channel, ObjectType, Profile등이 있습니다.

LineTrace는 Trigger가 아닌 Block이 체크되어 있어야 합니다.

bUsingGrenadeGun이 false면 

bHit = GetWorld()->LineTraceSingleByChannel(hitInfo, startPos, endPos, ECC_Visibility, params);

로 라인트레이스를 수행하고 결과가 hitInfo에 담깁니다. hitInfo는 구조체라  hit된 Actor, Location, Rotation및 다양한 정보가 있습니다

총알파편처리

if(bHit) { 총알파편처리구현} 

 hitInfo의 충돌지점을 이용해 충돌효과를 SpawnEmittterAtLocation해줍니다. 충돌효과는 TPSPlayer.h에 변수로 선언되어 있고 P_BulletEffect를 지정해줘야 합니다.

// 총알 파편 효과 공장
UPROPERTY(EditAnywhere, Category = BulletEffect)
class UParticleSystem* bulletEffectFactory;

마지막으로 Hit된 물체에 Physics가 있다면 Addforce()를 이용하여 충격을 줍니다. -ImpactNormal을 주어야 반대로 날라갑니다.

if (hitComp && hitComp->IsSimulatingPhysics())
{
    // 2. 날려버릴 힘과 방향이 필요
    FVector force = -hitInfo.ImpactNormal * hitComp->GetMass() * 500000;
    // 3. 그 방향으로 날려버리고 싶다.
    hitComp->AddForce(force);
}
void ATPSPlayer::InputJump(const FInputActionValue& Value)
{
	Jump();
}

void ATPSPlayer::InputFire(const FInputActionValue& Value)
{
	if (bUsingGrenadeGun)
	{
		// 총알 발사 처리
		FTransform firePosition = gunMeshComp->GetSocketTransform(TEXT("FirePosition"));
		GetWorld()->SpawnActor<ABullet>(bulletFactory, firePosition);
	}
	// 스나이퍼건 사용 시
	else
	{
		// LineTrace 의 시작 위치
		FVector startPos = tpsCamComp->GetComponentLocation();
		// LineTrace 의 종료 위치
		FVector endPos = tpsCamComp->GetComponentLocation() + tpsCamComp->GetForwardVector() * 5000;
		// LineTrace 의 충돌 정보를 담을 변수
		FHitResult hitInfo;
		// 충돌 옵션 설정 변수
		FCollisionQueryParams params;
		// 자기 자신(플레이어) 는 충돌에서 제외
		params.AddIgnoredActor(this);
		// Channel 필터를 이용한 LineTrace 충돌 검출(충돌 정보, 시작 위치, 종료 위치, 검출 채널, 충돌 옵션)
		bool bHit = GetWorld()->LineTraceSingleByChannel(hitInfo, startPos, endPos,
			ECC_Visibility, params);
		// LineTrace가 부딪혔을 때
		if (bHit)
		{
			// 충돌 처리 -> 총알 파편 효과 재생
			// 총알 파편 효과 트랜스폼
			FTransform bulletTrans;
			// 부딪힌 위치 할당
			bulletTrans.SetLocation(hitInfo.ImpactPoint);
			// 총알 파편 효과 인스턴스 생성
			UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), bulletEffectFactory, bulletTrans);

			auto hitComp = hitInfo.GetComponent();
			// 1. 만약 컴포넌트에 물리가 적용되어 있다면
			if (hitComp && hitComp->IsSimulatingPhysics())
			{
				// 2. 날려버릴 힘과 방향이 필요
				FVector force = -hitInfo.ImpactNormal * hitComp->GetMass() * 500000;
				// 3. 그 방향으로 날려버리고 싶다.
				hitComp->AddForce(force);
			}

			// 부딪힌 대상이 적인지 판단하기
			auto enemy = hitInfo.GetActor()->GetDefaultSubobjectByName(TEXT("FSM"));
			if (enemy)
			{
				//auto enemyFSM = Cast<UEnemyFSM>(enemy);
				//enemyFSM->OnDamageProcess();
			}
		}
	}
}

 

 

일반모드에서도 조준모드를 위해 위젯을 하나 더 생성하고 왼쪽 Ctrl키를 누르면 스나이퍼 모드가 됩니다. bSniperAim변수를 이용해 스위칭합니다.

'인생언리얼TPS' 카테고리의 다른 글

3.1-9 적AI제어를 위한 FSM 제작하기  (0) 2023.12.15
3.1-8 적Enemy생성하기  (0) 2023.12.15
스나이퍼 모드 구현하기  (0) 2023.12.13
3.1-6 총알발사하기  (0) 2023.12.13
3.1-5 총알 제작하기  (0) 2023.12.13