{"id":307,"date":"2014-10-05T20:00:46","date_gmt":"2014-10-05T20:00:46","guid":{"rendered":"http:\/\/blog.restemeier.com\/?p=307"},"modified":"2014-12-14T09:47:42","modified_gmt":"2014-12-14T09:47:42","slug":"unity3d-component-setup-from-a-blender-scene","status":"publish","type":"post","link":"http:\/\/blog.restemeier.com\/?p=307","title":{"rendered":"Unity3D component setup from a Blender scene"},"content":{"rendered":"<p>It is sometimes convenient to specify the component setup of a model in <a href=\"http:\/\/www.blender.org\/\">Blender<\/a> instead of <a href=\"http:\/\/unity3d.com\/\">Unity3D<\/a>, to hide implementation details and to make the life of artists and designers easier. This is for example useful to specify invisible hits, or areas that can&#8217;t be navigated by AI. The easiest way to do that is to use custom properties in Blender.<\/p>\n<p><a href=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.55.07.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.55.07.png\" alt=\"Custom Properties\" width=\"301\" height=\"409\" style=\"float: none;\" class=\"alignleft size-full wp-image-309\" srcset=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.55.07.png 301w, http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.55.07-220x300.png 220w\" sizes=\"auto, (max-width: 301px) 100vw, 301px\" \/><\/a><\/p>\n<p>Blender allows to add custom string, float or integer properties to an object. The custom properties that are attached to meshes, materials or textures are stored separately, and are currently not picked up by Unity3D.<\/p>\n<p>The new binary FBX exporter (from Blender version 2.71+) can write these values to a FBX file, which Unity3D can pick up during import. The automatic Blender importer in Unity3D still uses the old Ascii exporter, so you need to either export files manually, or modify the Unity3D import script to select the new exporter. I think it is a good idea to get the latest FBX exporter scripts from the <a href=\"https:\/\/builder.blender.org\/download\/\">nightly build<\/a>, to get many fixes over the version released in 2.71.<\/p>\n<p><a href=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-20.10.01.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-20.10.01.png\" alt=\"Bildschirmfoto 2014-10-05 um 20.10.01\" width=\"242\" height=\"696\" style=\"float: none;\" class=\"alignleft size-full wp-image-312\" srcset=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-20.10.01.png 242w, http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-20.10.01-104x300.png 104w\" sizes=\"auto, (max-width: 242px) 100vw, 242px\" \/><\/a><\/p>\n<p>The new binary exporter needs to be selected, and &#8220;custom properties&#8221; needs to be enabled. I modified my Blender and Unity3D scripts to always use the new exporter and have &#8220;custom properties&#8221; enabled when models are imported.<\/p>\n<p>On the Unity3D side they are received with <a href=\"http:\/\/docs.unity3d.com\/ScriptReference\/AssetPostprocessor.OnPostprocessGameObjectWithUserProperties.html\">AssetPostprocessor.OnPostprocessGameObjectWithUserProperties<\/a>. It is a good idea to start with the example script to see if the custom properties are coming across.<\/p>\n<p><a href=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.58.11.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.58.11.png\" alt=\"Example setup\" width=\"814\" height=\"526\" style=\"float: none;\" class=\"alignleft size-full wp-image-308\" srcset=\"http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.58.11.png 814w, http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.58.11-300x193.png 300w, http:\/\/blog.restemeier.com\/wp-content\/uploads\/2014\/10\/Bildschirmfoto-2014-10-05-um-19.58.11-624x403.png 624w\" sizes=\"auto, (max-width: 814px) 100vw, 814px\" \/><\/a><\/p>\n<p>I am using several custom properties currently:<\/p>\n<ul>\n<li>static: mark GameObject as static, using <a href=\"http:\/\/docs.unity3d.com\/ScriptReference\/GameObjectUtility.SetStaticEditorFlags.html\">GameObjectUtility.SetStaticEditorFlags<\/a><\/li>\n<li>navigation: set or clear the StaticEditorFlags.NavigationStatic flag on a GameObject<\/li>\n<li>render: delete the MeshFilter\/MeshRenderer\/SkinnedMeshRenderer if set to 0<\/li>\n<li>collision: delete the MeshCollider if set to 0<\/li>\n<li>receiveshadows: set or clear the receiveShadows flag on a <a href=\"http:\/\/docs.unity3d.com\/ScriptReference\/MeshRenderer.html\">MeshRenderer<\/a> or <a href=\"http:\/\/docs.unity3d.com\/ScriptReference\/SkinnedMeshRenderer.html\">SkinnedMeshRenderer<\/a><\/li>\n<li>castshadows: set or clear the castShadows flag on a <a href=\"http:\/\/docs.unity3d.com\/ScriptReference\/MeshRenderer.html\">MeshRenderer<\/a> or <a href=\"http:\/\/docs.unity3d.com\/ScriptReference\/SkinnedMeshRenderer.html\">SkinnedMeshRenderer<\/a><\/li>\n<\/ul>\n<p>It is of course possible to add more game specific components to a GameObject. It seems to be safe to delete components, though deleting GameObjects causes an error. I just mark those objects as EditorOnly.<\/p>\n<p>My AssetPostprocessor script is probably a bit overkill, because it supports setting file-global defaults in an Empty called &#8220;defaults&#8221; in Blender. If you hardwire the defaults you may be able to just process the properties once in OnPostprocessGameObjectWithUserProperties. That function is called once for each Blender node that has custom properties. To process all GameObject nodes you need to handle OnPostprocessModel as well.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing UnityEngine;\r\nusing UnityEditor;\r\nusing System.Collections.Generic;\r\n\r\n\/\/ automatically configure game objects imported from a model\r\npublic class SetupGameObject : AssetPostprocessor {\r\n\tDictionary&lt;string, object&gt; defaults = new Dictionary&lt;string, object&gt; ();\r\n\tDictionary&lt;GameObject, Dictionary&lt;string, object&gt;&gt; settings = new Dictionary&lt;GameObject, Dictionary&lt;string, object&gt;&gt;();\r\n\t\r\n\tprivate bool GetProperty(GameObject go, string key, bool defaultValue) {\r\n\t\tDictionary&lt;string, object&gt; objectSettings = null;\r\n\t\tif (settings.TryGetValue (go, out objectSettings)) {\r\n\t\t\tobject o = null;\r\n\t\t\tif (objectSettings.TryGetValue(key, out o)) {\r\n\t\t\t\treturn (int)o != 0;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tobject os = null;\r\n\t\tif (defaults.TryGetValue (key, out os)) {\r\n\t\t\treturn (int)os != 0;\r\n\t\t}\r\n\t\t\r\n\t\treturn defaultValue;\r\n\t}\r\n\t\r\n\tprivate void ApplySettings(GameObject go) {\r\n\t\tbool castShadows = GetProperty (go, &quot;castshadows&quot;, true);\r\n\t\tbool receiveShadows = GetProperty(go, &quot;receiveshadows&quot;, true);\r\n\t\tbool generateCollider = GetProperty(go, &quot;collision&quot;, true);\r\n\t\tbool isStatic = GetProperty(go, &quot;static&quot;, false);\r\n\t\tbool renderMesh = GetProperty (go, &quot;render&quot;, true);\r\n\t\tbool navigation = GetProperty (go, &quot;navigation&quot;, true);\r\n\r\n\t\tif (isStatic) {\r\n\t\t\tGameObjectUtility.SetStaticEditorFlags (go, StaticEditorFlags.LightmapStatic | StaticEditorFlags.OccluderStatic | StaticEditorFlags.OccludeeStatic | StaticEditorFlags.BatchingStatic | StaticEditorFlags.NavigationStatic | StaticEditorFlags.OffMeshLinkGeneration);\r\n\t\t} else {\r\n\t\t\tGameObjectUtility.SetStaticEditorFlags (go, 0);\r\n\t\t}\r\n\r\n\t\tif (navigation) {\r\n\t\t\tGameObjectUtility.SetStaticEditorFlags (go, GameObjectUtility.GetStaticEditorFlags (go) | StaticEditorFlags.NavigationStatic);\r\n\t\t} else {\r\n\t\t\tGameObjectUtility.SetStaticEditorFlags (go, GameObjectUtility.GetStaticEditorFlags (go) &amp; ~StaticEditorFlags.NavigationStatic);\r\n\t\t}\r\n\r\n\t\tMeshRenderer meshRenderer = go.GetComponent&lt;MeshRenderer&gt;();\r\n\t\tMeshFilter meshFilter = go.GetComponent&lt;MeshFilter&gt; ();\r\n\t\tSkinnedMeshRenderer skinnedMeshRenderer = go.GetComponent&lt;SkinnedMeshRenderer&gt; ();\r\n\t\tif (renderMesh) {\r\n\t\t\tif (meshRenderer) {\r\n\t\t\t\tmeshRenderer.castShadows = castShadows;\r\n\t\t\t\tmeshRenderer.receiveShadows = receiveShadows;\r\n\t\t\t}\r\n\t\t\tif (skinnedMeshRenderer) {\r\n\t\t\t\tskinnedMeshRenderer.castShadows = castShadows;\r\n\t\t\t\tskinnedMeshRenderer.receiveShadows = receiveShadows;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (meshRenderer) {\r\n\t\t\t\tObject.DestroyImmediate(meshRenderer);\r\n\t\t\t}\r\n\t\t\tif (meshFilter) {\r\n\t\t\t\tObject.DestroyImmediate(meshFilter);\r\n\t\t\t}\r\n\t\t\tif (skinnedMeshRenderer) {\r\n\t\t\t\tObject.DestroyImmediate(skinnedMeshRenderer);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tMeshCollider meshCollider = go.GetComponent&lt;MeshCollider&gt;();\r\n\t\tif (!generateCollider) {\r\n\t\t\tif(meshCollider) {\r\n\t\t\t\tObject.DestroyImmediate(meshCollider);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tprivate void ApplyToChildren (GameObject go) {\r\n\t\t\/\/ apply settings to all gameobjects\r\n\t\tApplySettings(go);\r\n\t\tforeach (Transform t in go.transform) {\r\n\t\t\tApplyToChildren (t.gameObject);\r\n\t\t}\r\n\t}\r\n\t\r\n\t\/\/ this is called once by Unit3D after OnPostprocessGameObjectWithUserProperties\r\n\tvoid OnPostprocessModel (GameObject go) {\r\n\t\tApplyToChildren (go);\r\n\t}\r\n\t\r\n\t\/\/ this is only called by Unity3D for game objects with properties\r\n\tvoid OnPostprocessGameObjectWithUserProperties(GameObject go, string&#x5B;] properties, object&#x5B;] values) {\r\n\t\tbool isDefault = go.name.ToLower () == &quot;defaults&quot;;\r\n\t\tif (isDefault) {\r\n\t\t\t\/\/ I can't seem to delete this without errors, so let's just mark it &quot;Editor only&quot;\r\n\t\t\tgo.tag = &quot;EditorOnly&quot;;\r\n\t\t}\r\n\t\tDictionary&lt;string, object&gt; newDict = new Dictionary&lt;string, object&gt; ();\r\n\t\tfor (int i=0; i&lt;properties.Length; i++) {\r\n\t\t\tstring prop = properties&#x5B;i].ToLower();\r\n\t\t\tobject val = values&#x5B;i];\r\n\t\t\tif (isDefault) {\r\n\t\t\t\tdefaults&#x5B;prop] = val;\r\n\t\t\t}\r\n\t\t\tnewDict&#x5B;prop] = val;\r\n\t\t}\r\n\t\tsettings &#x5B;go] = newDict;\r\n\t}\r\n\t\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>It is sometimes convenient to specify the component setup of a model in Blender instead of Unity3D, to hide implementation details and to make the life of artists and designers easier. This is for example useful to specify invisible hits, or areas that can&#8217;t be navigated by AI. The easiest way to do that is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,6],"tags":[],"class_list":["post-307","post","type-post","status-publish","format-standard","hentry","category-blender","category-unity3d"],"_links":{"self":[{"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=\/wp\/v2\/posts\/307","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=307"}],"version-history":[{"count":13,"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=\/wp\/v2\/posts\/307\/revisions"}],"predecessor-version":[{"id":324,"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=\/wp\/v2\/posts\/307\/revisions\/324"}],"wp:attachment":[{"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=307"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=307"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.restemeier.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}