找回密码
 立即注册
查看: 635|回复: 0

[笔记] 在Unity中制作Houdini工具

[复制链接]
发表于 2022-10-15 10:04 | 显示全部楼层 |阅读模式
需求说明

在项目中,我们给美术做了很多Houdini工具,但美术在使用的适合需要把HDA手动拖到场景中并配置输入。
为了减少美术使用工具的障碍(最理想的状态应该是让美术对Houdini无感,不过我觉得HDA的默认面板还是可以一用的),需要在Unity里开发一些小工具来辅助美术的工作。
制作工具你需要:对Unity Editor的一点了解 + 对HoudiniEngine For Unity的脚本的一点了解

一些具体功能的实现方式

好像没什么好描述说明的,直接贴代码吧,换个项目也能复用。
使用Unity C#来加载Houdini HDA

public static HEU_HoudiniAsset houdiniAsset = null; //为了后续赋值之类的需要HEU_HoudiniAsset的操作,要记录它
PTStartTool(path, out houdiniAsset); //前面那个参数是个Asset开头的路径string

//其实好像不需要拆成这么多个方法 ↓
public static void PTStartTool(string HDAAssetPath, out HEU_HoudiniAsset mHoudiniAsset)
{
    GameObject rootGO = PCGToolsUtils.PTLoadHoudiniHDA(HDAAssetPath);
    HEU_HoudiniAsset houdiniAsset = PCGToolsUtils.PTQueryHoudiniAsset(rootGO);
    mHoudiniAsset = houdiniAsset;
    if (houdiniAsset == null)
    {
        return;
    }
    PCGToolsUtils.PTCookAsset(houdiniAsset);
}

public static GameObject PTLoadHoudiniHDA(string HDAAssetPath)
{
    string HDAFullPath = HEU_AssetDatabase.GetAssetFullPath(HDAAssetPath);
    if (string.IsNullOrEmpty(HDAFullPath))
    {
        HEU_Logger.LogErrorFormat("Unable to load houdini asset at path: {0}", HDAAssetPath);
        return null;
    }
    HEU_SessionBase session = HEU_SessionManager.GetOrCreateDefaultSession();
    GameObject rootGO = HEU_HAPIUtility.InstantiateHDA(HDAFullPath, Vector3.zero, session, true);
    if (rootGO != null)
    {
        HEU_EditorUtility.SelectObject(rootGO);
    }
    return rootGO;
}

public static HEU_HoudiniAsset PTQueryHoudiniAsset(GameObject rootGO)
{
    HEU_HoudiniAssetRoot heuRoot = rootGO.GetComponent<HEU_HoudiniAssetRoot>();
    if (heuRoot == null)
    {
        HEU_Logger.LogWarningFormat("Unable to get the HEU_HoudiniAssetRoot from gameobject: {0}. Not a valid HDA.", rootGO.name);
        return null;
    }
    if (heuRoot.HoudiniAsset == null)
    {
        HEU_Logger.LogWarningFormat("Unable to get the HEU_HoudiniAsset in root gameobject: {0}. Not a valid HDA.", rootGO.name);
        return null;
    }
    return heuRoot.HoudiniAsset;
}
用脚本设置HDA工具参数

//直接用节点的名字就可以赋值
HEU_ParameterUtility.SetString(houdiniAsset, "name", "a name");
HEU_ParameterUtility.SetToggle(houdiniAsset, "input", true);
设置完参数以后需要手动Recook才能看到生成结果变化
//Recook
houdiniAsset.RequestCook(bCheckParametersChanged: true, bAsync: false, bSkipCookCheck: true, bUploadParameters: true);
//有的时候Recook没有反应(原因不明),可以尝试Rebuild
houdiniAsset.RequestReload(bAsync: false);
赋值的前提是要知道节点的数据类型,数据类型当然可以打开Houdini直接查看,不过也可以在Unity里直接检查:
public static void PTCheckParms(HEU_HoudiniAsset houdiniAsset)
{
    List<HEU_ParameterData> parms = houdiniAsset.Parameters.GetParameters();
    if (parms == null || parms.Count == 0)
    {
        HEU_Logger.LogFormat("No parms found");
        return;
    }
    StringBuilder sb = new StringBuilder();
    foreach (HEU_ParameterData parmData in parms)
    {
        sb.AppendLine(string.Format("Parm: name={0}, type={1}", parmData._labelName, parmData._parmInfo.type));
    }
    HEU_Logger.Log("Parameters: \n" + sb.ToString());
}
这个脚本的输出大概是这样的,像是图上这种,很容易就可以看出数据类型是Int,Float和Toggle,可以直接用HEU_ParameterUtility.SetXXX赋值。


除了这些,常用的还有HDA组件还有按钮,虽然按钮的type末尾是INT_END,但不能用SetInt赋值:
if (GUILayout.Button("获取按钮值"))
{
    int tmpInt;
    HEU_ParameterData buttonData = houdiniAsset.Parameters.GetParameter("cookbutton");
    tmpInt = buttonData._intValues[0];
    Debug.Log($"获取按钮值结果:{tmpInt}"); //0
}

if (GUILayout.Button("触发按钮"))
{
    houdiniAsset.Parameters.TriggerButtonParameter("cookbutton");
    //似乎也可以靠 buttonData._intValues[0]=1 来触发按钮,之前试过我有点忘了
}
用脚本设置保存和读取HDA预设

//保存HDA配置到路径
public static void SaveHDAPresetToDir(string fileName, string folderDir, HEU_HoudiniAsset houdiniAsset) {
    string filePattern = "heupreset";
    string newPath = EditorUtility.SaveFilePanel("Save HDA preset", folderDir, fileName + "." + filePattern, filePattern);
    if (newPath != null && !string.IsNullOrEmpty(newPath))
    {
        HEU_AssetPresetUtility.SaveAssetPresetToFile(houdiniAsset, newPath);
    }
}

//从路径加载HDA配置
public static void LoadHDAPresetFromDir(string folderDir, HEU_HoudiniAsset houdiniAsset)
{
    string filePattern = "heupreset,preset";
    string newPath = EditorUtility.OpenFilePanel("Load HDA preset", folderDir, filePattern);
    if (newPath != null && !string.IsNullOrEmpty(newPath))
    {
        HEU_AssetPresetUtility.LoadPresetFileIntoAssetAndCook(houdiniAsset, newPath);
    }
}

if (GUILayout.Button("保存配置", PTStyles.PTButtonStyle))
{
    string presetFolderPath = "Assets/Res/Houdini/HDAPreset";
    SaveHDAPresetToDir("Config", presetFolderPath, houdiniAsset);
}
if (GUILayout.Button("加载配置", PTStyles.PTButtonStyle))
{
    string presetFolderPath = "Assets/Res/Houdini/HDAPreset";
    LoadHDAPresetFromDir(presetFolderPath, houdiniAsset);
}
用脚本向InputNode中赋值



默认情况下,配置输入的时候需要美术把GameObject一个一个地拖到面板上,这个过程很耗费时间,可以用脚本来完成。
private static HEU_InputObjectInfo CreateInputObjectInfo(GameObject inputGameObject)
{
    HEU_InputObjectInfo newObjectInfo = new HEU_InputObjectInfo();
    newObjectInfo._gameObject = inputGameObject;
    newObjectInfo.SetReferencesFromGameObject();

    return newObjectInfo;
}

if (GUILayout.Button("自动填充输入"))
{
    HEU_InputNode node = houdiniAsset.InputNodes[0]; //第一个InputNode
    node.InputObjects.Clear(); //清空原有的输入
    string fullPath = "Assets/Res/Prefab/Sector/test.prefab";
    GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(fullPath);
    if (go != null)
    {
        node.InputObjects.Add(CreateInputObjectInfo(go));
    }
    if (node._uiCache != null)
    {
        node._uiCache._inputNodeSerializedObject.ApplyModifiedProperties();
    }
    node.RequiresUpload = true;
    node.ClearUICache();
    Resources.UnloadUnusedAssets();
}
需要注意的是, node.InputObjects 这个方法在Houdini的脚本中是internal的,只允许同一个程序集的脚本调用。可以用下面的代码来指定internal方法对其它的程序集可用:
#if UNITY_EDITOR
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("HoudiniEngineUnityEditor")]
[assembly: InternalsVisibleTo("HoudiniEngineUnityEditorTests")]
[assembly: InternalsVisibleTo("HoudiniEngineUnityPlayModeTests")]
[assembly: InternalsVisibleTo("Assembly-CSharp-Editor")]
#endif
脚本说明

目前我的开发涉及到的脚本包括以下这些,浅看一遍就能速成。
利用脚本加载HDA并修改参数的例子:HoudiniEngineUnity\Scripts\Examples\HEU_ExampleEvergreenQuery.cs
HDA的Inspector面板,可以用来找一些默认按钮的具体实现方式:HoudiniEngineUnity\Editor\UI\HEU_HoudiniAssetUI.cs
菜单栏里的面板和功能:HoudiniEngineUnity\Editor\HEU_EditorMenu.cs
InputNode的窗口面板:HoudiniEngineUnity\Editor\UI\HEU_InputNodeUI.cs

参考资料

Unity Editor编辑器功能详解:Unity3d Editor 编辑器扩展功能详解(1) 目录索引
Houdini Engine Scripting API In Unity:
官方API文档,很简洁地梳理了Houdini的Unity插件里的脚本结构:Houdini Engine for Unity: Plugin API
(过期资料)
官方在2014年出过一个教程,API版本太老了,已经没什么用了:Intro to Houdini Engine Scripting API | Collections | SideFX
2018年的一个例子,API版本太老了,已经没什么用了: [Houdini Snippet] Digital Asset Runtime Control on Unity Editor using Houdini Engine - YouTube

本帖子中包含更多资源

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

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-12-22 11:26 , Processed in 0.162267 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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