Bootstrap Chameleon Logo

为Python提供更多的编辑器API

一生二,二生三,三生万物

在之前的为虚幻编辑器添加菜单项中,我们使用 unreal.PythonBPLib.get_selected_folder() 获取到了用户在ContentBrowser中选中的目录。 这个unreal.PythonBPLib.get_selected_folder就是TAPython内置的200+个扩展的编辑器API之一,这些API补充了为UE编辑器添加了非常多的扩展功能。你可以在这里找它们的API介绍,也可以在这里,找到最常用的编辑器APIs中看到使用频率最高的100个编辑器API。

如何找到已有的API

当我们想要使用(调用)一个编辑器功能的时候,推荐照下面的顺序进行查找:

  1. 如果蓝图中可以有完成这个功能的节点,那么这个蓝图节点的功能就也有对应的Python函数可供调用
  2. 在设置好Auto Complete For TApython的编辑器中,尝试通过自动补全来查找是否有可用的API
  3. 搜索unreal的Stub文件,通过它可以快速得找到所需的API。在Auto Complete For TApython中有将unreal.py按类切分成小文件的介绍,这将是我们的搜索和跳转变得非常轻量和快速。
  4. 查看TAPython的扩展API里是提供类似的功能,比例
  5. 在编辑的cmd console命令中查找是否有可调用的cmd命令

TIP
在Cmd中输入help可以显示当前编辑器可使用的所有调试命令 'Help' text in Unreal Engine Editor Cmd window

  1. 在VS中搜索Unreal的各个Editor Library和各个EditorSubSystem相关的代码。

Extra
Epic一直在完善Python的编辑器API,这也是为什么TAPython的扩展的API中会有一些和UE内置的API重复

如果上面的步骤中都没有找到可用的API,我们就可用考虑需要通过C++添加一个额外的编辑器API,以供Python使用,具体做法如下:

添加步骤

1. C++ 类型工程

首先你需要一个C++类型的Unreal工程

2. 添加一个BlueprintLibrary类型的Unreal插件

打开Plugin窗口,点击左上角的Add按钮

Snapshot showing 'Add plugin button'

选择Blueprint Library类型的插件,填写插件名,Author等信息,点击Create Plugin按钮,这样就创建了一个BlueprintLibrary类型的插件。New plugin dialog, highlighting Blueprint Library category

在VS Studio中编译完成,其中工程之后,我们在Python命令行中就可以看到这个插件了。

Blueprint library info in Unreal Engine Output window

TIP
上图使用的是Python(REPL)模式,在非REPL模式下,需要使用print()函数来输出

插件默认会添加一个MagicBoxSampleFunction的实例函数,参数是一个float,返回-1.0f

MagicBoxBPLibrary.h

UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "MagicBox sample test testing"), Category = "MagicBoxTesting")
static float MagicBoxSampleFunction(float Param);

MagicBoxBPLibrary.cpp

float UMagicBoxBPLibrary::MagicBoxSampleFunction(float Param)
{
    return -1;
}

这个函数对于的PythonAPI为:unreal.MagicBoxBPLibrary.magic_box_sample_function,注意函数名的大小写与C++中的不同

help(unreal.MagicBoxBPLibrary.magic_box_sample_function)

# magic_box_sample_function(...) method of builtins.type instance
#     X.magic_box_sample_function(param) -> float
#     Magic Box Sample Function
#    
#     Args:
#         param (float): 
#    
#     Returns:
#         float:

A snapshot showing the added function call and its result

3. 添加蓝图可调用的函数

在VS Studio中打开c++工程,找到MagicBoxBPLibrary这个插件,在MagicBoxBPLibrary.hMagicBoxBPLibrary.cpp 分别添加给Python调用的函数的申明和定义,然后编译。通过之后,重启编辑器,我们就可以在编辑中使用新的Python API了

TIP
TAPython提供了额外的编辑器API,当然你编写的编辑器API也可以在TAPython中使用。

4. 编译后在Python中试验

函数转换规则

c++的函数名在Python中会有大小写的变化,UE的主要规则有以下几点

命名空间

  • 所有新增的BPLibrary都在unreal命名空间下
  • BPLibrary名字与C++中名字一致,大小写不便,如上面的例子中的MagicBoxBPLibrary

函数名大小写转换

  • c++中的函数名全部转换为小写字母,大写字母直接以"_"连接。有多个大写字母连续出现时,例如LOD,视为一个整体
  • bool类型的参数中,如果第一字母为小写的b,然后接着是大写字母。在Python中会将b去掉

例如c++ 中的参数bool bOverride;在python中对应的为override

  • 函数中的参数名亦遵循此规则

例如:

MessageDialog转换为message_dialog

SetStaticMeshLODMaterialID转换为set_static_mesh_lod_material_id

参数类型

  • c++ 中的bool, FString, int32, float 分别对应Python 中的bool,str, integer 和float
  • FName 对应Python中的unreal.Name。

NOTE
在Python中不用过分执着于区分FName,FString,甚至FText,它们在Python中有细微的区别,但实际使用时,可以直接使用str,UE会自动做类型转换

  • TArray,TMap分别对应Python中的unreal.Array, unreal.Map

  • 蓝图可见的UObject类型在python的unreal命名空间下有同名的python类型

非const引用

  • 非const引用类型的参数,在python中是对应的返回值,而非输入参数

举个例子

下面函数中的FIntPoint& OutSizeXY是一个非const的引用类型:

UFUNCTION(BlueprintCallable, meta = (Keywords = "Python Editor"), Category = "PythonEditor")
static TArray<FColor> GetViewportPixels(FIntPoint& OutSizeXY);

它对应的Python函数的签名为:

unreal.PythonBPLib.get_viewport_pixels() -> (Array[Color], out_size_xy=IntPoint)

返回的是一个tuple,其中第一项是Array[Color], 第二项为IntPointl类型的out_size_xy

又如: 下面例子中的InGuidStr变量类型是const的FString引用。正因为这个const,所以它是输入值;如果去掉const 则它也将变成返回值

UFUNCTION(BlueprintCallable, meta = (Keywords = "Python Editor"), Category = "PythonEditor")
static FGuid GuidFromString(const FString& InGuidStr);

它对应的Python函数的签名为:

unreal.PythonBPLib.guid_from_string(guid_str:str) -> Guid

Array

  • 参数/返回值中的TArray只能是1维的TArray,不支持2D Array和嵌套

因此对于2D的Array,例如Image中的Pixels,需要将Pixels展平后再输入或者输出

例如:

UFUNCTION(BlueprintCallable, Category = Scripting)
void SetImagePixels(FName AkaName, TArray<FLinearColor> PixelColors, int32 Width, int32 Height);

输入值pixel_colors是一个1D的Linear的数组

set_image_pixels(...)
    x.set_image_pixels(aka_name, pixel_colors, width, height) -> None
    Set SImage's Image content with Linear Colors.

    Args:
        aka_name (Name): The aka name of the widget
        pixel_colors (Array[LinearColor]): The pixel list of image, len(PixelColors) == Height * Width
        width (int32): Width of Image.
        height (int32): Height of Image.

多个返回值

利用上面介绍的非const引用的参数,我们可以在c++中将多个返回值,一同返回给Python。

其中c++函数自身的返回值一定是多个返回值(tuple)元素中的第一个,其他参数依次往后排列

自定义结构体

我们可以在c++中定义自定义的结构体,然后在Python中使用。

例如下面的例子中,我们定义了一个FStaticSwitchInfo结构体。在USturct中,我们可以通过USTRUCT(Blueprintable, BlueprintType)来声明这个结构体是蓝图/Python可见的

USTRUCT(Blueprintable, BlueprintType)
struct FStaticSwitchInfo
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PythonEditor)
    FName Name;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PythonEditor)
    bool Value;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PythonEditor)
    bool bOverride;

};

通过编译之后,我们就可以在Python中使用这个结构体了

class StaticSwitchInfo(StructBase):
    r"""
    Static Switch Info

    **C++ Source:**

    - **Plugin**: TAPython
    - **Module**: TAPython
    - **File**: PythonMaterialLib.h

    **Editor Properties:** (see get_editor_property/set_editor_property)

    - ``name`` (Name):  [Read-Write] Name
    - ``override`` (bool):  [Read-Write] Override
    - ``value`` (bool):  [Read-Write] Value
    """

对于为了在Python中使用自定义的结构体,而在c++中定义的结构体,经过我的实践。如果只是为了方便传参,那么不太推荐使用。原因是后续对这个结构体的修改,需要重新编译插件,使得Python在开发中快速敏捷的特性就失去了意义。 TIP
Some tips and Tricks

通过Python来熟悉虚幻引擎