前言
这个东西本来是 Minigame 的时候用来方便搭建场景的工具, 因为当时的情况,时间紧迫,所以最后没弄成,留下了些许遗憾。
后来也只是更新了个办成品到 PyToolkit 里面 随后的开发就一直落下了。
最近刚好天时地利人和,就把这个遗憾给补上了。
获取 Unreal 资源缩略图
Placer 我想要实现将 Unreal 内置的资产缩略图整合到 Qt 的 ListWidget 里面。 如此可以让 Qt 界面的功能性更强。
但是如何利用 C++ 获取到资源的缩略图呢?
经过网上搜查,我在 CSDN 找到一篇不错的文章 链接
Unreal 里面有 ThumbnailTools::RenderThumbnail
可以生成缩略图 文章里面还用了 GetUncompressedImageData
可以获取到图片的源数据。
1 2 3 4 5 6 7 TArray<uint8> UPyToolkitBPLibrary::GetThumbnail (UObject* MeshObject,int32 _imageRes) { FObjectThumbnail _objectThumbnail; ThumbnailTools::RenderThumbnail (MeshObject, _imageRes, _imageRes, ThumbnailTools::EThumbnailTextureFlushMode::AlwaysFlush, NULL , &_objectThumbnail); return _objectThumbnail.GetUncompressedImageData (); }
通过上面的调用可以获取到一串数字的数组,这个整数的数组让我啪的一下就反应过来,和我以前研究的 Maya 视窗截取有相似之处。 链接 代码链接
1 2 3 4 5 6 7 8 9 10 pixel = image.pixels().__long__() ptr = ctypes.cast(pixel, ctypes.POINTER(ctypes.c_char)) size = width * height * 4 if number: return [ord (char) for char in ctypes.string_at(ptr, size)] else : return ctypes.string_at(ptr, size)
当时是从 Maya 的 API 中获取到了字符数据,然后通过 Python 将数据转换为 0-255 的数字 这次反过来,我是获取到了一系列代表 0-255 的数字了。 随后我研究了一下 QImage 的类,发现它其实可以直接处理 字符 数据,不需要像之前那样进行处理。 链接
之前为了将数据传到 Maya API 的 Image 才需要如此大费周折。 因而这一次换一种思路管理获取的数据。
1 2 3 data = "" .join([chr (v) for v in py_lib.get_thumbnail(asset)]) image = QtGui.QImage(data, size, size, QtGui.QImage.Format_RGB32) widget.setPixmap(QtGui.QPixmap.fromImage(image).scaled(size / 2 , size / 2 ))
所以这里只要反其道而行之,将获取的数字数组转换为 字符串 即可。
2021-3-30 Python3 更新
升级 Py3 之后,使用上述的方法将 List 数据转成字符串传到 PySide2 之后会出错。 首先的问题是 Python3 默认使用 unicode 编码,直接会导致 overflow 错误 经过我的研究,需要将 unicode 转为 bytes 类型才不会出错,但是直接转 bytes 之后图片就对不上了。
后来实在没有办法了,请教了吴真大佬,最后得出是利用 struct 模块构建出合适类型的数据从而在长度上匹配,传入的数据才是正确的。
1 2 3 4 5 import structdata = py_lib.get_thumbnail(asset) data = struct.pack('=' +'B' *len (data), *data) image = QtGui.QImage(data, size, size, QtGui.QImage.Format_RGB32) widget.setPixmap(QtGui.QPixmap.fromImage(image).scaled(size / 2 , size / 2 ))
Unreal Python Undo 操作实现
之前做 Minigame 的时候,遇到了个棘手的问题,当时我通过 Python 的 set_actor_location_and_rotation
的方法来实现物体的吸附。
1 2 3 4 5 6 7 8 origin, target = self.get_selected_actors() location = origin.get_actor_location() rotation = origin.get_actor_rotation() target.set_actor_location_and_rotation( location, rotation, False , False ) level_lib.set_selected_level_actors([target])
当时上面的做法无法实现 Unreal 的撤销操作。 如果用户不想要撤销我就彻底没辙。
我想起之前看 虚幻官方直播有提及 撤销需要接入 begin_transaction 和 end_transaction 才可以。 Youtube链接 Github链接
有些时候直接用 上面方法是好使的 但是上面的移动物体的方法并不管用。 后来还是在老牌的 UnrealEnginePython
的仓库里面找到了很有用的说明文档 Transactions_API.md
非常凑巧,说明的例子也是移动物体。 里面提到需要加上 modify 方法才可以正常调用。 于是我查了一下官方 Python 文档也有这个方法,而且是配合 transaction 来使用的 链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 origin, targets = self.get_selected_actors() sys_lib.begin_transaction("ALIGN" , "align selected" , None ) for target in targets: location = origin.get_actor_location() rotation = origin.get_actor_rotation() target.modify(True ) target.set_actor_location_and_rotation(location, rotation, False , False ) level_lib.set_selected_level_actors([target]) target.modify(False ) sys_lib.end_transaction()
结合上面提到的就可以通过 modify 的设置来配置 Actor 的 Undo 了
C++ 直接调用右键菜单的功能
虚幻有些功能功能藏得很深,用起来也很不方便。
如上图所示吸附、选择等等功能,调用需要在大段的菜单里面找功能。 我希望将这些功能整合到一个面板上,方便使用。 当然这个调用靠 Python 做不到的,我希望通过简单的 C++ 就可以调用大量的这种内置的功能。
以前要解决这个问题,我通常多需要将引擎内部的源码拷贝到我的蓝图里面进行调用。 如果说这个需要我这边做一些源码的修改还算合理。 但是如果只是如上述所说的简单功能,那就很费劲了。 那么有没有更加简单的方式来调用菜单里面注册好的功能呢? 当然还是有的,答案也在 Unreal 的 C++ 源码里面。
之前我有研究过怎么用 C++ 添加菜单或者注册快捷键。 需要用到 UI_COMMAND
来生成 FUICommandInfo
然后通过 FUICommandList 的 MapAction 分配调用的方法。 既然是用 FUICommandList
映射了 FUICommandInfo
执行的方法。 那么能否只通过上述的两个类就能直接触发函数。 这样我实现脸触发函数的参数都省了,更加简单。 后来我翻阅 FUICommandList 的 API 文档发现他有 ExecuteAction 的方法。 理论上应该可以直接触发注册的 FUICommandInfo 的。 但是直接搜索 ExecuteAction 会找到很多无关的调用。 后来我灵机一动,搜索 CanExecuteAction
这个方法应该可以极大缩小范围。
[^F]CanExecuteAction\(
通过正则表达式过滤可以找到我想要的实现效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 bool UPyToolkitBPLibrary::ExecLevelEditorAction (FString Action) { FLevelEditorModule &LevelEditorModule = FModuleManager::GetModuleChecked <FLevelEditorModule>(TEXT ("LevelEditor" )); auto Actions = LevelEditorModule.GetGlobalLevelEditorActions (); auto Commands = LevelEditorModule.GetLevelEditorCommands (); TMap<FString, TSharedPtr<FUICommandInfo>> ActionMap; ActionMap.Add ("SaveAs" , Commands.SaveAs); ActionMap.Add ("EditAsset" , Commands.EditAsset); ActionMap.Add ("EditAssetNoConfirmMultiple" , Commands.EditAssetNoConfirmMultiple); ActionMap.Add ("SnapOriginToGrid" , Commands.SnapOriginToGrid); ActionMap.Add ("SnapOriginToGridPerActor" , Commands.SnapOriginToGridPerActor); ActionMap.Add ("AlignOriginToGrid" , Commands.AlignOriginToGrid); ActionMap.Add ("SnapTo2DLayer" , Commands.SnapTo2DLayer); ActionMap.Add ("SnapToFloor" , Commands.SnapToFloor); ActionMap.Add ("AlignToFloor" , Commands.AlignToFloor); ActionMap.Add ("SnapPivotToFloor" , Commands.SnapPivotToFloor); ActionMap.Add ("AlignPivotToFloor" , Commands.AlignPivotToFloor); ActionMap.Add ("SnapBottomCenterBoundsToFloor" , Commands.SnapBottomCenterBoundsToFloor); ActionMap.Add ("AlignBottomCenterBoundsToFloor" , Commands.AlignBottomCenterBoundsToFloor); ActionMap.Add ("DeltaTransformToActors" , Commands.DeltaTransformToActors); ActionMap.Add ("MirrorActorX" , Commands.MirrorActorX); ActionMap.Add ("MirrorActorY" , Commands.MirrorActorY); ActionMap.Add ("MirrorActorZ" , Commands.MirrorActorZ); ActionMap.Add ("LockActorMovement" , Commands.LockActorMovement); ActionMap.Add ("AttachSelectedActors" , Commands.AttachSelectedActors); ActionMap.Add ("SavePivotToPrePivot" , Commands.SavePivotToPrePivot); ActionMap.Add ("ResetPrePivot" , Commands.ResetPrePivot); ActionMap.Add ("ResetPivot" , Commands.ResetPivot); ActionMap.Add ("MovePivotHereSnapped" , Commands.MovePivotHereSnapped); ActionMap.Add ("MovePivotToCenter" , Commands.MovePivotToCenter); ActionMap.Add ("AlignToActor" , Commands.AlignToActor); ActionMap.Add ("AlignPivotToActor" , Commands.AlignPivotToActor); ActionMap.Add ("SelectAll" , FGenericCommands::Get ().SelectAll); ActionMap.Add ("SelectNone" , Commands.SelectNone); ActionMap.Add ("InvertSelection" , Commands.InvertSelection); ActionMap.Add ("SelectImmediateChildren" , Commands.SelectImmediateChildren); ActionMap.Add ("SelectAllDescendants" , Commands.SelectAllDescendants); ActionMap.Add ("SelectRelevantLights" , Commands.SelectRelevantLights); ActionMap.Add ("SelectAllWithSameMaterial" , Commands.SelectAllWithSameMaterial); bool Available = ActionMap.Contains (Action); if (Available) { auto ActionPtr = ActionMap[Action].ToSharedRef (); Available = Actions->CanExecuteAction (ActionPtr); if (Available) Actions->ExecuteAction (ActionPtr); } return Available; }
做了个字典用字符串映射内部的 FUICommandInfo 这样 Python 传入相应的字符串就可以调用内部的方法了。
1 2 3 import unrealpy_lib = unreal.PyToolkitBPLibrary() py_lib.exec_level_editor_action("SaveAs" )
总结
以上就是 Unreal Python 的一些调用上的增强。 其实最近还有一些别的 Unreal Python 探索。 回头抽空在把 菜单 ToolMenuEntryScript
和 蓝图内添加 Component 给加上吧。