Search code examples
c#file-ioxna

Outputting user data to a file XNA


namespace highscores
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Stuff for HighScoreData
        public struct HighScoreData
        {
            public string[] PlayerName;
            public int[] Score;

            public int Count;

            public HighScoreData(int count)
            {
                PlayerName = new string[count];
                Score = new int[count];

                Count = count;
            }
        }

        /* More score variables */
        HighScoreData data;
        public string HighScoresFilename = "highscores.dat";
        int PlayerScore = 0;
        string PlayerName;
        string scoreboard;
        string typedText = "";
        int score = 0;
        public static SpriteFont font;
        public static Texture2D textBoxTexture;
        public static Texture2D caretTexture;
        bool txt;

        // String for get name
        string cmdString = "Enter your player name and press Enter";
        // String we are going to display – initially an empty string
        string messageString = "";

        SpriteBatch spriteBatch;
        GraphicsDeviceManager graphics;
        KeyboardState oldKeyboardState;
        KeyboardState currentKeyboardState;
        private string textString;


        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

#if XBOX
            Components.Add(new GamerServicesComponent(this));
#endif

            currentKeyboardState = Keyboard.GetState();

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            txt = true;
            //Append characters to the typedText string when the player types stuff on the keyboard.
            KeyGrabber.InboundCharEvent += (inboundCharacter) =>
            {
                //Only append characters that exist in the spritefont.
                if (inboundCharacter < 32)
                    return;

                if (inboundCharacter > 126)
                    return;

                typedText += inboundCharacter;
            };


            // Get the path of the save game
            string fullpath = "highscores.dat";

            // Check to see if the save exists
#if WINDOWS
            if (!File.Exists(fullpath))
            {
                //If the file doesn't exist, make a fake one...
                // Create the data to save
                data = new HighScoreData(5);
                data.PlayerName[0] = "neil";
                data.Score[0] = 2000;

                data.PlayerName[1] = "barry";
                data.Score[1] = 1800;

                data.PlayerName[2] = "mark";
                data.Score[2] = 1500;

                data.PlayerName[3] = "cindy";
                data.Score[3] = 1000;

                data.PlayerName[4] = "sam";
                data.Score[4] = 500;

                SaveHighScores(data, HighScoresFilename);
            }
#elif XBOX

            using (IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (!iso.FileExists(fullpath))
                {
                    //If the file doesn't exist, make a fake one...
                    // Create the data to save
                    data = new HighScoreData(5);
                    data.PlayerName[0] = "neil";
                    data.Score[0] = 2000;

                    data.PlayerName[1] = "shawn";
                    data.Score[1] = 1800;

                    data.PlayerName[2] = "mark";
                    data.Score[2] = 1500;

                    data.PlayerName[3] = "cindy";
                    data.Score[3] = 1000;

                    data.PlayerName[4] = "sam";
                    data.Score[4] = 500;

                    SaveHighScores(data, HighScoresFilename, device);
                }
            }

#endif

            base.Initialize();
        }
        #region saving
        /* Save highscores */
        public static void SaveHighScores(HighScoreData data, string filename)
        {
            // Get the path of the save game
            string fullpath = "highscores.dat";

#if WINDOWS
            // Open the file, creating it if necessary
            FileStream stream = File.Open(fullpath, FileMode.Create);
            try
            {
                // Convert the object to XML data and put it in the stream
                XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
                serializer.Serialize(stream, data);
            }
            finally
            {
                // Close the file
                stream.Close();
            }

#elif XBOX

            using (IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication())
                {

                    using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fullpath, FileMode.Create, iso))
                    {

                        XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
                        serializer.Serialize(stream, data);

                    }

                }

#endif
        }

        /* Load highscores */
        public static HighScoreData LoadHighScores(string filename)
        {
            HighScoreData data;

            // Get the path of the save game
            string fullpath = "highscores.dat";

#if WINDOWS

            // Open the file
            FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate, FileAccess.Read);
            try
            {
                // Read the data from the file
                XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
                data = (HighScoreData)serializer.Deserialize(stream);
            }
            finally
            {
                // Close the file
                stream.Close();
            }


            return (data);

#elif XBOX

            using (IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fullpath, FileMode.Open,iso))
                {
                    // Read the data from the file
                    XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
                    data = (HighScoreData)serializer.Deserialize(stream);
                }
            }

            return (data);

#endif

        }

        /* Save player highscore when game ends */
        private void SaveHighScore()
        {
            // Create the data to saved
            HighScoreData data = LoadHighScores(HighScoresFilename);

            int scoreIndex = -1;
            for (int i = data.Count - 1; i > -1; i--)
            {
                if (score >= data.Score[i])
                {
                    scoreIndex = i;
                }
            }

            if (scoreIndex > -1)
            {
                //New high score found ... do swaps
                for (int i = data.Count - 1; i > scoreIndex; i--)
                {
                    data.PlayerName[i] = data.PlayerName[i - 1];
                    data.Score[i] = data.Score[i - 1];
                }

                data.PlayerName[scoreIndex] = PlayerName; //Retrieve User Name Here
                data.Score[scoreIndex] = score; // Retrieve score here

                SaveHighScores(data, HighScoresFilename);
            }
        }

        /* Iterate through data if highscore is called and make the string to be saved*/
        public string makeHighScoreString()
        {
            // Create the data to save
            HighScoreData data2 = LoadHighScores(HighScoresFilename);

            // Create scoreBoardString
            string scoreBoardString = "Highscores:\n\n";

            for (int i = 0; i < 5; i++) // this part was missing (5 means how many in the list/array/Counter)
            {
                scoreBoardString = scoreBoardString + data2.PlayerName[i] + "-" + data2.Score[i] + "\n";
            }
            return scoreBoardString;
        }
        #endregion
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Spritefont1");
            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }



        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            oldKeyboardState = currentKeyboardState;
            currentKeyboardState = Keyboard.GetState();
            //UpdateInput();
            score = 100;
            if(typedText != "")
                PlayerName = typedText;
            if (GamePad.GetState(PlayerIndex.One).Buttons.A == ButtonState.Pressed)
            {
                SaveHighScore();
                txt = false;
            }


            base.Update(gameTime);
        }

        //ignore this method
        private void UpdateInput()
        {
            oldKeyboardState = currentKeyboardState;
            currentKeyboardState = Keyboard.GetState();

            Keys[] pressedKeys;
            pressedKeys = currentKeyboardState.GetPressedKeys();

            foreach (Keys key in pressedKeys)
            {
                if (oldKeyboardState.IsKeyUp(key))
                {
                    if (key == Keys.Back) // overflows
                        textString = textString.Remove(textString.Length - 1, 1);
                    else
                        if (key == Keys.Space)
                            textString = textString.Insert(textString.Length, " ");
                        else
                            textString += key.ToString();
                }
            }
        } 

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            if (txt == true)
            {
                spriteBatch.DrawString(font, typedText, new Vector2(0, graphics.GraphicsDevice.PresentationParameters.BackBufferHeight * .5f), Color.White);
            }
            else if (txt == false)
            {
               spriteBatch.DrawString(font, makeHighScoreString(), new Vector2(500, 300), Color.Red);
            }
            //spriteBatch.DrawString(font, textString, new Vector2(256, 300), Color.Red);

            spriteBatch.End();
            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}

Above is my Game1.cs file of an XNA program I am testing. I am testing the keyboard input (which is using another class called KeyGrabber.cs) and also I am testing the ability to save an int score and a string name to a file so that I can use in my game for a high score system. They keyboard input seems to work fine as its outputting to screen. Saving seems to work also as its saving the default data.

My problem is I want to overwrite this data when someone finishes with a certain score value and enters their name on the screen. The issue is, once I write to file once the data doesn't change. even when I change the default values of the sample data (PlayerName = "neil", score = 2000 for example), the original data is still kept with no changes displayed.

My other issue is actually adding values dynamically. I thought it would be easy (maybe it is, I'm very tired) but I can't seem to output to the file with user provided data (for example, if they wrote their name or gained a certain score), not even initially.

Basically, I want to output to a file, a list of highscores. With two saved values (name and score). I want to read the name in from the text entered on screen (variable is "typedText").

EDIT

OK after writing all that, it hit me. Obviously the sample data values won't change once the file is created as they are only added if the file doesn't exist. So never mind that issue.


Solution

  • How about having a simple class that represents a single high score:

    public class HighScore
    {
        public String Name { get; set; }
        public int Score { get; set; }
    }
    

    You keep all the highscores in a List<HighScore>, and serialize them with

    XmlSerializer serializer = new XmlSerializer(typeof(List<HighScore>));
    

    Sample

    Having a private field of type List<HighScore> makes it easy to add and search through scores:

    List<HighScore> _highScores = new List<HighScore>();
    
    void LoadData()
    {
        //...
        Stream dataStream = //whichever way you open it
        XmlSerializer serializer = new XmlSerializer(typeof(List<HighScore>));
        _highScores = serializer.Deserialize(dataStream) as List<HighScore>;
    }
    

    To add a highscore, you simply call

    _highScores.Add(new HighScore(){ Name = someName, Score = someScore });
    

    To save it, you serialize it using the same serializer. Using LINQ, it is easy to sort the scores:

    var orderedScores = _highScores.OrderByDescending(s=>s.Score);
    HighScore bestScore = orderedScores.FirstOrDefault();
    

    Note

    When using Windows, you might want to save your data in the ApplicationData folder (as you are likely going to be unable to write to your installation folder when you publish the game). You can get your save game path like so:

    String dirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "GameName");
    DirectoryInfo directory = new DirectoryInfo(dirPath);
    if (!directory.Exists)
        directory.Create(); //so no exception rises when you try to access your game file
    
    this.SavegamePath = Path.Combine(dirPath, "Savegame.xml");