● 들어가기 전 : 코드 변경사항
// 캐릭터 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(IsLocallyControlled())
{
ClientMesh->HideBoneByName(FName("neck_01"),PBO_None);
GetMesh()->SetVisibility(false);
}
else
{
ClientMesh->DestroyComponent();
}
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() || HasAuthority())
{
CurrentIndex = Index;
const AWeapon* OldWeapon = CurrentWeapon;
CurrentWeapon = Weapons[Index];
OnRep_CurrentWeapon(OldWeapon);
}
if (!HasAuthority())
{
Server_SetCurrentWeapon(Weapons[Index]);
}
// ' if (IsLocallyControlled() || HasAuthority()), if (!HasAuthority()) '로 무기 바꾸는 것을 서버에 정상적으로 전달한다.
}
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);
}
무기가 캐릭터에 겹치지 않게 보이기
1. 컨트롤 릭 변수 추가
2. 컨트롤 릭에서 기본 자세 설정
3. 클라이언트 매시 복사를 위한 코드
// 캐릭터 헤더 파일
#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;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components")
class USkeletalMeshComponent* ClientMesh; // 캐릭터가 겹쳐 보이지 않기 위한 매시
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;
GetMesh()->SetTickGroup(ETickingGroup::TG_PostUpdateWork);
GetMesh()->bVisibleInReflectionCaptures=true;
GetMesh()->bCastHiddenShadow=true;
//해당 매시는 그림자, 반사의 모습을 보이게 함.
ClientMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("ClientMesh"));
ClientMesh->SetCastShadow(false);
ClientMesh->bCastHiddenShadow=false;
ClientMesh->bVisibleInReflectionCaptures = false;
ClientMesh->SetTickGroup(TG_PostUpdateWork);
ClientMesh->SetupAttachment(GetMesh());
//매시 복사를 위해 클라이언트 매시의 그림자, 반사의 모습을 보이지 않게 함.
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->bUsePawnControlRotation = true;
Camera->SetupAttachment(GetMesh(), FName("head"));
}
void ATrueFPSCharacter::BeginPlay()
{
Super::BeginPlay();
if(IsLocallyControlled())
{
ClientMesh->HideBoneByName(FName("neck_01"),PBO_None);
GetMesh()->SetVisibility(false);
}
else
{
ClientMesh->DestroyComponent();
}
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() || HasAuthority())
{
CurrentIndex = Index;
const AWeapon* OldWeapon = CurrentWeapon;
CurrentWeapon = Weapons[Index];
OnRep_CurrentWeapon(OldWeapon);
}
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);
}
4. 애니메이션 블루프린트 설정
1. 콘텐츠 브라우저에서 생성하고 싶은 파일 내에서 오른쪽 마우스 클릭
2. 애니메이션 블루프린트 생성
3. 애니메이션 블루프린트에서 변수 추가
4. 아래와 같이 설정
5. 블루프린트 설정
1. 블루프린트에서 Client Mesh에 지금 만들었던 애니메이션 블루프린트를 적용
● 참고 동영상 : https://www.youtube.com/watch?v=FqOjKLzs8EU&t=1856s
'언리얼엔진' 카테고리의 다른 글
언리얼 엔진 5 코딩 표준 1 (클래스 체계, 명명 규칙) (0) | 2025.01.23 |
---|---|
언리얼 엔진 5 (Unreal Engine 5 C++ Developer: Learn C++ & Make Video Games 113~125) (0) | 2023.04.22 |
언리얼 엔진 5 C++ (줌인) (0) | 2023.02.11 |
언리얼 엔진 5 C++ (총구 방향대로 움직이기) (1) | 2023.02.04 |
언리얼 엔진 4 C++ (표준 코드) (0) | 2023.01.31 |