9
\$\begingroup\$

Alright, formulated this way I'm sure it sounds obscure, but I'll do my best to describe my problem.

First, let me explain what's wrong with my real source code.

I'm making a platformer, and here are the current components of my game: a Level, which is holding its own grid of tiles and also the tile sheets and a Tile, which just keeps the name of its tile sheet and index within the tile sheet. All these are within a separate XNA game project, named GameLib.

Here is how my levels are formatted in a text file:

x x . . . . .

. . . b g r .

. . . . . . .

X X X X X X X

Where . represents an empty tile, b represents a blue platform, X represents a block with a random color, etc.

Instead of doing all the work of loading a level within the Level class, I decided to take advantage of the content pipeline extension and create a LevelContentPipelineExtension.

As soon as I started, I quickly realized that I didn't want to hard-code 20+ lines of the kind:

if (symbol == 'b')
    return new Tile(/*...*/);

So, in the hope of at least centralizing all the content creation into one class, I made a static class, called LevelContentCreation, which holds the information about which symbol creates what and all this kind of stuff. It also holds a list of all the asset paths of the tile sheets, to load them later on. This class is within my GameLib game library project.

The problem is, I'm using this LevelContentCreation static class in both my Level class (to load the actual tile sheets) and my content pipeline extension (to know which symbol represents what tile)...

And here's how my LevelContentCreation class looks like:

public static class LevelContentCreation
{
    private static Dictionary<char, TileCreationInfo> AvailableTiles =
        CreateAvailableTiles();

    private static List<string> AvailableTileSheets { get; set; }

    /* ... rest of the class */
}

Now, since the LevelContentCreation class is part of the game library, the content pipeline extension tries to use it but the call to CreateAvailableTiles() never happens and I'm unable to load a Level object from the content pipeline.

I know this is because the content pipeline executes before the actual compilation or something like that, but how can I fix the problem?

I can't really move the LevelContentCreation class to the pipeline extension, because then the Level class wouldn't be able to use it... I'm pretty lost and I don't know what to do... I know these are pretty bad design decisions, and I'd like it if somebody could point me in the right direction.

Thank you for your precious time.

If anything is not clear enough or if I should provide more information/code, please leave a comment below.


A little more information about my projects:

  • Game - XNA Windows game project. Contains references to: Engine, GameLib and GameContent
  • GameLib - XNA game library project. Contains references to: Engine
  • GameContent - XNA content project. Contains references to: LevelContentPipelineExtension
  • Engine - XNA game library project. No references.
  • LevelContentPipelineExtension - XNA content pipeline extension. Contains references to: Engine and GameLib

I know close to nothing about XNA 4.0 and I'm a little bit lost about how content works now. If you need more information, tell me.

\$\endgroup\$
4
  • \$\begingroup\$ I can't quite put my finger on what - but I think you are missing some information. Could you explain exactly what projects you have (game, game library, content pipeline extension, content project) and what references what? \$\endgroup\$ Jun 16, 2011 at 2:17
  • \$\begingroup\$ Sure, I knew it wouldn't be clear enough. Editing right now. \$\endgroup\$ Jun 16, 2011 at 2:28
  • \$\begingroup\$ By the way everything seems fine except that an exception is thrown at compile time within the pipeline extension. The exception actually tells me that the content of the level file can't be converted, since the available symbols are not yet loaded within my LevelContentCreation file. I even tested by putting an exception in both the pipeline extension and the static class and the pipeline extension's exception was thrown first, so the problem may actually come from there and I'd need to restructure my code. \$\endgroup\$ Jun 16, 2011 at 2:46
  • \$\begingroup\$ For future reference: here is a method for debugging content pipeline extensions. \$\endgroup\$ Jun 16, 2011 at 12:24

3 Answers 3

11
\$\begingroup\$

In cases where you are using singletons, static classes, or global variables, 99% of the time what you should really be using is a game service. Game services are used when you want one instance of a class available to all other classes, but the tight coupling of the other options gives you a funny taste. Services also don't have the instantiation problems that you've seen, they're more reusable, and they can be mocked (if you care about automated unit-testing).

To register a service, you need to give your class its own interface. Then just call Game.Services.AddService(). To later use that service in another class, call Game.Services.GetService().

In your case, your class would look something like this:

public interface ILevelContentCreation
{
    void SomeMethod();
    int SomeProperty { get; }
}

public class LevelContentCreation : ILevelContentCreation
{
    private Dictionary<char, TileCreationInfo> availableTiles =
        CreateAvailableTiles();

    private List<string> AvailableTileSheets { get; set; }

    public void SomeMethod() { /*...*/ }
    public int SomeProperty { get; private set; }

    /* ... rest of the class ... */
}

At some point at the start of the program, you'd have to register your service with Game.Services. I prefer to do this at the start of LoadContent(), but some people prefer to register each service in that class's constructor.

/* Main game */
protected override void LoadContent()
{
    RegisterServices();

    /* ... */
}

private void RegisterServices()
{
    Game.Services.AddService(typeof(ILevelContentCreation), new LevelContentCreation());
}

Now whenever you want to access your LevelContentCreation class in any other class, you would simply do this:

ILevelContentCreation levelContentCreation = (ILevelContentCreation) Game.Services.GetService(typeof(ILevelContentCreation));
levelContentCreation.SomeMethod(); //Tada!

As a side note, this paradigm is known as Inversion of Control (IoC), and is widely used outside of XNA development as well. The most popular framework for IoC in .Net is Ninject, which has nicer syntax than the Game.Services methods:

Bind<ILevelContentCreation>().To<LevelContentCreation>(); //Ninject equivalent of Services.AddService()
ILevelContentCreation levelContentCreation = kernel.Get<ILevelContentCreation>(); //Ninject equivalent of Services.GetService()

It also supports dependency injection, a form of IoC which is slightly more complex, but much nicer to work with.

[Edit] Of course, you can get that nice syntax with XNA too:

static class ServiceExtensionMethods
{
    public static void AddService<T>(this GameServiceContainer services, T service)
    {
        services.AddService(typeof(T), service);
    }

    public static T GetService<T>(this GameServiceContainer services)
    {
        return (T)services.GetService(typeof(T));
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Wow, even though I did fix my problem, this will just make my code so much cleaner. Thank you for the help, accepting your answer. \$\endgroup\$ Jun 17, 2011 at 0:24
1
\$\begingroup\$

I actually found a way to fix the problem: I removed the static class and made it a normal class. This way I'm sure the right statements get executed at the right time.

I wished I had not wasted your time like so but I actually got the idea of doing so by reading the comments/answers.

Thank you anyway.

\$\endgroup\$
2
  • \$\begingroup\$ Another note unrelated to my answer above/below: if you find yourself doing if/switch statements that simply return different types, you probably want those values to be part of the class instead. In your case, I would create a TileType class, which contains information (texture, save-file-letter, etc.) about each type of tile. Then to get a TileType from a letter, just search through your list of TileTypes, or if you're concerned about performance, store a static Dictionary<char, TileType> in the TileType class (you can fill it automatically in the TileType constructor). \$\endgroup\$ Jun 16, 2011 at 21:25
  • \$\begingroup\$ Then each Tile (class which represents a single tile displayed on the screen) would reference a single TileType. If enough of your tiles have unique behaviour that requires special code, you will want to use a class hierarchy instead. \$\endgroup\$ Jun 16, 2011 at 21:25
0
\$\begingroup\$

Have a look at this: http://msdn.microsoft.com/en-us/library/bb447745.aspx

For level layout i'd just put a function inside my Level class called "LoadFromFile(string name)". Because if you want to add support for custom maps in your game later, you will have to save and load the levels from your own file format anyway.

You could also still create a content importer, to load from the included assets and also from user provided files. Look at this page for more information about how to create an importer

http://msdn.microsoft.com/en-us/library/bb447743.aspx

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your answer. My problem doesn't actually come from the creation of the content pipeline extension, but from the fact that my level creation class is used both by my Level class and my content pipeline extension, so the pipeline code is executed before the static class declarations and nothing is initialized there. \$\endgroup\$ Jun 16, 2011 at 5:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .