무기 장착
1. 무기 헤더 파일 및 cpp 파일 생성
1. Tools -> New C++ Class 선택
2. Actor -> Class type은 Public -> Create Class 선택
2. 무기 장착 코드
// 무기(액터) 클래스의 헤더파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"
UCLASS(Abstract)
class TRUEFPSTUTORIAL_API AWeapon : public AActor
{
GENERATED_BODY()
public:
AWeapon();
protected:
virtual void BeginPlay() override;
public:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components")
class USceneComponent* Root;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components")
class USkeletalMeshComponent* Mesh; //Root와 Mesh는 무기의 Mesh를 이용하기 때문에 필요하다.
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "State")
class ATrueFPSCharacter* CurrentOwner; //무기를 들고 있는 캐릭터. 즉, 플레이어를 지칭한다.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Configurations")
FTransform PlacementTransform; //무기의 위치를 표시하는 것이다.
};
// 무기(액터) 클래스의 cpp 파일
#include "Weapons/Weapon.h"
AWeapon::AWeapon()
{
PrimaryActorTick.bCanEverTick = false;
SetReplicates(true);
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
RootComponent = Root;
Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(Root); // 서버에서 무기를 스폰하고 클라이언트에 복제하기 위해서 이와 같이 만든다.
}
void AWeapon::BeginPlay()
{
Super::BeginPlay();
if(!CurrentOwner)
Mesh->SetVisibility(false); //장착하지 않으면 보이지 않게 만든다.
}
// 캐릭터 클래스의 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "TrueFPSCharacter.generated.h"
UCLASS()
class TRUEFPSTUTORIAL_API ATrueFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
ATrueFPSCharacter();
protected:
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; // 복제를 위한 함수이다.
public:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components")
class UCameraComponent* Camera;
protected:
UPROPERTY(EditDefaultsOnly, Category = "Configurations")
TArray<TSubclassOf<class AWeapon>> DefaultWeapons; //디폴트의 무기 클래스이다.
public:
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Replicated, Category = "State")
TArray<class AWeapon*> Weapons; //무기 호출을 위한 배열이다.
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, ReplicatedUsing = OnRep_CurrentWeapon, Category = "State")
class AWeapon* CurrentWeapon; //현재 들고 있는 무기를 나타낸다.
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "State")
int32 CurrentIndex = 0; //현재 무기의 번호를 나타낸다.
protected:
UFUNCTION()
virtual void OnRep_CurrentWeapon(const class AWeapon* OldWeapon); //무기를 바꿀 때 필요한 함수이다.
protected:
void MoveForward(const float Value);
void MoveRight(const float Value);
void LookUp(const float Value);
void LookRight(const float Value);
};
// 캐릭터 클래스의 cpp 파일
#include "Character/TrueFPSCharacter.h"
#include "..\..\Public\Character\TrueFPSCharacter.h"
#include <Runtime/Engine/Public/Net/UnrealNetwork.h> //이 라이브러리를 포함해야 'DOREPLIFETIME_CONDITION'를 사용할 수 있다.
#include "Weapons/Weapon.h"
#include "Camera/CAmeraComponent.h"
// Sets default values
ATrueFPSCharacter::ATrueFPSCharacter()
{
PrimaryActorTick.bCanEverTick = false;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->bUsePawnControlRotation = true; // 폰의 움직임
Camera->SetupAttachment(GetMesh(), FName("head")); // 머리쪽으로 카메라 설치 위함
}
void ATrueFPSCharacter::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
for (const TSubclassOf<AWeapon>& WeaponClass : DefaultWeapons)
{
if (!WeaponClass) continue;
FActorSpawnParameters Params;
Params.Owner = this;
AWeapon* SpawnedWeapon = GetWorld()->SpawnActor<AWeapon>(WeaponClass, Params); //서버와 클라이언트에 무기 스폰을 해야하기 때문에 반복문을 이용해준다.
const int32 Index = Weapons.Add(SpawnedWeapon); //무기의 개수를 나타내주는 변수이다.
if (Index == CurrentIndex)
{
CurrentWeapon = SpawnedWeapon;
OnRep_CurrentWeapon(nullptr);
} //무기가 하나인 경우를 나타낸다.
}
}
}
void ATrueFPSCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(ATrueFPSCharacter, Weapons, COND_None);
DOREPLIFETIME_CONDITION(ATrueFPSCharacter, CurrentWeapon, COND_None); //서버와 클라이언트에서 복제해주는 문장이다.
}
void ATrueFPSCharacter::OnRep_CurrentWeapon(const AWeapon* OldWeapon)
{
if (CurrentWeapon)
{
if (!CurrentWeapon->CurrentOwner)
{
const FTransform PlacementTransform = CurrentWeapon->PlacementTransform * GetMesh()->GetSocketTransform(FName("hand_r"));
CurrentWeapon->SetActorTransform(PlacementTransform, false, nullptr, ETeleportType::TeleportPhysics);
CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepWorldTransform, FName("hand_r")); //큰 따옴표 안에는 본인이 장착하고 싶은 부위를 적으면된다. 이 부위는 스켈레탈 메시에 Skeleton Tree에서 확인할 수 있다.
CurrentWeapon->CurrentOwner = this; //플레이어에게 장착하게 만들어주는 코드이다.
} //무기가 장착되지 않은 경우에 장착하게 만들어준다.
CurrentWeapon->Mesh->SetVisibility(true); //무기를 보여주게 만든다.
}
}
// Called to bind functionality to input
void ATrueFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis(FName("MoveForward"), this, &ATrueFPSCharacter::MoveForward);
PlayerInputComponent->BindAxis(FName("MoveRight"), this, &ATrueFPSCharacter::MoveRight);
PlayerInputComponent->BindAxis(FName("LookUp"), this, &ATrueFPSCharacter::LookUp);
PlayerInputComponent->BindAxis(FName("LookRight"), this, &ATrueFPSCharacter::LookRight);
}
void ATrueFPSCharacter::MoveForward(const float Value)
{
const FVector& Direction = FRotationMatrix(FRotator(0.f, GetControlRotation().Yaw, 0.f)).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
void ATrueFPSCharacter::MoveRight(const float Value)
{
const FVector& Direction = FRotationMatrix(FRotator(0.f, GetControlRotation().Yaw, 0.f)).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
void ATrueFPSCharacter::LookUp(const float Value)
{
AddControllerPitchInput(Value);
}
void ATrueFPSCharacter::LookRight(const float Value)
{
AddControllerYawInput(Value);
}
3. 무기 블루프린트 생성 및 위치 선정
1. 콘텐츠 브라우저에서 생성하고 싶은 파일 내에서 오른쪽 마우스 클릭
2. Blueprint Class -> ALL CLASSES에서 무기 클래스를 선택 -> Select
3. Mesh 내에 있는 Skeletal Mesh에서 본인이 장착하고 싶은 무기를 선택
4. 캐릭터 블루프린트에서 Configuration내에 있는 Default Weapons의 '+' 버튼을 눌러 무기 블루프린트를 적용
5. 왼쪽 Components에 '+Add'를 눌러 Skeletal Mesh를 Mesh 하위에 생성
6. Skeletal Mesh에 Mesh를 무기 블루프린트에서 적용한 Mesh로 적용 및 Sockets내에 있는 Parent Sockets은 위 코드의 큰 따옴표에 있는 소켓을 적용
7. 자연스럽게 장착한 모습으로 위치 조정
8. 그 위치를 복사하고 무기 블루프린트에서 붙임
9. 캐릭터 클래스에서 만든 Skeletal Mesh는 삭제
무기 교체
1. 입력 설정
1. 메인 화면 상위의 탭 중에서 Edit -> Project Settings 선택
2. Engine -> Input 에서 아래의 그림과 같이 설정
2. 무기 교체 코드
// 캐릭터 클래스의 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "TrueFPSCharacter.generated.h"
UCLASS()
class TRUEFPSTUTORIAL_API ATrueFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
ATrueFPSCharacter();
protected:
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
public:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components")
class UCameraComponent* Camera;
protected:
UPROPERTY(EditDefaultsOnly, Category = "Configurations")
TArray<TSubclassOf<class AWeapon>> DefaultWeapons;
public:
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Replicated, Category = "State")
TArray<class AWeapon*> Weapons;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, ReplicatedUsing = OnRep_CurrentWeapon, Category = "State")
class AWeapon* CurrentWeapon;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "State")
int32 CurrentIndex = 0;
UFUNCTION(BlueprintCallable, Category = "Character")
virtual void EquipWeapon(const int32 Index); //무기를 바꾸기 위해 필요함
protected:
UFUNCTION()
virtual void OnRep_CurrentWeapon(const class AWeapon* OldWeapon);
UFUNCTION(Server, Reliable)
void Server_SetCurrentWeapon(class AWeapon* Weapon); //서버에 무기를 호출하기 위한 함수
virtual void Server_SetCurrentWeapon_Implementation(class AWeapon* NewWeapon); //위를 실행하는 함수
protected:
virtual void NextWeapon(); //다음 무기로 넘어가기 위한 함수
virtual void LastWeapon(); //이전 무기로 넘어가기 위한 함수
void MoveForward(const float Value);
void MoveRight(const float Value);
void LookUp(const float Value);
void LookRight(const float Value);
};
// 캐릭터 클래스의 cpp 파일
#include "Character/TrueFPSCharacter.h"
#include "..\..\Public\Character\TrueFPSCharacter.h"
#include <Runtime/Engine/Public/Net/UnrealNetwork.h>
#include "Weapons/Weapon.h"
#include "Camera/CAmeraComponent.h"
ATrueFPSCharacter::ATrueFPSCharacter()
{
PrimaryActorTick.bCanEverTick = false;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->bUsePawnControlRotation = true; // 폰의 움직임
Camera->SetupAttachment(GetMesh(), FName("head")); // 머리쪽으로 카메라 설치 위함
}
void ATrueFPSCharacter::BeginPlay()
{
Super::BeginPlay();
if (HasAuthority())
{
for (const TSubclassOf<AWeapon>& WeaponClass : DefaultWeapons)
{
if (!WeaponClass) continue;
FActorSpawnParameters Params;
Params.Owner = this;
AWeapon* SpawnedWeapon = GetWorld()->SpawnActor<AWeapon>(WeaponClass, Params);
const int32 Index = Weapons.Add(SpawnedWeapon);
if (Index == CurrentIndex)
{
CurrentWeapon = SpawnedWeapon;
OnRep_CurrentWeapon(nullptr);
}
}
}
}
void ATrueFPSCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(ATrueFPSCharacter, Weapons, COND_None);
DOREPLIFETIME_CONDITION(ATrueFPSCharacter, CurrentWeapon, COND_None);
}
void ATrueFPSCharacter::OnRep_CurrentWeapon(const AWeapon* OldWeapon)
{
if (CurrentWeapon)
{
if (!CurrentWeapon->CurrentOwner)
{
const FTransform PlacementTransform = CurrentWeapon->PlacementTransform * GetMesh()->GetSocketTransform(FName("hand_r"));
CurrentWeapon->SetActorTransform(PlacementTransform, false, nullptr, ETeleportType::TeleportPhysics);
CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepWorldTransform, FName("hand_r"));
CurrentWeapon->CurrentOwner = this;
}
CurrentWeapon->Mesh->SetVisibility(true);
}
if (OldWeapon)
{
OldWeapon->Mesh->SetVisibility(false); //무기를 바꿀 때 이전에 쓰였던 무기는 보이지 않게 하기 위해서 false로 둔다
}
}
void ATrueFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction(FName("NextWeapon"), EInputEvent::IE_Pressed, this, &ATrueFPSCharacter::NextWeapon);
PlayerInputComponent->BindAction(FName("LastWeapon"), EInputEvent::IE_Pressed, this, &ATrueFPSCharacter::LastWeapon); //바인딩을 위한 코드
PlayerInputComponent->BindAxis(FName("MoveForward"), this, &ATrueFPSCharacter::MoveForward);
PlayerInputComponent->BindAxis(FName("MoveRight"), this, &ATrueFPSCharacter::MoveRight);
PlayerInputComponent->BindAxis(FName("LookUp"), this, &ATrueFPSCharacter::LookUp);
PlayerInputComponent->BindAxis(FName("LookRight"), this, &ATrueFPSCharacter::LookRight);
}
void ATrueFPSCharacter::EquipWeapon(const int32 Index)
{
if (!Weapons.IsValidIndex(Index) || CurrentWeapon==Weapons[Index]) return; //Index에 무기를 사용할 수 없거나 교체할 무기가 없는 경우
if (IsLocallyControlled())
{
CurrentIndex = Index;
const AWeapon* OldWeapon = CurrentWeapon;
CurrentWeapon = Weapons[Index];
OnRep_CurrentWeapon(OldWeapon);
} // 무기를 바꿀 수 있는 환경인 경우
else if (!HasAuthority())
{
Server_SetCurrentWeapon(Weapons[Index]);
} // 클라이언트로 승인 호출을 못 할 경우
}
void ATrueFPSCharacter::Server_SetCurrentWeapon_Implementation(AWeapon* NewWeapon)
{
const AWeapon* OldWeapon = CurrentWeapon;
CurrentWeapon = NewWeapon; // 무기 교체
OnRep_CurrentWeapon(OldWeapon);
} //서버에 무기를 호출해줌
void ATrueFPSCharacter::NextWeapon()
{
const int32 Index = Weapons.IsValidIndex(CurrentIndex + 1) ? CurrentIndex + 1 : 0;
EquipWeapon(Index);
} // 휠로 다음 무기로 교체
void ATrueFPSCharacter::LastWeapon()
{
const int32 Index = Weapons.IsValidIndex(CurrentIndex - 1) ? CurrentIndex - 1 : Weapons.Num()-1;
EquipWeapon(Index);
} // 휠로 이전 무기로 교체
void ATrueFPSCharacter::MoveForward(const float Value)
{
const FVector& Direction = FRotationMatrix(FRotator(0.f, GetControlRotation().Yaw, 0.f)).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
void ATrueFPSCharacter::MoveRight(const float Value)
{
const FVector& Direction = FRotationMatrix(FRotator(0.f, GetControlRotation().Yaw, 0.f)).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
void ATrueFPSCharacter::LookUp(const float Value)
{
AddControllerPitchInput(Value);
}
void ATrueFPSCharacter::LookRight(const float Value)
{
AddControllerYawInput(Value);
}
3. 무기 블루프린트 생성 및 위치 선정
※ 위에 있는 설명과 동일하므로 생략
● 참고 동영상 : https://www.youtube.com/watch?v=QA-3466E6LU&t=2835s
● 무기 에셋들은 마켓플레이스 Military Weapons Silver에서 받았습니다.
'언리얼엔진' 카테고리의 다른 글
언리얼 엔진 5 C++ (총구 방향대로 움직이기) (1) | 2023.02.04 |
---|---|
언리얼 엔진 4 C++ (표준 코드) (0) | 2023.01.31 |
언리얼 엔진 5 C++ (1인칭 시점 카메라 및 입력을 통한 캐릭터 움직임) (0) | 2023.01.29 |
언리얼 엔진 4 C++ (TSharedPtr, TWeakPtr, TUniquePtr) (0) | 2023.01.26 |
언리얼 엔진 4 C++ (UCLASS, UPROPERTY, USTRUCT, UENUM, UFUNCTION) (0) | 2023.01.25 |