Search Unity

Using Visual Studio T4 code generation with Unity

Discussion in 'Scripting' started by greg-h, Aug 30, 2013.

  1. greg-h

    greg-h

    Joined:
    Jul 29, 2012
    Posts:
    17
    Hi there,

    We're having some trouble managing resources, so I wrote a Visual Studio T4 script that generates a static class containing constant string variables with the relative paths to the assets we want to load.

    Like... if you have a folder Assets/Resources/Whatever/Hello.jpg, it will generate a C# file that contains
    Code (csharp):
    1.  
    2. public static class Assets
    3. {
    4.     public static class Resources
    5.     {
    6.         public static class Whatever
    7.         {
    8.              public const string Hello = "Resources\\Whatever\\Hello";
    9.         }
    10.     }
    11. }
    12.  
    So that way we can do Resources.Load(Assets.Resources.Whatever.Hello);

    If the file disappears or changes names, then our code breaks. And we know where and why, too. Also no more magic strings and typos breaking the game at runtime.


    Anyway, it works great! Except....

    Well... there's this problem.

    The generated .cs file is picked up by Unity just fine! But when Unity rebuilds the solution, the .TT file (the T4 file that generates the code) is NOT included in the csproj reference. So to rebuild the generated file, we have to manually include the .TT file in the project every time Unity rebuilds the solution.

    Is there a way to force Unity to include a particular file extension in the rebuilt projects?

    For reference, this is what the .csproj file includes as an ItemGroup node:

    Code (csharp):
    1.  
    2. <Compile Include="Assets\Scripts\CodeGeneration\AssetList.cs">
    3.       <AutoGen>True</AutoGen>
    4.       <DesignTime>True</DesignTime>
    5.       <DependentUpon>AssetList.tt</DependentUpon>
    6. </Compile>
    7. ...
    8. <None Include="Assets\Scripts\CodeGeneration\AssetList.tt">
    9.       <Generator>TextTemplatingFileGenerator</Generator>
    10.       <LastGenOutput>AssetList.cs</LastGenOutput>
    11. </None>
    12. ...
    13.  
    14.  
    Actually...you know it would just be easier to forgo the T4 engine (as cool as it is) and just write a Unity Editor script that does the same thing. I'll post this anyway in case anybody wants to use the idea.

    Question still stands, though: Is there a way to customize how Unity generates the solution files?
     
  2. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,051
    Up.

    I'm thinking of doing something like this and I want a solution also.

    Someone of Unity please!!
     
  3. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    Last edited: Apr 6, 2024
  4. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Maybe a stupid question... But why would you do that?
     
  5. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    Why would you do what? What are you referring to?
     
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Why building a collection of hard-coded reference?
     
  7. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    We do something similar to keep track of directories instead of specific assets because we are potentially maintaining 2 directories (our stuff and mod stuff). So you can end up with something like this

    Code (csharp):
    1.  
    2. public static Texture2D LoadTexture(string textureName)
    3. {
    4.     return Resources.Load<Texture2D>(GameConstants.RootDirectory + GameConstants.TextureDirectory + textureName);
    5. }
    6.  
    7. publi static Texture2D LoadModTexture(string textureName)
    8. {
    9.     WWW www = new WWW("file://" + GameConstants.DataDirectory + ModDirectory + GameConstants.TextureDirectory + textureName);
    10.     return www.texture;
    11. }
    12.  
    That being said - if you want a manifest of assets in your Resources folder you might be better of writing an Editor script that will build some kind of data file that you load at run time.
     
    Last edited: Mar 26, 2014
  8. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    True, that sounds already more useful...

    However, I don't remember ever needing a list of object in the Resources folder.

    For example, if I needed to load all my "items" definition, I would do Resources.LoadAll<ItemDef>().

    I could imagine keeping track of the existing folders as once build you lose that data. Maybe sorting "themes" per folder and keeping track of the different folders. Or in each folder I could put a "ThemeDef" asset that would keep track of the different object needed for a theme.

    In all, I've yet to encounter a situation where I would want to manually keep tracks of the different specific objects.
     
  9. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    We have a Model Viewer in our mod tools that presents a GUI with a list of models in our Resources folder.
     
  10. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    So... LoadAll<GameObject>("Models/")?
     
  11. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    I've actually run into a similar problem of needing to know all the files in a Resources directory, but I didn't want to load every file in the directory. (The files are large and it would take a lot of time.) I ended up writing a PostProcess script that would use System.IO to get a list of all the files at build time, then write it to a .txt file so I could access it in the game at runtime.

    As for copying TT or T4 files to the build directory, is it as simple as just copying the file? If you want to automate it, you should check out Unity's Build Player Pipeline page.
    http://docs.unity3d.com/Documentation/Manual/BuildPlayerPipeline.html
     
    Last edited: Mar 26, 2014
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    We didn't want to load everything at once, as Garth alluded to.
     
  13. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    Why not write an editor script which automatically generates a manifest file of the "Resources" folder using the AssetDatabase? One single mouse click and the manifest is up-to-date.

    That's the approach I would consider if a manifest was needed...

    You can get the list of asset paths using AssetDatabase.GetAllAssetPaths(); and then filter those down by those paths which start with "Assets/Resources/". Then you can collate them however you want :)
     
  14. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    What would you do with that manifest then ?

    What if you programmatically need access to someof those resources? (E.g load them without specifying their name as a string)
     
  15. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    It looks like the OP wants to access them by string. But if you want to access a group of resources in one hit then just use the regular Resources class, or amend your editor script to create various collations of the resources as needed. Like, if you had a selection of prefabs with tags, you could collate them by their tags so that you can access all of the prefabs with a given tag...
     
  16. eddieparkerami

    eddieparkerami

    Joined:
    May 26, 2013
    Posts:
    6
    Sorry for necroing this post, but it's the first hit for 'unity tt files' in Google.

    I've written something that works in later versions of Unity that adds the .tt files to the Visual Studio projects generated. Hopefully this helps someone.

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using System.Linq;
    4. using System.IO;
    5. using SyntaxTree.VisualStudio.Unity.Bridge;
    6. using System.Collections.Generic;
    7.  
    8. [InitializeOnLoad]
    9. public class VisualStudioProjectAddTTFileReferences
    10. {
    11.     static VisualStudioProjectAddTTFileReferences()
    12.     {
    13.         ProjectFilesGenerator.ProjectFileGeneration += (string name, string content) =>
    14.         {
    15.             var lines = content.Split('\n').ToList();
    16.  
    17.             var isEditorProject = name.EndsWith(".Editor.csproj");
    18.  
    19.             var ttfileInsert = GetTTFileItemGroups(isEditorProject);
    20.  
    21.             var insertIndex = lines.Count - 1;
    22.  
    23.             for(; insertIndex >= 0; insertIndex--)
    24.             {
    25.                 if(lines[insertIndex].Contains("</ItemGroup>"))
    26.                 {
    27.                     insertIndex++;
    28.                     break;
    29.                 }
    30.             }
    31.  
    32.             lines.InsertRange(insertIndex, ttfileInsert);
    33.  
    34.             var s = string.Join("\n", lines.ToArray());
    35.  
    36.             return s;
    37.         };
    38.     }
    39.  
    40.     static List<string> GetTTFileItemGroups(bool isEditorProject)
    41.     {
    42.         var assetsPath = Application.dataPath;
    43.         var parentPath = Directory.GetParent(assetsPath);
    44.  
    45.         var toTrimAmount = parentPath.FullName.Length+1;
    46.  
    47.         var toReturn = new List<string>();
    48.         foreach (var ttFile in Directory.GetFiles(assetsPath, "*.tt", SearchOption.AllDirectories))
    49.         {
    50.             var ttFilePath = ttFile.Replace("/", "\\");
    51.  
    52.             var isEditorPath = ttFilePath.ToLower().Contains(@"\editor\");
    53.  
    54.             if(isEditorProject && !isEditorPath)
    55.             {
    56.                 continue;
    57.             }
    58.  
    59.             if(!isEditorProject && isEditorPath)
    60.             {
    61.                 continue;
    62.             }
    63.  
    64.             var pathIncludingAssetsToTTFile = ttFilePath.Substring(toTrimAmount);
    65.             var csFilenameOnly = pathIncludingAssetsToTTFile;
    66.  
    67.             var lastSlash = csFilenameOnly.LastIndexOf('\\');
    68.  
    69.             if (lastSlash != -1)
    70.             {
    71.                 csFilenameOnly = csFilenameOnly.Substring(lastSlash + 1);
    72.             }
    73.  
    74.             csFilenameOnly += ".cs";
    75.  
    76.             string formattedEntry = string.Format(@"<ItemGroup>
    77.    <Content Include=""{0}"">
    78.      <Generator>TextTemplatingFileGenerator</Generator>
    79.      <LastGenOutput>{1}</LastGenOutput>
    80.    </Content>
    81.  </ItemGroup>", pathIncludingAssetsToTTFile, csFilenameOnly);
    82.  
    83.             toReturn.Add(formattedEntry);
    84.         }
    85.         return toReturn;
    86.     }
    87. }
    88.  
     
    Arkenvoss likes this.
  17. liortal

    liortal

    Joined:
    Oct 17, 2012
    Posts:
    3,562
    You could also use the (undocumented) editor callback:
    Code (CSharp):
    1. public static void OnGeneratedCSProjectFiles()
    2. {
    3. }