虚幻4之Slate学习四 —— 委托与宏

本章简介

  • 了解虚幻4委托
  • 了解Slate相关宏
  • 了解Slate控件的相关回调函数

虚幻4委托

  • 委托用于将方法作为参数传递给其他方法。 事件处理程序就是通过委托调用的方法。 你可以创建一个自定义方法,当发生特定事件时,某个类(如 Windows 控件)就可以调用你的方法。
  • 简单来说,delegate实现了将一个方法传递给其他类,使得其他的类在编译时期内不需要了解具体是什么方法被调用,而只是知道这个被调用方法的函数签名。有点类似于C++中函数指针的存在。

Delegate宏

  • 虚幻4用c++封装了自定义的委托,提供各种参数和返回值可供使用。
函数签名 声明宏
void Function() DECLARE_DELEGATE( DelegateName )
void Function( ) DECLARE_DELEGATE_OneParam( DelegateName, Param1Type )
void Function( , , … ) DECLARE_DELEGATE_Params( DelegateName, Param1Type, Param2Type, … )
Function() DECLARE_DELEGATE_RetVal( RetValType, DelegateName )
Function( ) DECLARE_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type )
Function( , , … ) DECLARE_DELEGATE_RetVal_Params( RetValType, DelegateName, Param1Type, Param2Type, … )

声明Delegate变量

  • 利用刚定义的宏声明一个Delegate变量,注意变量类型是上述的DelegateName。
  • 这个成员变量就是未来我们可以用来动态绑定其他和之前在宏声明的一样的函数签名的方法
DelegateName MyDelegate

绑定

  • Bind()

    直接Bind一个已经存在的Delegate

  • BindRaw()

    绑定c++原生指针,非smart指针

  • BindUobject()

    绑定继承自UObject的类

  • BindUFUCNTION()

    绑定的类成员方法是UFUNCTION

  • BindSP

    绑定智能指针

  • BindStatic

    绑定的类成员方法是UFUNCTION

调用代理方法

  • 调用代理的方法就相对的简单,我们只需要使用下方方法即可。当然如果这个delegate是含参数的,可以在其中添加参数。这样所绑定的方法就会被调用,类似于通过函数指针来调用。

    MyDelegateMemVar.Execute()
    
    MyDelegateMemVar.Execute(Param1, Param2 ...)
    
  • IsBound()用于判断委托是否绑定

    MyDelegateMemVar.IsBound()
    
  • ExecuteIfBound(…)用于如果绑定特定内容则执行

    MyDelegateMemVar.ExecuteIfBound(...)
    

Slate宏(macro)

  • Slate框架结合了声明式语法,并且提供了一套完整的宏来简化声明及创建新控件的过程。下面就来学习一下slate创建中一些常见的宏:

SLATE_BEGIN_ARGS(WidgetType)和SLATE_END_ARGS()

  • Widgets的创建者可以使用SLATE_BEGIN_ARGS和SLATE_END_ARGS来声明和构建一个Widget,使得Widget可以通过SNew()和SAssignNew()来创建一个Widget,从而可以添加到用户视口.
  • 其内部可放置相关参数
    SLATE_BEGIN_ARGS(SSlAiMenuItemWidget){}
     SLATE_ATTRIBUTE(FText, ItemText)
     SLATE_EVENT(FItemClicked, MyOnClicked)
     SLATE_ATTRIBUTE(EMenuItem::Type,ItemType)
    SLATE_END_ARGS()
    
    定义上述参数后,可以进行赋值等操作
    SNew(SSlAiMenuItemWidget)
    .ItemText(NSLOCTEXT("SlAiMenu","StartGame","StartGame"))
    .ItemType(EMenuItem::StartGame)
    .MyOnClicked(this,&SSlAiMenuWidget::MenuItemOnClicked)
    

SLATE_ATTRIBUTE(AttrType,AttrName)

  • Use this macro to add a attribute to the declaration of your widget.
  • An attribute can be a value or a function.

    使用这个宏可以为你正在声明的Widget添加一个属性(TAttribute<>类型).

一个属性可以是一个值或者方法参数

AttrType:属性类型,可以是任意数据类型
AttrName:属性名

SLATE_ARGUMENT(ArgType, ArgName)

  • Use this macro to declare a slate argument.
  • Arguments differ from attributes in that they can only be values.

    使用这个宏可以为你的正在构建的Widget声明一个参数,这个参数不是一个TAttribute<>类型(属性),只是一个一般数据类型的变量

Argumet和Attribute的区别在于Argument只能是一个值,而Attitude可以是一个值或者方法,并且可以为其绑定代理。

SLATE_EVENT(DelegateName,EventName)

  • 用于自定义事件委托
  • 注意与事件是不一样的
  • 可以用于创建自定义单击事件

    SLATE_EVENT( FOnClicked, OnClicked )

FORCEINLINE

  • 虚幻4自定义的声明内敛函数的宏

    FORCEINLINE void Debug(FString Message, float duration)

常用Slate控件函数

OnClicked

  • 事件原型
    /** Called when the button is clicked */
    SLATE_EVENT( FOnClicked, OnClicked )
    
  • FOnClicked原型

    /**
    * A delegate that is invoked when widgets want to notify a user that they have been clicked.
    * Intended for use by buttons and other button-like widgets.
    */
    DECLARE_DELEGATE_RetVal( 
      FReply, 
      FOnClicked )
    // FReply FOnClicked()
    
  • 因此OnClicked()调用的函数的函数原型必须是FReply FOnClicked()

  • FReply 告诉引擎如何处理事件

    A Reply is something that a Slate event returns to the system to notify it about certain aspect of how an event was handled. For example, a widget may handle an OnMouseDown event by asking the system to give mouse capture to a specific Widget. To do this, return FReply::CaptureMouse( NewMouseCapture ).

  • FReply::Handled() 告诉引擎事件处理完

    An event should return a FReply::Handled() to let the system know that an event was handled.

  • 下面举一个例子
/** SMyHUDWidget.h **/
#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "DeclarativeSyntaxSupport.h"
#include "MySlateWidgetStyle.h"
#include "SImage.h"

class MYSLATE_API SMyHUDWidget : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SMyHUDWidget)
    {}
    SLATE_END_ARGS()

    // Constructs this widget with InArgs 
    void Construct(const FArguments& InArgs);

    //注意SAssignNew需要使用共享指针
    TSharedPtr<SImage> MyImage;
private:
    const FMySlateStyle *MySlateStyle;

    FReply addImage();
};
/** SMyHUDWidget.cpp **/
#include "SMyHUDWidget.h"
#include "SlateOptMacros.h"
#include "SButton.h"
#include "SImage.h"
#include "MyStyle.h"
#include "SOverlay.h"

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SMyHUDWidget::Construct(const FArguments& InArgs)
{
    MySlateStyle = &MyStyle::Get().GetWidgetStyle<FMySlateStyle>("BP_MySlateWidgetStyle");

    ChildSlot
    [
        SNew(SOverlay)
        + SOverlay::Slot()
        .HAlign(HAlign_Fill)
        .VAlign(VAlign_Fill)
        [
            SAssignNew(MyImage, SImage)
        ]
        + SOverlay::Slot()
        .HAlign(HAlign_Center)//这些属性设计是应用于插槽
        .VAlign(VAlign_Center)
        [
            SNew(SButton)
            .OnClicked(this,&SMyHUDWidget::addImage)
        ]

    ];


}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

FReply SMyHUDWidget::addImage()
{
    MyImage->SetImage(&MySlateStyle->BackGroupBrush);
    return FReply::Handled();
}

效果如下图

SOverlay::Expose和SOverlay::FOverlaySlot

  • 上述两个结合使用可以获取SOverlay的插槽作为变量使用

  • 在OnClicked的例子中修改

/** SMyHUDWidget.h **/
public:
    //注意SOverlay::FOverlaySlot 是内部类
    SOverlay::FOverlaySlot* MySlot;
/** SMyHUDWidget.cpp **/
+ SOverlay::Slot()
.HAlign(HAlign_Center)//这些属性设计是应用于插槽
.VAlign(VAlign_Center)
.Expose(MySlot)
[
    SNew(SButton)
    .OnClicked(this,&SMyHUDWidget::addImage)
]
/** SMyHUDWidget.cpp **/
FReply SMyHUDWidget::addImage()
{
    MyImage->SetImage(&MySlateStyle->BackGroupBrush);
    MySlot->HAlign(HAlign_Left);
    return FReply::Handled();
}

点击后按钮到最左侧

总结

  • 我们尝试编写图片的点击效果,来使用委托和宏。SImage默认不带这个效果。
  • 首先我们创建一个继承于SlateWidget的C++类命名为MyImage
  • 创建一个委托,用于与SLATE_EVENT绑定。相当于自定义一个需要单击会触发函数的函数原型
/** SMyImage.h **/
DECLARE_DELEGATE_RetVal(FReply,FImageOnClicked)
  • 创建一个SLATE_EVENT,将之前创建的委托与该SLATE_EVENT绑定
/** SMyImage.cpp **/
SLATE_BEGIN_ARGS(SMyHUDWidget)
{}

SLATE_EVENT(FImageOnClicked, ImageOnClicked)

SLATE_END_ARGS()
  • 然后在原来的SMyHUDWidget将SImage换成SMyImage并且可以调用ImageOnClicked()
/** SMyHUDWidget.cpp **/
#include "SMyHUDWidget.h"
#include "SlateOptMacros.h"
#include "SButton.h"
#include "SImage.h"
#include "MyStyle.h"
#include "SOverlay.h"

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SMyHUDWidget::Construct(const FArguments& InArgs)
{
    MySlateStyle = &MyStyle::Get().GetWidgetStyle<FMySlateStyle>("BP_MySlateWidgetStyle");

    ChildSlot
    [
        SNew(SOverlay)
        + SOverlay::Slot()
        .HAlign(HAlign_Fill)
        .VAlign(VAlign_Fill)
        [
            /**
              * TSharedPtr<SMyImage> MyImage;
            **/
            SAssignNew(MyImage, SMyImage)
            //.ImageOnClicked()
        ]
        + SOverlay::Slot()
        .HAlign(HAlign_Center)//这些属性设计是应用于插槽
        .VAlign(VAlign_Center)
        .Expose(MySlot)
        [
            SNew(SButton)
            .OnClicked(this,&SMyHUDWidget::addImage)
        ]

    ];

}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

FReply SMyHUDWidget::addImage()
{
    //删除原来语句
    MySlot->HAlign(HAlign_Left);
    return FReply::Handled();
}
  • 在SMyImage中添加图片
/** SMyImage.cpp **/
#include "SMyImage.h"
#include "SlateOptMacros.h"
#include "SImage.h"
#include "Public/UI/MySlateWidgetStyle.h"
#include "Public/UI/MyStyle.h"

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SMyImage::Construct(const FArguments& InArgs)
{
    MySlateStyle = &MyStyle::Get().GetWidgetStyle<FMySlateStyle>("BP_MySlateWidgetStyle");
    ChildSlot
    [
        SNew(SImage)
        .Image(&MySlateStyle->BackGroupBrush)
    ];

}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

此时效果如下

  • 接着我们需要创建一个回调函数用于ImageOnClicked(),传入后event对应FImageOnClicked就绑定该函数了
/** SMyHUDWidget.cpp **/
FReply SMyHUDWidget::ImageClicked()
{
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, "Suceess");
    return FReply::Handled();
}
/** SMyHUDWidget.cpp **/
/**
    * TSharedPtr<SMyImage> MyImage;
**/
SAssignNew(MyImage, SMyImage)
.ImageOnClicked(this, &SMyHUDWidget::ImageClicked)
  • 但是这时候会发现,单击并没有什么效果。这是因为程序并不知道要根据单击触发。
  • 我们需要在MyImage中重写鼠标相关函数
/** SMyImage.h **/
public:
    FImageOnClicked ImageOnClick;
    virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
    virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
    virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
    bool BIsMouseButtonDown = false;
  • 我们这时候需要在鼠标按下时调用委托,使用我们必须事先获取到这个委托,FImageOnClicked ImageOnClick就是用来声明该委托。
  • 在构造中,通过InArgs._*可以来获取SLATE_BEGIN_ARGS(SMyHUDWidget)和SLATE_END_ARGS()之间的属性。
/** SMyImage.cpp **/
void SMyImage::Construct(const FArguments& InArgs)
{
    ImageOnClick = InArgs._ImageOnClicked;
    MySlateStyle = &MyStyle::Get().GetWidgetStyle<FMySlateStyle>("BP_MySlateWidgetStyle");
    ChildSlot
    [
        SNew(SImage)
        .Image(&MySlateStyle->BackGroupBrush)
    ];

}
  • 这样我们就可以重写鼠标事件,让其在点击时调用相关委托
FReply SMyImage::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
    BIsMouseButtonDown = true;
    return FReply::Handled();
}

FReply SMyImage::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
    if (BIsMouseButtonDown == true)
    {
        BIsMouseButtonDown = false;
        ImageOnClick.Execute();
    }
    return FReply::Handled();
}

void SMyImage::OnMouseLeave(const FPointerEvent& MouseEvent)
{
    BIsMouseButtonDown = false;
}
  • 到此就完成了,点击图片时会显示SUCCEESS

    如果不成功可以尝试将播放模式设成模拟

相关链接