/// <summary> /// Retrieves import options for the texture we're currently inspecting. /// </summary> /// <returns>Texture import options object.</returns> private TextureImportOptions GetImportOptions() { TextureImportOptions output = null; LibraryEntry texEntry = ProjectLibrary.GetEntry(InspectedResourcePath); if (texEntry != null && texEntry.Type == LibraryEntryType.File) { FileEntry texFileEntry = (FileEntry)texEntry; output = texFileEntry.Options as TextureImportOptions; } if (output == null) { if (importOptions == null) { output = new TextureImportOptions(); } else { output = importOptions; } } return(output); }
/// <summary> /// Retrieves import options for the resource we're currently inspecting. /// </summary> /// <returns>Script code import options object.</returns> private ScriptCodeImportOptions GetImportOptions() { ScriptCode scriptCode = InspectedObject as ScriptCode; ScriptCodeImportOptions output = null; if (scriptCode != null) { LibraryEntry libEntry = ProjectLibrary.GetEntry(ProjectLibrary.GetPath(scriptCode)); if (libEntry != null && libEntry.Type == LibraryEntryType.File) { FileEntry fileEntry = (FileEntry)libEntry; output = fileEntry.Options as ScriptCodeImportOptions; } } if (output == null) { if (importOptions == null) { output = new ScriptCodeImportOptions(); } else { output = importOptions; } } return(output); }
/// <summary> /// Retrieves import options for the audio clip we're currently inspecting. /// </summary> /// <returns>Audio clip import options object.</returns> private AudioClipImportOptions GetImportOptions() { AudioClipImportOptions output = null; LibraryEntry meshEntry = ProjectLibrary.GetEntry(InspectedResourcePath); if (meshEntry != null && meshEntry.Type == LibraryEntryType.File) { FileEntry meshFileEntry = (FileEntry)meshEntry; output = meshFileEntry.Options as AudioClipImportOptions; } if (output == null) { if (importOptions == null) { output = new AudioClipImportOptions(); } else { output = importOptions; } } return(output); }
/// <summary> /// Triggered when the user double-clicked on the entry. /// </summary> /// <param name="path">Project library path of the double-clicked entry.</param> private void OnEntryDoubleClicked(string path) { delayedSelect = false; LibraryEntry entry = ProjectLibrary.GetEntry(path); if (entry != null) { if (entry.Type == LibraryEntryType.Directory) { owner.Window.EnterDirectory(path); } else { ResourceMeta meta = ProjectLibrary.GetMeta(path); FileEntry fileEntry = (FileEntry)entry; if (meta.ResType == ResourceType.Prefab) { EditorApplication.LoadScene(fileEntry.Path); } else if (meta.ResType == ResourceType.ScriptCode) { ProgressBar.Show("Opening external code editor...", 1.0f); delayedOpenCodeEditorFrame = Time.FrameIdx + 1; } else if (meta.ResType == ResourceType.PlainText || meta.ResType == ResourceType.Shader || meta.ResType == ResourceType.ShaderInclude) { string absPath = Path.Combine(ProjectLibrary.ResourceFolder, fileEntry.Path); Process.Start(absPath); } } } }
/// <summary> /// Saves the animation curves and events stored in this object, into the associated animation clip resource. /// Relevant animation clip resource must already be created and exist in the project library. /// </summary> public void SaveToClip() { if (!isImported) { EditorAnimClipData editorAnimClipData; Apply(out editorAnimClipData); string resourcePath = ProjectLibrary.GetPath(clip); ProjectLibrary.Save(clip); ProjectLibrary.SetEditorData(resourcePath, editorAnimClipData); } else { string resourcePath = ProjectLibrary.GetPath(clip); LibraryEntry entry = ProjectLibrary.GetEntry(resourcePath); if (entry != null && entry.Type == LibraryEntryType.File) { FileEntry fileEntry = (FileEntry)entry; MeshImportOptions meshImportOptions = (MeshImportOptions)fileEntry.Options; string clipName = PathEx.GetTail(resourcePath); List <ImportedAnimationEvents> newEvents = new List <ImportedAnimationEvents>(); newEvents.AddRange(meshImportOptions.AnimationEvents); bool isExisting = false; for (int i = 0; i < newEvents.Count; i++) { if (newEvents[i].Name == clipName) { newEvents[i].Events = events; isExisting = true; break; } } if (!isExisting) { ImportedAnimationEvents newEntry = new ImportedAnimationEvents(); newEntry.Name = clipName; newEntry.Events = events; newEvents.Add(newEntry); } meshImportOptions.AnimationEvents = newEvents.ToArray(); ProjectLibrary.Reimport(resourcePath, meshImportOptions, true); } } }
/// <summary> /// Creates a new material parameter GUI. /// </summary> /// <param name="shaderParam">Shader parameter to create the GUI for. Must be of texture type.</param> /// <param name="material">Material the parameter is a part of.</param> /// <param name="layout">Layout to append the GUI elements to.</param> internal MaterialParamTextureGUI(ShaderParameter shaderParam, Material material, GUILayout layout) : base(shaderParam) { LocString title = new LocEdString(shaderParam.name); GUITextureFieldType type = shaderParam.type == ShaderParameterType.Texture2D ? GUITextureFieldType.TextureOrSpriteTexture : GUITextureFieldType.Texture; guiElem = new GUITextureField(type, title); switch (shaderParam.type) { case ShaderParameterType.Texture2D: case ShaderParameterType.Texture3D: case ShaderParameterType.TextureCube: guiElem.OnChanged += (x) => { string path = ProjectLibrary.GetPath(x.UUID); if (!string.IsNullOrEmpty(path)) { if (ProjectLibrary.GetEntry(path) is FileEntry fileEntry) { if (fileEntry.ResourceMetas.Length > 0) { ResourceMeta meta = fileEntry.ResourceMetas[0]; if (meta.ResType == ResourceType.SpriteTexture) { material.SetSpriteTexture(shaderParam.name, x.As <SpriteTexture>()); } else if (meta.ResType == ResourceType.Texture) { material.SetTexture(shaderParam.name, x.As <Texture>()); } } } } else { material.SetTexture(shaderParam.name, null); } EditorApplication.SetDirty(material); }; break; } layout.AddElement(guiElem); }
/// <summary> /// Triggered when a resource is (re)imported in the project library. /// </summary> /// <param name="path">Path of the imported resource, relative to the project's resource folder.</param> private void OnEntryImported(string path) { LibraryEntry entry = ProjectLibrary.GetEntry(path); if (entry == null || entry.Type != LibraryEntryType.File) { return; } FileEntry fileEntry = (FileEntry)entry; ResourceMeta[] resourceMetas = fileEntry.ResourceMetas; bool found = false; foreach (var meta in resourceMetas) { if (meta.ResType == ResourceType.ScriptCode) { found = true; break; } } if (!found) { return; } ScriptCode codeFile = ProjectLibrary.Load <ScriptCode>(path); if (codeFile == null) { return; } if (codeFile.EditorScript) { isEditorAssemblyDirty = true; } else { isGameAssemblyDirty = true; } }
/// <summary> /// Triggered when the user clicks on the entry. /// </summary> /// <param name="path">Project library path of the clicked entry.</param> private void OnEntryClicked(string path) { LibraryEntry entry = ProjectLibrary.GetEntry(path); if (entry != null && entry.Type == LibraryEntryType.Directory) { // If entry is a directory delay selection as it might be a double-click, in which case we want to keep // whatever selection is active currently so that user can perform drag and drop with its inspector // from the folder he is browsing to. delayedSelect = true; delayedSelectTime = Time.RealElapsed + 0.5f; } else { owner.Window.Select(path); } }
/// <summary> /// Called every frame. /// </summary> public void Update() { if (delayedSelect && Time.RealElapsed > delayedSelectTime) { owner.Window.Select(path); delayedSelect = false; } if (delayedOpenCodeEditorFrame == Time.FrameIdx) { LibraryEntry entry = ProjectLibrary.GetEntry(path); if (entry != null && entry.Type == LibraryEntryType.File) { FileEntry resEntry = (FileEntry)entry; CodeEditor.OpenFile(resEntry.Path, 0); } ProgressBar.Hide(); } }
/// <summary> /// Checks is the resource at the provided path a file relevant to the code editor. /// </summary> /// <param name="path">Path to the resource, absolute or relative to the project's resources folder.</param> /// <returns>True if the file is relevant to the code editor, false otherwise.</returns> private bool IsCodeEditorFile(string path) { LibraryEntry entry = ProjectLibrary.GetEntry(path); if (entry != null && entry.Type == LibraryEntryType.File) { FileEntry fileEntry = (FileEntry)entry; ResourceMeta[] resourceMetas = fileEntry.ResourceMetas; foreach (var codeType in CodeEditor.CodeTypes) { foreach (var meta in resourceMetas) { if (meta.ResType == codeType) { return(true); } } } } return(false); }
/// <summary> /// Returns an icon that can be used for displaying a resource of the specified type. /// </summary> /// <param name="path">Path to the project library entry to display data for.</param> /// <param name="size">Size of the icon to retrieve, in pixels.</param> /// <returns>Icon to display for the specified entry.</returns> private static SpriteTexture GetIcon(string path, int size) { LibraryEntry entry = ProjectLibrary.GetEntry(path); if (entry.Type == LibraryEntryType.Directory) { return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Folder, size)); } else { ResourceMeta meta = ProjectLibrary.GetMeta(path); ProjectResourceIcons icons = meta.Icons; RRef <Texture> icon; if (size <= 16) { icon = icons.icon16; } else if (size <= 32) { icon = icons.icon32; } else if (size <= 48) { icon = icons.icon48; } else { icon = icons.icon64; } if (icon.Value != null) { return(new SpriteTexture(icon)); } switch (meta.ResType) { case ResourceType.Font: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Font, size)); case ResourceType.Mesh: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Mesh, size)); case ResourceType.Texture: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Texture, size)); case ResourceType.PlainText: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.PlainText, size)); case ResourceType.ScriptCode: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.ScriptCode, size)); case ResourceType.SpriteTexture: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.SpriteTexture, size)); case ResourceType.Shader: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Shader, size)); case ResourceType.ShaderInclude: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Shader, size)); case ResourceType.Material: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Material, size)); case ResourceType.Prefab: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.Prefab, size)); case ResourceType.GUISkin: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.GUISkin, size)); case ResourceType.PhysicsMaterial: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.PhysicsMaterial, size)); case ResourceType.PhysicsMesh: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.PhysicsMesh, size)); case ResourceType.AudioClip: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.AudioClip, size)); case ResourceType.AnimationClip: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.AnimationClip, size)); case ResourceType.VectorField: return(EditorBuiltin.GetProjectLibraryIcon(ProjectLibraryIcon.VectorField, size)); } } return(null); }
/// <summary> /// Loads curve and event information from the provided clip, and creates a new instance of this object containing /// the required data for editing the source clip in the animation editor. /// </summary> /// <param name="clip">Clip to load.</param> /// <returns>Editor specific editable information about an animation clip.</returns> public static EditorAnimClipInfo Create(AnimationClip clip) { EditorAnimClipInfo clipInfo = new EditorAnimClipInfo(); clipInfo.clip = clip; clipInfo.isImported = IsClipImported(clip); clipInfo.sampleRate = (int)clip.SampleRate; AnimationCurves clipCurves = clip.Curves; EditorAnimClipData editorClipData = null; EditorAnimClipData lGetAnimClipData(ResourceMeta meta) { object editorData = meta.EditorData; EditorAnimClipData output = editorData as EditorAnimClipData; if (output == null) { // Old editor data stores tangents only if (editorData is EditorAnimClipTangents tangents) { output = new EditorAnimClipData(); output.tangents = tangents; } } return(output); } string resourcePath = ProjectLibrary.GetPath(clip); if (!string.IsNullOrEmpty(resourcePath)) { LibraryEntry entry = ProjectLibrary.GetEntry(resourcePath); string clipName = PathEx.GetTail(resourcePath); if (entry != null && entry.Type == LibraryEntryType.File) { FileEntry fileEntry = (FileEntry)entry; ResourceMeta[] metas = fileEntry.ResourceMetas; if (clipInfo.isImported) { for (int i = 0; i < metas.Length; i++) { if (clipName == metas[i].SubresourceName) { editorClipData = lGetAnimClipData(metas[i]); break; } } } else { if (metas.Length > 0) { editorClipData = lGetAnimClipData(metas[0]); } } } } if (editorClipData == null) { editorClipData = new EditorAnimClipData(); editorClipData.tangents = new EditorAnimClipTangents(); } int globalCurveIdx = 0; void lLoadVector3Curve(NamedVector3Curve[] curves, EditorVector3CurveTangents[] tangents, string subPath) { foreach (var curveEntry in curves) { TangentMode[] tangentsX = null; TangentMode[] tangentsY = null; TangentMode[] tangentsZ = null; if (tangents != null) { foreach (var tangentEntry in tangents) { if (tangentEntry.name == curveEntry.name) { tangentsX = tangentEntry.tangentsX; tangentsY = tangentEntry.tangentsY; tangentsZ = tangentEntry.tangentsZ; break; } } } // Convert compound curve to three per-component curves AnimationCurve[] componentCurves = AnimationUtility.SplitCurve3D(curveEntry.curve); FieldAnimCurves fieldCurves = new FieldAnimCurves(); fieldCurves.type = SerializableProperty.FieldType.Vector3; fieldCurves.curveInfos = new EdCurveDrawInfo[3]; fieldCurves.isPropertyCurve = !clipInfo.isImported; fieldCurves.curveInfos[0] = new EdCurveDrawInfo(); fieldCurves.curveInfos[0].curve = new EdAnimationCurve(componentCurves[0], tangentsX); fieldCurves.curveInfos[0].color = GetUniqueColor(globalCurveIdx++); fieldCurves.curveInfos[1] = new EdCurveDrawInfo(); fieldCurves.curveInfos[1].curve = new EdAnimationCurve(componentCurves[1], tangentsY); fieldCurves.curveInfos[1].color = GetUniqueColor(globalCurveIdx++); fieldCurves.curveInfos[2] = new EdCurveDrawInfo(); fieldCurves.curveInfos[2].curve = new EdAnimationCurve(componentCurves[2], tangentsZ); fieldCurves.curveInfos[2].color = GetUniqueColor(globalCurveIdx++); string curvePath = curveEntry.name.TrimEnd('/') + subPath; clipInfo.curves[curvePath] = fieldCurves; } }; NamedQuaternionCurve[] rotationCurves = clipCurves.Rotation; NamedVector3Curve[] eulerRotationCurves = new NamedVector3Curve[rotationCurves.Length]; if (editorClipData.eulerCurves == null || editorClipData.eulerCurves.Length != rotationCurves.Length) { // Convert rotation from quaternion to euler if we don't have original euler animation data stored. for (int i = 0; i < rotationCurves.Length; i++) { NamedQuaternionCurve quatCurve = rotationCurves[i]; Vector3Curve eulerCurve = AnimationUtility.QuaternionToEulerCurve(quatCurve.curve); eulerRotationCurves[i] = new NamedVector3Curve(quatCurve.name, quatCurve.flags, eulerCurve); } } else { for (int i = 0; i < editorClipData.eulerCurves.Length; i++) { EditorNamedVector3Curve edCurve = editorClipData.eulerCurves[i]; eulerRotationCurves[i] = new NamedVector3Curve( edCurve.name, edCurve.flags, new Vector3Curve(edCurve.keyFrames)); } } lLoadVector3Curve(clipCurves.Position, editorClipData.tangents.positionCurves, "/Position"); lLoadVector3Curve(eulerRotationCurves, editorClipData.tangents.rotationCurves, "/Rotation"); lLoadVector3Curve(clipCurves.Scale, editorClipData.tangents.scaleCurves, "/Scale"); // Find which individual float curves belong to the same field Dictionary <string, Tuple <int, int, bool>[]> floatCurveMapping = new Dictionary <string, Tuple <int, int, bool>[]>(); { int curveIdx = 0; foreach (var curveEntry in clipCurves.Generic) { string path = curveEntry.name; string pathNoSuffix = null; string pathSuffix; if (path.Length >= 2) { pathSuffix = path.Substring(path.Length - 2, 2); pathNoSuffix = path.Substring(0, path.Length - 2); } else { pathSuffix = ""; } int tangentIdx = -1; int currentTangentIdx = 0; foreach (var tangentEntry in editorClipData.tangents.floatCurves) { if (tangentEntry.name == curveEntry.name) { tangentIdx = currentTangentIdx; break; } currentTangentIdx++; } Animation.PropertySuffixInfo suffixInfo; if (Animation.PropertySuffixInfos.TryGetValue(pathSuffix, out suffixInfo)) { Tuple <int, int, bool>[] curveInfo; if (!floatCurveMapping.TryGetValue(pathNoSuffix, out curveInfo)) { curveInfo = new Tuple <int, int, bool> [4]; } curveInfo[suffixInfo.elementIdx] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.isVector); floatCurveMapping[pathNoSuffix] = curveInfo; } else { Tuple <int, int, bool>[] curveInfo = new Tuple <int, int, bool> [4]; curveInfo[0] = Tuple.Create(curveIdx, tangentIdx, suffixInfo.isVector); floatCurveMapping[path] = curveInfo; } curveIdx++; } } foreach (var KVP in floatCurveMapping) { int numCurves = 0; for (int i = 0; i < 4; i++) { if (KVP.Value[i] == null) { continue; } numCurves++; } if (numCurves == 0) { continue; // Invalid curve } FieldAnimCurves fieldCurves = new FieldAnimCurves(); // Deduce type (note that all single value types are assumed to be float even if their source type is int or bool) if (numCurves == 1) { fieldCurves.type = SerializableProperty.FieldType.Float; } else if (numCurves == 2) { fieldCurves.type = SerializableProperty.FieldType.Vector2; } else if (numCurves == 3) { fieldCurves.type = SerializableProperty.FieldType.Vector3; } else // 4 curves { bool isVector = KVP.Value[0].Item3; if (isVector) { fieldCurves.type = SerializableProperty.FieldType.Vector4; } else { fieldCurves.type = SerializableProperty.FieldType.Color; } } bool isMorphCurve = false; string curvePath = KVP.Key; fieldCurves.curveInfos = new EdCurveDrawInfo[numCurves]; for (int i = 0; i < numCurves; i++) { int curveIdx = KVP.Value[i].Item1; int tangentIdx = KVP.Value[i].Item2; TangentMode[] tangents = null; if (tangentIdx != -1) { tangents = editorClipData.tangents.floatCurves[tangentIdx].tangents; } fieldCurves.curveInfos[i] = new EdCurveDrawInfo(); fieldCurves.curveInfos[i].curve = new EdAnimationCurve(clipCurves.Generic[curveIdx].curve, tangents); fieldCurves.curveInfos[i].color = GetUniqueColor(globalCurveIdx++); if (clipCurves.Generic[curveIdx].flags.HasFlag(AnimationCurveFlags.MorphFrame)) { curvePath = "MorphShapes/Frames/" + KVP.Key; isMorphCurve = true; } else if (clipCurves.Generic[curveIdx].flags.HasFlag(AnimationCurveFlags.MorphWeight)) { curvePath = "MorphShapes/Weight/" + KVP.Key; isMorphCurve = true; } } fieldCurves.isPropertyCurve = !clipInfo.isImported && !isMorphCurve; clipInfo.curves[curvePath] = fieldCurves; } // Add events clipInfo.events = clip.Events; return(clipInfo); }