[59. Attaching to Sockets]

 

캐릭터의 손에 무기를 쥐어줄 시간이다.

 

스켈레톤에서 RightHand에 소켓을 추가하고 무기 하나를 프리뷰 해서 위치를 적절히 조절한다.

공격 애니메이션을 확인하면서 각도를 조정해주면 조금 더 자연스럽게 나온다.

 

무기도 아이템의 일종이므로 Item을 상속받는 C++ 클래스를 생성한다.

 

// Weapon.h
public:
    virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override;
    virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) override;
    void Equip(class AMain* Char);
    
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="SkeletalMesh")
    class USkeletalMeshComponent* SkeletalMesh; // Item의 StaticMesh는 사용하지 않는다
    
// Weapon.cpp
#include "Engine/SkeletalMeshSocket.h"

AWeapon::AWeapon()
{
    SkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMesh"));
    SkeletalMesh->SetupAttachment(GetRootComponent());
}

void AWeapon::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);

    if (OtherActor)
    {
        AMain* Main = Cast<AMain>(OtherActor);

        if (Main)
        {
            Equip(Main);
        }
    }
}

void AWeapon::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
}

void AWeapon::Equip(AMain* Char)
{
    if (Char)
    {
        SkeletalMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
        SkeletalMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);

        SkeletalMesh->SetSimulatePhysics(false);

        const USkeletalMeshSocket* RightHandSocket = Char->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
        if (RightHandSocket)
        {
            RightHandSocket->AttachActor(this, Char->GetMesh());
            bRotate = false;
        }
    }
}

// Main.h
public:
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Items")
    class AWeapon* EquippedWeapon;

    FORCEINLINE void SetEquippedWeapon(AWeapon* WeaponToSet) { EquippedWeapon = WeaponToSet; }

 

추가로 Item에서 Destroy 되는 부분을 다른 클래스로 옮겨주어야 무기가 사라지지 않는다.

 

Weapon 클래스를 블루프린트 클래스로 만들고 메시를 설정해준 뒤 월드에 배치하면 무기 습득시 캐릭터의 손에 장착이 된다.

 

[60. Weapon Equipping]

 

바닥에 떨어진 무기를 획득했을 때 바로 장착하는게 아닌 선택권을 주려고 한다.

나중에 무기뿐만 아니라 방어구나 다른 습득 아이템도 장착의 선택권을 주기 위해 습득한 아이템을 Item의 포인터 타입으로 받아서 처리하도록 한다.

 

// Main.h
public:
    bool bLMBDown;
    void LMBDown();
    void LMBUp();
    
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Items")
    class AItem* ActiveOverlappingItem;

    FORCEINLINE void SetActiveOverlappingItem(AItem* Item) { ActiveOverlappingItem = Item; }

// Main.cpp
#include "Weapon.h"

void AMain::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    PlayerInputComponent->BindAction("LMB", IE_Pressed, this, &AMain::LMBDown);
    PlayerInputComponent->BindAction("LMB", IE_Released, this, &AMain::LMBUp);
}

void AMain::LMBDown()
{
    bLMBDown = true;

    if (ActiveOverlappingItem)
    {
        AWeapon* Weapon = Cast<AWeapon>(ActiveOverlappingItem);
        if (Weapon)
        {
            Weapon->Equip(this);
            SetActiveOverlappingItem(nullptr);
        }
    }
}

void AMain::LMBUp()
{
    bLMBDown = false;
}

 

// Weapon.h
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Item | Sound")
    class USoundCue* OnEquipSound;

// Weapon.cpp
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"

void AWeapon::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);

    if (OtherActor)
    {
        AMain* Main = Cast<AMain>(OtherActor);

        if (Main)
        {
            Main->SetActiveOverlappingItem(this);
        }
    }
}

void AWeapon::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);

    if (OtherActor)
    {
        AMain* Main = Cast<AMain>(OtherActor);

        if (Main)
        {
            Main->SetActiveOverlappingItem(nullptr);
        }
    }
}

void AWeapon::Equip(AMain* Char)
{
    if (Char)
    {
        SkeletalMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
        SkeletalMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);

        SkeletalMesh->SetSimulatePhysics(false);

        const USkeletalMeshSocket* RightHandSocket = Char->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
        if (RightHandSocket)
        {
            RightHandSocket->AttachActor(this, Char->GetMesh());
            bRotate = false;
            Char->SetEquippedWeapon(this);
            Char->SetActiveOverlappingItem(nullptr);
        }
        if (OnEquipSound) UGameplayStatics::PlaySound2D(this, OnEquipSound);
    }
}

 

[프로젝트 세팅 - 입력] 에서 마우스 좌클릭을 액션 매핑 LMB로 설정한다.

이제 더이상 무기와 겹침판정 즉시 획득되지 않고 무기와 겹친 상태에서 좌클릭을 눌러야 획득이 된다.

 

 

땅에 떨어진 장비에 가시성을 더하기 위해 파티클이나 아웃라인이 그려진 경우들이 많다. 파티클로 가시성을 더해주고 습득 시 파티클이 비활성화 되는 기능을 추가해 보도록 한다.

 

// Weapon.h

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item | Particles")
    bool bWeaponParticles;
    
// Weapon.cpp
#include "Particles/ParticleSystemComponent.h"

AWeapon::AWeapon()
{
    bWeaponParticles = false;
}
void AWeapon::Equip(AMain* Char)
{
    if (Char)
    {
        if (!bWeaponParticles)
        {
            IdleParticlesComponent->Deactivate();
        }
    }
}

 

파티클을 설정해주고 무기 습득시 파티클 재생이 중지된다. 

 

[61. Weapon Equipping #2]

 

무기를 습득한 상태에서 아무것도 하지 않아도 오브젝트에 피해를 주고 다니는 것은 의도한 사항이 아니다.

오직 공격할 때만 피해를 주고 싶기 때문에 무기에 상태를 설정하여 관리한다.

 

// Weapon.h

UENUM(BlueprintType)
enum class EWeaponState : uint8
{
    EWS_Pickup		UMETA(DisplayName="Pickup"),
    EWS_Equipped	UMETA(DisplayName="Equipped"),

    EWS_MAX			UMETA(DisplayName="DefaultMax")
};

public:
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Item")
    EWeaponState WeaponState;

    FORCEINLINE void SetWeaponState(EWeaponState State) { WeaponState = State; }
    FORCEINLINE EWeaponState GetWeaponState() { return WeaponState; }
    
// Weapon.cpp

AWeapon::AWeapon()
{
    WeaponState = EWeaponState::EWS_Pickup;
}

void AWeapon::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
    
    // 무기가 픽업 가능한 상태일때 && 겹침 대상이 있을 때
    if (WeaponState == EWeaponState::EWS_Pickup && OtherActor)
    {
        AMain* Main = Cast<AMain>(OtherActor);

        if (Main)
        {
            Main->SetActiveOverlappingItem(this);
        }
    }
}

 

차후 구현을 위해 우선 선언 및 정의를 해둔다.

 

우선 한 가지 구현해야 할 사항은 무기를 습득하여 들고 있는 상태에서 새로운 무기를 습득 시 기존 무기는 파괴시키는 방식을 선택할 것이다.

 

 

// Main.h
public:
    void SetEquippedWeapon(AWeapon* WeaponToSet);

// Main.cpp
void AMain::SetEquippedWeapon(AWeapon* WeaponToSet)
{
    // 장착중인 무기가 있다면
    if (EquippedWeapon)
    {
        // 해당 무기를 파괴한다
        EquippedWeapon->Destroy();
    }

    EquippedWeapon = WeaponToSet;
}

 

무기 몇개를 월드에 더 배치하고 하나를 습득한 상태에서 다른 무기를 습득하여 장착 시, 기존 무기는 파괴되고 새로운 무기로 변경이 된다.

여기서 유의할 점은 파괴가 일어난다고 해도 항상 즉시 소멸되는것은 아니고 삭제된 오브젝트로써 남아있다가 GC에 의해 특정 순간 완전히 소멸된다.

 

[정리]

 

  • 스켈레톤에 소켓을 추가하여 액터를 붙일 수 있다.
  • 오브젝트의 소멸을 호출하더라도 즉시 소멸되지 않고 GC에 의해 특정 시간마다 완전히 소멸된다.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Combat #3  (0) 2022.09.11
[UE C++] Combat #2  (0) 2022.09.11
[UE C++] Gameplay Mechanics #7  (0) 2022.09.09
[UE C++] Gameplay Mechanics #6  (0) 2022.09.09
[UE C++] Gameplay Mechanics #5  (0) 2022.09.08

+ Recent posts