Which method do you use for storing game data in external files?

Hello everyone,

I am working on a game that has a large variety of items, and all of these have different stats, such as sell price, etc. My question is, how would I create a system that depends on external files, is importable by the MonoGame Pipeline tool and allows me to change values throughout development without hardcoding them.

Keep in mind that I’m looking for a system that uses just one single class for an object rather than having a ton of different classes that serve the same purpose but have different stats.

I hope you cal help me! Thanks for reading through my topic.

EDIT: I’m not looking how to save game data. Instead, I want to store data of how different items of the same class should behave, such as display name, sell price, buy price, etc.

I might have something that can help you though it has nothing to do with the Content Pipeline

Its probably not the best way, but I m currently working on a game and do save/load with Streamwriter and txt files

For this Game I programmed myself a little MapEditor to place, rotate, scale my map Objects in a 3D environment, because placing things via code sucks^^

since the MapEditor and my Game are seperate Programms, I save all my Objects with their ID Position etc in a txt file and put this file in a directory in my main game folder, to load it…

for my main game I also save the current world and player state in txt files like that and load from them during runtime when the player dies for instance,

here an example how to use the Streamwriter/reader from my MapEditor which can save/ load a map into 2 arrays:

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

namespace MapEditor
{

public class TextFileManager
{

    public static void CreateFile(string name)
    {
        MapStuff.MapObject[] _itemArray = new MapStuff.MapObject[100];
        int _skipIndexes = 0;
        if (!File.Exists(name))
        {
            using (StreamWriter sw = File.CreateText("Saves/"+name))
            {
                sw.WriteLine("ID    Poisiton    Forward     Upward      Scale");
              for(int i = 1; i < Global._objectArray.Length;i++)
                {
                    if (Global._objectArray[i] != null)
                    {
                        if (Global._objectArray[i]._id > 100)
                        {
                            _itemArray[_skipIndexes] = Global._objectArray[i];
                            _skipIndexes++;
                        }
                        else
                        {
                            sw.WriteLine(Global._objectArray[i]._id + " " +
                                Global._objectArray[i]._position.X + ";" + Global._objectArray[i]._position.Y + ";" + Global._objectArray[i]._position.Z + " " +
                                Global._objectArray[i]._direction.X + ";" + Global._objectArray[i]._direction.Y + ";" + Global._objectArray[i]._direction.Z + " " +
                                Global._objectArray[i]._upwards.X + ";" + Global._objectArray[i]._upwards.Y + ";" + Global._objectArray[i]._upwards.Z + " " +
                                Global._objectArray[i]._scale);
                        }
                    }
                }
                for (int i = 0; i < _itemArray.Length; i++)
                {
                    if (_itemArray[i] != null)
                    {
                        sw.WriteLine(_itemArray[i]._id + " " +
                            _itemArray[i]._position.X + ";" + _itemArray[i]._position.Y + ";" + _itemArray[i]._position.Z + " " +
                            _itemArray[i]._direction.X + ";" + _itemArray[i]._direction.Y + ";" + _itemArray[i]._direction.Z + " " +
                            _itemArray[i]._upwards.X + ";" + _itemArray[i]._upwards.Y + ";" + _itemArray[i]._upwards.Z + " " +
                            _itemArray[i]._scale);
                    }
                }
            }
        }
    }

    public static void LoadFile(string name)
    {
        if (File.Exists("Saves/" + name))
        {
            Global._objectArray = new MapStuff.MapObject[Global._universeSize];
            Global._objectArray[0] = new MapStuff.MapObject(1, new Vector3(0, -10f, 5), new Vector3(0, 1, 0), new Vector3(0, 0, 1));
            Global._objectArray[0].Load();
            using (StreamReader sr = File.OpenText("Saves/" + name))
            {
                string _line = "";
                int i = 0;
                while ((_line = sr.ReadLine()) != null)
                {
                    //Position
                    if (i > 0)
                    {
                        int _posInd = 1+Math.Abs(_line.IndexOf(" "));
                        int _posCom1Ind = 1+Math.Abs(_line.IndexOf(";", _posInd));
                        int _posCom2Ind = 1+Math.Abs(_line.IndexOf(";", _posCom1Ind));

                        //Forward
                        int _directInd = 1+Math.Abs(_line.IndexOf(" ", _posInd + 1));
                        int _directCom1Ind = 1+Math.Abs(_line.IndexOf(";", _posCom2Ind));
                        int _directCom2Ind = 1+Math.Abs(_line.IndexOf(";", _directCom1Ind));

                        //Upwards
                        int _upInd = 1+Math.Abs(_line.IndexOf(" ", _directInd + 1));
                        int _upCom1Ind = 1+Math.Abs(_line.IndexOf(";", _directCom2Ind));
                        int _upCom2Ind = 1+Math.Abs(_line.IndexOf(";", _upCom1Ind));

                        int _lastSpaceInd = 1 + Math.Abs(_line.IndexOf(" ", _upCom2Ind + 1));

                        // Console.Out.WriteLine(_line.Substring(_directCom1Ind, _directCom2Ind - _directCom1Ind-1)); //Direct Y

                        //Position
                        float _xPos = (float)Convert.ToDouble(_line.Substring(_posInd, _posCom1Ind - _posInd - 1));
                        float _yPos = (float)Convert.ToDouble(_line.Substring(_posCom1Ind, _posCom2Ind - _posCom1Ind - 1));
                        float _zPos = (float)Convert.ToDouble(_line.Substring(_posCom2Ind, _directInd - _posCom2Ind - 1));

                        //Direction
                        float _xDirect = (float)Convert.ToDouble(_line.Substring(_directInd, _directCom1Ind - _directInd - 1));
                        float _yDirect = (float)Convert.ToDouble(_line.Substring(_directCom1Ind, _directCom2Ind - _directCom1Ind - 1));
                        float _zDirect = (float)Convert.ToDouble(_line.Substring(_directCom2Ind, _upInd - _directCom2Ind - 1));

                        //Direction
                        float _xUp = (float)Convert.ToDouble(_line.Substring(_upInd, _upCom1Ind - _upInd - 1));
                        float _yUp = (float)Convert.ToDouble(_line.Substring(_upCom1Ind, _upCom2Ind - _upCom1Ind - 1));
                        float _zUp = (float)Convert.ToDouble(_line.Substring(_upCom2Ind, _lastSpaceInd - _upCom2Ind - 1));

                        int _readId = Convert.ToInt32(_line.Substring(0, _posInd));
                        float _readScale = (float)Convert.ToDouble(_line.Substring(_lastSpaceInd, _line.Length-_lastSpaceInd));

                        Global._objectArray[i] = new MapStuff.MapObject(_readId, new Vector3(_xPos, _yPos, _zPos),
                            new Vector3(_xDirect, _yDirect, _zDirect), new Vector3(_xUp, _yUp, _zUp));
                        Global._objectArray[i]._scale = _readScale;
                        Global._objectArray[i].Load();

                    }





                    //  Global._room1[i] = new MapStuff.MapObject()
                    i++;
                }
            }
        }
    }
}

}

sorry I m not sure how to mark things as code in here^^

looks terrible right now

well anyway, just replace the Saves + name at Create/OpenFile() with your path

Edit:
this code creates, read .txt files that look like this

in my game I have for the loadfile function if branches, deciding with the Id about what to do, for instance, id>1000 means item and they behave different etc, for the MapEditor that wasnt neccessary
though because of that I also sort the .txt file list so the items come last

you could simply make a list of your Objects and there stats, and change them in the txt file now and then…

there are probably better ways to do this than txt though

Edit2: tryed marking code now to be better shown as code… this side makes posting code a bit wierd, or I am missing something

I have the same requirements though I didn’t use the content pipeline. I went with the Entity-Component-System approach. I only have one object which can have “plugin-able” components based off of what the external data files has defined. All my game data is stored as JSON. I used JsonNET and some custom stuff I wrote to save/load the JSON data. More or less wrote my own json “pipeline” for my game objects because I had some unique requirements.

I know some other devs here use sql.

I’m using LiteDB to store all my save data and procedurally generated stuff. Theoretically you could also use it to store item lists, it’d actually be perfect for that. You’d just need to code a frontend so you can add, remove and edit things.