uasset python_Unreal Python 结合 C++ 开发蓝图库插件
本文章轉載自 智傷帝的個人博客 - 原文鏈接
前言
上個月的這個時候我寫了一篇文章關于如何嵌入 PySide 調用 Qt 的 GUI 開發。 鏈接
Python 雖然很好,但是有些功能,并沒有從 C++ 里面暴露出來。
這種情況就需要通過 C++ 的藍圖開發來將這部分的功能進行暴露。
這樣 Python 基本上可以做任何 Unreal 的事情。
如何開發藍圖庫也基本可以參照上篇文章提到的 Unreal Python 教程。 鏈接
為什么需要開發 C++ 藍圖
上面的視頻鏈接有很詳細如何通過 Unreal C++ API 開發一個 Unreal 的藍圖,暴露給 Python 調用。
Unreal 的 Python 插件其實已經將 Unreal 內置的所有藍圖暴露給了 Python。
因此 藍圖 能夠做到的事情, Python 是絕對可以做到的。
而且經過一個多月的使用來看, Python 的 API 文檔做得比 藍圖 的 文檔要好很多。
有時候直接查 Python 的 API 反而更有效率,甚至會發現一些其他插件所引入的藍圖。
那么 Python 相較于 藍圖 的有什么異同?
我目前的使用感受來看,除了失去圖形節點編程的可視化之外,基本上碾壓藍圖,當然運行效率上沒有測試過。
藍圖 和 Python 的定位有很大的不一樣。
藍圖可以作為游戲運行邏輯的一部分, Python 只能當做編輯器的自動化工具。(Python 效率太低了,運行腳本寧愿用 lua 調 C++)
藍圖自身有它的優缺點,效率比不過 C++ 鏈接
但是圖形化編程,對于非 coding 人員很友好,而且一些簡單的邏輯也比較直觀。
但是復雜藍圖的連線還是太讓人勸退了。
Python 對于像我這種工具向開發的 TA 來說太友好了,畢竟很多 DCC 都使用 Python 。
Unreal 的 Python 插件大部分是對 藍圖 的分裝,基本上藍圖有的功能都可以通過 Python 來調。
同時 Python 還可以實現一些神奇的功能,比如說通過 Python 開發一個藍圖節點 ,調用 Python 的第三方庫諸如 PySide 包,或者調用系統的 cmd 或者 shell 命令。
因此從引擎的提升來說, Python 的確在這方面更勝一籌,復雜邏輯通過代碼看也比較直觀。
當然很明顯, 藍圖不能實現的引擎操作,基本上也不用指望 Python 能夠調用什么 API 來實現了。
這種情況下就需要 C++ 來擴展藍圖,實現 Python 調用。
C++ 開發藍圖庫插件
我們目前的需求并不是開發游戲調用的藍圖,因此我們可以開發一個藍圖庫插件。
這樣可以輕易將這些藍圖遷移到不同的項目里面去。
Unreal 搭建藍圖庫開發其實并不難,按照官方的指引去做即可。
首先需要創建一個 C++ 工程,如果是藍圖工程是無法寫 C++ 代碼的。
然后打開插件面板,選擇 New Plugin
然后引擎就會自動創建一個基礎插件的模板出來。
后續就是在這個基礎模板上調用 C++ 的 API 實現一些特殊的功能。
unreal C++ 插件注意事項
插件的默認結構是 .uplugin 文件加 Resource 和 Source 文件夾。
uplugin 就是一個 Json 配置,配置了插件在引擎的插件列表的顯示,以及加載方式。
Resource 存放插件顯示的圖標。
Source 存放的是 C++ 源碼了。
前面兩個不需要太過關注,重點的 Source 文件夾的東西。
里面會有 *.Build.cs 文件以及 Public 和 Private 文件夾。
*.Build.cs是 C# 代碼,通過虛幻的 Reflect 機制生成 Intermediate 的中間代碼用來編譯生成 dll。
Public 默認存放頭文件
Private 默認存放cpp源碼
引用了引擎內部的一些庫,需要在 build.cs 文件里面添加上。
否則編譯的時候回報某些類型無法識別的錯誤。
試過排查這種小錯誤花了我大半天。
前面兩個部分是添加路徑的,用來縮短頭文件索引的路徑長度。
后面的 Private 和 Public Module 則是最重要的索引頭文件的,必須要在這里配置才能在 c++ 里面調用。
這里怎么填寫可以參考引擎 Source 源碼下的文件夾名稱。
cs 里面配置就可以找 Source 源碼的一些頭文件進行引用了。
因為虛幻開源了,所以內部 Private 和 Public 沒有什么區別,也可能是我的 C++ 造詣還不夠。
配置頭文件就可以愉快地使用官方提供的一些 C++ 了。
C++ 實現 Add Component 藍圖功能
這個功能看似非常簡單,奈何 Python 就是實現不了。
就是給現有 Actor 添加新的 Component 組件而已。
但是查了 API 文檔,即便使用 Attach 相關的方法,也無法新的 Component 添加到 Actor 上。
應該說 Python 的操作沒有問題, Component 也加上了,可以通過 Python 獲取到,但是 Component 沒有注冊,無法在 UI 上顯示出來。
經過我查閱大量網上的資料之后,只在論壇上找到了一個通過 C++ 實現的方案。 鏈接
這段代碼里面有很關鍵的 RegisterComponent 的操作。
而這些操作并沒有暴露到 Python 或者說 藍圖 里面。
當然這個添加 Component 的方法估計也和 Unreal 的機制有關,我對 Unreal 引擎還不是很熟,就不做無關的揣測了。
Python 的文檔在 Actor 的部分有所涉及。
不過這個問題就非常蛋疼,
unreal.EditorLevelLibrary.get_all_level_actors_components 可以獲取所有注冊的 Component
Actor 也可以刪除現有的 Component ,偏偏無法添加新的 Component
C++ 的部分我簡化了上面回答的代碼。
如果沒有傳入具體的 Component 類型就返回 None 給 Python 就好了。
UActorComponent* URedArtToolkitBPLibrary::AddComponent(AActor* a, USceneComponent *future_parent, FName name, UClass* NewComponentClass)
{
UActorComponent* retComponent = nullptr;
if (NewComponentClass)
{
UActorComponent* NewComponent = NewObject(a, NewComponentClass, name);
FTransform CmpTransform;// = dup_source->GetComponentToWorld();
//NewComponent->AttachToComponent(sc, FAttachmentTransformRules::KeepWorldTransform);
// Do Scene Attachment if this new Comnponent is a USceneComponent
if (USceneComponent* NewSceneComponent = Cast(NewComponent))
{
if (future_parent != 0)
NewSceneComponent->AttachToComponent(future_parent, FAttachmentTransformRules::KeepWorldTransform);
else
NewSceneComponent->AttachToComponent(a->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
NewSceneComponent->SetComponentToWorld(CmpTransform);
}
a->AddInstanceComponent(NewComponent);
NewComponent->OnComponentCreated();
NewComponent->RegisterComponent();
a->RerunConstructionScripts();
retComponent = NewComponent;
}
return retComponent;
}
頭文件怎么去 #include ,我基本就是用 VScode 搜索引擎源碼,查找頭文件的位置,然后逐個添加。
C++有點麻煩的地方就是 cpp 代碼寫完之后還要將函數注冊到 頭文件 里面
不過基本上復制 cpp 的函數第一行就可以了,只需要把 :: 前面的類名刪除掉而已。
下面就是點擊 VS 上面的 本地 windows 調試,編譯插件并啟動項目。
我用 VS2017 編譯經常遇到 clxx.dll 命令行過長 的錯誤。
網上了查了要將項目的編譯改為 Release 版本,或者升級到 VS2019 才可以解決。(網上查到這個是 VS 的 BUG)
后來我是隨便將一些 Intermediate 文件夾刪除,然后重新調用 UnrealHeaderTool 生成反射代碼就不會有這個編譯報錯了。
完成到這里基本可以參照老外的教程,使用 Python 可以在 unreal 庫下找到剛才藍圖擴展的類的,類下面就由剛才擴展的 函數 了。
行數名稱自動將 C++ 的駝峰轉為 Python pep8 規范的 sneak 寫法。
C++ 藍圖獲取當前 Sequencer 選擇的元素
上面介紹了 C++ 的編寫的流程,就不再追溯,這里著重看藍圖的實現。
我最近有一個需求是要獲取當前打開的 Sequencer 里面的元素,然后進行 FBX 導出。
但是查遍了 Unreal 的 Python 文檔也沒有找到這個方法。
對了這里記錄一個天坑,之前被坑慘了的。
Unreal Python 的老外教程里面也記錄一些使用 Sequencer 處理的 Python 方案。
但是我發現到我調用這些 API 的時候, Unreal 居然報錯找不到這些 API。
然后我就以為是我當前 Unreal 版本出 BUG 了,或者是官方刪除了這個功能。
后來折騰了好久之后才發現,我沒有開啟 Sequencer Scripting 插件,所以那些調用藍圖沒有加載(:з」∠)
我當時還不知道 Python 調用的就是藍圖, 踩了這個坑才有了深刻的認識。
回到這里要實現的功能,我查了 C++ 相關的問題,總算是找到了一個比較可靠的回復。 鏈接
于是我就抄了這里的代碼。
不過上面的代碼有點舊,其中 IAssetEditorInstance* AssetEditor = UAssetEditorSubsystem().FindEditorForAsset(LevelSeq, true); 編譯會報錯
修改為 IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(LevelSeq, true); 解決問題。
經過修改之后上面的代碼可以獲取到當前 Sequencer 打開的 LevelSequence
原理也不復雜,就是遍歷項目所有的 LevelSequence 然后找到那個開啟了 Editor 的 LevelSequence
然后在從這個 LevelSequence 里獲取 Editor 再從 Editor 獲取 Sequencer。
雖然這個遍歷有點不太合理,但是我在測試的項目上還是很奏效的。
但是當我將代碼編譯放到我們正在開發的項目上之后,出現了大問題。
項目有大量的 LevelSequence ,遍歷需要很長的時間,并且遍歷之后大量的材質啟動了編譯,導致電腦很卡。
于是我又查了一下 C++ API 文檔,發現有個很有用的函數 GetAllEditedAssets。
這個函數可以獲取當前打開在編輯器里面的 Assets ,能打開的 Asset 肯定就那么幾個。
這樣找 Editor 的速度可就快多了。
ULevelSequence* URedArtToolkitBPLibrary::GetFocusSequence()
{
UAssetEditorSubsystem* sub = GEditor->GetEditorSubsystem();
TArray assets = sub->GetAllEditedAssets();
for (UObject* asset : assets)
{
IAssetEditorInstance* AssetEditor = sub->FindEditorForAsset(asset, false);
FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;
if (LevelSequenceEditor != nullptr)
{
ULevelSequence* LevelSeq = Cast(asset);
return LevelSeq;
}
}
return nullptr;
}
上面只是找 LevelSequence ,還需要找當前 LevelSequence 里面選擇的元素。
好在 Sequencer 提供了 GetSelectedObjects 的方法
通過 LevelSequence 可以獲取到 Sequencer
TArray URedArtToolkitBPLibrary::GetFocusBindings(ULevelSequence* LevelSeq)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(LevelSeq, false);
FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;
TArray SelectedGuid;
if (LevelSequenceEditor != nullptr)
{
ISequencer* Sequencer = LevelSequenceEditor->GetSequencer().Get();
Sequencer->GetSelectedObjects(SelectedGuid);
return SelectedGuid;
}
return SelectedGuid;
}
這樣獲取返回的是 Guid , Python 有 Guid 類。
可以通過 LevelSequence 的 get_bindings 方法獲取 sequence 相關的 binding
再調用 get_id 方法獲取 guid ,然后通過 C++ 的藍圖將獲取到的 id 篩選一遍。
# NOTE 獲取當前 Sequencer 中的 LevelSequence
sequence = unreal.RedArtToolkitBPLibrary.get_focus_sequence()
# NOTE 獲取當前 Sequencer 中選中的 Bindings
id_list = unreal.RedArtToolkitBPLibrary.get_focus_bindings(sequence)
bindings_list = [binding for binding in sequence.get_bindings() if binding.get_id() in id_list]
這樣就獲取到了當前選擇的 SequencerBindingProxy 類。
通過 unreal.SequencerTools.export_fbx 就可以將選擇的元素導出 FBX 了。
import unreal
from Qt import QtCore, QtWidgets, QtGui
def alert(msg=u"msg", title=u"警告", button_text=u"確定"):
# NOTE 生成 Qt 警告窗口
msg_box = QtWidgets.QMessageBox()
msg_box.setIcon(QtWidgets.QMessageBox.Warning)
msg_box.setWindowTitle(title)
msg_box.setText(msg)
msg_box.addButton(button_text, QtWidgets.QMessageBox.AcceptRole)
unreal.parent_external_window_to_slate(msg_box.winId())
msg_box.exec_()
def unreal_export_fbx(fbx_file):
# NOTE 獲取當前 Sequencer 中的 LevelSequence
sequence = unreal.RedArtToolkitBPLibrary.get_focus_sequence()
if not sequence:
alert(u"請打開定序器")
return
# NOTE 獲取當前 Sequencer 中選中的 Bindings
id_list = unreal.RedArtToolkitBPLibrary.get_focus_bindings(sequence)
bindings_list = [binding for binding in sequence.get_bindings() if binding.get_id() in id_list]
if bindings_list:
# NOTE 導出 FBX 文件
option = unreal.FbxExportOption()
option.set_editor_property("collision",False)
world = unreal.EditorLevelLibrary.get_editor_world()
unreal.SequencerTools.export_fbx(world,sequence,bindings_list,option,fbx_file)
else:
alert(u"請選擇定序器的元素進行 FBX 導出")
return
上面就是完整的示例代碼。
當然導出的 FBX 是帶動畫的,還需要將動畫處理成帶 蒙皮骨骼 的 FBX 。
這個操作我是通過 FBX Python SDK 實現的。
官方的 ExportScene01 包含了蒙皮創建,關鍵幀處理等等的操作,絕大部分的代碼可以照抄。
這里蒙皮轉換的需求很簡單,因此稍微修改一下就可以用了。
處理完成之后將 FBX 輸出到臨時目錄,然后用 Python 調 windows 命令打開路徑。
總結
其實調用 C++ API 并不難,這種程度的操作還沒有修改到 Unreal 的底層,很多機制也沒有用到,我作為個外行還是可以應付的。
而且 Unreal C++ 本身做了很多工作,比如實現了 垃圾回收,含有智能指針,都降低了開發難度(同時增加了學習的難度)
Unreal 開發比較難受的地方時教程文檔各方面都不全, Unity 文檔還有代碼示例,Unreal 因為開源,基本上就是讓你直接看源碼(:з」∠)
有時候遇到的一些奇奇怪怪的問題還找不到任何網上的提問,就很難受了。
最后引擎編譯非常耗時,如果要搞這一塊的研究,一定一定要配臺好電腦。
總結
以上是生活随笔為你收集整理的uasset python_Unreal Python 结合 C++ 开发蓝图库插件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么做批注_BIM平台是什么?有何用?怎
- 下一篇: 如何让开关打开_安卓手机如何打开USB调