ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 블루아카이브 루트슈터 팬게임 프로젝트(5) - 드래그 앤 드롭 기능
    루트슈터 프로젝트 2024. 1. 1. 00:51

    새해가 왔습니다. 여러분들 모두 행복하고 하는일마다 잘되기만 하는 2024년이 됐으면 좋겠습니다. 2023년의 마지막인 어제도 저는 하루종일 언리얼과 블렌더와 씨름하면서(..) 하루를 알차게 보냈습니다. 새해가 와도 저는 한동안 계속 이러고 있을것 같네요. 


    오늘 소개할 기능은 인벤토리의 드래그 앤 드롭 기능입니다. 인벤토리의 공간 정리라던가, 장비 장착및 해제를 위해서는 필수적인 기능입니다. 이 기능 또한 별빛상영관님의(https://starlight-showcase.tistory.com/41) 글을 참고하여 구현하였습니다. 이건 뭐 제가 혼자서 하는게 없군요(..) 

     

     

    UCLASS()
    class PROJECTBLUELANDS_API UInventoryDragDrop : public UDragDropOperation
    {
        GENERATED_BODY()
       

    public:
        int fromSlotNum; //드래그 당한 슬롯의 번호

        EItemType fromSlotItemType; //슬롯에 들어가있는 아이템 타입
        ESlot fromSlotType;

    };

     

    먼저 UDragDropOperation클래스를 상속받아 새로운 c++파일을 만들어 변수를 선언해줍니다. fromSlotNum은 드래그 당한 인벤토리의 번호이고 fromSlotItemType는 드래그 당한 슬롯의 아이템 타입을 가집니다. fromSlotType는 드래그 당한 슬롯의 타입입니다. 이 말의 의미는 제 인벤토리는 지금 총기류 장비칸과 인벤토리칸으로 나누어져 있는데 그 그분하기 위한 값을 가지는 ENUM 타입입니다. 

     

     

    UISlot.h

    protected: //드래그 처리에 대한 함수
        FReply NativeOnMouseButtonDown(const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) override; 
        void NativeOnDragDetected(const FGeometry & InGeometry, const FPointerEvent & InMouseEvent, UDragDropOperation *& OutOperation) override;
        bool NativeOnDrop(const FGeometry & InGeometry, const FDragDropEvent & InDragDropEvent, UDragDropOperation * InOperation) override;
        UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
        TSubclassOf<UUISlot> DragVisualClass; //마우스 드래그할때 보여지는 슬롯

     

    UISlot 헤더파일에 함수들을 추가로 선언해줍니다. 차례대로 설명하자면 

     

    NativeOnMouseButtonDown은 자신이 클릭받았을때(여기서는 슬롯이 클릭받았을때) 어떻게 할것인지 처리를 해주는 함수입니다.

    그리고 NativeOnDragDetected는 자신이 클릭받고 드래그까지 감지됐을때 어떻게 할 것인지에 대한 함수입니다.

    그리고 NativeOnDrop은 목적지에 드롭당했을때 어떻게 할것인지 입니다. 모두 이미 언리얼 내부에서 선언돼있는 함수이므로 오버라이드 해서 사용하시면 됩니다.

    DragVisualClass는 마우스로 드래그 할때 같이 움직이면서 보여줄 슬롯입니다. 

     

    UISlot.cpp

    FReply UUISlot::NativeOnMouseButtonDown(const FGeometry &MyGeometry, const FPointerEvent &MouseEvent)
    {
        FEventReply reply; //return 변수 선언
        reply.NativeReply = Super::NativeOnMouseButtonDown(MyGeometry, MouseEvent);

        if(MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton) == true) //오른쪽 마우스 버튼이 눌렸다면
        {
            //나중에 필요하다면 추가
        }
        else if(MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) == true) //왼쪽 마우스 버튼이 눌렸다면 드래그를 통해 장비교체
        {
            UE_LOG(LogTemp, Error, TEXT("Left mouse button down to drag"));
            if(slotItemType != EItemType::EIT_DEFAULT) //빈 슬롯을 클릭한것이 아니라면
            {
                reply = UWidgetBlueprintLibrary::DetectDragIfPressed(
                    MouseEvent, this, EKeys::LeftMouseButton); //마우스 버튼이 눌린상태로 드래그되는지 확인하는 함수
            }
        }
        return reply.NativeReply;
    }

     

    먼저 NativeOnMouseButtonDown 의 구현입니다. return해줄 FEventReply 변수를 선언해주고 마우스중에 어떤 마우스가 눌렸는지 확인해줍니다. 오른쪽 버튼은 저는 어떤 기능을 구현해야 할지 몰라서 아직 정해놓지 않았습니다. 대신 왼쪽 버튼이 눌렸을때 드래그 확인을 시작합니다. 빈 슬롯이 눌렸다면 ( EItemType::EIT_DEFAULT 라면) 드래그할 아이템이 없다는 뜻이니 반응할 필요가 없고, 드래그할 아이템이 있다면 DetectDragIfPressed 함수를 이용하여 드래그 되는지 확인합니다. 

     

    void UUISlot::NativeOnDragDetected(const FGeometry &InGeometry, const FPointerEvent &InMouseEvent, UDragDropOperation *&OutOperation)
    {
        Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);

        if(OutOperation == nullptr)
        {
            UE_LOG(LogTemp, Error, TEXT("Drag start"));

            UInventoryDragDrop* oper = NewObject<UInventoryDragDrop>();
            OutOperation = oper;
            oper->fromSlotNum = this->slotNum; //드래그 담당을 해줄 클래스에게 정보 넘겨주기
            oper->fromSlotItemType = this->slotItemType;
            oper->fromSlotType = this->slotType;

            if(DragVisualClass)
            {
                UUISlot* visual = CreateWidget<UUISlot>(Cast<APlayerController>(player->Controller), DragVisualClass);
                visual->slotItemType = this->slotItemType; //visual은 드래그 할때 보여질 슬롯, 그 정보를 클릭당한 슬롯에게서 복사하고 있다.
                visual->player = this->player;
                visual->slotNum = this->slotNum;
                visual->icon = this->icon;
                visual->backColor = this->backColor;
                visual->Refresh();
                oper->DefaultDragVisual = visual;
            }
            else UE_LOG(LogTemp, Error, TEXT("Drag failed!"));
        }
    }

     

      이제 드래그 될때 발동되는 함수입니다. 먼저 첫번째에 만들어준 UInventoryDragDrop 클래스의 객체를 하나 만들어 주고 OutOperation에 할당해줍니다. 그리고 드래그 당한 슬롯의 정보들을 하나하나 옮겨주고 있습니다. 

     

    그리고 드래그 할때 보여줄 Visual 슬롯도 정보를 갱신하여 OutOperation의 DefaultDragVisual에 할당시켜줍니다.

     

     

    bool UUISlot::NativeOnDrop(const FGeometry &InGeometry, const FDragDropEvent &InDragDropEvent, UDragDropOperation *InOperation) //드래그 하고 손을 뗏을때 발동
    {
        Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);

        UInventoryDragDrop* oper = Cast<UInventoryDragDrop>(InOperation);
        
        if(oper)
        {
            //플레이어가 인벤토리 교체작업 실시, 플레이어 클래스에서 함수
            player->DragSwap(oper->fromSlotNum, oper->fromSlotItemType, oper->fromSlotType, this->slotNum, this->slotItemType, this->slotType);
            UE_LOG(LogTemp, Error, TEXT("Drag 성공하여 %d에서 %d번으로 교체작업 실시!"), oper->fromSlotNum, this->slotNum);
            this->Refresh();
            return true;
        }
        else
        {
            UE_LOG(LogTemp, Error, TEXT("Drag 실패!"));
            return false;
        }
    }

     

    그리고 NativeOnDrop 함수입니다. 드래그 후 목적지에서 손을 떼면 발동하는 함수입니다. 

     

    방금 위의 함수에서 얻은 OutOperation을 여기서 InOperation에서 받아왔으므로 캐스팅을 해줍니다. 그리고 받은 정보를 바탕으로 플레이어가 인벤토리 교체 과정을 해주면 됩니다. 교체를 해주고 슬롯을 Refresh를 해주는것도 잊으면 안되겠죠. 이 함수는 드래그로 하려고 한 행동이 성공했냐 실패했냐를 bool로 반환해야 합니다. 

     

    void APlayerCharacter::DragSwap(int fromSlotNum, EItemType fromSlotItemType,
        ESlot fromSlotType , int toSlotNum, EItemType slotItemType, ESlot toSlotType) //인벤토리 드래그 스왑 처리 함수
    {
        if(fromSlotType == ESlot::ES_INV && toSlotType == ESlot::ES_INV) //인벤토리에서 인벤토리라면 그냥 바꿔줌
        {
            FItemData temp = itemInventory[toSlotNum];
            itemInventory[toSlotNum] = itemInventory[fromSlotNum];
            itemInventory[fromSlotNum] = temp;
           
            RefreshInventory(); //인벤토리 초기화
            UE_LOG(LogTemp, Error, TEXT("인벤에서 인벤으로 옮기기 완료!"));
        }
        else if(fromSlotType == ESlot::ES_INV && toSlotType == ESlot::ES_GUNEQ)//인벤에서 총 장비창으로 드래그
        {
            if(fromSlotItemType == slotItemType || slotItemType == EItemType::EIT_DEFAULT) //아이템 종류가 같거나 총장비창이 비어있다면 스왑
            {
                FItemData temp = gunSlot[toSlotNum];
                gunSlot[toSlotNum] = itemInventory[fromSlotNum];
                itemInventory[fromSlotNum] = temp;
                RefreshInventory(); //인벤토리 초기화
                UE_LOG(LogTemp, Error, TEXT("인벤에서 총 장비창으로 옮기기 완료!"));
            }
        }else if(toSlotType == ESlot::ES_INV && fromSlotType == ESlot::ES_GUNEQ)//총 장비창에서 인벤으로 드래그
        {
            if(fromSlotItemType == slotItemType || slotItemType == EItemType::EIT_DEFAULT) //아이템 종류가 다르다면 스왑안됨!!
            {
                FItemData temp = itemInventory[toSlotNum];
                itemInventory[toSlotNum] = gunSlot[fromSlotNum];
                gunSlot[fromSlotNum] = temp;
                RefreshInventory(); //인벤토리 초기화
                UE_LOG(LogTemp, Error, TEXT("총 장비창에서 인벤으로 옮기기 완료!"));
            }
        }  
        return;
    }

     

    뭔가 많이 복잡한 swap 함수입니다. 여기는 좀 구조를 개편할 필요가 있어보이는데요, 여기는 인벤토리칸에서 인벤토리 칸으로 드래그 한 경우나 인벤토리칸에서 장비창으로, 장비창에서 인벤토리로 드래그 한 경우를 다 나누어서 검증 후 조건이 맞다면 서로의 데이터를 교환해줍니다. 단순하지만 경우의 수가 많아서 이렇게 코드가 길어지고 말았습니다....

     

     

     

    인벤토리에 있던 파란색 장비 아이템을 밑으로 옮겨봤습니다. 장비칸으로 옮겨봤을때도 잘 옮겨지네요. 
    이만 줄이겠습니다. 다음은 아마도 툴팁 기능을 소개할 것 같네요. 즐거운 새해되세요 

Designed by Tistory.