목차

     

     


     

     

    무기 줍기

     


     

    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로 설정
    }

     

    실행화면