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 :-)

This is version 1 of the component, from 2008. It is saved here for historical reasons. You want to get the latest version at http://www.enchantedage.com/highscores which has more features, bug fixes, and is easier to use.

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
highscores-1.png13.72 KB
highscores-2.png64.43 KB
Highscores20081022b.zip60.81 KB

Comments

Found a bug

I found a bug in the HighscoreComponent which concerns the handling of dead game sessions (or failure to connect to a session for whatever reason) -- If a dead session is in the list of sessions found, the HighscoreComponent will get stuck trying to connect to that over and over again.

The fix is to mark the server as visited before trying to connect. Then if the connection fails, it won't be continually re-tried. Specifically, instead of ...

            // line 459 
            session_ = NetworkSession.EndJoin(async_); 
            async_ = null; 
            previousHosts_.Add(session_.Host.Gamertag, true); 

... previousHosts_ should be updated here ...

              // line 483 
              if (!previousHosts_.ContainsKey(ans.HostGamertag)) 
              { 
                //  connect to this guy 
                isJoin_ = true; 
                System.Diagnostics.Trace.WriteLine(String.Format("{0}: Beginning PlayerMatch join.", HostType)); 
                previousHosts_.Add(ans.HostGamertag, true); 
                async_ = NetworkSession.BeginJoin(ans, null, null); 

Martin.

jwatte's picture

Thanks for sharing! That

Thanks for sharing! That sounds like a great solution.
Dead sessions are such a pain. You'd think Xbox Live could remove those by itself...

Thanks for your excellent highscore component!

Hello Jon,

Thanks for your excellent highscore component!
I'm using it in my pipes puzzler game Paipa which is in playtest right now.
It would be nice if you took a look when you get the chance and let me know what you think :D

... Can't post the link because its spam :(

Chris

Session's Not Starting Unexpectedly

Hey, thanks for the component. It is great!

However, recently during my testing, the component stopped creating sessions with others playing the same game completely. It had bounced fine for over 2 weeks, but recently, specifically in the last 12ish hours, I haven't been able to connect to a single session. This is the same for the others trying to bounce off of me.

I've tried restarting my box, deleting all data and starting anew, etc, to no avail. There has been no code change in this entire process.

This sounds like a Microsoft problem to me - is that possible with the code you've written? It seems strange that all sessions have stopped working randomly.

Thanks in advance.

jwatte's picture

Any Xbox networking code is

Any Xbox networking code is at the mercy of Microsoft and their Live operations. This means that if your console gets banned, highscores will stop working, too...

I've been having random Live! connectivity problems recently, too, and I've heard others on the XNA forums report it. If connectivity comes back, I'd blame Microsoft (or Internet Gremlins -- they pop up when you least expect them!)

Nevermind!

Nevermind - immediately after my last reply the Gremlins decided to punch out! It's all working again - Thanks for your time and component though!

Not Banned

I've done nothing that would warrant my console being banned, and the issue seems to be more widespread than just my console. At minimum, it is two different boxes under the same generally connection (attending University). It is likely that a couple friends in completely different parts of the state are also having this issue.

Does this mean their Live! service is somehow either limiting or completely stopping our connections, or connections for the entire game itself?

Thanks in advance.

jwatte's picture

The Xbox is not like a PC. It

The Xbox is not like a PC. It requires Live! to be up and operating to do pretty much anything network related, unless you do System Link connections.

Xbox Live Gold Subscription

Hey, great work on the component.

When a player that doesn't have a gold subscription plays a game with the component inside, does the game still store scores in the local saved list despite being not connected to any network session? Or does the entire component not enable itself if a user does not have a gold subscription?

Thanks in advance!

jwatte's picture

I believe that it will still

I believe that it will still store local scores, and just act as if it cannot create a connection to other boxes. However, I wrote it a while ago, so I don't remember for sure.

Enable/Disable bug

I've just come across a little bug related to enabling and disabling the component. If the component is disabled when the component is creating a session then NetworkSession.EndCreate() is called. However, the result of the method call is currently ignored.

In my situation I was attempting to find a game shortly after disabling the component and getting a "there's already a session around" exception when calling NetworkSession.EndFind(). Obviously the garbage collector hadn't kicked in to properly dispose of HighscoreComponent's session.

Thankfully this is easily fixed by assigning the result of EndCreate() and EndJoin() to this.session_ which will then be explicitly disposed of if it's not null. The same goes for disposing of the return value of EndFind().

Cheers,

Dave

jwatte's picture

Thanks for the note. That

Thanks for the note. That sounds like a great fix!

Using this component to gather analytics.

This is helpful. Can't wait to put this into my first game. I am new to this game design stuff so excuse me for sounding like a "noob." Does Microsoft supply us with back end analytics? Like Total players, scores, ect.

Once again I am such a noob!

jwatte's picture

Once you have released, you

Once you have released, you will get some amount of data from Microsoft (trial downloads, actual conversions). However, you won't get any rich metrics, such as you'd want to use to change the game design.

Copyright question

If I put "Highscore table Copyright 2008 Jon Watte, All Rights Reserved" at the bottom of my highscore table, would that be acceptable?

jwatte's picture

That would be perfect!

That would be perfect!

Content Unavailable

After extracting and running Highscroes.sln, the project loads into Visual Studio without a Content folder. VS says
The project file 'C:\XNA\Highscores\Content\Content.contentproj' has been moved, renamed, or is not on your computer. The project will be labeled as "unavailable" in Solution Explorer.

I've never seent his before. In VS the Content folder is (unavailable).

Any idea what I'm doing wrong? Is this an XNA version issue? I'm using 3.1 the current version. I'm sure I can just copy everything over into a new project but I thought I would mention this.

Thanks for the sample.
Allan

jwatte's picture

Probably an absolute path vs

Probably an absolute path vs relative path problem. If you open the sln file in a text editor and edit the path to that project, it should find it.

could you explain that a bit

could you explain that a bit more? I dont even have content.contentproj in the download.....

jwatte's picture

You are right -- it's not in

You are right -- it's not in the download. Weird.

I've uploaded a new archive with that project. It's easy to just right-click and "Add->New Content Project" and add the three pieces of content, though, as you say.

Works now! :)

Works now! :)

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