총알을 발사하기 위해 InputAction을 추가하고
Value Type은 Digital이면 됩니다. Thirdperson Input의 IMC_Default에 추가해주고 Left Mouse Button을 설정합니다.
라이프언리얼 깃의 자료실에서 FPWeapon을 다운받아 탐색기에서 Content폴더 아래 복사해 놓습니다.
플레이어에 총 추가하기
이렇게 추가한 FPWeapon 에셋을 TPSPlayer에 등록하겠습니다.
BP_TPSPlayer를 열어 Bullet Factory를 BP_Bullet으로 설정합니다.
이제 총알이 나갈 위치를 정합니다. SK_FPGun을 열어 줍니다. Skeleton Tree Grip_Bone우클릭후 Add_Socket후 이름을 FirePosition으로 변경합니다.
FirePosition을 선택하고 소켓의 위치를 총구 앞으로 이동시켜줍니다.
블루프린트에서 특정 변수값을 수정했을 때 특정 처리
// 액터의 특정 속성을 수정하면 호출되는 이벤트 함수
void ABullet::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// speed 값이 수정되었는지 체크
if (PropertyChangedEvent.GetPropertyName() == TEXT("speed"))
{
// 프로젝타일 무브먼트 컴포넌트에 speed 값 적용
movementComp->InitialSpeed = speed;
movementComp->MaxSpeed = speed;
}
}
Bullet.h전체코드
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Bullet.generated.h"
UCLASS()
class TPSPROJECT_API ABullet : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ABullet();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
// 발사체의 이동을 담당할 컴포넌트
UPROPERTY(VisibleAnywhere, Category = Movement)
class UProjectileMovementComponent* movementComp;
// 충돌 컴포넌트
UPROPERTY(VisibleAnywhere, Category = Collision)
class USphereComponent* collisionComp;
// 외관 컴포넌트
UPROPERTY(VisibleAnywhere, Category = BodyMesh)
class UStaticMeshComponent* bodyMeshComp;
// 총알 제거 함수
void Die();
// 총알속도
UPROPERTY(EditAnywhere, Category = Settings)
float speed = 5000;
// 액터의 특정 속성을 수정하면 호출되는 이벤트 함수
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
};
Bullet.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Bullet.h"
#include <Components/SphereComponent.h>
#include <GameFramework/ProjectileMovementComponent.h>
//void LambdaTestFunc()
//{
// int32 sum = 10;
// // 함수객체 생성
// auto lamdaFunc = [&sum](int number)->void {
// sum += number;
// };
// // 함수객체 실행
// lamdaFunc(20);
// UE_LOG(LogTemp, Warning, TEXT("sum : %d"), sum);
//}
ABullet::ABullet()
{
PrimaryActorTick.bCanEverTick = true;
// 1. 충돌체 등록하기
collisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComp"));
// 2. 충돌프로파일 설정
collisionComp->SetCollisionProfileName(TEXT("BlockAll"));
// 3. 충돌체 크기 설정
collisionComp->SetSphereRadius(13);
// 4. 루트로 등록
RootComponent = collisionComp;
// 5. 외관 컴포넌트 등록하기
bodyMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BodyMeshComp"));
// 6. 부모 컴포넌트 지정
bodyMeshComp->SetupAttachment(collisionComp);
// 7. 충돌 비활성화
bodyMeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// 8. 외관 크기 설정
bodyMeshComp->SetRelativeScale3D(FVector(0.25f));
// 발사체 컴포넌트
movementComp = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("MovementComp"));
// movement 컴포넌트가 갱신시킬 컴포넌트 지정
movementComp->SetUpdatedComponent(collisionComp);
// 초기속도
movementComp->InitialSpeed = 5000;
// 최대속도
movementComp->MaxSpeed = 5000;
// 반동여부
movementComp->bShouldBounce = true;
// 반동 값
movementComp->Bounciness = 0.3f;
// 생명 시간 주기
//InitialLifeSpan = 2.0f;
}
// Called when the game starts or when spawned
void ABullet::BeginPlay()
{
Super::BeginPlay();
FTimerHandle deathTimer;
//GetWorld()->GetTimerManager().SetTimer(deathTimer, this, &ABullet::Die, 2.0f, false);
//타이머를 이용해 총알을 제거
GetWorld()->GetTimerManager().SetTimer(deathTimer, FTimerDelegate::CreateLambda([this]()->void
{
Destroy();
}), 2.0f, false);
}
// Called every frame
void ABullet::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ABullet::Die()
{
Destroy();
}
// 액터의 특정 속성을 수정하면 호출되는 이벤트 함수
void ABullet::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// speed 값이 수정되었는지 체크
if (PropertyChangedEvent.GetPropertyName() == TEXT("speed"))
{
// 프로젝타일 무브먼트 컴포넌트에 speed 값 적용
movementComp->InitialSpeed = speed;
movementComp->MaxSpeed = speed;
}
}
TSPlayer.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;
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 = SniperUI)
//TSubclassOf<class UUserWidget> sniperUIFactory;
// 스나이퍼 UI 위젯 인스턴스
class UUserWidget* _sniperUI;
// 총알 파편 효과 공장
UPROPERTY(EditAnywhere, Category = BulletEffect)
class UParticleSystem* bulletEffectFactory;
// 일반 조준 크로스헤어UI 위젯
//UPROPERTY(EditDefaultsOnly, Category = SniperUI)
//TSubclassOf<class UUserWidget> crosshairUIFactory;
// 크로스헤어 인스턴스
//class UUserWidget* _crosshairUI;
private:
void Locomotion();
FVector moveDirection;
bool fireReady;
float fireTimerTime;
};
TPSPlayer.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 <Blueprint/UserWidget.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 위젯 인스턴스 생성
//_sniperUI = CreateWidget(GetWorld(), sniperUIFactory);
// 2. 일반 조준 UI 크로스헤어 인스턴스 생성
//_crosshairUI = CreateWidget(GetWorld(), crosshairUIFactory);
// 3. 일반 조준 UI 등록
//_crosshairUI->AddToViewport();
// 기본으로 스나이퍼건을 사용하도록 설정
//ChangeToSniperGun();
ChangeToGrenadeGun();
}
// 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);
}
}
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();
}
}
'인생언리얼TPS' 카테고리의 다른 글
3.1-7 스나이퍼모드 C++ (0) | 2023.12.15 |
---|---|
스나이퍼 모드 구현하기 (0) | 2023.12.13 |
3.1-5 총알 제작하기 (0) | 2023.12.13 |
3.1-4 플레이어 이동 처리하기 (1) | 2023.12.12 |
3.1-2 3인칭카메라 붙이기 (0) | 2023.12.12 |