Getting started

This guide will introduce you with the basic concepts of XNAchievement. These are the themes this guide covers:

Introduction & Download

Achievements are great! They motivate gamers to play your game longer and to try crazy things. They provide feedback and competition. Look at Valves Team Fortress 2: It has 411 Achievements!

But achievements can be a pain - when it comes to implement them. And its always the same; but it looks like there is no good library for this on the internet. So I started to create XNAchievement. I provides the backend for you own Achievement Solution. It contains the part that is the same for every game and is flexible enough to fit into your game.

At the moment it has some dependencies to XNA, but it can be ported to another .NET game engine in minutes.

Please note: XNAchievement is really just the backend, it has no online system, no pre build achievement list and (currently) no good notifier (that "achievement unlocked" popup). These are to different from game to game to include a one-fits-it-all solution.

So your convinced to use XNAchievement? Then download it! There are to versions: A binary and a source code version. The binary version contains only the pre compiled DLL, the source code version the...source code and a sample project. As this guide contains some references to the sample we suggest the source code version. If you like to have the most recent version you can check them out from subversion.

Adding achievements

The first step is to define all the achievements the gamer can earn. These are represented by the class "Achievement". You can use this class on its own, but is more comfortable to use the achievement manager included in XNAchievement. So lets look at some code:

//taken from the sample's Game.cs

screenManager.AchievementManager = new AchievementManager();

screenManager.AchievementManager.Add(new Achievement("simple_select", null, null)
{
	Name = "The selector",
	LockedDescription = "Select the Button \"Select me\"",
	UnlockedDescription = "You selected the correct button!",
});
screenManager.AchievementManager.Add(new CounterAchievement("select_count", null, null)
{
	Name = "Multi Selector",
	LockedDescription = "Select the Button twenty times",
	UnlockedDescription = "You selected the button 20x. That was a lot!",
	UnlockValue = 20,
	NotificationInterval = 0.2f //A notification every 20%
});

screenManager.AchievementManager.LoadContent(Content);
We have our AchievementManager (here in the screenManager) and add to achievement to him. The "Achievement" class (and the derived CounterAchievement class) have three constructor parameters. The first is a internal name (not what the gamer sees!). This name is used to refer to the achievement and it has to be distinct for all achievements managed by the achievement manager! As IntelliSense will tell you the second two are the names of the textures that represent the locked and unlocked achievement. When LoadContent() is called (more on this later) XNAchievement tries to load these textures by Content.Load<Texture2D() unless you pass null. These textures have no direct use but you will probably include them in a "Achievement unlocked" PopUp.

And then there are these nasty curly brackets... They look strange at this place? Read about C# 3.0 object initializers here or here

Back? As you now understand these brackets are used to initialize the class with some values the easy way. IntelliSense will tell you more about every of the properties or you can have a look at the list of achievement types.

Until now we passed only texture names so we need to load the real textures. That is were LoadContent() comes in place. You can call it separately for every single achievement or just one time on the achievement manager, it will pass the call to all registered achievements.

"Achievement unlocked!"

Ok, we added the achievements and now we want to unlock them! In our sample the "game" is a simple menu with some buttons. These buttons unlock the achievements. Some code first:

//From UnlockGameScreen.cs
void SimpleUnlockMenuEntry_Selected(object sender, PlayerIndexEventArgs e)
{
	//The first button was pressed, we handle the achievement progress here
	ScreenManager.AchievementManager.GetByName("simple_select").Unlock(); //The simple one, just unlock it. (No problem if is was already unlocked)
	((CounterAchievement)ScreenManager.AchievementManager["select_count"]).IncreaseCounter(); //Increase the counter. Note the index operator ([]). It works like GetByName().
}
This is pretty self-explanatory: We retrieve the achievement from the manager with it's internal name (remember that?) and call Unlock(). The second achievement needs to be casted to CounterAchievement and IncreaseCounter() is called. Understand what "requiring minimal changes to your code" means? ;)

The list of achievement types has information about the methods you have to call for any achievement. Or just ask IntelliSense, we commented well...

Ok, the achievement is unlocked and ... nothing happens! We need a notifier to show a notification. See you on the next part.

Notifier

XNAchievement know three types of notifications: Unlocked, Progressed and Special. Every achievement has a event called "Notification" that is fired when a notification happens, and the achievement manager has a event called "AnyNotification" that is fired when any of the managed achievements has triggered a notification. Note: There is also a even "Unlocked" and "AnyUnlocked". These are just there to simplify things.

The recommended way of handling notification is to create a class that implements the "INotificationHandler" interface. This class can be passed to AchievementManager.RegisterNotificationHandler() and will from then on every notification a achievement produces. XNAchievement has a build in notifier called "DebugNotificationHandler". This notifier will write the notifications to the Debug-Stream witch will land in the Visual Studio Output Window (Strg+W, O). This code add a DebugNotificationHandler (you can have more than one handler registered at the same time!):

//From Game.cs
screenManager.AchievementManager.RegisterNotificationHandler(new DebugNotificationHandler());
It will look like this: output.png

This is nice for debugging but your gamers wan't better PopUps? No problem, but you have to create them on your own ;) But the sample contains a custom notifier that uses the Game State System to show a PopUp. This is the important part:

//From PopUpNotificationHandler.cs
public void HandleAnyNotification(object sender, AnyNotificationEventArgs e)
{
	StringBuilder sb = new StringBuilder();

	sb.AppendLine("Notification");
	switch (e.NotificationType)
	{
		case NotificationType.Unlocked:
			sb.AppendFormat("{0} Unlocked!{1}{2}", e.Achievement.Name, Environment.NewLine, e.Achievement.Description);
			break;
		case NotificationType.Progressed:
			sb.AppendFormat("{0} Progressed to {1:##%}!", e.Achievement.Name, (float)e.UserData);
			break;
		case NotificationType.Special:
			sb.AppendFormat("{0}: Special event!", e.Achievement.Name);
			break;
	}

	screenManager.AddScreen(new MessageBoxScreen(sb.ToString(), false), null);
}
The AnyNotificationEventArgs contain a field called "UserData". In case of a unlocked notification it will be null, but in case of a progressed notification it will contain a float with the new progress value as seen in the code above. When a special event happens the field may contain additional information, but you will have to look at the triggering code's documentation to find out.

Listing Achievements

OK, now you know how to add and unlock achievements and how to create a notifier. Now you want a list of all the available achievements in you game.

Again there a two options:

Variant A:

//From DisplayAchievementsScreen.cs
foreach (Achievement a in ScreenManager.AchievementManager.GetVisibleAchievements()) //Only visible achievements are shown on this page!
{
	//Create a Button for each achievement, clicking it will pop up a details screen.
	MenuEntry tmp = new MenuEntry(a.Name);
	tmp.Selected += new EventHandler<PlayerIndexEventArgs>(tmp_Selected);
	MenuEntries.Add(tmp);
}
Iterate over GetVisibleAchievements(). You will have your own Menu System that you can use for displaying, just look at the sample for inspiration. One important fact: Achievements can have the proprty "VisibleWhenLocked" set to false. When this flag is set, the achievements won't show up in GetVisibleAchievements() until they are unlocked. Great for easter eggs!

Variant B:

The easy way: Use AchievementManager.GetAllAsString() to get a string with all visible achievement to show somewhere. You will need to pass a formatter to this method, more on this later.

Load & Save achievements

One important bit is still missing: Loading and saving of the achievement progress. Luckily for you XNAchievement contains code to reduce this to a few lines:

//From MainMenuScreen.cs
void writeAchievmentsMenuEntrySelected(object sender, PlayerIndexEventArgs e)
{
	using (FileStream fs = new FileStream(Path.Combine(Environment.CurrentDirectory, "achievements.bin"), FileMode.Create))
	{
		//Write the complete achievement data to a file calles "achievements.bin"
		//You will probably do this, when your game starts
		ScreenManager.AchievementManager.WriteAllToStream(fs);
	}
}

void readAchievmentsMenuEntrySelected(object sender, PlayerIndexEventArgs e)
{
	using (FileStream fs = new FileStream(Path.Combine(Environment.CurrentDirectory, "achievements.bin"), FileMode.Open))
	{
		//Read that file back
		//You will probably to this, when the game terminates (or better: just before ;))
		ScreenManager.AchievementManager.ReadAllFromStream(fs);
	}
}
Just use WriteAllToStream() and ReadAllFromStream() and your done! The achievements are saved with a BinaryFormatter (Serialisation) witch is very hard to read/edit by hand but theoretical prone to cheating. You will probably want to wrap the file stream in a CryptoStream

Extend XNAchievement

There are three hooks for extensions in XNAchievement: Formatter, Notifiers and Achievement Types. They will be described here:

Formatters

A Formatter is a class that implements the "IAchievementFormatter" interface. It has a method that turns a achievement into a string. This is needed eg. by AchievementManager.GetAllAsString(). XNAchievement contains some predefined formatters in the file AchievementFormatters.cs you can see this list to find out what they do or look at the source code to use them for your own formatter.

Notifiers

A Notifier is a class that implements the "INotificationHandler" interface. For more information see the section "notifier" above. You will probably need to write your own notifier as these are very game specific.

Achievement Types

When the predefined achievement classes that come with XNAchievement are not enough for you, you can create you own derived class. You may have a look at the list of predefined achievement types and look at the source code for inspiration. Tip: Make sure that you mark any events with [field:NonSerialized] or the serialization won't work!

Conclusion

Now you should have a basic idea of XNAchievement. Look at the included sample project, especially on the files Game.cs, DisplayAchievementsScreen.cs, UnlockGameScreen.cs and MainMenuScreen.cs to fully understand the mechanics. If you still have questions feel free to start a discussion or create a work item if you think you found a bug.

I hope you enjoy using XNAchievement!

Last edited Jul 1, 2012 at 11:27 AM by simsmaster, version 9

Comments

No comments yet.