목차

     

     


     

     

    무기 줍기

     


     

    BaseCharacter

     

    BaseCharacter.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "BaseCharacter.generated.h"
    
    UCLASS()
    class MULTIPLAYER_API ABaseCharacter : public ACharacter
    {
    	GENERATED_BODY()
    public:
    	ABaseCharacter();
    	virtual void Tick(float DeltaTime) override;
    	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
    	virtual void PostInitializeComponents() override;
    
    protected:
    	virtual void BeginPlay() override;
        
    	void EquipButtonPressed();
    
    private:
    	UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
    	class UWidgetComponent* OverheadWidget;
    
    	//OnRep_OverlappingWeapon 함수가 client에 호출되었을때 Replicate해준다.
    	UPROPERTY(ReplicatedUsing = OnRep_OverlappingWeapon)  
    	class AWeapon* OverlappingWeapon;
    
    	UPROPERTY(VisibleAnywhere)
    	class UCombatComponent* Combat;
    
    	UFUNCTION()
    	void OnRep_OverlappingWeapon(AWeapon* LastWeapon);
    
    	//Reliable RPC는 무조건 실행, Unreliable RPC는 패킷 Drop 가능
    	UFUNCTION(Server, Reliable)
    	void ServerEquipButtonPressed();
    
    public:
    	void SetOverlappingWeapon(AWeapon* Weapon);
    };

     

    BaseCharacter.cpp

    더보기
    #include "BaseCharacter.h"
    #include "GameFramework/SpringArmComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "Camera/CameraComponent.h"
    #include "Components/WidgetComponent.h"
    #include "Net/UnrealNetwork.h"
    #include "Multiplayer/Weapon/Weapon.h"
    #include "Multiplayer/Components/CombatComponent.h"
    
    ABaseCharacter::ABaseCharacter()
    {
    	//...
    	OverheadWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadWidget"));
    	OverheadWidget->SetupAttachment(RootComponent);
    
    	Combat = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));
    	Combat->SetIsReplicated(true); //Combat을 Replicated 컴포넌트로 만들어준다.
    }
    
    void ABaseCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    	DOREPLIFETIME_CONDITION(ABaseCharacter, OverlappingWeapon, COND_OwnerOnly);//OwnerOnly: 해당 캐릭터를 가지고 있는 Client만 적용
    }
    
    void ABaseCharacter::PostInitializeComponents()
    {
    	Super::PostInitializeComponents();
    
    	if (IsValid(Combat))
    	{
    		Combat->Character = this;
    	}
    }
    
    void ABaseCharacter::EquipButtonPressed()
    {
    	if (Combat)
    	{
    		if (HasAuthority()) //서버 기준. Server에서 validate하는 HasAuthority()
    		{
    			Combat->EquipWeapon(OverlappingWeapon); //무기를 주어서 캐릭터에 장착시킨다
    		}
    		else //클라이언트 기준. Authority가 없는 경우, RPC를 통해 ServerEquipButtonPressed_Implementation()에서 무기장착 수행
    		{
    			ServerEquipButtonPressed(); //무기 장착
    		}
    	}
    }
    
    void ABaseCharacter::ServerEquipButtonPressed_Implementation()
    {
    	// 서버를 통해 무기 장착하는 경우
    	if (Combat)
    	{
    		Combat->EquipWeapon(OverlappingWeapon);//무기를 줍고 캐릭터에 장착시킨다
    	}
    }
    
    void ABaseCharacter::SetOverlappingWeapon(AWeapon* Weapon)
    {
    	if (OverlappingWeapon)
    	{
    		OverlappingWeapon->ShowPickupWidget(false);
    	}
    
    	OverlappingWeapon = Weapon;
    	if (IsLocallyControlled())
    	{
    		if (OverlappingWeapon)
    		{
    			OverlappingWeapon->ShowPickupWidget(true);
    		}
    	}
    }
    
    void ABaseCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon)
    {
    	if (OverlappingWeapon)
    	{
    		OverlappingWeapon->ShowPickupWidget(true);
    	}
    	if (LastWeapon)
    	{
    		LastWeapon->ShowPickupWidget(false);
    	}
    }

     

     

    Weapon

     

    Weapon.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "Weapon.generated.h"
    
    UENUM(BlueprintType)
    enum class EWeaponState : uint8 //무기 상태 Enum
    {
    	EWS_Initial UMETA(DisplayName = "Initial State"),
    	EWS_Equipped UMETA(DisplayName = "Equipped"),
    	EWS_Dropped UMETA(DisplayName = "Dropped"),
    
    	EWS_MAX UMETA(DisplayName = "DefaultMAX")
    };
    
    UCLASS()
    class MULTIPLAYER_API AWeapon : public AActor
    {
    	GENERATED_BODY()
    
    public:
    	AWeapon();
    	virtual void Tick(float DeltaTime) override;
    	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
    	void ShowPickupWidget(bool bShowWidget);
    
    protected:
    	virtual void BeginPlay() override;
    
    	UFUNCTION()
    	virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep,	const FHitResult& SweepResult);
    	UFUNCTION()
    	void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,	int32 OtherBodyIndex);
    
    private:
    	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
    	USkeletalMeshComponent* WeaponMesh;
    
    	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
    	class USphereComponent* AreaSphere;
    
    	UPROPERTY(ReplicatedUsing = OnRep_WeaponState, VisibleAnywhere, Category = "Weapon Properties")
    	EWeaponState WeaponState; //무기 상태
    
    	UFUNCTION()
    	void OnRep_WeaponState();
    
    	UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
    	class UWidgetComponent* PickupWidget; //무기줍기 Widget(Press E-PickUp)
    
    public:
    	void SetWeaponState(EWeaponState State);
    	FORCEINLINE USphereComponent* GetAreaSphere() const { return AreaSphere; }
    };

     

    Weapon.cpp

    더보기
    #include "Weapon.h"
    #include "Components/SphereComponent.h"
    #include "Components/WidgetComponent.h"
    #include "Multiplayer/Character/BaseCharacter.h"
    #include "Net/UnrealNetwork.h"
    
    AWeapon::AWeapon()
    {
    	PrimaryActorTick.bCanEverTick = false;
    	bReplicates = true;
    
    	WeaponMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponMesh"));
    	SetRootComponent(WeaponMesh);
    
    	WeaponMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);//WeaponMesh의 channel 충돌을 block 처리
    	WeaponMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);//Pawn의 경우, 충돌 무시, 무기가 Pawn(ex.적 캐릭터)에 부딪혔을때 통과할 수 있도록 설정.
    	WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);//처음 무기가 나왔을때 No Collision
    
    	AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere"));
    	AreaSphere->SetupAttachment(RootComponent);
    	AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);//channel 충돌x, AreaSphere는 충돌x
    	AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    
    	PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget"));
    	PickupWidget->SetupAttachment(RootComponent);
    }
    
    void AWeapon::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    
    }
    
    void AWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    	DOREPLIFETIME(AWeapon, WeaponState);
    }
    
    void AWeapon::ShowPickupWidget(bool bShowWidget)
    {
    	if (PickupWidget)
    	{
    		PickupWidget->SetVisibility(bShowWidget);
    	}
    }
    
    void AWeapon::BeginPlay()
    {
    	Super::BeginPlay();
    
    	//서버가 Weapon 객체를 관리할 수 있도록 한다
    	if (HasAuthority()) //HasAuthority()와 GetLocalRole() == ENetRole::ROLE_Authority는 같다.
    	{
    		AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    		AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
    		AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap); //무기 AreaSphere와 겹치면 OnSphereOverlap()함수 Delegate호출
    		AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap); //무기 AreaSphere와 겹친게 해제되면 OnSphereEndOverlap()함수 Delegate호출
    	}
    	if (PickupWidget)
    	{
    		PickupWidget->SetVisibility(false); //PickupWidget을 꺼주고 시작한다.
    	}
    }
    
    void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
    	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    {
    	//ABaseCharacter* BaseCharacter = Cast<ABaseCharacter>(OtherActor);
    	TWeakObjectPtr<ABaseCharacter> BaseCharacter = Cast<ABaseCharacter>(OtherActor);
    	if (BaseCharacter.IsValid())
    	{
    		BaseCharacter->SetOverlappingWeapon(this);//캐릭터와 무기AreaSphere이 겹치면 무기정보 넘김
    	}
    }
    
    void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
    	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
    {
    	TWeakObjectPtr<ABaseCharacter> BaseCharacter = Cast<ABaseCharacter>(OtherActor);
    	if (BaseCharacter.IsValid())
    	{
    		BaseCharacter->SetOverlappingWeapon(nullptr);//캐릭터와 무기AreaSphere이 안 겹치면 nullptr
    	}
    }
    
    void AWeapon::SetWeaponState(EWeaponState State)
    {
    	WeaponState = State; //무기상태 변경
    
    	switch (WeaponState)
    	{
    	case EWeaponState::EWS_Equipped: //무기 장착상태 시
    		ShowPickupWidget(false); //PickupWidget 꺼줌
    		AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);//AreaSphere충돌x
    		break;
    	}	
    }
    
    void AWeapon::OnRep_WeaponState()
    {
    	switch (WeaponState)
    	{
    	case EWeaponState::EWS_Equipped: //무기 장착상태 시
    		ShowPickupWidget(false); //PickupWidget 꺼줌
    		break;
    	}
    }

     

    CombatComponent

     

    CombatComponent.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "Components/ActorComponent.h"
    #include "CombatComponent.generated.h"
    
    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
    class MULTIPLAYER_API UCombatComponent : public UActorComponent
    {
    	GENERATED_BODY()
    public:
    	UCombatComponent();
    	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    	friend class ABaseCharacter;
    
    	void EquipWeapon(class AWeapon* WeaponToEquip);
    
    protected:
    	virtual void BeginPlay() override;
    
    private:
    	class ABaseCharacter* Character;
    	AWeapon* EquippedWeapon;		
    };

     

    CombatComponent.cpp

    더보기
    #include "CombatComponent.h"
    #include "Multiplayer/Weapon/Weapon.h"
    #include "Multiplayer/Character/BaseCharacter.h"
    #include "Engine/SkeletalMeshSocket.h"
    #include "Components/SphereComponent.h"
    
    UCombatComponent::UCombatComponent(){
    	PrimaryComponentTick.bCanEverTick = false;	
    }
    
    void UCombatComponent::BeginPlay(){
    	Super::BeginPlay();
    }
    
    void UCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction){
    	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    }
    
    void UCombatComponent::EquipWeapon(AWeapon* WeaponToEquip)
    {
    	if (Character == nullptr || WeaponToEquip == nullptr) return;
    
    	EquippedWeapon = WeaponToEquip;
    	EquippedWeapon->SetWeaponState(EWeaponState::EWS_Equipped);//무기 상태를 Equipped(=장착)으로 변경
    	const USkeletalMeshSocket* HandSocket = Character->GetMesh()->GetSocketByName(FName("RightHandSocket"));//소켓을 변수로 저장
    	if (IsValid(HandSocket)) // 해당 소켓이 존재하면
    	{
    		HandSocket->AttachActor(EquippedWeapon, Character->GetMesh()); //무기를 해당 소켓에 붙여준다.
    	}
    	EquippedWeapon->SetOwner(Character); // 무기의 Owner을 Character로 설정
    }

     

    실행화면