Highscore

Hello I’m looking for a simple method to save the score and highscore then display them in another topic discusses the question with xml but I encountered a lot of difficulty even when I managed to have something that turns virtual cannot install the app in real life I’m waiting for your suggestions thanks in advance :slight_smile:

You could try SQLite. It is relatively easy to set up and work with. You would just create a class that represents a score and insert the scores into the table. The other option is if you add Xamarin.Forms there is access to storage that doesn’t require file access at all.

1 Like

Thank you for the quick reply very interesting
:slight_smile:
do you have an example?

I don’t have a high score example but I can whip one up really quick. I’ll be back with the result. If I recall from your previous post that this is for Android?

1 Like

thank you yes it is with android :slight_smile:

First, you need to add a NuGet package to your game project. The package is sqlite-net-pcl.

The steps are:

  1. Create a class that represents a high score
  2. Create a class that represents a data store for CRUD operations
  3. In your game initialize the database
  4. Insert some test data that makes sense for your game
  5. In your Draw method retrieve the scores, order them descending

A bit of code so bare with me. First, you need a class that represents a high score that includes an ID column. Here is my example:

    public class HighScore
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Score { get; set; }
    }

Here is the code for the data store:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using SQLite;

namespace HighScores
{
    class HighScoreDataStore
    {
        readonly SQLiteAsyncConnection _database;
        public HighScoreDataStore()
        {
            string dbPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
            dbPath = Path.Combine(dbPath, "highscoredata.sqlite");
            _database = new SQLiteAsyncConnection(dbPath);
            _database.CreateTableAsync<HighScore>().Wait();
        }

        public async Task<bool> AddAsync(HighScore HighScore)
        {
            await _database.InsertAsync(HighScore);

            return await Task.FromResult(true);
        }

        public async Task<bool> DeleteAsync(string id)
        {
            await _database.DeleteAsync(id);

            return await Task.FromResult(true);
        }

        public async Task<HighScore> GetAsync(string id)
        {
            return await _database.GetAsync<HighScore>(id);
        }

        public async Task<List<HighScore>> GetAsync(bool forceRefresh = false)
        {
            return await _database.Table<HighScore>().ToListAsync();
        }

        public async Task<int> UpdateAsync(HighScore HighScore)
        {
            return await _database.UpdateAsync(HighScore);
        }
    }
}

There are methods for insert, delete, update and read operations. They are async so you need to await them in the method you call them from. The ones that will be most important for you are the insert for adding a new high score and the read for retrieving the score(s). I will demonstrate adding and reading.

You want to add a field, probably static so you can share it among all of your classes or create a service that you can retrieve in your other classes. In your constructor initialize the data store. You may want to call a method to add in some test data like I did. Here are my constructor and test method. You will notice it is async so I can await the calls to the data store.

        HighScoreDataStore _highScores;
        SpriteFont _font;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;

            _highScores = new HighScoreDataStore();
             Task<bool> result = InsertData(); // Only run this once
        }

        public async Task<bool> InsertData()
        {
            try
            {
                await _highScores.AddAsync(new HighScore() { Id = 0, Name = "Jill", Score = 10000 });
                await _highScores.AddAsync(new HighScore() { Id = 1, Name = "Dave", Score = 5000 });
                await _highScores.AddAsync(new HighScore() { Id = 2, Name = "Fred", Score = 9000 });
            }
            catch
            {
                return false;
            }

            return true;
        }

Now you need to retrieve the data and draw the high scores in a Draw method somewhere. In my test I did it in the Draw method of the Game1 class. You can do it wherever you need to. This is my Draw method.

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            Task<List<HighScore>> scores = _highScores.GetAsync(false);
            List<HighScore> sorted = scores.Result.OrderByDescending(o => o.Score).ToList();

            _spriteBatch.Begin();
            Vector2 position = new Vector2();

            foreach (HighScore score in sorted)
            {
                _spriteBatch.DrawString(_font, score.Name + " - " + score.Score, position, Color.White);
                position.Y += _font.LineSpacing;
            }

            _spriteBatch.End();

            base.Draw(gameTime);
        }

That is how to save/load high scores using SQlite. You can look into running delete queries to remove high scores if they are more than say 10 and if they are lower than the minimum of the top 10.

Here is a link to a GitHub repository so you can see the complete project.

1 Like

thank you it’s awesome :+1: in such a short time in addition a contribution in github I’m imprinted :open_mouth:

I went to test the code :slight_smile: I couldn’t resist
it shows me a black screen I realized that it blocked here
it’s strange

List <HighScore> sorted = scores.Result.OrderByDescending (o => o.Score) .ToList ();

_font = Content.Load(“fonts\font”);

 _font = Content.Load<SpriteFont>("fonts/font");

and just a typo at the “fonts / font”

thank you again for this work

@Guess_Game
Wrong commit! The Draw method you want to use the code in needs to be async. Replace the Draw method with the following.

        protected async override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            List<HighScore> scores = await _highScores.GetAsync(false);
            List<HighScore> sorted = scores.OrderByDescending(o => o.Score).ToList();

            _spriteBatch.Begin();
            Vector2 position = new Vector2();

            foreach (HighScore score in sorted)
            {
                _spriteBatch.DrawString(_font, score.Name + " - " + score.Score, position, Color.White);
                position.Y += _font.LineSpacing;
            }

            _spriteBatch.End();

            base.Draw(gameTime);
        }

I update the repository.

1 Like

thank you :+1:

I’m hoping this is just to illustrate a point and that you’re not actually loading and sorting the High Score table in the Draw method…

It’s just a demonstration. Doing async stuff in the Draw method makes no sense.

Hi
ok just a question comment i could do my asynchronous processing in the program please?
thanks in advance :slight_smile:

I don’t know the structure of your program but the Initialize/LoadContent method would be a good place to get it initially. UnloadContent would be a good place to update the list on quitting. If you’re updating in between you’ll have to do a call when you check game over or something.

1 Like

for the moment I’m trying to understand how the code works before adapting it

i just want
retrieve and display the score you entered manually with InsertData ()

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HighScorev1
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        HighScoreDataStore _highScores;
        private SpriteFont _font;

        Task<List<HighScore>> scores;
        List<HighScore> sorted;



        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;

            _highScores = new HighScoreDataStore();
            Task<bool> result = InsertData(); // Only run this once

           
        }

        public async Task<bool> InsertData()
        {
            try
            {
                await _highScores.AddAsync(new HighScore() { Id = 0, Name = "Jill", Score = 10000 });
                await _highScores.AddAsync(new HighScore() { Id = 1, Name = "Dave", Score = 5000 });
                await _highScores.AddAsync(new HighScore() { Id = 2, Name = "Fred", Score = 9000 });
            }
            catch
            {
                return false;
            }

            return true;
        }

     
        protected override void Initialize()
        {
 
            scores = _highScores.GetAsync(false);
            List<HighScore> sorted = scores.Result.OrderByDescending(o => o.Score).ToList();

            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            _font = Content.Load<SpriteFont>("fonts/font");


        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        protected  override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

           
            _spriteBatch.Begin();
            Vector2 position = new Vector2(0,0);

            foreach (HighScore score in sorted)
            {
                _spriteBatch.DrawString(_font, score.Name + " - " + score.Score, position, Color.White);
                position.Y += _font.LineSpacing;
            }

            _spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

correct me if I’m wrong :slight_smile:

It should be:

protected async override void Initialize()
{
    scores = await _highScores.GetAsync(false);
...

You have to make the method async so you can use await the call to GetAsync to wait for it to finish and retrieve the scores. You won’t have access to sorted outside of the Initialize method. I’ll update the GitHub to show you. Give me a little time to do it.

1 Like

Thank you
I understand better but that does not pose a problem thereafter for the non-asynchronous objects?

take all your time :slight_smile:

Because of the nature of MonooGame it does have an impact. You need to make sure sorted is not null before rendering as it might take a frame or two. The best place to do the async stuff is in the LoadContent method after creating the SpriteBatch. Here is a working example. I can’t commit right now but I will tomorrow.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HighScores
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        HighScoreDataStore _highScores;
        SpriteFont _font;
        List<HighScore> scores, sorted;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;

            _highScores = new HighScoreDataStore();
            // Task<bool> result = InsertData(); // Only run this once
        }

        public async Task<bool> InsertData()
        {
            try
            {
                await _highScores.AddAsync(new HighScore() { Id = 0, Name = "Jill", Score = 10000 });
                await _highScores.AddAsync(new HighScore() { Id = 1, Name = "Dave", Score = 5000 });
                await _highScores.AddAsync(new HighScore() { Id = 2, Name = "Fred", Score = 9000 });
            }
            catch
            {
                return false;
            }

            return true;
        }
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        protected async override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            _font = Content.Load<SpriteFont>("fonts/font");
            scores = await _highScores.GetAsync(false);
            sorted = scores.OrderByDescending(o => o.Score).ToList();
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            _spriteBatch.Begin();
            Vector2 position = new Vector2();

            if (sorted != null)
            {
                foreach (HighScore score in sorted)
                {
                    _spriteBatch.DrawString(_font, score.Name + " - " + score.Score, position, Color.White);
                    position.Y += _font.LineSpacing;
                }
            }

            _spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
2 Likes

great thanks again :slight_smile: