/* * Created by jiadong chen * https://jiadong-chen.medium.com/ */ using System; using UnityEngine; using UnityEditor; using System.IO; using UnityEngine.Rendering; public class AnimMapBakerWindow : EditorWindow { private enum SaveStrategy { // Only anim map AnimMap, // With shader Mat, // Prefab with mat Prefab } #region FIELDS private const string BuiltInShader = "chenjd/BuiltIn/AnimMapShader"; private const string URPShader = "chenjd/URP/AnimMapShader"; private const string ShadowShader = "chenjd/BuiltIn/AnimMapWithShadowShader"; private static readonly int MainTex = Shader.PropertyToID("_MainTex"); private static readonly int AnimMap = Shader.PropertyToID("_AnimMap"); private static readonly int AnimLen = Shader.PropertyToID("_AnimLen"); private GameObject _targetGo; private AnimMapBaker _baker; private string _path = "AnimMapBaker"; private string _subPath = "SubPath"; private SaveStrategy _strategy = SaveStrategy.Prefab; private Shader _animMapShader; private Shader _prevAnimMapShader; private bool _isShadowEnabled = false; #endregion #region METHODS [MenuItem("Window/AnimMapBaker")] public static void ShowWindow() { var window = GetWindow(); window._baker = new AnimMapBaker(); var shaderName = GraphicsSettings.renderPipelineAsset != null ? URPShader : BuiltInShader; window._animMapShader = Shader.Find(shaderName); } private void OnGUI() { _targetGo = (GameObject)EditorGUILayout.ObjectField(_targetGo, typeof(GameObject), true); _subPath = _targetGo == null ? _subPath : _targetGo.name; var resultPath = "Wrong path entered!"; var canProceed = true; try { resultPath = Path.Combine(_path, _subPath); } catch (Exception e) { canProceed = false; } EditorGUILayout.LabelField(string.Format($"Output Path: {resultPath}")); _path = EditorGUILayout.TextField(_path); _subPath = EditorGUILayout.TextField(_subPath); _strategy = (SaveStrategy)EditorGUILayout.EnumPopup("Output Type:", _strategy); _isShadowEnabled = EditorGUILayout.Toggle("Enable Shadow", _isShadowEnabled); if(_isShadowEnabled) { var style = new GUIStyle(EditorStyles.label); style.normal.textColor = Color.yellow; EditorGUILayout.LabelField("Warning: Enabling shadows will cause additional draw calls to draw shadows.", style); _prevAnimMapShader = _animMapShader; _animMapShader = Shader.Find(ShadowShader); } else if(_prevAnimMapShader != null) { _animMapShader = _prevAnimMapShader; } var prevGuiEnabled = GUI.enabled; GUI.enabled = canProceed; var requestedBake = GUILayout.Button("Bake"); GUI.enabled = prevGuiEnabled; if (!requestedBake) return; if(_targetGo == null) { EditorUtility.DisplayDialog("err", "targetGo is null!", "OK"); return; } if(_baker == null) { _baker = new AnimMapBaker(); } _baker.SetAnimData(_targetGo); var list = _baker.Bake(); if (list == null || list.Count == 0) { EditorUtility.DisplayDialog("err", "No baked data was generated. Possible, selected wrong asset.\nYou need to select prefab with Animation component and Animations list filled with animations.", "OK"); return; } foreach (var t in list) { var data = t; Save(ref data); } } private void Save(ref BakedData data) { switch(_strategy) { case SaveStrategy.AnimMap: SaveAsAsset(ref data); break; case SaveStrategy.Mat: SaveAsMat(ref data); break; case SaveStrategy.Prefab: SaveAsPrefab(ref data); break; } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } private Texture2D SaveAsAsset(ref BakedData data) { var folderPath = CreateFolder(); var animMap = new Texture2D(data.AnimMapWidth, data.AnimMapHeight, TextureFormat.RGBAHalf, false); animMap.LoadRawTextureData(data.RawAnimMap); AssetDatabase.CreateAsset(animMap, Path.Combine(folderPath, data.Name + ".asset")); return animMap; } private Material SaveAsMat(ref BakedData data) { if(_animMapShader == null) { EditorUtility.DisplayDialog("err", "shader is null!!", "OK"); return null; } if(_targetGo == null || !_targetGo.GetComponentInChildren()) { EditorUtility.DisplayDialog("err", "SkinnedMeshRender is null!!", "OK"); return null; } var smr = _targetGo.GetComponentInChildren(); var mat = new Material(_animMapShader); var animMap = SaveAsAsset(ref data); mat.SetTexture(MainTex, smr.sharedMaterial.mainTexture); mat.SetTexture(AnimMap, animMap); mat.SetFloat(AnimLen, data.AnimLen); var folderPath = CreateFolder(); AssetDatabase.CreateAsset(mat, Path.Combine(folderPath, $"{data.Name}.mat")); return mat; } private void SaveAsPrefab(ref BakedData data) { var mat = SaveAsMat(ref data); if(mat == null) { EditorUtility.DisplayDialog("err", "mat is null!!", "OK"); return; } var go = new GameObject(); go.AddComponent().sharedMaterial = mat; go.AddComponent().sharedMesh = _targetGo.GetComponentInChildren().sharedMesh; var folderPath = CreateFolder(); PrefabUtility.SaveAsPrefabAsset(go, Path.Combine(folderPath, $"{data.Name}.prefab") .Replace("\\", "/")); } private string CreateFolder() { var folderPath = Path.Combine("Assets/" + _path, _subPath); if (!AssetDatabase.IsValidFolder(folderPath)) { AssetDatabase.CreateFolder("Assets/" + _path, _subPath); } return folderPath; } #endregion }