diff --git a/README.md b/README.md index 4ffaf23..c6ff546 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ Start new FNA projects with Nez quickly and easily with handy setup scripts, a v 2. Run `./getFNA.sh` (macOS) to download the latest Nez, FNA and fnalibs to the directory. You can run this script again if you want to update either FNA or the fnalibs at a later point. Nez is setup as a submodule so you can update it in the normal fashion. 3. Open the newly-created and named `PROJECT_NAME_YOU_CHOSE.code-workspace` file (or open the project folder in Visual Studio Code or the top-level sln in Visual Studio) -That's it! Now you're ready to build and run the base project! When developing raw content (files not processed by the Pipeline tool) should be placed in the `Content` folder and anything that needs processing should go in the `CompiledContent` folder and added to the Pipeline tool. +That's it! Now you're ready to build and run the base project. When developing raw content (files not processed by the Pipeline tool) should be placed in the `Content` folder and anything that needs processing should go in the `CompiledContent` folder and added to the Pipeline tool. + +If you want to see the output of `Debug.*` calls in the VS Code Debug Console, you have to install a listener by adding this somewhere in your code (Game1 is a good spot): `System.Diagnostics.Debug.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(System.Console.Out));` ## Build Tasks ## @@ -33,6 +35,7 @@ That's it! Now you're ready to build and run the base project! When developing r - **Build Content:** Runs good old MGCB.exe on the Content.mgcb file - **Force Build Content:** Force builds the content (MGCB.exe -r) - **Open Pipeline Tool:** Opens the MonoGame Pipeline tool +- **Process T4 Templates:** Processes any T4 templates found in the `T4Templates` folder. Note that the install script will attempt to install the t4 command line program which requires the `dotnet` command line program to be installed. The install command it will run is `dotnet tool install -g dotnet-t4`. ## License and Credits ## diff --git a/getFNA.sh b/getFNA.sh index 0b4604b..444a1c2 100755 --- a/getFNA.sh +++ b/getFNA.sh @@ -94,6 +94,10 @@ if [[ $shouldDownloadLibs =~ ^[Yy]$ ]]; then fi +# install t4 engine +dotnet tool install -g dotnet-t4 + + # Rename project read -p "Enter the project name to use for your folder and csproj file or 'exit' to quit: " newProjectName if [[ $newProjectName = 'exit' ]]; then diff --git a/project_name/.vscode/processT4Templates.sh b/project_name/.vscode/processT4Templates.sh new file mode 100755 index 0000000..06531f9 --- /dev/null +++ b/project_name/.vscode/processT4Templates.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# buildEffects +# Compiles all .fx files found in the project's Content directory. +# Intended for usage with VS Code Build Tasks tooling. +# You may need to change the path to fxc.exe depending on your installation. + +printf "Starting T4 processing...\n" + +for file in `find ./T4Templates/** -name "*.tt"` ; +do + # Build the template + t4 -r=System.dll -r=mscorlib.dll -r=netstandard.dll -r=System.IO.FileSystem.dll -r=System.Linq.dll -r=System.Text.RegularExpressions.dll `dirname $file`/`basename $file` -o `dirname $file`/Output/`basename $file .tt`.cs + + echo "Built `basename $file`" + +done diff --git a/project_name/.vscode/tasks.json b/project_name/.vscode/tasks.json index 8fca028..dc3564f 100644 --- a/project_name/.vscode/tasks.json +++ b/project_name/.vscode/tasks.json @@ -120,5 +120,13 @@ "command": "export MONOGAME_PIPELINE_PROJECT=${workspaceFolder}/CompiledContent/Content.mgcb && /Applications/Pipeline.app/Contents/MacOS/Pipeline", "problemMatcher": "$msCompile" }, + + { + "label": "Process T4 Templates", + "type": "shell", + "command": "${workspaceFolder}/.vscode/processT4Templates.sh", + "group": "build", + "problemMatcher": "$msCompile", + }, ] } \ No newline at end of file diff --git a/project_name/T4Templates/ContentPathGenerator.tt b/project_name/T4Templates/ContentPathGenerator.tt new file mode 100644 index 0000000..3376cc3 --- /dev/null +++ b/project_name/T4Templates/ContentPathGenerator.tt @@ -0,0 +1,141 @@ +<#@ template language="C#" hostSpecific="true" #> +<#@ assembly name="System.Core.dll" #> +<#@ assembly name="System.dll" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="System.Globalization" #> +<#@ import namespace="System.Text.RegularExpressions" #> + +<# var sourceFolders = new string[] { "CompiledContent/bin/DesktopGL", "Content" }; #> + + +namespace Nez +{ + class Content + { +<# + + // loop through all of our sourceFolders + foreach( var sourceFolder in sourceFolders ) + { + var directories = Directory.GetDirectories( sourceFolder ); + // loop through all the directories in our sourceFolder + foreach( var dir in directories ) + { + var dirName = new DirectoryInfo( dir ).Name.ToLower(); + if( dirName == "bin" || dirName == "obj" || dirName == "content" ) + continue; + + // dont delve into directories that don't contan xnb files + var xnbFiles = Directory.GetFiles( dir, "*.xnb", SearchOption.AllDirectories ); + if( xnbFiles.Length == 0 ) + continue; + + // start off the recursive directory printing + printDirectoryClass( dir, 2, sourceFolder ); + } + + // handle any files in the root sourceFolder + printContentFiles( sourceFolder, 2, sourceFolder ); + } + +#> + + } +} + + + +<#+ + // C# reserved keywords + private System.Collections.Generic.List keywords = new System.Collections.Generic.List + { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", + "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", + "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", + "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", + "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while" + }; + + // recursively creates a class for each directory + void printDirectoryClass( string dir, int depth, string sourceFolder ) + { + var dirInfo = new DirectoryInfo( dir ); + var firstIndent = new string( ' ', depth * 5 ); + var className = generateClassName( dirInfo.Name, true ); + WriteLine( "{0}public static class {1}\n{2}{{", firstIndent, className, firstIndent ); + + // handle subdirectories + foreach( var subdir in Directory.GetDirectories( dir ) ) + printDirectoryClass( subdir, depth + 1, sourceFolder ); + + // handle files + printContentFiles( dir, depth + 1, sourceFolder ); + + WriteLine( "{0}}}\n", firstIndent ); + } + + + // prints a const string for each file in the directory + void printContentFiles( string dir, int depth, string sourceFolder ) + { + var firstIndent = new string( '\t', depth ); + + var files = Directory.EnumerateFiles( dir ) + .Where( s => s.EndsWith( ".xnb" ) || s.EndsWith( ".png" ) || s.EndsWith( ".ogg" ) || s.EndsWith( ".wav" ) || s.EndsWith( ".fxb" ) ); + foreach( var file in files ) + { + // clear out all of the path up to the sourceFolder so we get just the relative path to the Content folder + var finalPath = file.Substring( file.IndexOf( sourceFolder ) + sourceFolder.Length ); + var fileInfo = new FileInfo( file ); + var fileName = fileInfo.Name.Replace( ".xnb", string.Empty ); + var className = generateClassName( fileName, false ); + + if( finalPath[0] == '/' || finalPath[0] == '\\' ) + finalPath = finalPath.Substring( 1 ); + + // if file name is reserved insert a leading '@' + if( keywords.Contains( className ) ) + className = className.Insert( 0, "@" ); + + WriteLine( "{0}public const string {1} = @\"{2}\";", firstIndent, className, finalPath ); + } + } + + + string stripInvalidPathChars( string input ) + { + var invalidChars = Path.GetInvalidPathChars(); + return new string( input.Where( m => !invalidChars.Contains( m ) ).ToArray() ); + } + + + string stripInvalidFilenameChars( string input ) + { + var invalidChars = Path.GetInvalidFileNameChars(); + return new string( input.Where( m => !invalidChars.Contains( m ) ).ToArray() ); + } + + + // attempts to generate a proper path name + string generateClassName( string className, bool uppercaseFirstChar ) + { + // handle upper or lower casing the first char in the className + if( uppercaseFirstChar && char.IsLower( className[0] ) ) + className = char.ToUpper( className[0] ) + className.Substring( 1 ); + else if( !uppercaseFirstChar && char.IsUpper( className[0] ) ) + className = char.ToLower( className[0] ) + className.Substring( 1 ); + + // remove invalid characters + var regex = new Regex( @"[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Nl}\p{Mn}\p{Mc}\p{Cf}\p{Pc}\p{Lm}]" ); + className = regex.Replace( className, "" ); + + // class name doesn't begin with a letter, insert an underscore + if( !char.IsLetter( className, 0 ) ) + className = className.Insert( 0, "_" ); + + return className.Replace( " ", string.Empty ); + } +#> diff --git a/project_name/T4Templates/EnumEqualityComparerGenerator.tt b/project_name/T4Templates/EnumEqualityComparerGenerator.tt new file mode 100644 index 0000000..b0ad58c --- /dev/null +++ b/project_name/T4Templates/EnumEqualityComparerGenerator.tt @@ -0,0 +1,40 @@ +<#@ template language="C#" hostSpecific="true" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Collections.Generic" #> +<# var enumTypes = new string[] { "System.StringSplitOptions" }; #> +using System.Collections.Generic; + + +namespace Nez +{ +<# + // loop through all of our enumTypes and generate a comparer + foreach( var enumType in enumTypes ) + { + var classPrefix = char.ToUpper( enumType[0] ) + enumType.Substring( 1 ).Replace( ".", string.Empty ); + if( enumType.IndexOf( "." ) > 0 ) + { + var enumTypeSansNS = enumType.Substring( enumType.IndexOf( "." ) + 1 ); + classPrefix = char.ToUpper( enumTypeSansNS[0] ) + enumTypeSansNS.Substring( 1 ).Replace( ".", string.Empty ); + } + + WriteLine( "\tpublic class {0}Comparer : IEqualityComparer<{1}>", classPrefix, enumType ); + WriteLine( "\t{" ); + + WriteLine( "\t\tstatic public readonly {0}Comparer default{0}Comparer = new {0}Comparer();\n", classPrefix ); + + WriteLine( "\t\tpublic bool Equals( {0} x, {0} y )", enumType ); + WriteLine( "\t\t{" ); + WriteLine( "\t\t\treturn x == y;" ); + WriteLine( "\t\t}\n\n" ); + + WriteLine( "\t\tpublic int GetHashCode( {0} b )", enumType ); + WriteLine( "\t\t{" ); + WriteLine( "\t\t\treturn (int)b;" ); + WriteLine( "\t\t}" ); + + WriteLine( "\t}\n\n" ); + } + +#> +} \ No newline at end of file diff --git a/project_name/project_name.csproj b/project_name/project_name.csproj index 6672410..01f3c9c 100644 --- a/project_name/project_name.csproj +++ b/project_name/project_name.csproj @@ -6,13 +6,13 @@ false AnyCPU project_name - DesktopGL + DesktopGL - +