publicstring Pathlink { get { #if UNITY_EDITOR // in editor time, it is no need to save the actual path, just use guid to get the path if (Application.isPlaying) { m_path = AssetDatabase.GUIDToAssetPath(m_guid); return m_path; } string path = AssetDatabase.GUIDToAssetPath(m_guid); return path; #else // in build, just return path return m_path; #endif } }
}
[Serializable] public class PrefabPath : AssetPath<GameObject> { } [Serializable] public class MaterialPath : AssetPath<Material> { } [Serializable] public class ScriptableObjectPath : AssetPath<ScriptableObject> { }
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button); buttonStyle.alignment = TextAnchor.MiddleLeft; Rect rect = new Rect(position.x, position.y, position.width, elementHeight); string guidText = guidProperty.stringValue; if (GUI.Button(rect, new GUIContent($"Guid: {guidText}"), buttonStyle)) { GUIUtility.systemCopyBuffer = guidText; // copy to clipboard Debug.Log($"Copy guid text '{guidText}' to clipboard"); }
rect = new Rect(position.x, position.y + elementHeight, position.width, elementHeight); string pathText = pathProperty.stringValue; if (GUI.Button(rect, new GUIContent($"Path: {pathText}"), buttonStyle)) { GUIUtility.systemCopyBuffer = pathText; // copy to clipboard Debug.Log($"Copy path text '{guidText}' to clipboard"); }
rect = new Rect(position.x, position.y + elementHeight * 2, position.width, elementHeight); SerializedProperty assetProperty = property.FindPropertyRelative("m_assetCache"); if (null != assetProperty) { UnityObject assetValue = assetProperty.objectReferenceValue; Type assetType; if (null == assetValue) { Type fieldType = fieldInfo.FieldType; Type[] genericTypes = fieldType.BaseType.GenericTypeArguments; if (null == genericTypes || 0 == genericTypes.Length) { assetType = typeof(UnityObject); } else { assetType = genericTypes[0]; } } else { assetType = assetValue.GetType(); } UnityObject next = EditorGUI.ObjectField(rect, assetValue, assetType, false); // do not allow scene obj so it will actually be asset if (next != assetValue) // value has changed, set guid and path { pathText = AssetDatabase.GetAssetPath(next); guidText = AssetDatabase.AssetPathToGUID(pathText); pathProperty.stringValue = pathText; guidProperty.stringValue = guidText; assetProperty.objectReferenceValue = next; } }
publicstaticclassBundlePreprocess_AssetPath { privatestaticvoidPreProcess_AssetPathSoftLink() { /* * @Hiko * * Need to match case below, then we get guid, parse actual path * m_path: Assets/XX/resource.xx * m_guid: 382c74c3721d4f3480e557657b6cbc27 */ voidDoTextReplace(string[] assetGUIDs) { // I have 0 knowledge of Regex, so I ask AI do it :) // HACK put 4 space here is becuz there is at least 4 spc before the field name if you take a look into asset yaml file string patternAsSingleField = @"\s*\s\s\s\s\m_path:\s*(?<path>.*)?\s*m_guid:\s*(?<guid>[a-fA-F0-9]*)?"; // if it is a array element, there should be a '-' string patternAsArrayElement = @"\s*-\m_path:\s*(?<path>.*)?\s*m_guid:\s*(?<guid>[a-fA-F0-9]*)?"; for (int i = 0, assetCount = assetGUIDs.Length; i < assetCount; i++) { string assetGuid = assetGUIDs[i]; string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); if (assetPath.StartsWith("Packages")) { continue; // dun know hot to use asset search fitler, skip asset in package here } string assetFullpath = Application.dataPath + assetPath.Substring("Assets".Length); string assetText = File.ReadAllText(assetFullpath); string resultText = assetText;
// AssetPath as normal field MatchCollection matchCollection = Regex.Matches(assetText, patternAsSingleField, RegexOptions.Multiline); ulong execCount = 0; int matchCount = matchCollection.Count; if (0 < matchCount) { for (int matchIndex = 0; matchIndex < matchCount; matchIndex++) { Match match = matchCollection[matchIndex]; if (match.Success) { string matchContent = match.Value; int spaceCount = matchContent.IndexOf("p") - 1; string guidString = match.Groups["guid"].Value; if (string.IsNullOrEmpty(guidString) || 36 > guidString.Length) // invalid guid { continue; } string space = newstring(' ', spaceCount); // add space string path = AssetDatabase.GUIDToAssetPath(guidString); string result1 = $"m_path: {path}"; string result2 = $"m_guid: {guidString}"; string result = $"\n{space}{result1}\n{space}{result2}"; resultText = resultText.Replace(matchContent, result); ++execCount; } } }
// Asset Path as array element assetText = resultText; matchCollection = Regex.Matches(assetText, patternAsArrayElement, RegexOptions.Multiline); matchCount = matchCollection.Count; if (0 < matchCount) { for (int matchIndex = 0; matchIndex < matchCount; matchIndex++) { Match match = matchCollection[matchIndex]; if (match.Success) { string matchContent = match.Value; int spaceCount = matchContent.IndexOf("p") - 1; string guidString = match.Groups["guid"].Value; if (string.IsNullOrEmpty(guidString) || 36 > guidString.Length) // invalid guid { continue; } string space = newstring(' ', spaceCount); // add space string path = AssetDatabase.GUIDToAssetPath(guidString); string result1 = $"- m_path: {path}"; string result2 = $"m_guid: {guidString}"; string result = $"\n{new string(' ', spaceCount - 2)}{result1}\n{space}{result2}"; resultText = resultText.Replace(matchContent, result); ++execCount; } } }
if (0 < execCount) // has changes { File.WriteAllText(assetFullpath, resultText); } } }
// do stuff for SO first string[] guids = AssetDatabase.FindAssets($"t: {nameof(ScriptableObject)}"); DoTextReplace(guids); // then prefab guids = AssetDatabase.FindAssets($"t: prefab"); DoTextReplace(guids); // finally scenes guids = AssetDatabase.FindAssets($"t: scene"); DoTextReplace(guids); // refresh assets AssetDatabase.Refresh(); } }
这边如果使用的让宿主来刷新的方法,那我觉得会有一个需要注意的小点。 这个情况下刷新操作其实应该注意对资源处理的顺序,比如先处理 ScriptableObject 再到 Prefab,最后才是 Scene 这个操作的实质是修改 asset 然后再把修改结果写回磁盘,宿主对修改的处理方式可能会和我们想的不太一样。
比如说 Scene 中很可能会包含 prefab,如果先让 Scene 文件执行这个刷新,如果监测和检查没有做充分的情况下会抓到 Scene 里的 Prefab 然后让其在 Scene 里做了更改,并因此在 Scene 文件了多写了一行 override 信息。 同样的,Nested Prefab 也会遇到同样的问题。至于如何排布资源处理的顺序可能还得根据实际项目的情况来指定具体的规则。