Skip to content

Lyra Common Game Dialog

Lyra Game Dialog 的代码设计上看,它是一个为多人同屏设计的,类似双人成行那种同个屏幕,双人游玩。

大部分代码来自CommonGame 模块,而CommonGame 又需要引用CommonUser 模块 和 ModularGameplayActors 模块。

所以使用Lyra的Dialog,需要引用CommonGame,CommonUser,ModularGameplayActors这三个模块。

根布局添加过程

1. UCommonGameInstance

  • 通过CommonGame 的 CommonGameInstance 通知新LocalPlayer 加入 alt text
  • 告知 UGameUIManagerSubsystem

2. UGameUIManagerSubsystem

这个类目前在我看来,有些鸡肋。可能需要一个管理全部布局的“层”做准备。

  • UGameUIManagerSubsystem 用于管理 新的 LocalPlayer 的 UI alt text
  • 但它只负责管理,不负责实现,具体功能都在 UGameUIPolicy 里面。
  • 需要配置 UGameUIPolicy 的蓝图子类。

3. UGameUIPolicy

  • UGameUIPolicy 会给 LocalPlayer 注册一个OnPlayerControllerSet监听 alt text
  • NotifyPlayerAdded 的意思是,新的LocalPlayer 加入,会去RootViewportLayouts数组里查找,没有就创建一个。
  • FindByKey的原理是:bool operator==(const ULocalPlayer* OtherLocalPlayer) const { return LocalPlayer == OtherLocalPlayer; }
  • PlayerController 接收到控制器时: alt text
  • 最终,创建的Widget一定是UPrimaryGameLayout的子类,这里的LayoutClass是需要配置的项。 alt text

4. UPrimaryGameLayout

PrimaryGameLayout 就是每个本地玩家的根布局,它提供Stack, 至于用多少层Stack,由开发者自己决定。关于Stack, 详见这里

自此,每个本地玩家都会有一个RootLayout

5.Lyra的 UGameUIManagerSubsystem

Lyra的 UGameUIManagerSubsystem 子类还做了一件事,每次tick 都检查要不要隐藏根布局。这里大概就是告诉开发者,用它可以做最顶层UI的事情。


弹出对话框

1. UAsyncAction_ShowConfirmation

Lyra 的弹窗是通过异步Action 来进行的。。

这个异步本质上是查找本地玩家并获得它的消息系统,并不是实现弹窗。

  • Lyra 弹窗采取的是异步操作,UAsyncAction_ShowConfirmation继承UBlueprintAsyncActionBase
  • 这个异步里,会用各种方式尝试获得对应的LocalPlayer
  • 然后通过LocalPlayer获得UCommonMessagingSubsystem
  • 最终调用UCommonMessagingSubsystem的ShowConfirmation alt text

2. UCommonMessagingSubsystem

这是个子系统,实际上是个接口,它提供了ShowConfirmation这类方法的空实现。具体实现需要开发者继承它。其中FCommonMessagingResultDelegate 这个回调也被传递进来, 用于告知异步操作的结果。该消息子系统也不是弹出的具体实现位置,它只负责创建布局。先获得对应的根布局,也就是之前创建的UPrimaryGameLayout,然后实例化弹窗的Widget,把它push到根布局的Stack里面。

  • 子系统是最外层自动实例化的,所以不需要手动实例化。
  • 需要开发者配置 弹窗的Widget的class,必须继承于UCommonGameDialog.
  • 获取根布局,并传入Dialog构造器UCommonGameDialogDescriptor和委托: alt text

3. UCommonGameDialogDescriptor

弹窗构造器,包含Header,Body 还有Action数组(也就是按钮数组)。

  • FConfirmationDialogAction,定义按钮类型是确定还是取消,还用按钮的Label
  • 默认提供了4个弹窗类型构造方法。

4. UCommonGameDialog

弹窗的真正实现,SetupDialog用于准备整个弹窗的UI。SetupDialog上面提到的UCommonMessagingSubsystem来调用。

  • Lyra 的实现例子:ULyraConfirmationScreen

    cpp
    void ULyraConfirmationScreen::SetupDialog(UCommonGameDialogDescriptor* Descriptor, FCommonMessagingResultDelegate ResultCallback)
    {
        Super::SetupDialog(Descriptor, ResultCallback);
    
        Text_Title->SetText(Descriptor->Header);
        RichText_Description->SetText(Descriptor->Body);
    
        EntryBox_Buttons->Reset<ULyraButtonBase>([](ULyraButtonBase& Button)
        {
            Button.OnClicked().Clear();
        });
    
        for (const FConfirmationDialogAction& Action : Descriptor->ButtonActions)
        {
            FDataTableRowHandle ActionRow;
    
            switch(Action.Result)
            {
                case ECommonMessagingResult::Confirmed:
                    ActionRow = ICommonInputModule::GetSettings().GetDefaultClickAction();
                    break;
                case ECommonMessagingResult::Declined:
                    ActionRow = ICommonInputModule::GetSettings().GetDefaultBackAction();
                    break;
                case ECommonMessagingResult::Cancelled:
                    ActionRow = CancelAction;
                    break;
                default:
                    ensure(false);
                    continue;
            }
    
            ULyraButtonBase* Button = EntryBox_Buttons->CreateEntry<ULyraButtonBase>();
            Button->SetTriggeringInputAction(ActionRow);
            Button->OnClicked().AddUObject(this, &ThisClass::CloseConfirmationWindow, Action.Result);
            Button->SetButtonText(Action.OptionalDisplayText);
        }
    
        OnResultCallback = ResultCallback;
    }
  • Lyra动态创建了全部按钮,然后设置了委托并都绑定了同一个CloseConfirmationWindow关闭弹窗。

  • 最后,蓝图可以根据CloseConfirmationWindow传递过来的ECommonMessagingResult来做相应的处理。


总结

  • UGameUIManagerSubsystem 负责注册,通知。
  • UGameUIPolicy 配置根部布局
  • UCommonMessagingSubsystem 创建Dialog
  • UCommonGameDialogDescriptor 定义弹窗的内容
  • UCommonGameDialog 实现弹窗内容
  • UAsyncAction_ShowConfirmation 发起弹窗并广播结果

个人看法, UGameUIManagerSubsystem 内部有个 SwitchPolicy 的方法,用于切换不同的 UGameUIPolicy,也就是切换不同的根布局。然而,根布局只是一个放置不同stack的容器,即使要实现类似博德之门那种,根据键鼠和手柄切换不同的操作UI,也不是用SwitchPolicy来实现的,因为它已经是根容器,根容器本身不做任何实现,切换它没有意义。总感觉这里是设计非常冗余。