The XNA Highscores Component (distributed leaderboards, sort-of)

This is a simple game that uses the "HighscoreComponent" to manage a highscore list. It stores the list both locally, and uses Xbox Live! sessions to exchante highscore information with other players who happen to be playing the game at the same time. This way, high scores from people who are not your local players will show up, which is all kinds of cool :-)

The game itself is very silly: Hold down left shift (or the left trigger) to make a yellow bar go across the screen. Make it stop before it hits the right hand side. Then hold down right shift (or right trigger) to make a blue bar go across the screen. Make it stop before it runs out of space created by the yellow bar. The score is a factor of how far right you managed to push the yellow bar and the blue bar without overrun. Yeah, a total hit at parties!

This allows you to implement a limited kind of leaderboards. Usage is really simple: just create a GamerServicesComponent and add to your components, then create the HighscoreComponent and add to your components. As arguments, pass your game, null, and the name of your game as a brief string.

When your player finishes a game, call HighscoreComponent.Global.SetNewScore() and pass in the player index, the score, and an optional message (could be an empty string) not to exceed 64 characters. That data will be stored in the highscore table, and exchanged with other players of your game. The best N scores for each local player, and the best N scores for all networked players you've seen (which includes players they have seen, etc) are remembered on the hard disk. N is currently 50.

When you ask for highscores, you can ask for highscores for a current user, or for the global highscore list including the network downloaded data (if any). You will receive a list of objects containing the gamertag, score, date and message for each of the entries. For example, to receive scores for the gamer currently signed in as PlayerIndex.One, simply call:

Highscore[] scores = new Highscore[10]; int n = HighscoreComponent.Global.GetHighscores(PlayerIndex.One, scores);

This will return the number of scores that were actually returned, in case the local player hasn't yet played (in this case) 10 full games yet.

Some additional notes:

  • If the user refuses to choose a storage device, in case there are multiple, then there will be no highscores loaded, and whatever scores you apply will not be saved. If the user chooses a device the next time a score is added, the current scores will be merged with those already on the storage device, and properly saved.
  • It takes a few cycles of Update() after you have called SetNewScore() before it is properly saved to disk. This is because of the need to ask for a new device if the current device has been disconnected, etc.
  • The network session system will try to both connect as a peer, and if no suitable sessions are found, host a new session. Thus, session management is mostly "magic" and should make it so that if at least two peers are playing at the same time for at least 30 seconds, highscore information will be exchanged.
  • You can call ClearHighscores() to remove all current highscore data. Again, this takes a couple of iterations of Update() to actually hit the disk or memory cartridge.
  • You can call PurgeHighscoresOlderThan(Date) to remove highscores that are too old. Typically, you will want to know that some highscores have been loaded from disk before you call this (because it's an immediate operation with no "memory" for later received or loaded data). You can check whether any data has yet been loaded from disk by checking the HasReadFile boolean property.
  • If you want to suspend the operation of the highscore component temporarily, you can set its Enabled property to false. It will immediately terminate any network session it may have open. If a save is pending, that save cannot complete until you re-enable the component, though!
  • When you create the component, you have the option of passing in network session filtering parameters. This is useful if you also want to use session management on your own for multiplayer games, and want to be able to tell highscore exchange sessions from "real" game sessions. Set one of the variables to some unique value for the highscore sessions. Remember to disable the highscore component before you create your own session, though!
  • The code is not written with an eye towards conserving garbage in the garbage collector. The game code allocates several strings every frame, so there will be small hitches in frame rate at some intervals. Perhaps I'll go through and clean that up in some future version, if there's a lot of interest in this component.

This code is released Open Source under the MIT license. Copyright 2008 Jon Watte, All Rights Reserved. You may use it free of charge for any purposes, provided that Jon Watte's copyright is reproduced in your use, and that you indemnify and hold harmless Jon Watte from any claim arising out of any use (or lack of use or lack of ability of use) you make of it. This software is provided as-is, without any warranty or guarantee, including any implicit guarantee of merchantability or fitness for any particular purpose. Use at your own risk!

For more information and updates, stop by my XNA programming area at http://www.enchantedage.com/highscores

You will have to register and log in to download the code to build the game (which includes the component).

AttachmentSize
Highscores20081022.zip48 KB
highscores-1.png13.72 KB
highscores-2.png64.43 KB

Comments

Local High Score

Can this be used without xbox live? My game isnt an onlinemultiplayer game so would i be able to just store scores locally without global scores?

jwatte's picture

Yes, it can be used to store

Yes, it can be used to store the high score locally, but you'd have to manually cut out all the Xbox Live bits -- it doesn't have a simple boolean to just turn that off.

GamePad.GetState?

In the component Update there is a call to GamePad.GetState, the result of which isn't used at all. Is there a purpose for doing this? Or is it leftover from something else?

byte[] data = new byte[1000];
GamePadState gps = GamePad.GetState(PlayerIndex.One);
using (StorageContainer sc = storage_.OpenContainer(titleName_))

Thanks.

Multiple high score tables

I was wondering if creating several version of HighscoreComponent would work? I'd like to have 3 different high score tables for the easy, medium and hard difficulties in my game. Will creating multiple versions with different NetworkSessionProperties just work or would I need to keep flicking the Enabled properties so only one is enabled at any one time?

Thanks!

jwatte's picture

Actually, it would probably

Actually, it would probably be best if you marked each highscore entry with a "hard," "medium" or "easy" tag, and kept say 30 of each. Then exchange all of them with the other nodes in the code. Make sure that you always keep the 30 best of each (plus the local player scores if you want), and sort out which particular scores you display to the user in the UI code.

will not work for me

I have not be able to get this to work even the sample. when i run it the output of one of the pc / xboxs says this

Xbox360: Beginning PlayerMatch find.
Xbox360: Beginning PlayerMatch join.
A first chance exception of type 'Microsoft.Xna.Framework.Net.NetworkSessionJoinException' occurred in Microsoft.Xna.Framework.dll
Xbox360: Session not found. It may have ended, or there may be no network connectivity between the local machine and session host.

since the games seems to work for everyone else im asuming that somthing is wrong with my computer. cound a firewall be at fault?

jwatte's picture

I don't know whether

I don't know whether firewalls can make Live! not work -- it's possible.
Are you using a gamer profile that has a Live! gold subscription?
Also, is that profile signed in anywhere else (such as on an Xbox)?

got live accounts at both

got live accounts at both ends and not got anyone signed in anywhere else.

I get the same error

Did you ever figure out what the problem was?

Crashing issue

I really like this component and have integrated it into my game. I have run into a couple of problems however.

The first was down to the Highscore.Encode() method using a semi-colon as a seperator. I'm letting users enter their own name for a high score and storing that as the 'Message' component instead of displaying the Gamertag. However, this means they can enter a semi-colon which will obviously mean the Decode() method will then find an extra semi-colon which screws things up. I've changed this in my game so char(0) is used as a seperator instead. That shouldn't cause any problems should it?

I've got a problem at the moment which has been reported as a result of a failed review. The trouble is I've been unable to reproduce the problem but I wondered if it might be down to the issue where a save file can have garbage at the end of it as a result of using File.OpenOrCreate() (See http://forums.xna.com/forums/t/19525.aspx caveat 2). I know this code uses StreamWriter but I wonder if that could suffer from the same problem?

Dave

I checked the documentation

I checked the documentation for StreamWriter, and it doesn't actually explicitly promise to remove/truncate the old file first. However, in practice, that's what I've found that it does, so I do not believe this component has the "junk data at end" problem.

I believe a C# string can store a char[0] just fine. I also believe (but haven't verified) that network packet sending marshals strings with 0 fine, too, as it should marshal a length value first, followed by the actual characters.

Finally, an alternative to using 0 for separators would be to encode the elements as uuencode (base-64) or similar, and use a character that's not part of that encoding as separator.

PinoEire's picture

Credits

Hi,

well done! I'm extending it just a little to use it in my games. I've added you in the "Acknowledgements and Copyrights" section of the Credits.

Thanks,
Pino

I really like this component

I really like this component and believe that it's absolutely critical for games that keep track of any sort of score.
My one wish is that it also saved friend data to a seperate list allowing the user to view his/her friends best scores. I usually find myself more concerned with the scores of my friends than the scores of strangers.

Thanks for the suggestion. I

Thanks for the suggestion. I have no immediate plans to update the component, but if I ever do, that sounds like a great feature to add. Given that the source is included, perhaps you could even add that on your own :-)

Nice idea!

We were planning to include a similar service in our game to enable automated level exchange between users (the game cames with integrated level editor). We don't really now for sure that is was possible until this, so a big thank you, you saved me a lot of research time!! xD