找回密码
 立即注册
查看: 238|回复: 1

Unreal Python 修改蓝图组件属性

[复制链接]
发表于 2022-9-4 11:21 | 显示全部楼层 |阅读模式
本文章转载自 智伤帝的个人博客 - 原文链接

前言

  记录一下这周搞到头秃的需求。
  需要修改修改蓝图组件的属性。
  过去我修改组件属性都是通过 Actor 实现。
  但是 Actor 需要在 Game World 当中进行修改,而这个时候其实是已经实例化了。
  这里需要将值赋值到蓝图当中,等于下次生成 Actor 的时候已经自动带有配置好的初始值了。
  原先我觉得 Actor 修改就是分分钟的事,蓝图相信也可以直接获取进行修改吧。
  然而我是图样图森破了_(:з」∠)_
修改蓝图组件属性尝试

  我之前修改资源的属性,比如说修改 Texture2D 资源,都需要通过 Content Browser 获取当前选择的 Asset 资源。
  然后通过 Python 的 set_editor_property 来实现修改。
  所以最开始我也是沿着这个方向去尝试的。
import unreal
assets = unreal.EditorUtilityLibrary.get_selected_assets()
print(assets)

# LogPython: [Blueprint'"/Game/sequence/NewBlueprint.NewBlueprint"']
  通过上面的方法可以获取到当前选择的 蓝图 Asset。
  但是去 Python 文档查 Asset 完全没有任何有价值的方法可以获取到它内部的 Component 链接
  最初是想通过 Actor 的 component 看看有没有办法获取到 蓝图 内部的 Component , 然并卵 ~
  于是我开始走弯路了。
  我想到蓝图编辑器窗口既然可以获取到这些 Component ,应该会有相关的线索的。
  所以我开始查阅引擎的源码,希望能够从中找到解决方案~
蓝图编辑窗口 SCSEditor 源码查阅

  首先要快速定位蓝图编辑器的源码位置。
  依然是使用老办法,通过 Tooltip 来定位最快。


  然后可以定位到 SBlueprintEditorToolbar.cpp 脚本,并且可以定位模块在引擎的 Kismet 文件夹里面。
  由于这里面的代码已经非常多了,所以新开一个 VScode 在 Kismet 目录下进行快速的搜索。
  下一步我要获取的左侧的 Component 树状图的结构。
  于是我又找了个简单的 Tooltip 来定位


  这里可以直接定位到 SCSCEditor.cpp 脚本,
  然后就开始了我更大弯路的阅读源码,其中还去找了右侧的 Detail 界面生成逻辑去看,但是其实是我想多了。
  我只要找到 蓝图的 Component 是怎么生成出树的即可。
  后来总算找到了对的 函数 OnApplyChangesToBlueprint
  当蓝图的 Component 有任何的变动应该就会触发这个函数来进行 组件树的更新


  Component 更新相关的逻辑就是红框框住的部分。
  可以看到这其中有一个关键操作。
AActor* Actor = GetActorContext();
// 代码省略 ...
AActor* BlueprintCDO = Actor->GetClass()->GetDefaultObject<AActor>();   
  这个操作通过 Actor 获取类,然后再通过 类 获取 DefaultObject 来进行操作。
  这个变量的命名就很灵性,于是去查一下 unreal cdo 相关的内容,然后可以追查到 AnswerHub 链接
  根据最佳回答可以知道, CDO 是 Unreal 的 Class Default Object 的简称。
  专门用来存储 类 的实例化初始值对象,也就是当 类实例化的时候,会通过反射系统从 CDO 对象中取值。
  所以针对蓝图这种就更为重要,因为蓝图其实是一个 C++ 类生成器,可以简单理解为 Compile 的时候生成相应的 C++ 类以及对应的 Class Default Object
  进而通过 Class Default Object 来衍生出其他的实例。
  所以修改蓝图 Component 的属性,本质上就是要修改 Class Default Object 即可。
Python 尝试修改 CDO

  通过上面的 C++ 源码,我想到在游戏场景里面获取 Actor 然后模仿上面的方式获取类。
import unreal
actors = unreal.EditorLevelLibrary.get_selected_level_actors()
for actor in actors:
    print(actor.get_class())

# LogPython: <Object '/Game/sequence/NewBlueprint.NewBlueprint_C' (0x00000209683ADD00) Class 'BlueprintGeneratedClass'>   
  果然 Actor 获取到的 Class 是特殊的 BlueprintGeneratedClass
  然而在 Python 里面也提供太多特殊的调用方式。
  我用 dir 列出这个类可以调用的方法,发现还有 get_default_object 方法,继承于 unreal._ObjectBase 链接


import unreal
actors = unreal.EditorLevelLibrary.get_selected_level_actors()
for actor in actors:
    print(actor.get_class().get_default_object())

# LogPython: LogPython: <Object '/Script/Engine.Default__BlueprintGeneratedClass' (0x0000020943CB0100) Class 'BlueprintGeneratedClass'>   
  然而类的显示路径虽然变了,但是实际上的类型是不对的,我要获取到 Actor 类型才是对的。
  于是我又搜索了一下关于 Python 蓝图 修改 CDO 相关的信息,还真的搜到官方的工程师提供的方案 链接
  没想到官方工程师给的方案更加简单,可以直接从蓝图路径中获取到 CDO ,只需要在路径上加上 _C 后缀 ,Actor获取也给省了。
  我测试了官方的代码,通过 unreal.get_default_object 这个方法获取到的 object 是 Actor 类型,是对的。
import unreal
bp_gc = unreal.load_object(None, "/Game/sequence/NewBlueprint.NewBlueprint_C")
bp_cdo = unreal.get_default_object(bp_gc)
print(bp_cdo)
print(type(bp_cdo))
# LogPython: <Object '/Game/sequence/NewBlueprint.Default__NewBlueprint_C' (0x000002096AEC4D00) Class 'NewBlueprint_C'>   
# LogPython: <type 'Actor'>   
  本来以为官方提供的就是最佳解决方案,困扰良久的问题总算解决了。
  最后证明我还是太Naive
  当我获取 Actor 的 root_component 之后,我发现没法获取 component。
import unreal
bp_gc = unreal.load_object(None, "/Game/sequence/NewBlueprint.NewBlueprint_C")
bp_cdo = unreal.get_default_object(bp_gc)
print (bp_cdo.root_component)
print (bp_cdo.get_component_by_class(unreal.ActorComponent))
print (bp_cdo.get_component_by_class(unreal.SceneComponent))
# LogPython: None
# LogPython: None
# LogPython: None
  上面测试用的蓝图比较简单,我当时用项目的蓝图还是可以获取到 root_component 但是获取不到它的子对象。
  于是到头来 Python 还是无法解决这个问题。
C++ 获取 CDO Component

  我又追查了一下 C++ 的 API 。
  发现 Actor 可以通过 GetComponents 获取
TArray<UActorComponent *> UPyToolkitBPLibrary::GetCDOInheritedComponents(AActor *CDO)   
{
    TArray<UActorComponent *> ComponentArray;   
    TInlineComponentArray<UActorComponent *> Components;   
    CDO->GetComponents(Components);   
    for (UActorComponent *Component : Components)
    {
        ComponentArray.Add(Component);
    }

    return ComponentArray;
}
  但是只是这样还是没法获取全部的 Component , 一些后续添加的 Component 还是通过 Python 无法获取。
  于是我又开始去查 SCSCEditor.cpp 的源码,因为我相信既然可以生成组件树,一定是可以获取到 Component 的。
  但是当时总是想着 Component 的线索去追查,完全没想到这个东西居然和 Node 有关。
  所以就踩了个巨坑,浪费了好多时间,然后最后实在没办法了,找了程序来帮忙,然后程序也没有搞过,还是得自己查。
  最后又从网上挣扎了一下,没想到找到了解决方案 链接
TArray<UActorComponent *> UPyToolkitBPLibrary::GetCDONodeComponents(AActor *CDO)   
{
    UBlueprintGeneratedClass *ActorBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>(CDO->GetClass());   

    // NOTE https://answers.unrealengine.com/questions/558236/how-to-get-a-component-from-a-classdefaultobject.html
    TArray<UActorComponent *> Components;   
    const TArray<USCS_Node *> &ActorBlueprintNodes = ActorBlueprintGeneratedClass->SimpleConstructionScript->GetAllNodes();   
    for (USCS_Node *Node : ActorBlueprintNodes)
    {
        Components.Add(Node->ComponentTemplate);   
    }
    return Components;
}

SimpleConstructionScript 获取到 USimpleConstructionScript 这个类
  这个类是 蓝图生成 Actor 的时候调用给 Actor 添加组件用的。
  这里并不包含所有的 Component ,只有一些蓝图额外添加的 Component 才会放到这里。
  还好有人已经做好了获取的参考代码,要我自己查到是这么个逻辑估计得天荒地老了_(:з」∠)_
  于是通过上面这个蓝图的暴露就可以获取到 蓝图 中所有的 Component (但是是没有层级关系的)
  直接获取的 Component 中用 set_editor_property 设置值,就可以实现蓝图 Component 的修改了~
  由于项目使用了一些自定义的 Component 组件,每每都要去查 C++ 源码看有什么 property 可以设置是非常不方便的。
  幸好之前推荐多次的 Unreal Python 教程已经提供了解决方案 链接
TArray<FString> UPyToolkitBPLibrary::GetAllProperties(UClass *Class)   
{
    TArray<FString> Ret;   
    if (Class != nullptr)
    {
        for (TFieldIterator<FProperty> It(Class); It; ++It)   
        {
         FProperty *Property = *It;
            if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit))   
            {
                Ret.Add(Property->GetName());   
            }
        }
    }
    return Ret;
}
  最新版本的引擎提示 UProperty 改名为了 FProperty
  目前编译可以通过,估计下一个版本就会直接报错了,所以这里我稍稍修改了一下。
  通过这个方法就可以确切知道组件下有什么属性可以通过 Python 进行设置了。
总结

  一个人搞 Unreal Python 就是痛苦无比,所有的雷都得自己解决。
  解决需求根本无法评估到底需要多少时间_(:з」∠)_,这次就踩到了天坑.
  真正解决问题的代码其实也就那么几行就够了,但是走了弯路就耗费了好多倍的时间o(╥﹏╥)o
  ε=(ο`*)))唉,但是就像我昨天吐槽的一样, C++ 的开发效率实在是太低了,能用 Python 还是尽量用 Python 解决了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-9-4 11:31 | 显示全部楼层
帮到我了,最近也在弄怎么修改蓝图中的组件
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-6-30 08:54 , Processed in 0.090297 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表