1
\$\begingroup\$

I was just being introduced to the AssetDatabase class, and I was curious of creating Script Templates, such as Interfaces, pure C# classes, and a MonoBehaviour template more suitable for my work style.

I tried to emulate a solution I found on Unity Forums, and the template creation goes well, except that the name gets setted once it is created, and it doesn't let you name the created asset, as the Unity Scripts let you once you create them. The fact that the name is not changed by the user, makes the script on its default state, with the exact code the template has, without changing the #SCRIPTNAME#s declared, throwing errors, and being counterproductive with the time optimization goals I had in mind.

using UnityEditor;
using UnityEngine;
using System.IO;

public class CustomScriptsCreator
{
    public const string CONTEXT_MENU_PATH = "Assets/Create/Custom Scripts";     /// <summary>Custom Scripts default Path.</summary>
    public const string CONTEXT_MENU_CUSTOM_C_SHARP = "/C# MonoBehaviour";
    public const string CUSTOM_TEMPLATE_FOLDER_PATH = "Assets/Custom Script Templates";
    public const string CUSTOM_C_SHARP_TEMPLATE_PATH = "C#Script_CustomMonoBehaviourScript.cs.txt";
    public const string PATH_NOT_FOUND_MESSAGE = "Script Template Not Found";
    public const string WINDOW_ERROR_MESSAGE = "There is no Template at path: \n\n";
    public const string WINDOW_OK_ANSWER = "Ok";

[MenuItem(CONTEXT_MENU_PATH + CONTEXT_MENU_CUSTOM_C_SHARP)]
public static void Create(MenuCommand _cmd)
{
    Object selection = Selection.activeObject;

    if (selection != null)
    {
        string path = AssetDatabase.GetAssetPath(selection);

        if (File.Exists(path))
        {
            path = Path.GetDirectoryName(path);
        }
        if (string.IsNullOrEmpty(path))
        {
            path = "Assets/";
        }

        /// THIS WAS JUST A FAILED TEST...
        /*Object obj = Resources.Load("ScriptTemplates/" + CUSTOM_C_SHARP_TEMPLATE_PATH);

        if(obj != null)
        {
            AssetDatabase.CreateAsset(obj, Path.Combine(path, "NewScript.cs"));
        }
        else
        {
            EditorUtility.DisplayDialog
            (
                PATH_NOT_FOUND_MESSAGE,
                WINDOW_ERROR_MESSAGE + (CUSTOM_TEMPLATE_FOLDER_PATH + "/" + CUSTOM_C_SHARP_TEMPLATE_PATH),
                WINDOW_OK_ANSWER
            );
        }*/

        //string newName = EditorUtility.SaveFilePanelInProject("Save Your Prefab", "NewScript.cs", "cs", "Please select file name to save prefab to:");
        //File.Copy((CUSTOM_TEMPLATE_FOLDER_PATH + "/" + CUSTOM_C_SHARP_TEMPLATE_PATH), Path.Combine(path, "NewScript.cs"));
        if(AssetDatabase.CopyAsset((CUSTOM_TEMPLATE_FOLDER_PATH + "/" + CUSTOM_C_SHARP_TEMPLATE_PATH), Path.Combine(path, "NewName.cs")))
        {
            // IT CHANGES THE FILE NAME SUCCESSFULLY, BUT NOT ON THE SCRIPT.
            //AssetDatabase.RenameAsset(Path.Combine(path, "NewName.cs"), "KillMePlease.cs");

            AssetDatabase.Refresh();
        }
        else 
        {
            EditorUtility.DisplayDialog
            (
                PATH_NOT_FOUND_MESSAGE,
                WINDOW_ERROR_MESSAGE + ((CUSTOM_TEMPLATE_FOLDER_PATH + "/" + CUSTOM_C_SHARP_TEMPLATE_PATH)),
                WINDOW_OK_ANSWER
            );
        }
    }

}

}

Result:

The paths are ok, since the script is being created. If there is more information you need, let me know. Thanks in advance.

\$\endgroup\$
1
\$\begingroup\$

In case anyone has, or had the same problem:

I found a good solution on a GIT repository provided by a guy identified as Liortal, with additional extensions I made for more templates creations (interfaces, structs, scriptableobjects, etc.), but Liorta's base code was enough to achieve my goals. The use of a EndNamedEditAction inhereted class was necessary to handle the name editing (so far I haven't found information about the class, so I lefted it as it was).

These are both scripts used, combined on a single scripts for example purposes:

using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
using UnityEngine;

public class CodeTemplateGenerator
{
    /// Inherits from EndNameAction, must override EndNameAction.Action
    public class DoCreateCodeFile : EndNameEditAction
    {
        public override void Action (int instanceId, string pathName, string resourceFile)
        {
            Object o = CodeTemplates.CreateScript(pathName, resourceFile);
            ProjectWindowUtil.ShowCreatedAsset (o);
        }
    }

private const string MENU_ITEM_PATH = "Assets/Create Template";     ///< <summary>Menu Item's path.</summary>
private const string CLASS = "C# Class";                            ///< <summary>Class's path [must be combined with menu item's path].</summary>
private const string INTERFACE = "Interface";                       ///< <summary>Interface's path [must be combined with menu item's path].</summary>
private const string REPLACABLE_NAME_TAG = "##NAME##";              ///< <summary>NAME's replacement tag.</summary>
private const string REPLACABLE_TABSPACE_TAG = "##TABSPACE##";      ///< <summary>TABSPACE's replacement tag.</summary>

/// <summary>C#'s Script Icon [The one MonoBhevaiour Scripts have].</summary>
private static Texture2D scriptIcon = (EditorGUIUtility.IconContent ("cs Script Icon").image as Texture2D);

/// <summary>Creates a new C# Class.</summary>
[MenuItem(MENU_ITEM_PATH + CLASS)]
private static void CreateClass()
{
    CodeTemplates.CreateFromTemplate
    (
        "NewClass.cs",                                              // Class's temporal name.
        @"Assets/CodeTemplates/Editor/Templates/ClassTemplate.txt"  // Template's path.
    );
}

/// <summary>Creates a new C# interface.</summary>
[MenuItem(MENU_ITEM_PATH + "C# Interface", false, MENU_ITEM_PRIORITY)]
private static void CreateInterface()
{
    CodeTemplates.CreateFromTemplate
    (
        "NewInterface.cs",
        @"Assets/CodeTemplates/Editor/Templates/InterfaceTemplate.txt"
    );
}

/// <summary>Creates Script from Template's path.</summary>
internal static UnityEngine.Object CreateScript(string pathName, string templatePath)
{
    /// Subtract spaces [" "].
    string className = NormalizeClassName(Path.GetFileNameWithoutExtension(pathName));
    string templateText = string.Empty;

    UTF8Encoding encoding = new UTF8Encoding (true, false);

    if (File.Exists(templatePath))
    {
        /// Read procedures.
        StreamReader reader = new StreamReader(templatePath);
        reader.ReadToEnd();
        reader.Close();

        templateText = templateText.Replace(REPLACABLE_NAME_TAG, className);
        templateText = templateText.Replace(REPLACABLE_TABSPACE_TAG, string.Empty);
        /// You can replace as many tags you make on your templates, just repeat Replace function
        /// e.g.:
        /// templateText = templateText.Replace("#NEWTAG#", "MyText");

        /// Write procedures.
        StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding);
        writer.Write (templateText);
        writer.Close();

        AssetDatabase.ImportAsset(pathName);            
        return AssetDatabase.LoadAssetAtPath (pathName, typeof(Object));
    }
    else
    {
        Debug.LogError(string.Format("The template file was not found: {0}", templatePath));
        return null;
    }
}

/// <summary>Creates a new code file from a template file.</summary>
/// <param name="initialName">The initial name to give the file in the UI</param>
/// <param name="templatePath">The full path of the template file to use</param>
public static void CreateFromTemplate(string initialName, string templatePath)
{
    ProjectWindowUtil.StartNameEditingIfProjectWindowExists
    (
        0,
        ScriptableObject.CreateInstance<DoCreateCodeFile>(),
        initialName,
        scriptIcon,
        templatePath
    );
}

/// <summary>Subtracts white spaces [" "] for Class's name.</summary>
private static string NormalizeClassName(string fileName)
{
    return fileName.Replace(" ", string.Empty);
}

}

To create templates, remember to have them stored like "MYTEMPLATE.cs.txt" and consider using this kind of format, with replacement tags:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu]
public class ##NAME## : ScriptableObject
{
    ##TABSPACE##
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I'm glad you found a solution! Just note that if the linked GIT repository is ever removed, temporarily offline, or changes its URL, future users will have a hard time puzzling out how to solve the problem. This answer could be even better if it included a summary of what the code does and how, so it still provides some guidance toward a solution even if the link is unavailable. \$\endgroup\$ – DMGregory Nov 5 '17 at 20:00
  • \$\begingroup\$ @DMGregory I have just added a summary of the code, following your advice. \$\endgroup\$ – LifGwaethrakindo Nov 5 '17 at 23:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.