Three Hundred :: Mechanic #165
  Squidi.net Sites:     Webcomics   |   Three Hundred Mechanics   |   Three Hundred Prototypes
 Three Hundred
   - Index Page
   - About...
   - By Year

 Collections
   - Comp-Grid
   - Procedural
   - Tactics
   - Tiny Crawl
   - Misc


 
PreviousMechanic #165Next

  Mechanic #165 - PGC: Blueprints
Posted: Oct 19, 2012

How to control procedural generation inputs in a standardized, but useful way.


  Blueprints

This entry is based on material published in the
Blind Mapmaker from Oct 2009 to Mar 2011.
In [#037 - PGC Templates], I described the beginnings of an idea for how to control inputs to procedural generation algorithms. Over the next few years, I've taken that concept and worked on it - even implementing it - and come to a more refined, more useful approach that I call "Blueprints". A majority of the Blind Mapmaker deals with this process of refinement, so if you want more details on the thought process or even how I, personally, implemented this system, by all means, check out the articles there.

The best way to describe Blueprints is that they represent all the potential inputs to a procedural algorithm (which I call a generator). It is basically a named object with a bunch of properties that are defined, not as values, but as functions. You can create a blueprint "master" by evaluating those functions to arrive at singular values for each property. Thus a blueprint is an instruction on how to create values, while a blueprint master is a blueprint which has evaluated those instructions to one possible set of final values.  

Simple Blueprint
@blueprint FireDungeon
  @property tileSet = "firetiles.png"
  @property numberOfRooms = (rand 5 15)
@end 

This is an example of a very simple blueprint. It is a named object (FireDungeon) which it can be referred to by other blueprints, and a series of properties. Using LISP-like code, I represent each property as a single function. Note that, like LISP, individual expressions, like "firetiles.png", are basically functions that evaluate to themselves.

The property numberOfRooms will return a random value between 5 and 15. Generally speaking, you do not want to have a different value every time you query that property. That's where the mastering process comes in. Mastering will create a new blueprint where all the properties have been evaluated to final values. So, if you were to master FireDungeon, it would look like this:  

Mastered Simple Blueprint
@blueprint FireDungeon MASTER
  @property tileSet = "firetiles.png"
  @property numberOfRooms = 7
@end 

Now, each time you query the property numberOfRooms, it will return the same value. Only properties are mastered, so if you need a function that does return a unique value each time it is called, there are also more generic functions that you can define. Note that if you call one of these function from a property, the property will still be mastered to a single value.

The concept behind mastering a blueprint is the idea that generators, given identical input, will create new, identical objects. Once you have mastered a blueprint, that master represents a single, specific object that you can create and recreate each time you pass it to a generator.


  Hierarchal

Blueprints are hierarchal. Each blueprint can have a single parent from which it inherits all its properties, user functions, and variables from. So you can have a Spear which is descended from Weapon, and Weapon which is a child of Item. Any properties declared on Item will also be on Spear. Spear can also override any inherited properties with its own values.

The final wrinkle in the basic Blueprint is that blueprints can reference each other. For instance, you can create a Blueprint that creates a spear, then the Blueprint which creates the CaveMan can reference the spear blueprint. You can even create lists of blueprints or procedurally make decisions about them:  

Blueprint Inheritance
 @blueprint Item
  @property name = "Some Item"
  @property value = 1
@end

@blueprint Weapon : Item
  @property name = ""
  @property damage = 1
@end

@blueprint Spear : Weapon
  @property name = "Worn Spear"
  @property damage = (rand 10 15)
@end

@blueprint PointedStick : Weapon
  @property name = "Pointed Stick"
  @property damage = 6
  @property value = 2
@end

@blueprint CaveMan : Enemy
  @property name = "Angry CaveMan"
  @property weapon = (pickOne PointedStick Spear)
  @property loot = (Fire LoinCloth PirateHat)
@end 

When you try to create a CaveMan, the weapon property will be randomly set to either the PointedStick or Spear blueprints, which you can then pass to the weapon generator algorithm to be built. His loot property will return a list of three blueprints as well. We just defined a new enemy with a randomly selected weapon without touching a line of code!

In this way, we can define a CaveMan as both a series of specific values and as a composition of several other blueprints. In fact, most objects in a videogame (or in real life) can be described based on their attributes and composition. By progressively building up, composing objects of other objects, we can literally define an entire game through blueprints.


  Domain

Blueprints are powerful enough as is, but having to hard code references to other blueprints means that they don't gracefully expand as new blueprints are added. To correct this, Blueprints incorporate [#161 - PGC: Keyword Sets] as a way to refer to groups of blueprints by what is desired of them. In fact, I sort of came up with the whole keyword sets idea while working with blueprints.

The main distinction is that blueprints have named groups of keywords, which I call domains. For instance, you can have a domain called 'type' with the keyword 'weapon'. You would reference objects like this with [type: weapon]. Though domains are largely used as a type of keyword namespace (you could functionally use TYPE-WEAPON just as easily), it does make it a little easier to identify the keywords you are interested in.

Anyway, an example. Say you want to add a new potential weapon to the CaveMan's arsenal? Well, you'd have to manually add it to the CaveMan each time you wanted to add one. If you have a dozen or even hundreds of enemies, this can be a very big pain in the butt, and a problem which can lead to lots of small mistakes that are difficult to catch.

What would be better is to be able to grab all the blueprints that were weapons and also primitive. First, we'd have to manually add these keywords to the weapons, but that's easy enough to do (especially since the keywords will be inherited):  

Blueprint Domains
 @blueprint Weapon : Item
  @domain type = weapon
@end

@blueprint PointedStick : Weapon
  @domain type += primitive
@end 

Through inheritance and a plus-equals, the PointedStick will have the keywords weapon and primitive associated with the domain named type. When the blueprints are added to the big list of blueprints, they will also be collected into sets based on these keywords, which you can then query.  

Using Blueprint Domains
@blueprint CaveMan : Enemy
  @property weapon = (pickOne [type: weapon primitive])
@end 

The command [type: weapon primitive] will return a set of all the blueprints with both of those keywords in the type domain. If you add a new weapon to the big list of blueprints, it'll be added to these domain sets as well, so the sets returned by that command will automatically be up to date whenever they are called. You can add as many weapons as you want to the game, and the CaveMan will automagically randomly select it to use. And if you want a weapon that is not primitive, that's easy too: [type: weapon !primitive]

Automatic Domain Sets are the cornerstone to creating an easily extensible collection of blueprints. Being able to define a new object and have it automatically integrated is a very powerful approach to procedural design. It's not enough to define new blueprints. It has to be easy to integrate them as well.


  Mods

Playing RPGs, especially of the Diablo variety, you'll frequently come across and item that reads like "Gnarled Staff of Whoop Ass +1". It is an item that is modified from its original value, usually through some named modifier. For instance, in this case, the Staff has the modifiers "Gnarled", "of Whoop Ass", and "+1". Each one of these mods changes the values of the original Staff in some small way.

As luck would have it, mods are an easy addition to blueprints. When mastering a blueprint, you can apply any number of mods in a particular order. The mod will return a new, mastered blueprints in which the named properties now have values based on functions provided by the mod.  

Blueprint Mod
@mod OfWhoopAss
  @domain itemSuffix
  @property name = (strcat &source.name " of Whoop Ass")
  @property damage = (* &source.damage 2.3)
  @property value = (+ &source.value 120)
@end 

This mod, affectionally named OfWhoopAss will modify a blueprint. It will change the name from XXX to XXX of Whoop Ass. It will increase the damage 130% and increase the value by 120.

The whole &source.name thing is just a little syntactic sugar for the designer to know that the mod properties refer to the blueprint properties that are being modified. In practice, mods can only reference properties on the original blueprint, and then only the properties with the same name. I figured it was too much trouble to allow properties to reference both their own properties and other, perhaps un-mastered properties on the new blueprint.

Mods also have domains, but rather than having a bunch of named ones, they have only a single domain, MODS. To reference an automatic set of mods, just use something like, [MODS: itemPrefix], which will return a set of all mods with the itemPrefix keyword.


  Factories

The next major feature of Blueprints are a special type of blueprint called a Factory. Similar to mods, a Factory will modify another blueprint. Unlike mods, a Factory is treated as if it were a blueprint. It'll probably be easier if I just show you:  

Blueprint Factory
@factory {MagicalWeapon}
  @substitute (pickOne [type: weapon])
  @modlist ((pickOne [MODS: itemPrefix]) (pickOne [MODS: itemSuffix]))
  @property value = (* &source.value 1.2)
@end 

The {MagicalWeapon} isn't a final blueprint, in and of itself. Instead, when it is mastered, it will instead select a different blueprint to master. This substitute blueprint will be modified by the specified modlist. Any properties included are treated as if they were mod properties, and will be applied to the substitute blueprint after any and all mods.

This particular factory will return a random weapon with a random prefix mod and a random suffix mod, and increase the value by 20%. Again, because of the use of automatic domain sets, you can add new weapons and modifiers and they will automatically be used at no additional cost to you. So, if you want to randomly create a "Gnarled Staff of Whoop Ass", this blueprint factory can do it.

It's through factories that you can do your most impressive composition. For instance, let's say that you want to define a generic weapon drop that is a regular weapon 60% of the time, a magical weapon 30% of the time, and unique 10% of the time... simple. Just define {RandomWeaponDrop} with the property:

substitute = (pickOnChance 60 {AnyBasicWeapon} 30 {AnyMagicWeapon} 10 {AnyUniqueWeapon}).


  Conclusion

If you can define an object based on values and composition (especially PROCEDURAL values and composition), the blueprint system can do it. It has easy expansion built into it, and has several mechanisms that can make a lot of major decisions as data and not code. To give you an idea of how expansive this is, many, if not most, of the roguelike games out there could be done in a single game engine, with their differences being defined entirely as blueprints.

But blueprints aren't all powerful. Though they have their own programming language, they aren't intended to do a lot of the heavy lifting there. You aren't going to write the code to generate a dungeon map inside a blueprint. It is possible, I guess, but it would be going against its specialized purpose. Blueprints are intended to provide the inputs to the algorithms, not the algorithms themselves.

Likewise a blueprint isn't a game object, even though it ultimately describes one. You still need some way to convert the values on a blueprint into a game object usable by the game system. A blueprint is a collection of values. It doesn't know or care how those values will be used. So though you can define an entire game through blueprints, you'll still need code to translate that description into actual game files.

And finally, blueprints don't have a rigid structure. How you define the blueprints, mods, domains, and factories will play a major part in the system's successes. If you aren't careful, an improperly designed system of blueprints could collapse under its own weight. That being said, once the system has been designed, you and your players can add to it easily, and these new additions will just work.

Blueprints solve a small but complicated problem in a broad but simple way. They are the first step in a three step process. The blueprints will define the game, the generators will build the game, and the game engine will play the game. But so powerful are blueprints that with proper generators, they can build the data set for an entire game. Any game.

 

 





Copyright 2007-2014 Sean Howard. All rights reserved.