필요한 개념
<무기 선택 UI>
메뉴를 클릭하면 전체적으로 게임이 느려지면서 UI창이 나옴, 아이템, 인벤토리 작업을 함
우리도 이런방식으로 UserWidget을 만들고, Ctrl키를 누르면 무기선택이 나옴, 마우스 클릭하면 무기 선택되서 교체됨
- ItemWidget을 만들고 Item을 깔아주는 List를 만듬
-> 마우스로 버튼을 클릭해서 선택하는 작업
* UserWidget을 상속받는 CUserWidget_ActionItem 클래스 생성
아이템이 보여지고 선택할 수 있는 위젯
* WB_ActionItem 생성
- CUserWidget_ActionItem으로 부모변경
DesiredOnScreen으로 실제 사이즈로 본다.
SizeBox : 사이즈를 조정하면 하위 것들도 전부 사이즈가 조절됨
Border를 추가하고, Border는 변수로 체크한다.
Overlay(오버레이) : 여러개의 이미지를 겹쳐서 올릴 수 있음
Image로 Background 해주고
클릭할 수 있도록 Button 하나 추가한다.(버튼 이미지는 아이템 이미지가 된다.)
* Button의 Style 설명
Normal : 평소 상태
Hoverd : 마우스가 올라갔을 때 상태
Pressed : 마우스로 클릭했을 때 상태
Disabled : 선택할 수 없을 때
* 중요
Button의 디테일->개입->IsFocusable 해제 필수
ImageButton의 Focusable을 해제해야 함
이렇게 해제함으로써 ActionList쪽에서 포커스를 받도록 처리해줘야 ActionList쪽의 클릭이 정상적으로 이루어짐
(반면, ActionList쪽에는 포커스를 활성화 시킴)
각 이미지를 정해준다.
DrawAs에는 Image로 설정해줘야 정상적인 텍스쳐 렌더링이 가능
똑같은 아이템 이미지를 설정해주고 Tint의 Alpha값을 조절해 누르거나, 올라갔을때의 느낌을 주면 된다.
ex) Noraml Tint Alpha : 0.5, Hoverd Tint Alpha : 0.75, Pressed Tint Alpha : 1.0을 주면 효과가 보인다.
이벤트 그래프에서 Button의 이벤트를 보면
On Clicked(눌렀다 떼었을때), On Pressed(누름 상태), On Released(키를 떼었을때), On Hoverd(마우스를 올려놓았을 때), On Unhorverd(영역을 벗어났을 때)가 있다.
이런것들을 CUserWidget_ActionItem에서 정의해줌
* CUserWidget_CActionItem에 추가
// Implement로 안하는 이유는 이미 거기에 정의되어있는 것을 호출만 해줄 거기때문에
// 그냥 BlueprintCallable(블루프린트에서 콜 가능하도록) 세팅
protected:
UFUNCTION(BlueprintCallable)
void Click();
UFUNCTION(BlueprintCallable)
void Hover();
UFUNCTION(BlueprintCallable)
void Unhover();
WB_ActionItem 이벤트 그래프를 보면 Button에서
OnClicked에서는 Click() 호출
OnHover에서는 Hover() 호출
OnUnhover에서는 Unhover() 호출
이렇게 해주면 CUserWidget_CActionItem에 있는 내용이 호출됨
* 부모(CUserWidget_CActionItem)의 함수들을 눌렀을 때 콜해줌
아이템을 선택하는 리스트를 만든다.
* UserWidget을 상속받는 CUserWidget_ActionList 클래스 생성
* WB_ActionList 생성
- 부모 CUserWidget_ActionList로 변경
- List 이용해서 그리드를 써봄
- 화면에 1대 1로 설정할 꺼여서 CanvasPanel 유지
- Overlay, Border 추가
Tip)
오버레이를 이용해서 FHD 해상도(1920x1080)로 크기를 맞춰준다.
뷰포트 게임 플레이에서는 화면 사이즈가 다르므로
정상적으로 채워지지 않을 가능성이 높다.
독립형이나 게임 빌드 결과로 보면 정상적으로 잘 나옴
- Grid를 깔기 위해 GridPanel 추가
나중에 리스트로 얻어와야 하니까 GridPanel은 변수로 체크
가운데 정렬 시키고, Fill Rules에서 행 2개 열 3개로 만듬
열, 행 안에 숫자는 여백을 조정하는 값이다.
우리가 만든 WB_ActionItem을 그리드 밑으로 추가시켜준다.
그러면 Grid에 추가됨
각 Item에 Row(행), Column(열)을 조절해서 그리드를 만들어준다.
총 2행 3열이 만들어짐
* Grid에 있는 애들을 다 찾아와서 이미지 연결을 해줄 것이다.
이벤트그래프의 Construct 이벤트에 세팅
이미지 텍스쳐 배열을 만들어서 각 텍스쳐를 세팅하고 그리드의 인덱스마다 해당 텍스쳐를 Normal, Hoverd, Pressed Texture에 세팅한다.
* 프로젝트 세팅 -> 입력 -> 위젯 띄우기 위한 액션 매핑 키 세팅
ViewActionList, 왼쪽 Ctrl
* CPlayer에서 위젯 생성
private:
// 액션 리스트 클래스 세팅가능하도록 설정
UPROPERTY(EditDefaultsOnly)
TSubclassOf<class UCUserWidget_ActionList> ActionListClass;
private:
class UCUserWidget_ActionList* ActionList;
// 생성자()
CHelpers::GetClass<UCUserWidget_ActionList>(&ActionList, "WidgetBlueprint'/Game/Widgets/WB_ActionList.WB_ActionList_C'");
// BeginPlay()
ActionList = CreateWidget<UCUserWidget_ActionList, APlayerController>(GetController<APlayerController>(), ActionListClass);
ActionList->AddToViewport();
ActionList->SetVisibility(ESlateVisibility::Hidden);
PlayerInputComponent->BindAction("ViewActionList", EInputEvent::IE_Pressed, this, &ACPlayer::OnViewActionList);
PlayerInputComponent->BindAction("ViewActionList", EInputEvent::IE_Released, this, &ACPlayer::OffViewActionList);
* 입력에 따른 위젯 Visibility 및 마우스 입력 세팅
void ACPlayer::OnViewActionList()
{
ActionList->SetVisibility(ESlateVisibility::Visible);
// 마우스 커서 보이게
GetController<APlayerController>()->bShowMouseCursor = true;
// UI 모드인지, 게임 모드인지
// SetInptuMode() : 게임모드인지 UI모드인지를 지정함
// 게임모드로 하면 포커스가 UI선택이 안되고, UI모드로 하면 게임쪽에서 마우스 선택이 안됨
// FInputModeGameAndUI() : 이것은 게임모드와 UI모드에서 전부 마우스 입력이 가능하도록 처리
GetController<APlayerController>()->SetInputMode(FInputModeGameAndUI());
// 10배정도 시간을 늦춤
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 0.1f);
}
void ACPlayer::OffViewActionList()
{
ActionList->SetVisibility(ESlateVisibility::Hidden);
GetController<APlayerController>()->bShowMouseCursor = false;
// FInputModeGameOnly() : 게임모드만 되도록
GetController<APlayerController>()->SetInputMode(FInputModeGameOnly());
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
}
* 결과를 보면 마우스로 위젯 클릭하면 클릭 이벤트가 없어져 꺼져버림(바로 작업할 것임)
ActionList에서 아이템을 선택할 수 있는 것을 구현해봄
* CUserWidget_ActionList에 추가
// 아이템을 클릭했을 때 호출될 델리게이트를 생성
// 어떤 무기를 선택해라 알려줄 애
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FUserWidget_ActionItem_Clicked);
USTRUCT(BlueprintType)
struct FUserWidget_Action_Data
{
GENERATED_BODY()
public:
class UCUserWidget_ActionItem* Item; // 아이템이 눌렸을때
FUserWidget_ActionItem_Clicked OnUserWidget_ActionItem_Clicked; // 어떤 이벤트를 발생하라
};
protected:
// BP의 Construct 이벤트 재정의하는 애
virtual void NativeConstruct() override;
public:
// ActionItem을 클릭 할시 여기로 넘어오게 만듬
void Clicked(FString InName);
void Hovered(FString InName);
void Unhovered(FString InName);
void UCUserWidget_ActionList::NativeConstruct()
{
// 그리드를 통해 아이템들을 찾아옴
// GetWidgetFromName() : 이름으로 위젯을 찾아옴
UGridPanel* gridPanel = Cast<UGridPanel>(GetWidgetFromName("Grid"));
// 위젯이 자손을 가질 수 없습니다.라고 뜨는 것들은 GetAllChildren()을 사용할 수 없음
TArray<UWidget*> widgets = gridPanel->GetAllChildren();
for (UWidget* widget : widgets)
{
FUserWidget_Action_Data data;
data.Item = Cast<UCUserWidget_ActionItem>(widget);
Datas.Add(data);
}
// BP에 있는 Constuct를 콜하는 거여서 맨 나중에
Super::NativeConstruct();
}
void UCUserWidget_ActionList::Hovered(FString InName)
{
for (const FUserWidget_Action_Data& data : Datas)
{
// ActionItem의 Border를 불러옴
UBorder* border = Cast<UBorder>(data.Item->GetWidgetFromName("BG_Border"));
// 이름이 같다면 선택된 것이다.
if (data.Item->GetName() == InName)
{
// 색상 변경
border->SetBrushColor(FLinearColor::Red);
break;
}
}
}
void UCUserWidget_ActionList::Unhovered(FString InName)
{
for (const FUserWidget_Action_Data& data : Datas)
{
UBorder* border = Cast<UBorder>(data.Item->GetWidgetFromName("BG_Border"));
if (data.Item->GetName() == InName)
{
// 원래 색상으로
border->SetBrushColor(FLinearColor::White);
break;
}
}
}
Tip) 클래스를 기본 클래스로 불러올때, 그 클래스를 수정하면 에디터 터짐
-> 그래서 기본 클래스로 불러오는 것들은 비어있도록 그냥 편집가능하도록 세팅해두는게 좋을 수 있다.
또는 Dynamic을 쓴다.
* CUserWidget_ActionItem에 추가
private:
// 액션리스트를 찾아서 가져옴
class UCUserWidget_ActionList* GetActionList();
* CUserWidget_ActionList는 Player에서 가지고 있기에 Player에서 리턴해주는 함수 세팅
- CPlayer에 추가
FORCEINLINE class UCUserWidget_ActionList* GetActionList() { return ActionList; }
* CUserWidget_ActionItem에 추가
UCUserWidget_ActionList* UCUserWidget_ActionItem::GetActionList()
{
ACPlayer* player = Cast<ACPlayer>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
return player->GetActionList();
}
void UCUserWidget_ActionItem::Click()
{
GetActionList()->Clicked(GetName());
}
void UCUserWidget_ActionItem::Hover()
{
GetActionList()->Hovered(GetName());
}
void UCUserWidget_ActionItem::Unhover()
{
GetActionList()->Unhovered(GetName());
}
* 결과를 보면 Horverd 하면 아이템의 Border(배경)이 적색으로 보임, Unhoverd 하면 다시 흰색으로 바뀜
* Click을 할 시 델리게이트에 바인딩된 함수들 콜해줌(클릭 하면 무기 선택 가능하도록 하기 위해)
void UCUserWidget_ActionList::Clicked(FString InName)
{
for (const FUserWidget_Action_Data& data : Datas)
{
// 이름이 같다면 선택된 것이다.
if (data.Item->GetName() == InName)
{
// 연결되어 있는 델리게이션을 콜해줌
if (data.OnUserWidget_ActionItem_Clicked.IsBound())
data.OnUserWidget_ActionItem_Clicked.Broadcast();
// 닫힘
SetVisibility(ESlateVisibility::Hidden);
UGameplayStatics::GetPlayerController(GetWorld(), 0)->bShowMouseCursor = false;
UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetInputMode(FInputModeGameOnly());
// 원래 게임 속도로
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
break;
}
}
}
* ActionItem의 Focus가 들어간게 아니라, ActionList에 포커스가 들어간 것임
-> 그래서 정상적으로 클릭이 잘 됨
<아이템 클릭에 따라 무기 선택하게 설정>
-> ActionList의 델리게이트에 함수를 바인딩 시켜줌
* Get함수 만들어서 Player에서 델리게이트에 함수 연결할 수 있도록 처리
* CUserWidget_ActionList에 추가
public:
FORCEINLINE FUserWidget_Action_Data& GetData(uint32 InIndex) { return Datas[InIndex]; }
CPlayer에 OnFist(), OnOneHand()....
각종 무기 장착 함수들을 만들어줬었다.
-> 그대로 바인딩 시켜주면 됨
* 델리게이트에 함수 바인딩 시키기 위해 UFUNCTION()을 붙여줌
- CPlayer 수정
private:
UFUNCTION()
void OnFist();
UFUNCTION()
void OnOneHand();
UFUNCTION()
void OnTwoHand();
UFUNCTION()
void OnWarp();
UFUNCTION()
void OnFireStorm();
UFUNCTION()
void OnIceBall();
// BeginPlay()
// 무기 교체
// ActionList 위젯에 클릭 처리 위해 델리게이트에 함수 바인딩
ActionList->GetData(0).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnFist);
ActionList->GetData(1).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnOneHand);
ActionList->GetData(2).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnTwoHand);
ActionList->GetData(3).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnWarp);
ActionList->GetData(4).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnFireStorm);
ActionList->GetData(5).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnIceBall);
* 실제 게임에서는 공격 중에 UI 선택이 되면 공격이 캔슬되고 무기 선택이 됨
그거 일일히 작업하기에는 양이 많아서, Idle모드일때만 ActionList 위젯을 켜줄 수 있도록 처리
// CPlayer의 OnViewActionList()
// Idle모드일때만
CheckFalse(State->IsIdleMode());
CUserWidget_ActionItem.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CUserWidget_ActionItem.generated.h"
UCLASS()
class UONLINE_04_ACTION_API UCUserWidget_ActionItem : public UUserWidget
{
GENERATED_BODY()
// Implement로 안하는 이유는 이미 거기에 정의되어있는 것을 호출만 해줄 거기때문에
// 그냥 BlueprintCallable(블루프린트에서 콜 가능하도록) 세팅
protected:
UFUNCTION(BlueprintCallable)
void Click();
UFUNCTION(BlueprintCallable)
void Hover();
UFUNCTION(BlueprintCallable)
void Unhover();
private:
// 액션리스트를 찾아서 가져옴
class UCUserWidget_ActionList* GetActionList();
};
CUserWidget_ActionItem.cpp
#include "CUserWidget_ActionItem.h"
#include "Global.h"
#include "Characters/CPlayer.h"
#include "CUserWidget_ActionList.h"
void UCUserWidget_ActionItem::Click()
{
GetActionList()->Clicked(GetName());
}
void UCUserWidget_ActionItem::Hover()
{
GetActionList()->Hovered(GetName());
}
void UCUserWidget_ActionItem::Unhover()
{
GetActionList()->Unhovered(GetName());
}
UCUserWidget_ActionList* UCUserWidget_ActionItem::GetActionList()
{
ACPlayer* player = Cast<ACPlayer>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
return player->GetActionList();
}
CUserWidget_ActionList.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CUserWidget_ActionList.generated.h"
// 아이템을 클릭했을 때 호출될 델리게이트를 생성
// 어떤 무기를 선택해라 알려줄 애
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FUserWidget_ActionItem_Clicked);
USTRUCT(BlueprintType)
struct FUserWidget_Action_Data
{
GENERATED_BODY()
public:
class UCUserWidget_ActionItem* Item; // 아이템이 눌렸을때
FUserWidget_ActionItem_Clicked OnUserWidget_ActionItem_Clicked; // 어떤 이벤트를 발생하라
};
UCLASS()
class UONLINE_04_ACTION_API UCUserWidget_ActionList : public UUserWidget
{
GENERATED_BODY()
public:
FORCEINLINE FUserWidget_Action_Data& GetData(uint32 InIndex) { return Datas[InIndex]; }
protected:
// BP의 Construct 이벤트 재정의하는 애
virtual void NativeConstruct() override;
public:
// ActionItem을 클릭 할시 여기로 넘어오게 만듬
void Clicked(FString InName);
void Hovered(FString InName);
void Unhovered(FString InName);
private:
TArray<FUserWidget_Action_Data> Datas;
};
CUserWidget_ActionList.cpp
#include "CUserWidget_ActionList.h"
#include "Global.h"
#include "CUserWidget_ActionItem.h"
#include "Components/GridPanel.h"
#include "Components/Border.h"
void UCUserWidget_ActionList::NativeConstruct()
{
// 그리드를 통해 아이템들을 찾아옴
// GetWidgetFromName() : 이름으로 위젯을 찾아옴
UGridPanel* gridPanel = Cast<UGridPanel>(GetWidgetFromName("Grid"));
// 위젯이 자손을 가질 수 없습니다.라고 뜨는 것들은 GetAllChildren()을 사용할 수 없음
TArray<UWidget*> widgets = gridPanel->GetAllChildren();
for (UWidget* widget : widgets)
{
FUserWidget_Action_Data data;
data.Item = Cast<UCUserWidget_ActionItem>(widget);
Datas.Add(data);
}
// BP에 있는 Constuct를 콜하는 거여서 맨 나중에
Super::NativeConstruct();
}
void UCUserWidget_ActionList::Clicked(FString InName)
{
for (const FUserWidget_Action_Data& data : Datas)
{
// 이름이 같다면 선택된 것이다.
if (data.Item->GetName() == InName)
{
// 연결되어 있는 델리게이션을 콜해줌
if (data.OnUserWidget_ActionItem_Clicked.IsBound())
data.OnUserWidget_ActionItem_Clicked.Broadcast();
// 닫힘
SetVisibility(ESlateVisibility::Hidden);
UGameplayStatics::GetPlayerController(GetWorld(), 0)->bShowMouseCursor = false;
UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetInputMode(FInputModeGameOnly());
// 원래 게임 속도로
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
break;
}
}
}
void UCUserWidget_ActionList::Hovered(FString InName)
{
for (const FUserWidget_Action_Data& data : Datas)
{
// ActionItem의 Border를 불러옴
UBorder* border = Cast<UBorder>(data.Item->GetWidgetFromName("BG_Border"));
// 이름이 같다면 선택된 것이다.
if (data.Item->GetName() == InName)
{
// 색상 변경
border->SetBrushColor(FLinearColor::Red);
break;
}
}
}
void UCUserWidget_ActionList::Unhovered(FString InName)
{
for (const FUserWidget_Action_Data& data : Datas)
{
UBorder* border = Cast<UBorder>(data.Item->GetWidgetFromName("BG_Border"));
if (data.Item->GetName() == InName)
{
// 원래 색상으로
border->SetBrushColor(FLinearColor::White);
break;
}
}
}
CPlayer.h 수정된 부분
...
UCLASS()
class UONLINE_04_ACTION_API ACPlayer : public ACharacter, public IICharacter
{
GENERATED_BODY()
...
public:
FORCEINLINE class UCUserWidget_ActionList* GetActionList() { return ActionList; }
private:
UFUNCTION()
void OnFist();
UFUNCTION()
void OnOneHand();
UFUNCTION()
void OnTwoHand();
UFUNCTION()
void OnWarp();
UFUNCTION()
void OnFireStorm();
UFUNCTION()
void OnIceBall();
...
void OnViewActionList();
void OffViewActionList();
private:
class UCUserWidget_ActionList* ActionList;
};
CPlayer.cpp 수정된 부분
...
ACPlayer::ACPlayer()
{
...
CHelpers::GetClass<UCUserWidget_ActionList>(&ActionListClass, "WidgetBlueprint'/Game/Widgets/WB_ActionList.WB_ActionList_C'");
}
void ACPlayer::BeginPlay()
{
...
ActionList = CreateWidget<UCUserWidget_ActionList, APlayerController>(GetController<APlayerController>(), ActionListClass);
ActionList->AddToViewport();
ActionList->SetVisibility(ESlateVisibility::Hidden);
// 무기 교체
// ActionList 위젯에 클릭 처리 위해 델리게이트에 함수 바인딩
ActionList->GetData(0).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnFist);
ActionList->GetData(1).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnOneHand);
ActionList->GetData(2).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnTwoHand);
ActionList->GetData(3).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnWarp);
ActionList->GetData(4).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnFireStorm);
ActionList->GetData(5).OnUserWidget_ActionItem_Clicked.AddDynamic(this, &ACPlayer::OnIceBall);
}
void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
PlayerInputComponent->BindAction("ViewActionList", EInputEvent::IE_Pressed, this, &ACPlayer::OnViewActionList);
PlayerInputComponent->BindAction("ViewActionList", EInputEvent::IE_Released, this, &ACPlayer::OffViewActionList);
}
void ACPlayer::OnViewActionList()
{
// Idle모드일때만
CheckFalse(State->IsIdleMode());
ActionList->SetVisibility(ESlateVisibility::Visible);
// 마우스 커서 보이게
GetController<APlayerController>()->bShowMouseCursor = true;
// UI 모드인지, 게임 모드인지
// SetInptuMode() : 게임모드인지 UI모드인지를 지정함
// 게임모드로 하면 포커스가 UI선택이 안되고, UI모드로 하면 게임쪽에서 마우스 선택이 안됨
// FInputModeGameAndUI() : 이것은 게임모드와 UI모드에서 전부 마우스 입력이 가능하도록 처리
GetController<APlayerController>()->SetInputMode(FInputModeGameAndUI());
// 10배정도 시간을 늦춤
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 0.1f);
}
void ACPlayer::OffViewActionList()
{
ActionList->SetVisibility(ESlateVisibility::Hidden);
GetController<APlayerController>()->bShowMouseCursor = false;
// FInputModeGameOnly() : 게임모드만 되도록
GetController<APlayerController>()->SetInputMode(FInputModeGameOnly());
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
}
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
<Unreal C++> 78 - Action RPG (Enemy Melee AI) (0) | 2022.06.07 |
---|---|
<Unreal C++> 73 - Action RPG (Inverse Kinemetics(IK)) (0) | 2022.06.07 |
<Unreal C++> 65 - Action RPG (Throw IceBall) (0) | 2022.05.30 |
<Unreal C++> 63 - Action RPG (FireStorm) (0) | 2022.05.30 |
<Unreal C++> 61 - Action RPG (Dead) (0) | 2022.05.30 |