※ 이전 글을 보면서 따라하면 위아래로 움직일 때 총구 방향이 그대로인 것을 확인할 수 있습니다. 이것에 대한 해결 방법으로 나온 글이니 참고하시길 바랍니다.
총구 방향대로 움직이기
1. 애님 인스턴스 헤더 파일 및 cpp 파일 생성
1. Tools -> New C++ Class 선택
2. AnimInstance -> Class type은 Public -> Create Class 선택
2. 소켓과 카메라 사이의 코드
// 무기 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"
USTRUCT(BlueprintType)
struct FIKProperties
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UAnimSequence* AnimPose; //줌인을 위한 애니메이션
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float AimOffset = 15.f; //캐릭터와 에임과의 거리
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FTransform CustomOffsetTransform; //무기 위치
};
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;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "State")
class ATrueFPSCharacter* CurrentOwner;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Configurations")
FIKProperties IKProperties;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Configurations")
FTransform PlacementTransform;
};
// 캐릭터 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "TrueFPSCharacter.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCurrentWeaponChangedDelegate, class AWeapon*, CurrentWeapon, const class AWeapon*, OldWeapon);
// 다수의 함수에 바인딩시켜 실행
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(BlueprintAssignable, Category = "Delegates")
FCurrentWeaponChangedDelegate CurrentWeaponChangedDelegate; //현재 무기가 바뀌어질 때 호출된다.
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);
}
CurrentWeaponChangedDelegate.Broadcast(CurrentWeapon, OldWeapon); //무기가 바뀌어질 때 바인딩해준다.
}
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;
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);
}
// 애니메이션 인스턴스 헤더 파일
#pragma once
#include "Weapons/Weapon.h"
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "TrueFPSAnimInstance.generated.h"
UCLASS()
class TRUEFPSTUTORIAL_API UTrueFPSAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
UTrueFPSAnimInstance();
protected:
virtual void NativeBeginPlay() override;
virtual void NativeUpdateAnimation(float DeltaTime) override;
virtual void CurrentWeaponChanged(class AWeapon* NewWeapon, const class AWeapon* OldWeapon);
virtual void SetVars(const float DeltaTime); //카메라 변경
virtual void CalculateWeaponSway(const float DeltaTime);
public:
UPROPERTY(BlueprintReadWrite, Category = "Anim")
class ATrueFPSCharacter* Character;
UPROPERTY(BlueprintReadWrite, Category = "Anim")
class USkeletalMeshComponent* Mesh;
UPROPERTY(BlueprintReadWrite, Category = "Anim")
class AWeapon* CurrentWeapon;
UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Anim")
FIKProperties IKProperties; //물리법칙 적용
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Anim")
FTransform CameraTransform;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Anim")
FTransform RelativeCameraTransform;
};
// 애니메이션 인스턴스 cpp 파일
#include "Character/TrueFPSCharacter.h"
#include "Character/TrueFPSAnimInstance.h"
#include "Camera/CAmeraComponent.h"
UTrueFPSAnimInstance::UTrueFPSAnimInstance()
{
}
void UTrueFPSAnimInstance::NativeBeginPlay()
{
Super::NativeBeginPlay();
Character = Cast<ATrueFPSCharacter>(TryGetPawnOwner()); //캐릭터에 대한 레퍼런스를 호출
if (Character)
{
Mesh = Character->GetMesh();
Character->CurrentWeaponChangedDelegate.AddDynamic(this, &UTrueFPSAnimInstance::CurrentWeaponChanged); //무기를 바꾸어주는 델리게이트
CurrentWeaponChanged(Character->CurrentWeapon, nullptr); //대기 없이 즉시 무기 바꾸어준다.
} //캐릭터가 사용가능한 경우 또 다른 레퍼런스 호출
}
void UTrueFPSAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
Super::NativeUpdateAnimation(DeltaTime);
if (!Character) return;
SetVars(DeltaTime);
CalculateWeaponSway(DeltaTime);
}
void UTrueFPSAnimInstance::CurrentWeaponChanged(class AWeapon* NewWeapon, const class AWeapon* OldWeapon)
{
CurrentWeapon = NewWeapon;
if (CurrentWeapon)
{
IKProperties = CurrentWeapon->IKProperties;
}
}
void UTrueFPSAnimInstance::SetVars(const float DeltaTime)
{
CameraTransform = FTransform(Character->GetBaseAimRotation(), Character->Camera->GetComponentLocation()); //카메라의 회전과 위치
const FTransform RootOffset = Mesh->GetSocketTransform(FName("root"), RTS_Component).Inverse() * Mesh->GetSocketTransform(FName("ik_hand_root")); //법칙의 계산이 정확하지 않아 쓰이는 것
RelativeCameraTransform = CameraTransform.GetRelativeTransform(RootOffset);
}
void UTrueFPSAnimInstance::CalculateWeaponSway(const float DeltaTime)
{
}
3. 컨트롤 릭 블루프린트 생성
1. 애니메이션 블루프린트의 Class Settings에서 CLASS OPTIONS에 있는 Parent Class를 방금 만든 애니메이션 인스턴스 클래스로 설정
2. Edit->Plugins에서 Control Rig Enabled에 체크(기본적으로 체크되어 있다.)
3. 콘텐츠 브라우저에서 생성하고 싶은 파일 내에서 오른쪽 마우스 클릭
4. Animation에 있는 Control Rig 클릭
5. Rig Hierarchy에서 캐릭터 스켈레탈 메시를 임포트
6. VARIABLES에서 변수를 추가하여 Variable 타입을 Transform으로 변경
7. 애니메이션 블루프린트에서 Control Rig 노드를 추가하여 연결시켜준다. 이 때 Control Rig 노드 클릭해서 아래 사진과 같이 Details에서 INPUT에 있는 OR Use Curve을 체크(체크를 해주면 OUTPUT에 있는 OR Use Curve도 자동적으로 선택되기 때문에 별도의 처리가 없다.)
8. 컨트롤 릭에서 아래와 같이 설정
참고 동영상 : https://www.youtube.com/watch?v=5EDqUE0Yt3A&t=2006s
'언리얼엔진' 카테고리의 다른 글
언리얼 엔진 5 C++ (캐릭터 겹쳐 보이지 않기) (0) | 2023.02.12 |
---|---|
언리얼 엔진 5 C++ (줌인) (0) | 2023.02.11 |
언리얼 엔진 4 C++ (표준 코드) (0) | 2023.01.31 |
언리얼 엔진 5 C++ (무기 장착 및 교체) (0) | 2023.01.31 |
언리얼 엔진 5 C++ (1인칭 시점 카메라 및 입력을 통한 캐릭터 움직임) (0) | 2023.01.29 |