Wednesday, February 22, 2017

c# - XNA - Static classes from game libraries executing after content pipeline extensions, how to avoid the problem?


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 AvailableTiles =
CreateAvailableTiles();

private static List 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.



Answer



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 availableTiles =

CreateAvailableTiles();

private List 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().To(); //Ninject equivalent of Services.AddService()
ILevelContentCreation levelContentCreation = kernel.Get(); //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(this GameServiceContainer services, T service)
{
services.AddService(typeof(T), service);

}

public static T GetService(this GameServiceContainer services)
{
return (T)services.GetService(typeof(T));
}
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...