如何创建一个强类型结构来访问XNA内容项目中的文件?

时间:2011-08-14 18:13:06

标签: c# xna xna-4.0 strong-typing

序言
我正在使用XNA内容项目来保存我正在使用的所有各种纹理(以及可能的其他资源)作为开发游戏的一部分。

将Content项目中的图像加载到XNA纹理对象的默认方法涉及使用硬编码字符串文字指向各种文件。

我想自动将内容项目中的目录/文件树投影到对象层次结构中,以避免直接使用字符串文字,并获得使用强类型对象的好处。


示例:
而不是使用
Texture2D tex = Content.Load("Textures/defaultTexture32");
我更喜欢这个 Texture2D tex = Content.Load(Content.Textures.defaultTexture32);


问题:
这个问题是否已有解决方案? (我在谷歌找不到任何东西)


额外详情:
我相当肯定这可以通过T4模板完成;可能与DTE工具一起使用。我已经初步尝试这样做了,但由于我对两种工具都缺乏经验,我一直在打块,但我过去曾与T4MVC合作过类似的事情。不幸的是,它投射的是类结构而不是文件系统,并且不易调整。

我不要求解决方案使用T4或DTE,它们看起来好像很可能是解决方案的一部分。

仅包含属于VS项目的文件(而不是整个磁盘上的文件系统)将是首选,但不是必需的。

按文件类型等进行额外过滤的能力将是额外的特殊奖励。

对于任何没有立即看到这样做的好处的人;考虑重命名或删除文件会发生什么。应用程序将继续编译正常,但它会在运行时崩溃。也许直到满足某个特定文件才能访问某个文件。如果将所有文件名都投影到一个对象结构中(每次构建项目时都会重新生成,或者甚至每次修改时都会重新生成),那么您将得到编译时错误,指出缺少的资源并可能避免很多未来疼痛

感谢您的时间。

3 个答案:

答案 0 :(得分:2)

这是一个T4模板,它将从项目目录中读取“Textures”文件夹中的所有文件。然后它们将作为字符串写入类中,如果您希望限制文件搜索,则可以更改Directory.GetFiles()。

添加/删除文件后,您可以单击解决方案资源管理器中的“转换所有模板”以生成新类。

希望这有帮助!

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ import namespace="System.IO" #>
<# var files = Directory.GetFiles(Host.ResolvePath("Textures"), "*.*"); #>
namespace Content
{
    class Textures
    {
<#      foreach (string fileName in files) { #>
        public const string <#= Path.GetFileNameWithoutExtension(fileName) #> = @"Textures/<#= Path.GetFileNameWithoutExtension(fileName) #>";
<#      } #>
    }
}

答案 1 :(得分:2)

我创建了一个更复杂的T4模板,它使用DTE扫描VS项目。

我离开Ronny Karlsson的答案被标记为已接受的答案,因为他用更简单的解决方案帮助我达到这一点,但我想让任何可能发现它有用的人都可以使用它。

在您自己使用此代码之前,请记住我是T4和DTE的新手,所以请谨慎使用,虽然它看起来对我来说很好,但您的里程可能会有所不同。

此模板将为项目及其内部的所有文件夹创建嵌套命名空间...在这些命名空间内,它将为每个文件创建一个类。这些类包含一组有关该文件的有用信息的字符串。

另请注意顶部附近的两个变量...它们定义要扫描和构建对象的项目,第二个List<string> AcceptableFileExtensions执行您所期望的操作,并指示要创建对象时要考虑的文件扩展名。例如,您可能不希望包含任何.cs或.txt等文件。或者你可以。适当调整。我现在只包括png,因为这就是我现在所需要的。

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE"#>
<#@ import namespace="System"#>
<#@ import namespace="System.IO"#>
<#@ import namespace="System.Collections.Generic"#>
<#
    // Global Variables (Config)
    var ContentProjectName = "GameContent";
    List<string> AcceptableFileExtensions = new List<string>(){".png"};

    // Program
    IServiceProvider serviceProvider = (IServiceProvider)this.Host;
    DTE dte = (DTE) serviceProvider.GetService(typeof(DTE));

    Project activeProject = null;

    foreach(Project p in dte.Solution.Projects)
    {
        if(p.Name == ContentProjectName)
        {
            activeProject = p;
            break;
        }
    }

    emitProject(activeProject, AcceptableFileExtensions);
#>

<#+
private void emitProject(Project p, List<string> acceptableFileExtensions)
{
    this.Write("namespace GameContent\r\n{\r\n");
    foreach(ProjectItem i in p.ProjectItems)
    {
        emitProjectItem(i, 1, acceptableFileExtensions);
    }
    this.Write("}\r\n");
}

private void emitProjectItem(ProjectItem p, int indentDepth, List<string>         acceptableFileExtensions)
{
    if(String.IsNullOrEmpty(Path.GetExtension(p.Name)))
    {
        emitDirectory(p, indentDepth, acceptableFileExtensions);
    }
    else if(acceptableFileExtensions.Contains(Path.GetExtension(p.Name)))
    {
        emitFile(p, indentDepth);
    }
}

private void emitDirectory(ProjectItem p, int indentDepth, List<string>     acceptableFileExtensions)
{
    emitIndent(indentDepth);
    this.Write("/// Directory: " + Path.GetFullPath(p.Name) + "\r\n");
    emitIndent(indentDepth);
    this.Write("namespace " + Path.GetFileNameWithoutExtension(p.Name) + "\r\n");
    emitIndent(indentDepth);
    this.Write("{" + "\r\n");

    foreach(ProjectItem i in p.ProjectItems)
    {
        emitProjectItem(i, indentDepth + 1, acceptableFileExtensions);
    }

    emitIndent(indentDepth);
    this.Write("}" + "\r\n" + "\r\n");
}

private void emitFile(ProjectItem p, int indentDepth)
{
    emitIndent(indentDepth);
    this.Write("/// File: " + Path.GetFullPath(p.Name) + "\r\n");
    emitIndent(indentDepth);
    this.Write("public static class " + Path.GetFileNameWithoutExtension(p.Name) +     "\r\n");
    emitIndent(indentDepth);
    this.Write("{" + "\r\n");

    emitIndent(indentDepth + 1);
    this.Write("public static readonly string Path      = @\"" +     Path.GetDirectoryName(Path.GetFullPath(p.Name)) + "\";" + "\r\n");
    emitIndent(indentDepth + 1);
    this.Write("public static readonly string Extension = @\"" +     Path.GetExtension(p.Name) + "\";" + "\r\n");
    emitIndent(indentDepth + 1);
    this.Write("public static readonly string Name      = @\"" +     Path.GetFileNameWithoutExtension(p.Name) + "\";" + "\r\n");

    emitIndent(indentDepth);
    this.Write("}" + "\r\n" + "\r\n");
}

private void emitIndent(int depth)
{
    for(int i = 0; i < depth; i++)
    {
        this.Write("\t");
    }
}
#>

答案 2 :(得分:1)

我以前从未见过这样的事情。我这样做的方法是创建一个单独的c#控制台应用程序,扫描内容文件夹或内容项目xml,并将所需的c#代码写入项目中包含的文件。然后,您可以将其作为预构建步骤(或每次添加内容时手动)运行。我不熟悉T4或DTE,所以我不能对这些选项发表评论。

请记住,扫描内容项目XML有它的缺点。您将能够提取内容项的类型(或至少分配的内容导入程序/处理器),但它可能无法获取所有内容。例如,3D模型会自动包含其引用的纹理,因此这些纹理不会列在内容项目中。这可能没问题,因为您不太可能想直接引用它们。