Nineteen Stone Ninjas

Indie Game Development Studio

  • RSS Dev Blog RSS Feed
  • ATOM Dev Blog Atom Feed
  • Nineteen Stone Ninjas Twitter Account Nineteen Stone Ninjas Facebook Page
    Nineteen Stone Ninjas YouTube Channel Nineteen Stone Ninjas on Google+ Nineteen Stone Ninjas on Indie DB Nineteen Stone Ninjas on Giant Bomb
[ Login ]

Devlog

Subnet Devlog: Week 2 of Subnet: Contracts

Thu 8 October 2015 12:32

I'm nearly at the end of week 2 of the development for Subnet: Contracts, and here's how I've approached it:

  • Days 1 & 2: Initial design document for the game
  • Day 3: Prototyping the "City Area" GUI using 2D polygon colliders, and putting the AudioManager together
  • Days 4 to 7: Prototyping the procedural generation aspect of the game
  • Day 8: Procrastinate
  • Day 9: Redesign the hacking mechanic from the ground up

Procgen

The main reason I spent so much time on the procedural generation prototype is that I knew the ideas I had for it were solid, and the only parts that would change was the generation probabilities and rules - which I have made configurable.

The result of this bulk of work is that I am now able to feed my generator a single integer - the number of "areas" in the city. Each area will generate a random number of contracts (or "jobs") for the player to complete; Contracts subsequently have a number of properties assigned to them - such as "Company Name" and "Building Type". Within a Contract a number of "Nodes" are generated that are each designated a "Network Device Type", along with various other properties pertinent to the hacking mechanic (such as a "Defence Rating". Nodes also have a placement algorithm to determine where on the map they will appear, and which other nodes they are connected to. All properties throughout the object hierarchy are generated based on a a set of rules - but the rules are only there to ensure the procedural generation has a more realistic feel to it. After tweaking, I expect every game to be fairly distinct.

I've used two main types of rule storage - enumerations with attributes (which are accessed via reflection), and arrays of classes. The following code excerpts show some enumeration examples:

public enum AreaTypes {
    [AreaTypeConfiguration(10, 15, 1, 1, 1, 3, 10, 15, 4, 4)]
    SlumResidential,

    [AreaTypeConfiguration(12, 18, 2, 3, 2, 5, 15, 20, 4, 4)]
    SlumIndustrial
}

public enum BuildingTypes {
    [BuildingTypeConfiguration("Pharmaceutical")]
    Pharmaceutical,

    [BuildingTypeConfiguration("Guard Post")]
    GuardPost
}

The attributes are relatively simple classes:

[AttributeUsage(AttributeTargets.Field)]
public class AreaTypeConfigurationAttribute : Attribute, IAreaTypeConfiguration {
    public AreaTypeConfigurationAttribute(int minContracts, int maxContracts,
        int minAreaNumber, int maxAreaNumber,
        int minFailureRating, int maxFailureRating,
        int minNetworkNodesPerContract, int maxNetworkNodesPerContract,
        int minContractStages, int maxContractStages) {
        Contracts = new MinMaxSetting(minContracts, maxContracts);
        AreaNumber = new MinMaxSetting(minAreaNumber, maxAreaNumber);
        ContractFailureRating = new MinMaxSetting(minFailureRating, maxFailureRating);
        NetworkNodesPerContract = new MinMaxSetting(minNetworkNodesPerContract, maxNetworkNodesPerContract);
        ContractStages = new MinMaxSetting(minContractStages, maxContractStages);
    }

    public MinMaxSetting Contracts { get; private set; }

    public MinMaxSetting AreaNumber { get; private set; }

    public MinMaxSetting ContractFailureRating { get; private set; }

    public MinMaxSetting NetworkNodesPerContract { get; private set; }

    public MinMaxSetting ContractStages { get; private set; }
}

[AttributeUsage(AttributeTargets.Field)]
public class BuildingTypeConfigurationAttribute : Attribute {
    public string FriendlyName { get; set; }

    public BuildingTypeConfigurationAttribute(string friendlyName) {
        FriendlyName = friendlyName;
    }
}

The class arrays are stored in a singleton (they will be used when the game is first started up, then at different points throughout the game depending on the player's actions). They provide the generation algorithms with mapping data and other metadata:

 public static readonly AreaBuildingType[] AreaBuildingTypeVariations = {
        // SlumResidential
        new AreaBuildingType(AreaTypes.SlumResidential, BuildingTypes.ResidentialHouseSmall, 85),
        new AreaBuildingType(AreaTypes.SlumResidential, BuildingTypes.ResidentialHouseLarge, 5),
        new AreaBuildingType(AreaTypes.SlumResidential, BuildingTypes.ResidentialHouseMedium, 15),
        new AreaBuildingType(AreaTypes.SlumResidential, BuildingTypes.ReisdentialHouseHuge, 5),
        new AreaBuildingType(AreaTypes.SlumResidential, BuildingTypes.GuardPost, 5),
        new AreaBuildingType(AreaTypes.SlumResidential, BuildingTypes.Factory, 2),
        // SlumIndustrial
        new AreaBuildingType(AreaTypes.SlumIndustrial, BuildingTypes.Factory, 60),
        new AreaBuildingType(AreaTypes.SlumIndustrial, BuildingTypes.Warehouse, 30),
        new AreaBuildingType(AreaTypes.SlumIndustrial, BuildingTypes.GuardPost, 15),
        new AreaBuildingType(AreaTypes.SlumIndustrial, BuildingTypes.ResearchLab, 2)
}

public class AreaBuildingType {
    public AreaBuildingType(AreaTypes areaType, BuildingTypes buildingType, int generationProbability) {
        AreaType = areaType;
        BuildingType = buildingType;
        GenerationProbability = generationProbability;
    }

    public AreaTypes AreaType { get; private set; }

    public BuildingTypes BuildingType { get; private set; }

    public int GenerationProbability { get; private set; }
}

A random number generator is then used to generate values depending on the rules and constraints set up:

    var areaTypeSettings = Helpers.EnumerableOf()
        .Select(at => new {
            AreaType = at,
            Settings = at.GetAttribute()
        })
        .ToArray();
    var areaTypes = areaTypeSettings
        .Where(x => areaNumber >= x.Settings.AreaNumber.Min
                    && areaNumber <= x.Settings.AreaNumber.Max)
        .ToArray();
    var areaType = areaTypes.Random();
    var newArea = new Area(areaNumber, startupSettings.NumberOfAreas, areaType.AreaType, Player);

There is a lot of missing code - including extension and helper methods, but hopefully you get the idea.

Configuration Storage

At present all configuration data is stored in the source code. A few developers I've spoken to have suggested using JSON or XML structures to house this data - which I heavily considered when I started putting this together - I also put a tweet out to ask other people's opinions, but ultimately decided against it for a few reasons:

  • Once the configuration values are right, they won't need to change
  • I don't want players to modify the generation rules at this stage
  • There is an additional cost of deserializing the data into meaningful objects

I will need to serialize this data into some kind of storage format for save games, but I have already written some generic routines to do that for Subnet - which I can reuse easily.

Redesign

I'm sure many budding game designers have the same problem; making the game fun, challenging and interesting for the player. My main goal was to fill in some story gaps in Subnet's main arc, and I think I've achieved that quite well. However, I have succumbed to the realisation that I'm facing same issue I still have in Subnet; how the hell is the hacking supposed to work? I'm still not happy with what I've done for Subnet's hacking mechanic, and I wasn't happy with the hacking game I designed in Subnet: Contracts either. I was hoping that doing Subnet: Contracts would provide some inspiration for Subnet.

Being totally honest, this is the second reason why I've spent so much time on the procedural generation part; many of the design decisions I put down on paper were arbitrary, and I just couldn't see it being fun or challenging. On a positive note, now I have the procedural generation working quite nicely, I've decided to hit the drawing board again with a few more ideas for the hacking game; so far I'm feeling quite hopeful.

comments powered by Disqus