I love video games; so much so that I'm going to start things off with a video game I've been working on (and is still very much in development). It's a top-down 2D RPG/Adventure where the player pilots a customized ship, written in C#/XNA, and this will probably not be the last time you read about "Spacer" on this blog... The quandary conquered stems from setting up something that's considered good programming practice: layers. Maybe it's already a known solution, but I couldn't find much when I was researching it...
I don't really see much of this in scientific applications, but in a lot of business-type applications, there are often three layers: database, logic, and view. I'm going to focus on the separation of the database and logic layers.
In my initial commit to the game, every object had it's own hard-coded statistics associated to it. For instance, the WeaponSystem class looked something like this:
class WeaponSystem : Equipment { bool canFire; double rateOfFire; float energyPerShot; int numOfShots, numOfShotsMax; TimeSpan timer; public WeaponSystem() { //arbitrary values: rateOfFire = 0.25; //in seconds energyPerShot = 10f; energyReserve = 100f; numOfShotsMax = 50; equipmentName = "Weapons"; //calculated quantities: energyReserveMax = numOfShotsMax * energyPerShot; numOfShots = (int)(energyReserve / energyPerShot); timer = TimeSpan.Zero; } //There is more code about how the WeaponSystem works, but not important here }
(hey, don't judge. Guns are important to a space adventurer... Jayne agrees, see?)
This would mean creating a class for every type of weapon system: lasers, missiles, torpedoes, etc... Bleck! The logic of the game shouldn't depend on the specific values you give to your objects, and honestly, shouldn't care what the values are so long as they are valid values!
Since hard-coding values is bad, the idea was to create a sort of "Stats Library;" a single place to store all these values to look up as needed. This really just meant separating the "arbitrary values" in the above code to it's own serializable class, and dumping it's corresponding XML file(s) into the content project. For organization's sake, all these "StatsLibrary objects" were put into a separate VS project (this project comprises the data layer, the original game project is the logic layer). The separation turned out something like this:
public class WeaponSystemStats { public string Name { get; set; } public string BulletTypeFired { get; set; } public double CoolDownTime { get; set; } public float EnergyPerShot { get; set; } int MaximumNumberOfShotsStored { get; set; } }
<?xml version="1.0" encoding="utf-8" ?>
<XnaContent>
<Asset Type="Spacer.StatsLibrary.WeaponSystemStats">
<Name>TestGun</Name>
<BulletTypeFired>TestLaser</BulletTypeFired>
<CoolDownTime>0.25</CoolDownTime>
<EnergyPerShot>10.0</EnergyPerShot>
<MaximumNumberOfShotsStored>50</MaximumNumberOfShotsStored>
</Asset>
</XnaContent>
(keep in mind, this advancement didn't happen over sequential commits, the WeaponSystem class evolved a bit. For instance "RateOfFire" turned into "CoolDownTime", also note the "BulletTypeFired" string: this same idea was applied to the projectiles fired from the WeaponSystem)
Now, we have a nice way to store data, a "database layer," if you will. Rather than each object storing it's own statistics, all it has to know is what kind of object it is (use a unique string, enum, or whatever other identifying method you want; in this example, it happens to be the "Name" property), everything else can just be looked up from a single source! Originally, this single-source was just a Dictionary object, which works fine for getting things to work, but why stop there?
Being completely OCD, the content project was already organized into folders of XML documents for each game object, why not load everything at once? Create a generic StatisticsLibrary object:
class StatisticLibrary<t> where T : IStatsLibraryEntry //This is just an interface with a single property: Name { Dictionary<string, T> library; public void LoadLibrary(ContentManager Content, string contentFolder) { library = new Dictionary<string, T>(); DirectoryInfo di = new DirectoryInfo(Content.RootDirectory + "\\" + contentFolder); if (!di.Exists) throw new DirectoryNotFoundException(); FileInfo[] files = di.GetFiles("*.*"); foreach (FileInfo file in files) { string extensionlessFile = Path.GetFileNameWithoutExtension(file.Name); T value = Content.Load<t>(contentFolder + "\\" + extensionlessFile); string key = value.Name; library.Add(key, value); } } public T GetStatistic(string Name) { return library[Name]; } }
Now, we can use a static object of each game object to lookup whatever data we need, where ever we need it! Just use the static object to access it from the logic layer. (Theoretically, you can access our new statistics library from the view layer, but it's generally regarded as bad coding practice; only allow a layer to access an adjacent layer). Now, you don't have to load everything in the specified content sub-folder, I just do because Spacer is still in pretty early development stages, and I don't have enough content to notice a difference in performance, but if you've got a lot of complex content, selectively loading material would be worth considering.
So there you have a basic database layer for a game:
- Create a serializable model for each game object (you can use inheritance to greatly simplify these!) that is separate from your logic that updates the object
- Make as many XML (or JSON, or binary,...) docs as you want for object data
- Populate and access via a static library object
No comments:
Post a Comment