https://benui.ca/unreal/common-ui-intro/

 

Common UI Introduction

What is the plugin for? What does it contain? Why should we care?

benui.ca

https://benui.ca/unreal/common-ui-button/

 

Common UI Button Widgets

Centralized styling, selected state, and more!

benui.ca

https://x157.github.io/UE5/CommonUI/

 

Common UI Plugin

Overview of the Common UI plugin for Unreal Engine 5

x157.github.io

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-the-common-bound-action-bar-in-unreal-engine


  • Common UI에서 제공하는 class들에 대한 간단한 용도 설명
  • 실질적인 코드는 Engine/Plugin/Runtime/CommonUI/Source/CommonUI/Public/ 경로에서 확인 가능

CommonUserWidget

  • CommonUI에서 사용하는 BaseWidget

CommonActionWidget

  • Platform에 따라 다르게 표현되는 Icon을 표시하고 이에 대한 Input Action을 처리하는 Widget

CommonActivatableWidgetSwitcher

  • Lifetime 중에 Activate/Inactivate 될 수 있는 Widget
    • 하지만 이 외에 수정되거나 파괴되지는 않는다.
  • 보통은 다음 기능을 제공한다.
    • 화면 상으로 빈번하게 사용되기 때문에 Construct/Destruct만으로 대응이 힘든 상황
      • Hierarchy에 미리 생성해두고 Active/Inactive를 조절하여 사용
      • ex) 모바일에서 조건부로 나타나는 입력 키
    • 현재 Widget에서 뒤로가기를 해야 하는 경우
      • 뒤로 가는 경로를 따라가는 경우
      • 모달을 닫는 경우
    • 이 Widget은 Hierarchy에서 Input을 Routing하는 ActivatableWidget Tree에서 하나의 Node로 작용한다.
  • 해당 Class는 다음 기본 설정이 되어 있다.
    • 생성 시 자동으로 Activate되지 않는다.
    • 뒤로가기, 앞으로가기 동작을 수신하도록 등록되어 있지 않다.
    • 뒤로가기 Handler로 구분되어 있을 시, 뒤로가기 Action을 수신하면 자동으로 Deactivate된다.
  • ActivatableWidget을 UI로부터 제거하는 것은 UWidget이 제거되지 않았더라도 항상 Widget을 Deactivate한다.
    • 이 때 AutoActivate가 가능하다면,
      기본 SWidget을 다시 생성하는 것만이 유일하게 다시 Widget을 Activate하는 방법이다.

CommonAnimatedSiwtcher

  • CommonActivatableWidget 전용 Widget Switcher
  • 관련 Animation을 Trigger 하는 기능을 제공한다.

CommonActivatableWidgetSwitcher

  • CommonActivableWidgets의 기능을 보존하는 Animation Switcher
  • 다른 Widget도 포함될 수 있다.

CommonBorder

  • CommonUI Project에서 Default로 설정된 Border Style Template

CommonButtonBase

  • CommonUI에서 제공하는 Customized Button
  • Widget 전체를 Disable하지 않고 Click을 Disable 할 수 있다.

CommonCustomNavigation

  • CommonUI에서 제공하는 Customized Border
  • Custom Behavior로 Default Border Navigation을 대체할 수 있는 Event를 노출시킨다.

CommonTextBlock

  • CommonUI에서 제공하는 Customized TextBlock
  • 다음 기능들을 기본적으로 제공한다.
    • FX를 이용해 자동 스크롤링
    • Large Text
    • 더 많은 Styling Option 제공
    • Mobile 플랫폼에서의 Custom Scaling

CommonDateTimeTextBlock

  • DateTime이나 TimeSpan을 직접 입력받아 날짜/시간 정보를 Text로 표시해주는 CommonTextBlock

CommonNumericTextBlock

  • Numeric 값을 입력 받아 Text로 표시해주는 CommonTextBlock
  • 다음 기능들을 제공한다.
    • 근사(반올림/올림/내림)
    • 숫자 표시
    • 퍼센티지 표시
    • 초 단위 표시
    • 거리 표시

CommonRichTextBlock

  • CommonUI에서 제공하는 Customized RichTextBlock
  • 다음 기능들을 제공한다.
    • Mobile 플랫폼에서의 Custom Scaling
    • 공간이 부족할 때 Icon만 표시하는 옵션

CommonUIRichTextData

  • RichTextData를 구현하는 Class
  • Project Settings -> Common UI Setting에서 등록할 수 있다.

CommonHardwareVisibilityBorder

  • Platform이나 Input에 따라 Visbility를 조절하는 기능을 제공하는 CommonBorder

CommonHierarchicalScrollBox

  • CommonUI에서 제공하는 Customized ScrollBox
  • 임의의 Scroll이 가능한 Widget Collection
  • 10~100개 정도를 표시하는데 적절하다.
  • Virtualization을 제공하지 않는다.
    • Virtualization: List 등에서 화면에 표시되는 영역의 Widget만 Memory에서 들고 있는 기능

CommonLazyImage

  • CommonUI에서 제공하는 Customized Image
  • Image Resource가 Load되지 않을 때 미리 지정 된 Unloaded Image를 표시해주는 Widget
  • SLoadGuard의 또 다른 Wrapper이지만, Image Loading과 Loading 중 Throbber만 관리한다.
  • 만약 이 Class가 Text를 표시하도록 바뀌면, 기본적으로 CoreStyle을 유지하게 된다.

CommonLazyWidget

  • Widget이 Load되지 않을 때 미리 지정된 Unloaded Widget을 표시해주는 Widget

CommonListView

  • CommonUI에서 제공하는 Customized ListView

CommonNativeListItem

  • 모든 UMG ListView Class에서 BaseItem으로 사용될 수 있는 Native non-UObject Item

CommonLoadGuard

  • CommonUI에서 제공하는 Customized ContentWidget
    • Border와 비슷하게 동작한다.
    • 필요한 Contents가 Load되거나 모종의 준비가 끝날 때까지 기본 Contents를 숨기고
      Loading Spinner와 Message를 출력할 수 있다
  • GuardAndLoadAsset 함수를 사용하면 자신이 Load 될 때까지 Loading 상태를 자동으로 표시할 수 있다.
  • 수동으로 Guard의 Loading State를 설정할 수 있다.
    • 예를 들어 Async Backend call이 종료될 때까지 대기 한다던가.

 

CommonPoolableWidgetInterface

  • WidgetFactory가 Widget Object 재사용을 구현할 경우에 Widget Pool 기능을 제공하는 Interface

CommonRotator

  • 주어진 Text Label을 순회하는 기능을 제공하는 CommonButtonBase
    • Slide Banner처럼 Text를 Shift 할 수 있음

CommonTabListWidgetBase

  • Selectable Tab 기능을 제공하는 Base Class
  • 다음 기능들을 제공한다. 
    • 연관된 Tab을 Activate
    • 연결된 Switchdr에서 연관된 Tab을 표시해준다.

CommonTileView

  • CommonUI에서 제공하는 Customized TileView
  • 다음 기능들을 제공한다.
    • Consol에서 Focus Navigation에 특화 됨.
    • Touch로 Focus되지 않을 경우에 Scroll을 제공

CommonTreeView

  • CommonUI에서 제공하는 TreeView
  • 다음 기능들을 제공한다.
    • Consol에서 Focus Navigation에 특화 됨.
    • Touch로 Focus되지 않을 경우에 Scroll을 제공

CommonVideoPlayer

  • CommonUI에서 제공하는 Media 재생 용 Widget

CommonVisibilitySwitcher

  • CommonUI에서 제공하는 Customized Overlay
    • Child Widget들의 Visibility를 Toggle하여 한번에 1개의 Widget만 표시하는 Siwtcher의 Base Class
    • 표시되는 Widget이 ActivatableWidget인 경우, 해당 Widget을 Activate한다.

CommonVisibilitySwitcherSlot

  • CommonUI에서 제공하는 Customized OverlaySlot

CommonvisualAttachment

  • CommonUI에서 제공하는 Customized SizeBox
  • Widget을 다른 Widget에 Zero-Size로 부착할수 있도록 한다.
    • 예를 들어 Icon을 Label에 추가하되, Label의 사이즈가 변하지 않도록 한다던가

CommonWidgetCarousel

  • CommonUI에서 제공하는 Customized PanelWidget
  • 최대 하나의 Widget만 표시되는 Widget
    • Carousel로 미루어 보아 SlideBanner의 Widget 버전으로 추정

CommonWidgetCarouselNavBar

  • CommonWidgetCarousel의 Navigation control을 담당하는 Widget

CommonButtonTableRow

  • CommonButtonBase를 인식할 수 있는 CommonUI 버전의 Object Table Row
  • Mouse Event를 직접적으로 처리하는 대신, 그 자체가 Button이 되어 Event에 반응한다.

CommonHierarchicalScrollBox

  • CommonUI에서 제공하는 Customized ScrollBox
  • 임의의 숫자의 Widget을 Scroll 할 수 있다.

CommonTileView

  • CommonUI에서 제공하는 Customzied TileView

'UE5 > UI' 카테고리의 다른 글

[UI] Common UI FAQ  (0) 2024.07.10
[UI] CommonUI Technical Guide  (0) 2024.07.10
[UI] Common UI Introduction  (0) 2024.07.10
[UI] UMG ViewModel  (0) 2024.07.08
[UI] Optimization  (0) 2024.07.01

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/common-ui-plugin-for-advanced-user-interfaces-in-unreal-engine

https://velog.io/@yoo06/%EC%96%B8%EB%A6%AC%EC%96%BC-%EC%97%94%EC%A7%84-UE5-Common-UI-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

 

언리얼 엔진 UE5 Common UI 알아보기

멀티플랫폼에서 UI를 생성하는 CommonUI 알아보기

velog.io

https://ctdidier27.medium.com/common-ui-plugin-ue5-b12050bf1fc0

 

Common UI Plugin UE5

Go to Edit>plugins>Common UI and check and restart Unreal.

ctdidier27.medium.com

https://dev.epicgames.com/community/learning/tutorials/BKJ7/unreal-engine-common-ui-tutorial-create-cross-platform-ui-easily-with-this-new-ue5-system

  • 화면을 그대로 유지하면서 Overlay 되는 UI를 노출하고, 화면입력을 막고 싶은 경우
  • 플랫폼 별로 UI Element를 다르게 관리하고 싶을 때
  • Popup들의 버튼을 누를 시 UI가 특정 상태가 되도록 흐름을 제어하고 싶을 때
  • 키보드나 콘솔 컨트롤러의 방향키로 UI 선택을 이동시키고 싶을 때(Cardinal Navigation)

핵심 개념

Input Routing

  • Selective Interaction을 구현하기 위해 채택한 방식
    • 입력을 수신하는 조건과 시기를 컨트롤 할 수 있다.
  • 예를 들어, 서로 다른 Widget에 버튼 별 Input을 분배해서 배치할 수 있다.
    • 이는 입력이 각 Widget에서 처리되는 것이 아니라 공용 Class에 전달되어 일괄처리 되기 때문이다.

Node

  • Common UI는 Widget을 Node로 변환해 Visual Hierarchy에 따라 상위에 Rendering 된 Widget의 입력을 Route한다.

  • 몇 가지 예외를 제외하고, 대부분의 Common UI는 Slate의 Hierarchy와 동일하게 구성된다.
    • 각 Tree는 Viewport에 직접 배치 된 Widget을 Root Node로,
      Button과 같은 개별 UI Element를 Leaf Node로 취급하여 구성된다.
  • Common UI는 Tick당 1번씩 다른 Tree보다 상단에 Render 된 Tree를 탐색해 Root Node로 Input을 Route한다.
  • 그럼 Root Widget은 Input을 처리할 수 있는 첫번째 Leaf Node에 입력을 전달한다.
  • 입력을 전달 받은 Leaf Node는 이를 처리하거나, 필요하다면 다른 곳으로 재전달한다.

ActivatableWidget

  • Common UI 중 Input 처리를 위해 Node로 변환되고, 이를 수신할 수 있는 Widget
    • Input 수신 시 Active 상태로 간주한다.
  • Activatable Widget은 다음 기능을 제공한다.
    • Input 수신 가능 여부(Active/Inactive) 상태 토글
    • 같은 Tree 내 다른 Activatable Widget에 Input 전달
    • 뒤로가기 등 특정 상황에서 Inactivate
  • 이 Widget을 이용해 현재 Input을 수신중인 Overlay UI가 닫힐 경우,
    적절한 Element로 복구해주는 기능을 작업할 수 있다.
    • Input은 항상 최상단에 Layer에만 Route 되기에 하단 Layer의 Widget도 문제 없이 Active 상태로 둘 수 있다.
    • 이 때 상단 Layer가 닫히면 자연스럽게 하단 Layer에 Input이 Route 된다.

사용법

Viewport Configure

  • Edit -> Project Settings -> Engine -> General Settings으로 이동
  • Game Viewport Client Class를 CommonGameViewportClient 혹은 이를 상속받은 Custom Class로 설정

Create InputActionDataTable

  • 한가지 잊지 말아야 할 점이 있다.
    • CommonUI InputAction DataTable은 Project Setting, Advanced Input System에 사용하는 것과 무관하다.
    • 오직 UI Input 관리에만 사용된다.

  • Content Browser 영역 우클릭 -> Miscellaneous -> CommonUI ActionInput Data Table선택

  • 위와 같이 Row를 추가해 Input을 설정한다.

  • 이렇게 추가된 InputAction은 CommonUI Widget에서 Mapping할 수 있다.
    • 위 사진은 CommonButtonBase의 Class Defaults에서 DataTable과 RowName을 통해
      InputAction을 Button에 Mapping 한 예시이다.
  • 좀 더 용이하게 관리하려면 관련된 Action들을 하나의 DataTable에서 작업하여 그룹화 하는 것이 좋다.

CommonInputActionDataBase

더보기
USTRUCT(BlueprintType)
struct COMMONUI_API FCommonInputActionDataBase : public FTableRowBase
{
	GENERATED_BODY()

	FCommonInputActionDataBase();
	
	/** User facing name (used when NOT a hold action) */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "CommonInput")
	FText DisplayName;

	/** User facing name used when it IS a hold action */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "CommonInput")
	FText HoldDisplayName;

	/** Priority in nav-bar */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "CommonInput")
	int32 NavBarPriority = 0;

protected:
	/**
	* Key to bind to for each input method
	*/
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	FCommonInputTypeInfo KeyboardInputTypeInfo;

	/**
	* Default input state for gamepads
	*/
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	FCommonInputTypeInfo DefaultGamepadInputTypeInfo;

	/**
	* Override the input state for each input method
	*/
	UPROPERTY(EditAnywhere, Category = "CommonInput", Meta = (GetOptions = "CommonInput.CommonInputBaseControllerData.GetRegisteredGamepads"))
	TMap<FName, FCommonInputTypeInfo> GamepadInputOverrides;

	/**
	* Override the displayed brush for each input method
	*/
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	FCommonInputTypeInfo TouchInputTypeInfo;
};
  • DisplayName
    • 해당 InputAction의 이름
    • Navigation Bar가 있는 경우 해당 Bar에 표시된다.
  • HoldDisplayName
    • 버튼 Hold 동작이 필요한 InputAction의 이름
  • NavBarPriority
    • Navibation Bar Action을 왼쪽에서 오른쪽으로 Sort할 때 사용하는 Priority
  • KeybaordInputTypeInfo
    • 마우스 및 키보드 Action의 InputType
  • DefaultGamepadInputTypeInfo
    • Gamepad Action의 InputType
  • GamepadInputOverrides
    • 특정 Gamepad에서 해당 Action에 사용되는 키
    • 콘솔 플랫폼 별 버튼 Override에 유용하다.
      • Nintendo Switch Gamepad의 앞뒤 버튼을 바꾸는 것 
  • TouchInputTypeInfo
    • Touch Interface Action의 InputType

CommonInputTypeInfo

더보기
USTRUCT(BlueprintType)
struct COMMONUI_API FCommonInputTypeInfo
{
	GENERATED_USTRUCT_BODY()

	FCommonInputTypeInfo();
private:
	/** Key this action is bound to	*/
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	FKey Key;
public:

	/** Get the input type key bound to this input type, with a potential override */
	FKey GetKey() const;

	/** Get the input type key bound to this input type, with a potential override */
	void SetKey(FKey InKey)
	{
		Key = InKey;
	};

	/** EInputActionState::Enabled means that the state isn't overriden and the games dynamic control will work */
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	EInputActionState OverrrideState;

	/** Enables hold time if true */
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	bool bActionRequiresHold;

	/** The hold time in seconds */
	UPROPERTY(EditAnywhere, Category = "CommonInput", meta = (EditCondition = "bActionRequiresHold", ClampMin = "0.0", UIMin = "0.0"))
	float HoldTime;

	/**
	*	Time (in seconds) for hold progress to go from 1.0 (completed) to 0.0.
	*	If the hold interaction was interrupted, then hold progress starts to roll back decreasing its value.
	*	Set to 0.0 to disable the rollback functionality.
	*/
	UPROPERTY(EditAnywhere, Category = "CommonInput", meta = (EditCondition = "bActionRequiresHold", ClampMin = "0", UIMin = "0", ClampMax = "10.0", UIMax = "10"))
	float HoldRollbackTime;
	
	/** Override the brush specified by the Key Display Data  */
	UPROPERTY(EditAnywhere, Category = "CommonInput")
	FSlateBrush OverrideBrush;
};
  • Key
    • Bind 될 Input Key
  • OverrideState
    • Input 및 Callback 호출 여부의 상태를 담당
  • bActionRequiredsHold
    • Input이 Hold로 작동되어야 하는지 여부
  • HoldTime
    • Input이 Hold로 작동 되어야 할 때, Hold해야 하는 시간
  • HoldRollbackTime
    • Hold가 작동하고 나서 완전히 비활성화 상태로 되돌아갈 때까지의 시간.
  • OverrideBrush
    • Key Display Data에서 정의된 Brush로 Override

EInputActionState

더보기
UENUM(BlueprintType)
enum class EInputActionState : uint8
{
	/** Enabled, will call all callbacks */
	Enabled,
	/** Disabled, will call all the disabled callback if specified otherwise do nothing */
	Disabled,
	
	/** The common input reflector will not visualize this but still calls all callbacks. NOTE: Use this sparingly */
	Hidden,
	
	/** Hidden and disabled behaves as if it were never added with no callbacks being called */
	HiddenAndDisabled,	
};
  • Enabled
    • Input이 활성화 되어 있음
    • Callback이 호출 됨
  • Disabled
    • Disable Callback이 선언되어 있는 경우 이를 호출 함
    • 그 외의 모든 Callback이 반응하지 않음
  • Hidden
    • Reflector가 시각적으로 보이지는 않지만 Callback이 호출 됨.
    • 자주 사용하지 않을 것을 권장 함.
  • HiddenAndDisabled
    • Reflector가 시각적으로 보이지 않고, Callback도 동작하지 않음.

Default Navigation Action Configure

  • Unreal Engine에서 Native Navigation을 지원하지만,
    CommonUI을 사용하면 CommonUIInputData를 기반으로 한 별도의 Navigation이 정의되어야 한다.

  • Create New Plueprint Class에서 CommonUIInputData를 선택해 BP 생성
    • 생성한 파일 내에 CommonUI InputActionDataTable과 Row를 지정하여 Navigation을 지정
    • HoldData의 경우 CommonUIHoldInputData를 기반으로 신규 BP를 생성한 뒤, 해당 파일을 연결해줘야 한다.
  • Click Action
    • 버튼이나 기타 상호작용 가능한 Element를 Highlight 할 때 Mouse Click을 대체
  • Back Action
    • 현재 Menu에서 이전 Menu로 이동할 때 공통으로 사용

  • Project Settings -> Game -> Common Input Settings의 InputData에 연결한다.
  • 설정 시, 지정된 Asset을 Default Navigation에 사용한다.

Bind Controller Data per Platform

  • Controller Data Asset은 Key-Action을 UI Elemnt에 연결해준다.
    • 각 Controller Data Asset은 Input Type, Gamepad, Platform과 연관되어 있다.
  • CommonUI는 이 정보를 이용해 현재 Platform과 Input Type을 기반으로
    올바른 Platform 별 UI Element를 자동으로 사용한다.
    • 이를 통해 다수의 Input Type이나 고유한 Gamepad를 지원하는 Platform에서
      User Input을 올바른 GamePad에 전달하거나, Runtime에서 UI Elemnt를 교체할 수도 있다.

  • Create New Blueprint Class에서 CommonInputBaseControllerData를 선택해 생성

  • 생성한 모든 Controller Data Asset은
    Project Settings -> Game -> Common Input Settings의 Platform Input에 추가되어야 한다.
    • Platform Input에서 입력하는 Default Gamepad Name은
      Controller Data Asset들의 Gamepad Name 필드들로만 입력되어야 한다.
    • 이 값이 일치하지 않으면 Controller Data를 인식하지 못하고 Icon이 표시되지 않는다.
  • 보통은 한 Platform의 Controller Data Array에 여러 개의 Gamepad Data를 작성해 다양한 컨트롤러를 지원한다.
    • 만약 별도의 Controller에 대한 게임플레이 지원을 해야 한다면,
      Gamepad Data를 새로 작성해 Controller Data에 추가하면 된다.

CommonInputBaseControllerData

더보기
/* Derive from this class to store the Input data. It is referenced in the Common Input Settings, found in the project settings UI. */
UCLASS(Abstract, Blueprintable, ClassGroup = Input, meta = (Category = "Common Input"))
class COMMONINPUT_API UCommonInputBaseControllerData : public UObject
{
	GENERATED_BODY()

public:
	virtual bool NeedsLoadForServer() const override;
	virtual bool TryGetInputBrush(FSlateBrush& OutBrush, const FKey& Key) const;
	virtual bool TryGetInputBrush(FSlateBrush& OutBrush, const TArray<FKey>& Keys) const;

	virtual void PreSave(FObjectPreSaveContext ObjectSaveContext) override;
	virtual void PostLoad() override;

private:
#if WITH_EDITORONLY_DATA
	UPROPERTY(Transient, EditAnywhere, Category = "Editor")
	int32 SetButtonImageHeightTo = 0;
#endif

public:
	UPROPERTY(EditDefaultsOnly, Category = "Default")
	ECommonInputType InputType;
	
	UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta=(EditCondition="InputType == ECommonInputType::Gamepad", GetOptions = GetRegisteredGamepads))
	FName GamepadName;

	UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta = (EditCondition = "InputType == ECommonInputType::Gamepad"))
	FText GamepadDisplayName;

	UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta=(EditCondition="InputType == ECommonInputType::Gamepad"))
	FText GamepadCategory;

	UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta = (EditCondition = "InputType == ECommonInputType::Gamepad"))
	FText GamepadPlatformName;

	UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta=(EditCondition="InputType == ECommonInputType::Gamepad"))
	TArray<FInputDeviceIdentifierPair> GamepadHardwareIdMapping;

	UPROPERTY(EditDefaultsOnly, Category = "Display")
	TSoftObjectPtr<UTexture2D> ControllerTexture;

	UPROPERTY(EditDefaultsOnly, Category = "Display")
	TSoftObjectPtr<UTexture2D> ControllerButtonMaskTexture;

	UPROPERTY(EditDefaultsOnly, Category = "Display", Meta = (TitleProperty = "Key"))
	TArray<FCommonInputKeyBrushConfiguration> InputBrushDataMap;

	UPROPERTY(EditDefaultsOnly, Category = "Display", Meta = (TitleProperty = "Keys"))
	TArray<FCommonInputKeySetBrushConfiguration> InputBrushKeySets;

	UFUNCTION()
	static const TArray<FName>& GetRegisteredGamepads();

private:
#if WITH_EDITOR
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};
  • InputType
    • CommonUI에서 인식하는 Input의 종류
UENUM(BlueprintType)
enum class ECommonInputType : uint8
{
	MouseAndKeyboard,
	Gamepad,
	Touch,
	Count
};
  • GamepadName
    • Controller가 Gamepad인 경우에 해당 Gamepad가 대응 할 Platform
USTRUCT()
struct FInputDeviceIdentifierPair
{
	GENERATED_BODY()

	UPROPERTY(EditDefaultsOnly, Category = "Gamepad")
	FName InputDeviceName;

	UPROPERTY(EditDefaultsOnly, Category = "Gamepad")
	FString HardwareDeviceIdentifier;
};
  • GamepadhardwareIdMapping
    • Gamepad Hardware ID를 게임에서 사용하는 Key값과 연결
USTRUCT(Blueprintable)
struct COMMONINPUT_API FCommonInputKeyBrushConfiguration
{
	GENERATED_BODY()

public:
	FCommonInputKeyBrushConfiguration();

	const FSlateBrush& GetInputBrush() const { return KeyBrush; }

public:
	UPROPERTY(EditAnywhere, Category = "Key Brush Configuration")
	FKey Key;

	UPROPERTY(EditAnywhere, Category = "Key Brush Configuration")
	FSlateBrush KeyBrush;
};
  • InputBrushDataMap
    • UI Element 및 Icon에 대한 Key Mapping
USTRUCT(Blueprintable)
struct COMMONINPUT_API FCommonInputKeySetBrushConfiguration
{
	GENERATED_BODY()

public:
	FCommonInputKeySetBrushConfiguration();

	const FSlateBrush& GetInputBrush() const { return KeyBrush; }

public:
	UPROPERTY(EditAnywhere, Category = "Key Brush Configuration", Meta = (TitleProperty = "KeyName"))
	TArray<FKey> Keys;

	UPROPERTY(EditAnywhere, Category = "Key Brush Configuration")
	FSlateBrush KeyBrush;
};
  • InputBrushKeySets
    • 단일 UI Element에 대한 다수의 Key Mapping
    • D-Pad 및 기타 여러 Axis에 Mapping 될 가능성이 있는 Input에 유용

Common UI widget

  • Common UI Plugin에서 제공하는 Widget
  • 기존에 자주 사용하는 Widget의 기능을 거의 그대로 제공
    • 다만 Base UMG Widget Style 기능은 제공하지 않는다.
    • 대신 Common Style Asset을 참조해 다수의 Menu와 HUD에 일관된 Style을 적용할 수 있다.
    • Styple Asset을 변경하면 모든 Common UI Widget에 효과가 나타난다.
  • 자세한 지원 Widget 항목에 대해서는 아래 개별 포스트 참고

Common Style Asset

  • Create New Blueprint Class를 통해 사진과 같이 Common Style BP를 생성한다.

  • 생성한 Common Style BP의 Detail 항목에 필요한 정보를 채워놓는다.

  • 생성한 Common Style BP는 이를 필요로 하는 다른 CommonUI에 연결하여 적용한다.
    • 위 사진의 경우, Common Button의 Widget Element인 Common Text에 CommonTextStyle을 적용하였다.

  • 생성된 Common Style BP는 Project Settings -> Plugins -> Common UI Editor의 Template Styles에 할당할 수 있다.
    • 그럼 수동으로 설정되지 않은 Common BP에서 Template Style을 자동으로 사용하게 된다.

  • 이와 같은 맥락으로, Project Settings -> Plugins -> Common UI Framework에서
    몇 가지 Global Assset을 추가로 지원한다.
    • Loading 화면에서 쓰는 Default Throbber Material
    • Load되지 않은 UI Asset에 표시되는 Default Image Resource Object 등

Technical Guide

FAQ

 

[UI] Common UI FAQ

Common UI의 사용 여부 판단주로 다음 상황에서 Common UI 사용이 권장된다.복잡한 Multiple Layer UI를 제공해야 하는 경우Cross-Platform을 지원하는 경우반대로 이 두 케이스에 모두 해당되지 않으면 Common U

redchiken.tistory.com

 

'UE5 > UI' 카테고리의 다른 글

[UI] Common UI FAQ  (0) 2024.07.10
[UI] CommonUI Technical Guide  (0) 2024.07.10
[UI] Common UI Widget  (0) 2024.07.10
[UI] UMG ViewModel  (0) 2024.07.08
[UI] Optimization  (0) 2024.07.01

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/umg-viewmodel?application_version=5.3#heading=h.qln36ehrtib7

https://harrisbarra.medium.com/ue5-mvvm-36907bdb34d9

 

UE5 & MVVM

Clean & Modular solution w/ example

harrisbarra.medium.com

https://dev.epicgames.com/community/learning/talks-and-demos/pw3Y/unreal-engine-umg-viewmodels-building-more-robust-and-testable-uis-using-mvvm-unreal-fest-2023?locale=ko-kr

https://miltoncandelero.github.io/unreal-viewmodel

 

Model View ViewModel for Game Devs

How to turn something meant for the boring app world into something useful for programming our game UIs

miltoncandelero.github.io

 

WorkFlow

Programmer

  • View Model 자체를 생성, Build
    • View Model은 UI에서 사용할 수 있는 변수들을 포함 함.
  • Application의 코드와 결합

Designer

  • View Binding 패널을 사용해 UI에서 ViewModel의 변수에 Bind
  • UMG Widget에 ViewModel 추가 시 다음 기능들을 이용할 수 있다.
    • Access
    • 함수 호출
    • 변수 업데이트
    • 변수 업데이트에 대한 Push 이벤트 Delegate
      • 이는 변수 값이 수정 될 때 등록된 Widget만 업데이트 하기 때문에 Attribute Bind보다 훨씬 효과적이다.
      • 동시에 시간 설정을 수동으로 구현 할 필요도 없어 Event Driven UI Framework의 이점을 살리기 좋다.

Config

View Model

역할

  • UI에 필요한 변수의 Manifest 관리
  • UI와 Application의 기타 요소간 Communication을 위한 매개체

UI가 변수를 인지해야 하는 경우

  • ViewModel에 변수 추가
  • Widget에 ViewModel 추가
  • Widget의 Field를 ViewModel에 Bind

변수를 업데이트 해야 하는 경우

  • ViewModel의 Reference를 가지고 있다면 언제든 직접 접근하여 수정
  • 변경 된 변수에 Bind된 Widget을 Notify하고 업데이트

ViewModel in BP

Creation

  • Content Borwser를 우클릭하여 New Blueprint를 통해 ViewMode BP 생성
    • ViewModel은 Widget이 아니기 때문에 User Interface에 없고 일반 BP 생성 방식을 통해야 한다.

FieldNotify Variable

  • BP의 변수 옆에 종 모양 UI가 FieldNotify 활성화 여부이다.

  • FieldNotify가 활성화 된 변수의 Set은 BP 라벨의 이름이 Set w/ Broadcast로 지정된다.
    • 위와 같이 설정 된 변수들은 값이 변경될 때마다 Bind된 Widget에 Update 메시지를 전송한다.

FieldNotify Function

  • Function 역시 FieldNotify로 취급 될 수 있으나 변수에 비해 몇 가지 조건을 요구한다.
    • Pure Function일 것
    • Const 마킹이 되어 있을 것
    • 하나의 값만 반환할 것
    • Input Parameter가 없을 것
  • FieldNotify를 사용한다면 가급적 값을 반환하는 목적만 있는 Getter 생성은 지양해야 한다.
    • 차후에 Widget을 Bind하려 할 때 추가되는 Getter 함수와 햇갈릴 수 있다.

Bind FieldNotify to Another FieldNotify

변수인 CurrentHP, MaxHP 뿐 아니라 Function인 GetHPPercent까지 FieldNotify 항목에 추가됨을 확인할 수 있따.

  • 만약 FieldNotify 변수가 변경 되는 경우, 그 변수에 FieldNotify Function을 Bind 해줘야 한다.
    • 이는 비단 Function 뿐 아니라 FieldNotify 변수에 다른 FieldNotify 변수를 Bind할 수도 있다.
    • FieldNotify에 다른 FieldNotify 변수나 함수가 Bind되어 있다면,
      변수가 수정될 때 Bind 된 함수의 실행은 물론 Bind된 변수에 Bind 된 Widget에까지 Update가 된다.

ViewModel in C++

Creation

#pragma once

#include "CoreMinimal.h"
#include "MVVMViewModelBase.h"
#include "MyMVVMViewModelBase.generated.h"

UCLASS()
class MYTEST_API UMyMVVMViewModelBase : public UMVVMViewModelBase
{
	GENERATED_BODY()
	
};
  • 기본적인 ViewModel은 UMVVMViewModelBase를 상속 받아 생성할 수 있다.
class INotifyFieldValueChanged : public IInterface
{
	GENERATED_BODY()

public:
	// using "not checked" user policy (means race detection is disabled) because this delegate is stored in a container and causes its reallocation
	// from inside delegate's execution. This is incompatible with race detection that needs to access the delegate instance after its execution
	using FFieldValueChangedDelegate = TDelegate<void(UObject*, UE::FieldNotification::FFieldId), FNotThreadSafeNotCheckedDelegateUserPolicy>;

public:
	/** Add a delegate that will be notified when the FieldId is value changed. */
	virtual FDelegateHandle AddFieldValueChangedDelegate(UE::FieldNotification::FFieldId InFieldId, FFieldValueChangedDelegate InNewDelegate) = 0;

	/** Remove a delegate that was added. */
	virtual bool RemoveFieldValueChangedDelegate(UE::FieldNotification::FFieldId InFieldId, FDelegateHandle InHandle) = 0;

	/** Remove all the delegate that are bound to the specified UserObject. */
	virtual int32 RemoveAllFieldValueChangedDelegates(const void* InUserObject) = 0;

	/** Remove all the delegate that are bound to the specified Field and UserObject. */
	virtual int32 RemoveAllFieldValueChangedDelegates(UE::FieldNotification::FFieldId InFieldId, const void* InUserObject) = 0;

	/** @returns the list of all the field that can notify when their value changes. */
	virtual const UE::FieldNotification::IClassDescriptor& GetFieldNotificationDescriptor() const = 0;

	/** Broadcast to the registered delegate that the FieldId value changed. */
	virtual void BroadcastFieldValueChanged(UE::FieldNotification::FFieldId InFieldId) = 0;
};

 

  • 하지만 이보다 더 근본적으로, INotifyFieldValueChanged만을 Implement 하여 생성할 수도 있다.
/** Base class for MVVM viewmodel. */
UCLASS(Blueprintable, Abstract, DisplayName="MVVM Base Viewmodel")
class MODELVIEWVIEWMODEL_API UMVVMViewModelBase : public UObject, public INotifyFieldValueChanged
{
	GENERATED_BODY()

public:
	//~ Begin INotifyFieldValueChanged Interface
	virtual FDelegateHandle AddFieldValueChangedDelegate(UE::FieldNotification::FFieldId InFieldId, FFieldValueChangedDelegate InNewDelegate) override final;
	virtual bool RemoveFieldValueChangedDelegate(UE::FieldNotification::FFieldId InFieldId, FDelegateHandle InHandle) override final;
	virtual int32 RemoveAllFieldValueChangedDelegates(const void* InUserObject) override final;
	virtual int32 RemoveAllFieldValueChangedDelegates(UE::FieldNotification::FFieldId InFieldId, const void* InUserObject) override final;
	virtual const UE::FieldNotification::IClassDescriptor& GetFieldNotificationDescriptor() const override;
	virtual void BroadcastFieldValueChanged(UE::FieldNotification::FFieldId InFieldId) override;
	//~ End INotifyFieldValueChanged Interface

/*etc*/
};

Sum of Code

  • C++에서 ViewModel을 구성하는 방법은 대략 다음과 같다.
UCLASS(BlueprintType)
class UVMCharacterHealth : public UMVVMViewModelBase
{
GENERATED_BODY()

private:
UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
    int32 CurrentHealth;

    UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
    int32 MaxHealth;

public:
    void SetCurrentHealth(int32 NewCurrentHealth)
    {
        if (UE_MVVM_SET_PROPERTY_VALUE(CurrentHealth, NewCurrentHealth))
        {
            UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
        }
    }

    void SetMaxHealth(int32 NewMaxHealth)
    {
        if (UE_MVVM_SET_PROPERTY_VALUE(MaxHealth, NewMaxHealth))
        {
            UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
        }

    }

    int32 GetCurrentHealth() const
    {
        return CurrentHealth;
    }

    int32 GetMaxHealth() const
    {
        return MaxHealth;
    }

public:

    UFUNCTION(BlueprintPure, FieldNotify)

    float GetHealthPercent() const
    {
        if (MaxHealth != 0)
        {
            return (float) CurrentHealth / (float) MaxHealth;
        }
        else
            return 0;
    }

};

FieldNotify Variable

  • FieldNotify 변수를 선언하려면 UPROPERTY에서 다음 지정자들을 지정해야 한다.

  • FieldNotify를 선언하지 않은 경우 Onetime,
    즉 변수가 최초로 변경될 때에만 Notify가 발생하고 그 이후에는 발생하지 않는다.
  • Setter와 Getter는 필요에 따라 추가한다.
    • 단, 추가하지 않으면 해당 동작에 대한 연산은 해당 Class는 물론 하위 Class에서도 수행할 수 없다.
    • Getter/Setter은 Bind 된 Function 실행이나 FieldNotify Update 외에 변수값을 얻기 전에 연산을 해야 하는 경우에도 적절하다.
  • Getter/Setter함수는 UFUNCTION으로 생성할 경우 BP에서 상당히 많은 목록을 생성하기에 이를 지양하는 것이 좋다.
    • UPROPERTY에서 이미 FieldNotify의 Get/Set에 연결을 해준 상태다.
  • Unreal Engine은 FiendNotify 변수에 대한 Getter/Setter 함수 호출을 강제하지 않는다.
    • 사용자 귀책 실수를 줄이고 싶다면 접근제어자를 조절하는 것이 필요하다.
  • BP에서는 FieldNotify 변수가 변경될 때 Bind된 FieldNotify 변수나 함수가 자동으로 Update되지만.
    C++로 작업한다면 이들을 직접 호출해줘야 한다.

FieldNotify Function

  • FieldNotify Function은 다음 조건을 만족해야 한다.
    • UFUNCTION에서 BlueprintPure, FieldNotify 지정자 선언
    • Parameter가 없어야 함
    • const 선언이 되어 있어야 함
    • out 인자 없이 단일 값을 반환해야 함.
  • Widget이 특정 변수에 Bind 되어 있으면서 동시에 값을 직접 사용하지 않고 연산을 거쳐야 하는 경우에 유용하다.
    • 일종의 임시 변수 생성

FieldNotify Macro

/** After a field value changed. Broadcast the event. */
#define UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(MemberName) \
	BroadcastFieldValueChanged(ThisClass::FFieldNotificationClassDescriptor::MemberName)

/** If the property value changed then set the new value and notify. */
#define UE_MVVM_SET_PROPERTY_VALUE(MemberName, NewValue) \
	SetPropertyValue(MemberName, NewValue, ThisClass::FFieldNotificationClassDescriptor::MemberName)

/** Use this version to set property values that can't be captured as a function arguments (i.e. bitfields). */
#define UE_MVVM_SET_PROPERTY_VALUE_INLINE(MemberName, NewValue) \
	[this, InNewValue = (NewValue)]() { if (MemberName == InNewValue) { return false; } MemberName = InNewValue; BroadcastFieldValueChanged(ThisClass::FFieldNotificationClassDescriptor::MemberName); return true; }()

Add ViewModel to Widget

  • Widget을 생성하고 Window->Viewmodels항목을 선택하면 다음 창이 뜬다.

  • 해당 창에서 미리 만든 ViewModel을 선택하면 된다.
  • ViewModel은 여러 개 등록 할 수 있다.
    • 그 말은 Widget과 ViewModel의 관계는 1:1이 아니라 다:다라는 의미이다.

Initialize ViewModel

  • ViewModel을 처음 추가하면 위와 같은 Noti를 볼 수 있다.
    • 이는 현재 Widget에서 등록된 ViewModel에 Bind가 없기에 굳이 Initialize를 하지 않겠다는 의미이다.
    • View Binding에서 Bind를 추가해주면 관계가 생성되며 자동으로 Instance가 생성된다.

Create Instance

  • Widget Instance 별로 각각 새로운 ViewModel Instance를 자동 생성
    • 동일한 Widget이 Viewport 상 여러 개 존재하더라도
      하나의 변수를 수정하면 그 ViewModel에 해당하는 Widget만 Update 된다.
    • 이와 동일하게, 여러 개의 서로 다른 Widget을 생성할 때에도 다른 Widget의 정보 변경을 인지하지 못한다.
  • C++의 Call-back 초기화 다음, 혹은 BP의 Call-back 초기화 도중에 ViewModel을 할당할 수 있다.
    • ViewModel이 설정되지 않으면 새 Instance만 생성한다.
    • ViewModel은 PreConstruct와 Construct 사이에 생성된다.

Manual

  • 코드 상 특정 위치에서 Instance를 생성하고 Widget에 할당하는 방식
    • Widget은 Reference를 가지지만, 할당되기 전까지는 Null 값을 갖는다.

  • Create Widget 노드에서 생성 시 ViewModel을 할당할 수도 있다.
  • ViewModel을 할당하면 Widget에 대한 Reference를 구하지 않고 UI를 Update 할 수 있다.
    • 이 방법을 통해 UI가 하나의 Actor Class로부터
      서로 다른 다수의 Widget에 동일한 ViewModel을 할당할 수 있게 된다.

Global Viewmodel Collection

  • MVVMSubsystem에서 Global로 Access 할 수 있는 ViewModel 목록
UCLASS(DisplayName="Viewmodel Engine Subsytem")
class MODELVIEWVIEWMODEL_API UMVVMSubsystem : public UEngineSubsystem
{
	GENERATED_BODY()

public:
	//~ Begin UEngineSubsystem interface
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;
	//~ End UEngineSubsystem interface

	UFUNCTION(BlueprintCallable, Category = "Viewmodel", meta = (DisplayName = "Get View From User Widget"))
	UMVVMView* K2_GetViewFromUserWidget(const UUserWidget* UserWidget) const;

	static UMVVMView* GetViewFromUserWidget(const UUserWidget* UserWidget);

	UFUNCTION(BlueprintCallable, Category = "Viewmodel")
	bool DoesWidgetTreeContainedWidget(const UWidgetTree* WidgetTree, const UWidget* ViewWidget) const;

	/** @return The list of all the AvailableBindings that are available for the Class. */
	UFUNCTION(BlueprintCallable, Category = "Viewmodel", meta = (DisplayName = "Get Available Bindings"))
	TArray<FMVVMAvailableBinding> K2_GetAvailableBindings(const UClass* Class, const UClass* Accessor) const;

	static TArray<FMVVMAvailableBinding> GetAvailableBindings(const UClass* Class, const UClass* Accessor);

	/**
	 * @return The list of all the AvailableBindings that are available from the SriptStuct.
	 * @note When FMVVMAvailableBinding::HasNotify is false, a notification can still be triggered by the owner of the struct. The struct changed but which property of the struct changed is unknown.
	 */
	static TArray<FMVVMAvailableBinding> GetAvailableBindingsForStruct(const UScriptStruct* Struct);

	static TArray<FMVVMAvailableBinding> GetAvailableBindingsForEvent(const UClass* Class, const UClass* Accessor);

	/** @return The AvailableBinding from a BindingName. */
	UFUNCTION(BlueprintCallable, Category = "Viewmodel", meta = (DisplayName = "Get Available Binding"))
	FMVVMAvailableBinding K2_GetAvailableBinding(const UClass* Class, FMVVMBindingName BindingName, const UClass* Accessor) const;

	static FMVVMAvailableBinding GetAvailableBinding(const UClass* Class, FMVVMBindingName BindingName, const UClass* Accessor);

	/** @return The AvailableBinding from a field. */
	static FMVVMAvailableBinding GetAvailableBindingForField(UE::MVVM::FMVVMConstFieldVariant Variant, const UClass* Accessor);

	static FMVVMAvailableBinding GetAvailableBindingForEvent(UE::MVVM::FMVVMConstFieldVariant FieldVariant, const UClass* Accessor);

	static FMVVMAvailableBinding GetAvailableBindingForEvent(const UClass* Class, FMVVMBindingName BindingName, const UClass* Accessor);
};
  • Option과 같이 UI를 통해 Access되어야 하는 변수 처리에 이상적이다.

Property Path

더보기
  • 다른 방식에 비해 좀 더 명확하고 코드 작업을 덜 요구하는 방식
  • 다른 Class들이 Viewmodel Reference를 구하기 위해 Widget 내에 접근하는 대신,
    특정 함수 호출을 통해 ViewModel Reference를 갖는다.
  • Editor의 Property Path 필드에 "."로 구분된일련의 Member 이름을 입력할 수 있다.
    • 이러한 함수 호출의 시작점을 Self, 즉 편집하고 있는 Widget에서 항상 시작한다.
    • Property Path에 Self를 직접 지정하면 안된다.
  • Property Path는 BP에서 선언된 함수 이름도 사용할 수 있다.
    • 이 방식을 사용하면 로직이 간소화하여 더 높은 유연성을 확보할 수 있다.

Access ViewModel Variable

  • Widget 에 ViewModel을 할당하면 BP에서 Widget의 Property를 통해 거꾸로 변수에 Access 할 수 있다.
    • 물론 이는 Viewmodel 옵션에 따라 다르긴 하다.

Work with Array

  • 일반적으로 Array는 ViewModel에서 Access 할 수 없다.
    • 이를 위해서는 ViewModel 자체적으로 배열을 직접 추가/삭제/탐색 할 수 있는 FieldNoitfy 함수를 만들어야 한다.
  • 다만 ListView, TreeView, TileView 등과는 함께 Array를 사용할 수 있다.
    • 이 경우, Element가 Array에 추가/제거/이동 될 경우에 Notify를 해줘야 한다.

View Binding

Add to Widget

Drag and Drop

  • ViewModel에서 Widget에 Bind할 변수나 함수를 클릭하고 Bind 할 영역의 Bind 드롭다운을 드래그

Detail Panel

  • Accessibility -> Override Accessibility 항목 오른쪽의 Bind 드롭다운을 눌러 필요한 FieldNotify Bind

  • 이 방식은 기존의 Property Binding과 혼동할 수 있는데, 이를 막아주는 옵션이 존재한다.
    • Project Settings -> Editor -> Widget Designer(Team) -> Property Binding Rule을 Prevent로 설정
    • 이 설정을 해주면 기존 Property를 Widget의 Parameter에 Binding하는 옵션이 제거된다.

  • Plugin -> Model View ViewModel -> Allow Binding from Detail View를 비활성화 하면
    ViewModel에 대한 Detail 패널 Binding도 비활성화 할 수 있다.
    • 이 설정을 쓰더라도 View Binding Menu를 통해 여전히 Bind를 할 수 있다.

View Binding Menu

  • UMG Designer -> Window -> View Binding 선택

  • Add Widget을 통해 View Binding 목록에 추가하고 Bind 진행

Configure

Select Target Widget

  • View Binding을 추가 할 Widget 선택

Create View Binding Entry

  • Target Widget 하위에 있는 개별 Property마다 별도의 Bind를 걸 수 있다.
    • 물론 하나의 Property에 여러 Bind를 걸 수도 있다.

Select Widget Property

  • Target Widget의 변수 및 함수 목록이 표시된다.
    • 이 항목에는 C++로 정의된 UFUNCTION(), UPROPERTY()도 포함된다.
    • BP에서 정의한 변수나 함수는 자동으로 사용 가능하다.

Select ViewModel Property

  • Target ViewModel과 Target Property를 선택한다.

Set Bind Direction

  • Bind Direction을 선택해 Widget과 ViewModel간의 정보가 흐르는 방식을 결정한다.

  • 기본적으로 모든 ViewModel은 PreConstruct와 Construct 사이에 한 번 실행된다.
    • Bind Direction이 Two Way인 경우에는 One Way Bind만 실행된다.
    • ViewModel 값이 SetViewModel을 이용해 변경되면 모든 Bind가 실행된다.

Set Execution Mode

  • Bind 된 Widget 등의 실행 방식을 지정
UENUM()
enum class EMVVMExecutionMode : uint8
{
	/** Execute the binding as soon as the source value changes. */
	Immediate = 0,
	/** Execute the binding at the end of the frame before drawing when the source value changes. */
	Delayed = 1,
	/** Always execute the binding at the end of the frame. */
	Tick = 2,
	/** When the binding can be triggered from multiple fields, use Delayed. Else, uses Immediate. */
	DelayedWhenSharedElseImmediate = 3 UMETA(DisplayName="Auto"),
};

Use Conversion Function

  • 변수에 대한 직접 Bind 대신 Conversion Function을 채택할 수 있다.
    • Conversion Function은 ViewModel의 변수를 다른 타입의 Data로 Convert하기 위한 Interface를 제공한다.

  • Convert 함수를 선택하면 설정할 수 있는 창이 드롭다운 아래 나타난다.
    • 만약 이미 드롭다운 데이터가 있다면 이 기능이 비정상 동작한다.
    • 이 때에는 Clear를 해서 한번 날려주고 하면 잘 된다.
  • 새로운 Conversion Function은 Global 단위 혹은 UserWIdget에 추가될 수 있다.
    • 단, 이 함수는 Event, Network, Deprecated, EditorOnly로 선언되면 안된다.
    • BP에 표시되어야 하고, 하나의 Parameter와 하나의 Return Value를 가지고 있어야 한다.
    • Global로 정의되는 경우에는 static으로 선언되어야 한다.
    • UserWidget에서 정의되는 경우에는 pure 및 const여야 한다.

ViewModel 작업 팁

  • 거대한 하나의 ViewModel 대신 작고 간결한 ViewModel을 권장한다.
    • 이는 UI 디버깅을 하기 훨씬 용이하다.
  • 예를 들어 Ability,Inventory 등으로 구성된 Array를 사용해 RPG에서 Character를 나타내는 ViewModel을 상상하자.
    • 이 ViewModel에 Bind 된 Widget 중 일부를 디버깅 하기 위해서는
      전체 Character를 Spawn해 ViewModel의 데이터를 채워줘야 한다.
    • 이 때, 서로 다른 Component로 분할하면 Debug 할 때 Test Data로 더 쉽게 채울 수 있다.
  • 또한 ViewModel은 다른 ViewModel 내부에 중첩하면, 복잡한 데이터 작업에서 유연성을 높일 수 있다.
    • 예를 들어, HP ViewModel과 Attribute ViewModel을 각각 생성하고 이를 Character ViewModel에 중첩할 수 있다.
    • 이 경우, Test에서 개별 Widget은 각자 연관 된 ViewModel에서 Data를 취할 수 있다.
      • 체력 표시줄이 Character의 HP를 REference 한다던가.
    • 동시에 최종 결과물에서는 중첩된 ViewModel에서 전체 Set를 사용할 수 있다.

'UE5 > UI' 카테고리의 다른 글

[UI] Common UI FAQ  (0) 2024.07.10
[UI] CommonUI Technical Guide  (0) 2024.07.10
[UI] Common UI Widget  (0) 2024.07.10
[UI] Common UI Introduction  (0) 2024.07.10
[UI] Optimization  (0) 2024.07.01

+ Recent posts