Warning
Documentation Work In Progress

Re-use

The core element to the generation process is the Procedure. In the same way a Procedure can be instanced in the world (via an Entity), any Procedure can be instanced within any other Procedure to re-use its functionality in more than one location. This is great for avoiding repetition of operator graphs, organising your functionality, and building up libraries of procedural components.

Recursion

Since Procedures are evaluated on demand, Apparance is able to support recursion, both by a Procedure containing an instance of itself, and at any depth within sub-procedures. This is a powerful feature that is the foundation of several techniques like iteration (loops), and subdivision of problems up into smaller parts. Recursion has a bit of a reputation as a difficult concept to wrap your head around (see Recursion), and can take some time to get used to.

Note
Whilst not the most intuitive way to handle iteration/loops it happens to be by far the easiest way to support them within the Apparance procedural evaluator. There is scope to support simpler ways of building loops in the future, but for now not so.

Generally, as with any recursive function, a Procedure invokes itself with slightly different parameters again and again, until some exit condition is met (e.g. a count reaches zero) when the stack of procedures all return their values back up from their outputs until the original instance is complete. If the exit condition is never met, or there are just too many, then it will run out of stack space and fail the generation, so you need to be careful with recursive designs.

See Subdivision & Iteration algorithm sections below for more.

Structures

Pass around lists as structures to simplify development. Lists in Apparance are dynamically typed so you can use one to represent structured data. By Appending your structure members together you produce a single ‘value’ that is much easier to pass around, can be switched, and modified without having to perform any re-wiring. Structure members are accessed by index where they are needed.

It is often nice to create ‘make’ and ‘break’ procedures to pack and unpack the structure members into list form. If it is a large structure, it may be prudent to provide multiple ‘break’ operators, extracting different sets of members depending on use case.

Since lists are order dependent, changes to a structure layout means later indexing of members may require updating. Using the make/break pattern allows you to confine this hard coded indexing to a few procedures instead of everywhere they are used. Having a single ‘wire’ to thread through your procedures can make for much less boring connection and routing maintenance throughout your procedural systems. Structures can be accumulated into lists themselves if you need arrays of structured data, e.g. list of rooms, again, simplifying the passing of rich data constructs around your procedures.

Multi-pass

Parallel paths can feed back, one to the next.

Procedural evaluation is performed on-demand, from ‘right to left’, i.e. evaluation only happens as a result of an output that is needed by an input that is being evaluated. This starts at the outputs of the top-level procedure (associated with an Entity in the world) and propagates back through the procedural graph as needed.

A consequence of this is that any given procedure may or may not be fully evaluated, and certainly isn’t likely to be evaluated in one go. This is especially true of procedures with more than one output, as the internal sub-graph each output depends on is only evaluated as each output is needed. This can occur much later in the overall evaluation for one output than another. By induction, this implies that large swathes of graph can be evaluated through many procedures before nodes along-side, in the same procedures, get evaluated.

The practical upshot of this is that you can build deep hierarchical and recursive systems that operate in multiple passes without having to think about it. Subsequent evaluation passes can even have inputs fed from outputs of earlier passes. An example of this is multi-pass layout logic where all elements need to be evaluated and sizes calculated before they can actually be positioned relative to each other. The logic for both can be created side-by-side, but evaluated in two passes, much simplifying the layout of the logic required.

Delegation

Delegate content to sub-procedures by placing them as Entities. In some situations, it is useful to delegate the generation and instancing of content to other procedural Entity instances in a scene. For example:

  • Large amounts of generated content – by splitting up a large structure into parts that handle their own generation and placement of content, they can be managed without overloading the generation systems.
  • Granular game control – Sections of the scene may need independent control by the game systems, and even live parameter control, such as interactive elements or objects that need to live update.
  • Level of detail – by splitting up a scene into distinct parts, their detail level can be switched independently, such as when the player moves closer or further away. (see Tiling, On Demand, and Detail Tiers below) Each engine has its own Entity wrapper (Prefabs in Unity, Blueprints in Unreal), and these are both placeable in the same way you would a mesh asset. In addition, you can pass placement parameters from the placing procedure that are available to the scripting system in each engine.

This principal can be applied recursively, with each placing further procedural objects. It is also quite useful to mix procedural and authored or scripted objects into this hierarchy of scene creation. Each tier making the best use of the different types and techniques as appropriate.

Tiling

Spread load by subdividing world into a grid (or lattice).

For larger coverages, it may be necessary to break a space up into pieces, each handling the generation of content needed for that area of the world. This spreads the work out into smaller chunks with several advantages:

  • Reduced memory footprint - Smaller synthesis buffer size can be used
  • Faster generation - can take advantage of multi-threaded synthesis.
  • Churn reduction – if areas of the world need updating then only those overlapping parts will rebuild.

Typically, this would be implemented in project-specific script, driving multiple instances of procedural Entities.

Visualisation

Always be visualising, particularly whilst interactively iterating.

As an interactive editing experience, development of procedural systems benefits from adding visualisations of the various elements being composed and created. They can show intermediate stages, e.g. bounds, or positions, and provide live feedback as you work, iterate, and tune your systems.

Some common useful examples are:

  • Frame – Visualise the exact edge extents of a Frame value. Perhaps with colour coded axis edges to help assess the orientation of the frame.
  • Box – Sometimes you just need to see the volume occupied by a number of frames, so a solid coloured cuboid may be preferable.
  • Value – Displaying a numerical or text value in-world is a great ‘printf style’ debugging technique to see what is going on in multiple parts or multiple instances of a procedure as you adjust input parameters.
  • Point – Show a point in space, perhaps with axis aligned lines to clearly illustrate the position.
  • Line – Show connection between two points.
  • Custom – Anything goes really, and to any level of complexity. The same building tools are available as for the actual content you are making. [show digital display in-world gen for dimension display] Simple framed cube operator geometry generation can be used to compose graphical elements, usually with a simple vertex colour material. For in-world text it usually requires a parameterised engine specific text display component/prefab/asset hooked up as a Resource that Apparance can place in the scene.

Magic Numbers

While building you will always need to include hard coded values as inputs to operators and sub-procedures, this is the nature of any designed system. These values (colloquially known as ‘magic numbers’, presumably because they are ‘plucked out of thin air’) can be as simple as 0.5 to split something in half, or as fiddly as a tuning value to get something to align properly, to a probability value controlling how often something occurs throughout the layout.

An advantage of a live-edit, fully interactive system is that these values, buried in your design can easily be played with and adjusted ‘by eye’ if needed. Exploring the range of values and the ‘result space’ that they produce exposes a designer to may more possibilities and potential avenues than a compiled or other non-interactive system could.

Sometimes these numbers become a crucial part of the design and you find you need to bring them up out of the procedure they are hidden within to be driven from some higher-level logic. Perhaps even all the way ‘to the top’ and have an external Entity parameter controlling or driving them.

Decision Points

A huge part of implementing procedural systems is working out the key requirements of your design and how they map into decisions and calculations. This “codification of design” requires deep thought about the fundamentals of what is important to see (output), what control is needed (input), and where variety needs to be introduced. When you are placing assets as part of your design, these decisions extend to how they are built, with modularity in mind and how well they integrate together in different arrangements.

When starting on a procedural implementation it can be daunting as it’s often not clear how the problem breaks down into parts that can be modularised.

Seed Hierarchy

Consistency and variation, at differing depths

On Demand

Populating/showing and depopulating/hiding a grid of tiles as needed (e.g. by player proximity).

Detail Tiers

Manage levels of procedural detail as multiple layers of differing size and/or complexity. Additionally, tiers can be linked to provide synchronised switching in/out of different details.

Palletisation

Resource switching, procedural descriptors, banks of resources