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

发布于 2024-11-29 16:01:49 字数 1008 浏览 1 评论 0原文

序言:
我正在与 XNA Content 项目合作,以保存我在开发游戏时使用的所有各种纹理(可能还有其他资源)。

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

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


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


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


额外详细信息:
我相当确定这可以通过 T4 模板来完成;可能与 DTE 工具结合使用。我已经初步尝试这样做,但由于我对这两个工具集都缺乏经验,所以我一直遇到阻碍,但我过去曾使用过 T4MVC,它做了类似的事情;不幸的是,它投射类结构而不是文件系统,并且不容易适应。

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

仅包含属于 VS 项目(而不是整个磁盘文件系统)一部分的文件会更好,但不是必需的。

按文件类型等进行额外过滤的能力将是一个额外的特殊好处。

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

感谢您的宝贵时间。

Preamble:
I'm working with an XNA Content project to hold all of the various textures (and possibly other resources) that I'm using as part of developing a game.

The default method of loading images from the Content project into an XNA texture object involves the use of hard coded string literals to point to the various files.

I would like to automate the projection of the directory/file tree inside the content project into an object hierarchy to avoid using string literals directly, and to gain the benefits of using strongly typed objects.

Example:
Instead of using
Texture2D tex = Content.Load("Textures/defaultTexture32");
I'd much prefer
Texture2D tex = Content.Load(Content.Textures.defaultTexture32);

Question:
Is there an already existing solution to this problem? (I couldn't find anything with Google)

Extra details:
I'm fairly certain this can be done through a T4 template; probably in conjunction with the DTE tools. I've made an initial attempt to do this, but I keep hitting blocks due to my inexperience with both tool sets, but I've worked with T4MVC in the past which does something similar; unfortunately it projects class structure rather than the file system and is not easily adapted.

I do not require the solution to use T4 or DTE, they simply seem as though they're likely to be part of a solution.

Only including files that are part of the VS project (rather than the entire on-disk file system) would be preferable, but not necessary.

The ability to additionally filter by file types, etc. would be an extra special bonus.

For anyone that doesn't immediately see the benefits of doing this; consider what would happen if you renamed or deleted the file. The application would continue to compile fine, but it would crash at runtime. Perhaps not until a very special set of circumstances are met for a certain file to be accessed. If all the file names are projected into an object structure (which is regenerated every time you build the project, or perhaps even every time you modify) then you will get compile-time errors pointing out the missing resources and possibly avoid a lot of future pain.

Thanks for your time.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

只涨不跌 2024-12-06 16:01:49

这是一个 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) #>";
<#      } #>
    }
}

Here is a T4-template which will read all the files in a "Textures" folder from your project-directory. Then they'll be written into a class as strings, you can just change Directory.GetFiles() if you wish to limit the file-search.

After adding/removing files you can click "Transform All Templates" in Solution Explorer to generate a new class.

Hope this helps!

<#@ 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) #>";
<#      } #>
    }
}
场罚期间 2024-12-06 16:01:49

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

我将罗尼·卡尔森的答案标记为已接受的答案,因为他用他更简单的解决方案帮助我达到了这一点,但我希望将其提供给任何可能觉得有用的人。

在您自己使用此代码之前,请记住,我是 T4 和 DTE 的新手,因此请谨慎使用,尽管它似乎对我来说工作正常,但您的里程可能会有所不同。

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

另请注意顶部附近的两个变量...它们定义要扫描和构建对象的项目,第二个是 List。 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");
    }
}
#>

I created a more complicated T4 template that uses DTE to scan through the VS project.

I'm leaving Ronny Karlsson's answer marked as the accepted answer as he helped me get to this point with his simpler solution, but I wanted to make this available to anyone that might find it useful.

Before you use this code yourself, please remember that I'm new to T4 and DTE, so use with caution, and although it seems to work fine for me your mileage may vary.

This template will create nested namespaces for the project and all the folders inside of it... inside those namespaces it will create a class for each file. The classes contain a set of strings for useful things about the file.

Also note the two variables near the top... they define which project to scan and build objects for, and the second, List<string> AcceptableFileExtensions does as you might expect, and indicates which file extensions to consider for creating objects. For example you might want not want to include any .cs or .txt, etc. files. Or you might. Adjust as appropriate. I've only included png at the moment, since that's all I need right now.

<#@ 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");
    }
}
#>
森罗 2024-12-06 16:01:49

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

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

I've never seen anything like this done before. The way I would do it would be to create a separate c# console app that scans either the content folder or the content project xml and writes the desired c# code to a file included in your project. You can then run that as a pre-build step (or manually each time you add content). I'm not familiar with T4 or DTE so I can't comment on those options.

Keep in mind that scanning the content project XML has it's drawbacks. You will be able to extract the type of the content item (or at least the content importer/processor assigned), but it may not pick up all content. For example, 3D models automatically include their referenced textures, so these wouldn't be listed in the content project. This might be fine as you are unlikely to want to reference them directly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文