Outdated. MapMagic documentation was moved to GitLab


Table of contents:

Quick start guide

Settings

Map Generators

Constant

Noise

Voronoi

Curve

Raw Input

Blend

Blur

Slope

Cavity

Terrace

Erosion

Object Generators

Scatter

Adjust

Split

Subtract

Combine

Propagate

Stamp

Forest

Slide

Floor

Output Generators

Layers

Height Output

Texture Output

Objects Output

Trees Output

Grass Output

Portals

Creating a custom generator


Welcome to MapMagic - a platform for terrain creation and automatic game object placement.

This tool uses a node-based visual scripting interface to determine creation logic. Each node represents a separate algorithm called a “generator”. Examples of generators include: noise, voronoi, blend, curve, erosion, object scatter, forest, etc. All nodes are presented on a  field called the “graph”.

Each generator node has input and/or output connectors. A generator takes some map or object information as inputs, processes it and returns the result as outputs. Normally all of the nodes in the graph are interconnected in a desired sequence. Currently there are two types of inputs/outputs:

The chains of generators should end in one of the “Output Generators”. There can only be one of each type of output generator per graph. There are 5 types of output generators:

MapMagic can work with multiple terrains (that’s what gives MapMagic the ability to create infinite worlds). Terrains can be divided into two types:

Quick start guide

The first thing you need to do is create a MapMagic object, which contains the MapMagic script, the initial terrain, and allows the editor interface to be opened. To create a MapMagic object, click the GameObject menu -> 3D Object -> MapMagic. A new game object named “MapMagic” will appear in the hierarchy menu. It already has the initial pinned terrain and three example generators, so the terrain is not flat. Clicking this object will show basic settings in the Inspector.

The “Pin Terrain” button allows pinned terrains to be selected in the Scene view. Left-clicking on an empty area will pin a terrain, and it will appear in a moment, after it’s been generated. Left-clicking on an already pinned terrain will unpin it. To exit pinning mode click on the “Pin Terrain” button once more.

Any change made to pinned terrains will be re-written on the next generate. To prevent this,  use the terrain lock by pressing the “Lock Terrain” button and selecting a pinned terrain that should be locked. When using a locked terrain, try not to modify a height greatly, otherwise you can get noticeable borders between your unchanged, locked terrains and their neighbours that have a new procedural heightmap.

Within the MapMagic object you created, the “Show Editor” button will open up the editor window. Here you can see three initial generators: Noise, Curve and Height (output generator), all connected together. The Editor window can be scrolled using the middle mouse button. To zoom in or out, use the scroll wheel. To reposition a generator, left-click and drag anywhere on a generator window.

Changing any of the generator’s parameters will force the generator and it’s dependent generators to re-calculate. Once the generatorsresults are ready the terrain will be changed.

The Generate button in the MapMagic window will force all generators in the Editor to be recalculated from scratch.

Note that the Generate button icon changes its appearance depending on the current generation state:

Next to the generate button is the Global Seed field. This number is used to initialize pseudo-random generators. Note that pseudo-random generators use their own Seed parameter, so the final result depends both on the Global Seed and Generator Seed parameter.

A new generator can be created by right-clicking on an empty field and then selecting Create -> Generator Type -> Generator from the popup menu. Note that all of the generators are grouped by type:

A generator can be removed by right-clicking on the generator and selecting Remove from popup menu.

A newly created generator can be connected to a currently existing generator system. Just drag and drop it’s input or output icons to the other generator’s input or output. Note that the generator’s output can have several connections, while an input accepts only one connection. Some generator inputs are mandatory, that means that they always require some input and generating will fail if they are not connected. Empty mandatory inputs are highlighted with a red asterisk.

Any output from any map generator can be viewed using the preview feature. Right-click on the map output and select Preview -> On Terrain. This will color the terrain in a red to green range of colors according to the preview map values. High map values are coloured green, while low values are red.

To exit preview mode, right-click anywhere in the Editor and select Preview -> Clear.

Moreover, a map can be previewed in a separate window. Clicking Preview -> In Window will open up a window with a grayscale map displayed. This map can be scrolled and zoomed the way the main graph can.

  Preview on terrain

Preview Window


Settings

General and Terrain settings can be found in the Inspector tab when a Map Magic object is selected.

General settings set up Map Magic’s parameters:

Note that pinned terrains are never destroyed in a playmode. This makes it possible to modify them manually, for example, to include handmade villages among automatic wilderness terrain.

Keep in mind that using high margins values results in longer apply time (i.e. the time when Unity freezes after generating) and can cause floating objects bugs (when terrain is lowered under the objects to perform the weld). Keep these values as low as possible and use the Safe Borders parameter to negate the terrain difference.

Terrain Settings and Trees, Details and Grass Settings are equivalent to the settings used for each terrain in the standard terrain’s Settings tab. The only difference is that they are applied to all of the MapMagic terrains. See the Unity docs for more information.


Map Generators

Figuratively speaking, a map is a one-channel, square raster image (or 2-dimensional array of floats if you are more of a programmer than an artist). All of the Map Generators are united by one principle: their input and output connections type is Map.

Most generators use a 0 - 1 value interval. In Height Output, for instance, 1 is the maximum height defined in settings; for masks 0 means full transparency, 1 - fully opaque.

Many of the Map generators have equivalent parameters. For convenience they are listed here and will not be repeated for each generator. Some generators can have all of these parameters, others can have only some of them and the rest have none at all.

Inputs:

Outputs:

Properties:

 Noise Amount

(when applied over Voronoi): 0

0.1

0.5

 Blur Generator

Using Mask

Safe Borders: 50

Constant

Creates a map filled with a given value. This is the most simple generator, used to create transparent masks or flat heightmaps.

Output: the planar at a certain level.

Properties:

Noise

The Noise Generator is one of the most basic generators in MapMagic. It generates a fractal Perlin noise map that is widely used as a base for various map creation algorithms.

Inputs:

Outputs:

Properties:

Size: 20

Amount: 0.1

100

0.5

400

1

 Detail: 0

0.4

0.6

Voronoi

Uses a Voronoi pattern to create maps. Splits the map into cells, generates a random point for each cell, and fills the map using evaluated distances to the closest point and the second closest point. A Voronoi map looks like a mosaiс composed of irregular convex polygons.

Inputs:

Outputs:

Properties:

 Flat

Closest

Second Closest

 Cellular

Organic

 Cell Count: 4

8

16

 Uniformity: 0

0.5

1

Curve

Adjusts map values using a user-defined curve. It works similar to the curves adjustment in graphics editors like Photoshop. The horizontal axis of the curve chart is the input value, vertical axis is the output value.

Map Magic uses Unity’s animation curve interface for curve editing, so working with the Curve Generator is done the same way. Keys and tangents can be dragged with the left mouse button, the right mouse button is used to create new keys and access key properties.

This generator provides plenty of opportunities for map editing: it can be used to invert maps, adjust minimum or maximum values, clamp map values or even make ladder slopes (although a terrace generator could be more handy for this).

For example, to give the map more contrast just move left and right keys horizontally closer to the central line. To invert the map move the left key to the top and the right key to the bottom so that the diagonal line is inverted (note that key tangents should be set to auto to achieve a straight line).

Input(mandatory) - a source map

Output - a processed map

Properties:

Raw Input

Raw Input generator was designed to import .raw heightmaps as a base for MapMagic terrains. In order to use it the .raw file should be properly prepared. It should be:

It does not matter where the heightmap is saved, whether it is the Assets folder or not, it will be converted to matrix format on import and saved within the scene. A saved .raw file can be added with the help of the ‘Browse’ button. Please note that MapMagic does not keep track of heightmap changes, so the ‘Refresh’ button should be pressed after any change.

Output - the RAW image applied as a MapMagic map.

Properties:

 Scale: 1

0.2

2

 Tile off

Tile on

Blend

Blends two maps together using the specified blend algorithm. This generator acts quite similar to the layer blending mode in Photoshop or other graphics editor. Think of the “Base” map input as an underlying layer, and the “Blend” map input is a blending layer.

Some generators like noise or voronoi have their own blend inputs: additive (Input) and multiplicative (Mask). In most cases it is enough to use them, but in some cases you might need the other blend type or need to blend generator chains or generators that do not have blend inputs. Blend generator was created to solve that.

Inputs:

Outputs:

Properties:

Here are the algorithms the blend generator offers:

if (a > 0.5f) return 1 - 2*(1-a)*(1-b);

else return 2*a*b;

 Base Map: Voronoi

Blend Map: Noise

Mix with 0.5 opacity

 Add

Subtract

Multiply

 Difference

Min

Max

Blur

Smooths the input map.

Inputs:

Outputs:

Properties:

 Iterations: 0

1

10

 Loss: 1 (10 iterations)

20

60

Slope

Generates the height difference map. For each map pixel it calculates the average height delta to four nearby pixels. Could be useful for separating horizontal and vertical surfaces (to fill them with grass and cliff respectively).

Input - the default map to be processed by the generator.

Output - stores the generator’s processing result.

Properties:

 Steepness: 5-90           

35-90

80-90

 Range: 1           

5

15

Cavity

Generates the maps of concavity and convexity. All of the bulges and cambers are regarded as convex and the hollows are regarded as concave. Note that output maps do not intersect: one pixel can not be convex and concave at the same time.

 Heights

Convex

Concave

Inputs:

Outputs:

Properties:

 Spread: 1

2.5

5

Terrace

Produces step-like land forms on the slopes of hills. Used together with a Noise (as terrace’s input) and Erosion (originates from terrace's output) Generator, it can produce fine landscapes. Don’t underestimate the Terrace Generator - even if it seems that pure terrace output is not good enough looking, together with erosion it can make a terrain much more realistic and diverse.

 Initial terrain           

Terrace generator

Terrace+Erosion

Inputs:

Outputs:

Properties:

 Treads: 10        

20

30

 Uniformity: 0

0.5

1

 Steepness: 0

0.5

1

Erosion

Reproduces water’s flowing action on the terrain’s surface. Flows erode cliff formation, transport eroded stratum to another location and settle it as a sediment. Note that all of the flows, erosion and sediment calculations are iterative and very resource intensive, this makes the Erosion Generator the slowest generator of  all the MapMagic generators. It usually takes about 3 seconds to generate erosion with the default parameters on a modern computer - compared to any other generator which are nearly realtime. But the result is usually worth it.

Inputs:

Outputs:

Properties:

 Iterations: 1

5

15

 Durability: 0

0.9

0.98

 Erosion: 0.5

1

2

 Sediment: 0

1

3

 Fluidity: 1

5

15

 Ruffle: 0

0.5

1

Shore

Creates a shore line with a beach and a ridge.

Inputs:

Properties:

 Water Level: 100

50

10

 Beach Size: 10

20

40

 Ridge Step: 0

10

20


Object Generators

Object Generators do all the calculations related to objects. They have at least one Object Input or Output(they can also have Map Inputs or Outputs).

Even though “Object” typically refers to “Game Object”, it does not contain a mesh or prefab - it is a special class, equivalent to the “Transform” component. It has the following exposed properties: position, height, size(uniform scale) and rotation(along the Y axis). The real objects are assigned to these object coordinate records in Object Output Generators.

Scatter

Scatters objects in the terrain area. Please note that scattered objects do not lay on the terrain surface but at the zero level - use the Floor Generator to align them according to the terrain height.

Probability input determines the chance of the object to appear on a certain map pixel (for the value of 0 the object will be never appear, for 1 it will always be created according to the generator’s settings).

Note that scattered objects are placed at the zero level. Use Floor Generator to set them to the ground.

Inputs:

Outputs:

Properties:

Count: 1  (terrain size:1000)

Count: 100

Count: 1000

 Uniformity: 0

0.1

1

Adjust

Modifies the height, rotation and size parameters of the objects. Note that object rotation and scale parameters are not taken into account by Object Output unless it has “Rotate” and “Scale” checkboxes turned on.

Inputs:

Outputs:

Properties:

 Initial Scene

Relative Size Adjustment (1.5x)

Absolute Size Adjustment (1.5x)

 No height adjustment

Height: -5, 0

Height: -5, -5

 No rotation adjustment

Rotation: 0-10

Rotation: 0-360

Split

Creates several new outputs and fills them with input’s objects using certain conditions. Split Generator has a layered structure. Each layer has its own output as well as minimum and maximum range parameters. For each of the input objects, Split Generator finds a layer that matches the object properties and writes this object to the layer output.

 

Two layers with the same conditions

Using the top one (bushes)

Bushes height starts from 0.2

For every object below Stones layer is used

Bushes height starts from 0.3

Input - the default object’s hash to be processed by the generator.

Each of the layers has an Output, which stores only those objects from an initial hash that passed the layer conditions filter.

Match Type: Note that the input object will be passed to only one layer output. If the object’s properties matches several layers’ conditions it will use the top layer if the “Layered” Match Type is selected or it will select a layer at random in the case of “Random” Match Type.

 

Stones Height: 0 - 0.25

Bushes Height: 0.15 - 1

Bushes atop

Stones atop

Random match type

Note that in range 0.15 - 0.25 bushes and stones are mixed

Split Generator uses the layered system, similar to the system used in Output Generators. A new layer can be added(with the “Add” button), the selected layer can be removed(with the “Remove” button). Layers order can be organized(with the “Up” and “Down” buttons).

The selected layer can be renamed. Just type the new name in the field next to its output icon.

Each of the layers has the following properties:

 Stones Scale Condition: 1- 4

2 - 4

3 - 4

 Bushes Chance: 1

 Stones Chance: 1

0.1

1

10

1

Subtract

Returns the modified Minuend Input so that all objects positioned at a certain distance from Subtrahend Input objects are removed.

 Subtracting stones from pines removes all pine objects within range of 5

Inputs:

Outputs:

Properties:

Combine

Object sum: returns the combined objects from the generator’s inputs. Output will have all of the objects from all of the inputs connected. The number of inputs can be set with the Inputs Count parameter.

Two or more Inputs- the hashes with the objects that will be combined in output

Output - a hash that has all the objects from all the input hashes combined.

Propagate

For each of the input objects Propagate Generator will create a certain number of clones and will offset the created clones from the object’s position by a certain distance in a random direction on a plane.

 Original object

Propagate with 5 clones

Propagate + Size Adjust + Combine with original

Note that this generator will not return the input objects themselves, it will just output their offset clones.

Two or more Inputs- the hashes with the objects that will be combined in output

Output - a hash that has all the objects from all the input hashes combined.

Properties:

  Growth: 2 - 2

 6 - 6

 1 - 8

 Distance: 1 - 1

 4 - 4

 1 - 4

Stamp

Stamp Generator draws circles on the map map in places where objects are located. It can be used both for painting maps under objects and for levelling areas where the are objects placed.

If the Canvas Input is available it will return the modified canvas map, otherwise it will create a new map and apply the stamps to it.

Inputs:

Outputs:

Properties:

  Radius: 1

 2

 4

 Noise: 0

 0.1

 0.5

 Canvas: enabled

 Maximum Height: off

 Canvas: disabled

 Maximum Height: off

 Canvas: disabled

 Maximum Height: on

Thus, the Stamp Generator could be used for two distinct purposes:

Forest

Emulates a natural forest growth: seed dispersal, the growth of trees, the adequacy of lighting, and soil quality. Forest Generator can generate several forests of one tree type. As forests grow and expand they will be joint together into a single whole. Each forest starts with a single tree - a seedling.

Along with Erosion Generator this Generator takes some time to compute.

Keep in mind that a forest is a system with negative feedback. Every little change in the beginning causes an absolutely different result in the end. So generating a forest with slightly different inputs can cause a very different final picture. For example, changing the seedlings position can cause the entire forest to die, while other forest arrays would appear in different places.

Inputs:

Outputs:

Properties:

  Years: 50

 100

 150

 Density: 10

 3

  1

  Seed Dist: 5

  10

  15

Slide

Pulls objects downhill. Returns objects that were moved down according to the terrain normals.

Inputs:

Outputs:

Properties:

 Iterations: 0

10

50

Floor

When objects were just scattered or adjusted(using absolute values) they have no information about terrain height at their position. The Floor generator aligns objects according to the Stratum Input map.

 Scattered objects are placed under terrain

Objects are aligned to the terrain level

Inputs:

Outputs:


Output Generators

Brings all the map and object inputs to life: converts maps to terrain heightmap, splatmap or detailmap and places real objects on terrain. All of the generator chains should end up in one (or more) Output Generators - otherwise they will not be generated at all.

If the Graph does not have an output of a certain type it will clear corresponding terrain information. For example, a graph with no Texture Output will fill terrain with gray color, removing all the terrain textures.

Layers

A MapMagic Graph should have only one Output Generator of a certain type: only one Height Output, only one Textures Output Generator, only one Objects Output Generator, etc. To output several textures or several objects using the single Output Generator a layer structure is used. Each layer represents the output object type: the Texture Output has the layers number equal to number of Splat (Alphamap) Prototypes and each layer contains a Splat Prototype properties (like texture and bump map); each of the Objects Output layers contains it’s own unique object prefab to instantiate its instances on terrain; and so on.

All of the layers[1] have their own input connection, so each of the layers could be considered a separate Output Generator, grouped together with outputs of the same type for technical reasons.


Height Output

Applies the Input map as a terrain height.

This is the only Output Generator that has no layers, because obviously there are no different objects to apply.

A scale parameter resizes the final heightmap before apply: when set to 0.5 the terrain heightmap resolution will be the half of the map resolution that is set in Settings. When the parameter is equal to 2 it will upscale the map twice with bilinear filtering.

 Scale: 1

 0.5

 2

Texture Output

Applies the terrain texturing information, i.e. “colors” the terrain with textures.

Texture Output final result depends on the layer order. Layers are blended together similarly to the layer system in Photoshop and other graphics editors:  Each of the upper layers overlaps the lower ones. A mathematical algorithm for each layer generator multiplies all underlying layer values with the inverse value (1-value) of the current layer.

The Background layer does not require an input since it is regarded as completely filled (a constant of 1).

Texture Output layers can have output connections. These connections store a processed and blended layer mask. The sum of the output map values is always equal to 1. These outputs could be used for further map processing (for example, for planting grass using the grass map so it really gets on the terrain).

                        

All the layer properties are similar to the standard terrain texture parameters (splat prototypes).

Note that due to better terrain welding splat prototypes the size parameter should be equal to a power of 2.

Objects Output

Applies objects to terrain. For each layer it instantiates the required number of objects and places them in the position and height prescribed by layer’s input. Object size and rotation are used too, if the object’s layer “Rotate” and “Scale” parameters are checked.

Instantiated objects are grouped as terrain’s child transforms.

Objects Output uses the object pool to instantiate objects faster: when the terrain gets out of generate distance its objects are not destroyed, but used to generate new terrain that appears within generate range. So be careful changing objects that are placed on terrain: a changed object will appear here and there as the player walks across the land. Use a separate prefab to make such unique objects.

In editor mode new objects will be created maintaining the prefab connection, in playmode objects are instantiating using prefab clones.

Each layer has these properties:

Trees Output

Tree Output works similarly to Objects Output: for each layer it instantiates several trees of a given type at the positions prescribed by layer’s input. The only difference is that trees in this case are not the Transforms but the terrain Tree Instances.

Note that prefab trees that do not have LODs can not be rotated or scaled. It’s not a MapMagic bug, it is the way Unity works.

If some layer’s tree prefab is not assigned, Tree Output will report an error in the console but it will not stop or fail generating process. Unassigned trees just will not be displayed.

Each layer has the properties:

Grass Output

Paints grass on terrain using layer’s maps as a mask. This generator can place detail meshes as well.

Obscure Layers toggle makes the grass behave the same way as Texture Output layers. If turned on for each new layer the grass quantity is blended using the layer opacity. If turned off all grass layers are set up independently.

Each layer has the following properties:

All of the grass parameters are similar to the standard terrain grass and detail properties:

Moreover, there is a patch res parameter that applies to all of the layers. It specifies the size in pixels of each individually rendered grass patch. A larger number reduces draw calls, but might increase triangle count since detail patches are culled on a per batch basis. A recommended value is 16. If you use a very large detail object distance and your grass is very sparse, it makes sense to increase the value.


Portals

Portals are the special generators used to organize the graph in a more convenient way. Sometimes it is necessary to connect some inputs and outputs on opposite sides of the graph. It is not easy to connect generators using the furthest zoom and makes it hard to read the graph.

Portals are the mean to solve the problem. One portal (an input form) has an Input connection and the other portal (an output form) has an Output connection. On generate the input portal just passes the object to the output one, connecting the two generators together.

There is no functional difference between a portal connection and the standard one.

Think of portal connection as a standard connection with no line displayed.


To switch between Input Portal and Output portal use the In/Out button in the middle.

To change the portal In/Out type (Map or Object) use the Map/Obj button to the left.

One more thing that’s important to know about portals in order to use them is the portal article. Only those portals that are linked share the same article - in the example above, the article is called “Height”. So to link the portals you’ve got to type portal article in an Input Portal and then select a typed name in an Output Portal. An article could be any name you like and which describes portal map the best in your opinion.

Portals are not linked because of an article mismatch

Portals are linked because they share the same article


In a complex graph you would like to use portals with a different articles. Keep in mind that only those portals that share the same name are connected.

Portals are cross-connected using the article principle

Multiple portal connections are also possible. To create them, use one Input Portal and multiple Output Portals. More complex combinations are also possible.

Possible portals connections

One might ask why do these portals are if they could be replaced with direct connections. It is even more confusing when using a simple graph when learning Map Magic. But I’d like to show a portal connection from a rather complex graph:

Demo scene portal system

So portals should not be used with every connection and not even on every fourth connection but they could be used from time-to-time to make a graph a bit more pretty and readable. Most likely even a complex graph will not have more than a dozen different portal articles.

Creating a custom generator

MapMagic can be extended with custom generators. All generators are made as modules, so a custom generator can be created without changing MapMagic’s code and without the need to re-write it on every MapMagic upgrade.

A generator template can be found in the SampleGenerator.cs file. You can copy this file and store it anywhere in your project. But for a better understanding an example of gradual generator creating will be given.

First of all let’s start with an empty class. All the generators derive from a base Generator class. Custom generators should override two abstract functions: Generate and OnGUI.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI () { }

}

This class will have two attributes: one that makes the class serializable and another one that adds the current generator to the MapMagic right-click context menu, for example this one could be created with Create > MyGenerators > MyGenerator. The last attribute also has a “disengageable” parameter that allows the generator to be turned off by clicking the eye icon in the upper left corner.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI () { }

}

Now let’s add the generator’s inputs and outputs. To work properly, all the generator’s inputs and outputs should:

So let’s add the Input and Output variables. Inputs and outputs constructors generally take two arguments: the first is the name of the input/output as it is displayed in the node and the second is its type in the form of InoutType enum (InoutType.Map or InoutType.Objects). In addition, Input can have an optional “mandatory” property: if set to true it will display a red mark for unconnected input.

Warning: remove MyGenerator from a graph before applying the following code and create it again when scripts are compiled. Otherwise it will have a null input/output.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output vars

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI () { }

}

On generating MapMagic iterates a generator’s inputs and outputs to process each of them. Enumerables are used to do this, just adding the variables will not make them accessible to MapMagic.

Each input should be included with “yield return” keyword in an overridden Inputs enumerable, while each output should be included in an overridden outputs enumerable.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{  

    //input and output vars

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI () { }

}

If the generator has, let’s say, three inputs and two outputs it should start with something like this:

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{  

    public Input firstIn = new Input("First", InoutType.Map);

    public Input secondIn = new Input("Second", InoutType.Map);

    public Input thirdIn = new Input("Third", InoutType.Map);

    public Output firstOut = new Output("First", InoutType.Map);

    public Output secondOut = new Output("Second", InoutType.Map);

    public override IEnumerable<Input> Inputs() 

        { yield return firstIn; yield return secondIn; yield return thirdIn; }

    public override IEnumerable<Output> Outputs() 

        { yield return firstOut; yield return secondOut; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI () { }

}

Now we have a correct and properly set-up generator. It has only two drawbacks: it does nothing and it has no displayed parameters in the GUI - not even input and output connection nodes.

We will fix the last one by exposing the input and output in the OnGUI function. This function is called when the generator node is rendered on the graph and uses a Layout wrapper, instead of UnityEditor.EditorGUILayout, which allows element scrolling, zooming and dragging.

Right now we will not dig into Layout, we will just add a new line with a 20-pixel height(with zoom=100%) layout.Par function and then draw the input and output (a circle with an icon and a label with input/output name) at that line by calling their own OnGUI functions.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{  

    //input and output vars

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI ()

    {

        layout.Par(20); 

        input.DrawIcon(layout); 

        output.DrawIcon(layout);

    }

}

Now you can see that MyGenerator has two map connections, and the input connection is displayed as mandatory.

Our 3 inputs and 2 outputs generator will have 3 lines, one input and one (or none) output per line:

    public override void OnGUI ()

    {

        layout.Par(20); firstIn.DrawIcon(layout); firstOut.DrawIcon(layout);

        layout.Par(20); secondIn.DrawIcon(layout); secondOut.DrawIcon(layout);

        layout.Par(20); thirdIn.DrawIcon(layout);

    }

Now let’s add a generate function. Let’s say it will do a simple function - it will invert an input map’s value(1-value). The same effect could be achieved with curves but sometimes it is handy to use special generator.

Here is the body of the Generate function. This function overrides the base Generator’s function. It takes a chunk as an argument and it is void - does not return anything. Later you will see that it loads maps and object hashes from the chunk and saves the processed result back to the chunk.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk) { }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

    }

}

Now we will load inputs using the input.GetObject function. This function returns an object type, so we have to cast it to the map’s type, which is internally called “Matrix”. This matrix will be called src (source).

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk)

    {

       Matrix src = (Matrix)input.GetObject(chunk);

    }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

    }

}

Now let’s add three guard clauses to the Generate function. They will exit the function before executing its body to prevent errors or to save computing time.

The first one occurs in the case where an input gets a null object - if it is not connected or the previous generator returned null. In this case this generator should not set any objects.

By the way, you can check if the generate thread is still running by checking the chunk.stop boolean value. If it is set to true all generating processes should end as soon as possible. It’s a good idea to check if the chunk has not stopped regularly in a function body, because due to the multithreaded approach it can change its value at any time, and often it does - during the generate function execution from the main thread.

The third way the function can exit early is if the generator is not enabled. But in this case it should pass the input object to the output. So it uses output.SetObject function and returns.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk)

    {

        Matrix src = (Matrix)input.GetObject(chunk);

        if (src==null || chunk.stop) return;

            if (!enabled) { output.SetObject(chunk, src); return; }

    }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

    }

}

We’re now at the most interesting part. We will do some simple calculations to invert a map.

But before doing that let’s make sure that it will not modify our original “src” matrix. This matrix could be used by other generators and, more importantly, it will be used by this generator each time it’s parameter changes if “save intermediate results” is checked in the MapMagic settings. So do not forget this simple rule: do not change the input maps or object hashes, use new matrices or hashes to write values.

So we will create a new matrix to write our changed src values and call it dst (destination). This matrix should have the same size and the same coordinates as the source matrix. The size and offset information is kept in a matrix.rect struct. We will use src.rect in a new matrix constructor.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk)

    {

        Matrix src = (Matrix)input.GetObject(chunk);

        if (src==null || chunk.stop) return;

            if (!enabled) { output.SetObject(chunk, src); return; }

        Matrix dst = new Matrix(src.rect);

    }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

    }

}

In order to invert the matrix we have to iterate all the src pixels and set them to dst = 1 - src. It’s very simple to do this using matrices. Matrix is a wrapper of a standard single-dimensional array of floats, with an interface that of a 2D array but makes it a bit faster. And it takes matrix offset into account - so that matrix coordinates start not from zero, but from the offset values.

For example, if we want to get some pixel at a coordinates, let’s say [768,256] we will call matrix[768,256]. If we want to iterate a matrix that has an offset [1024,0] and a size [512,512] we should use the following code:

for (int x=1024; x<1024+512; x++)

   for (int y=0; y<0+512; y++)

      float val = matrix[x,y];

By the way, since the matrix is used for 3D objects like terrain, and it is applied horizontally, it’s better to use the Z-axis name instead of Y(which usually indicates height).

So, the simple invert generator code should look like this:

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk)

    {

        Matrix src = (Matrix)input.GetObject(chunk);

        if (src==null || chunk.stop) return;

            if (!enabled) { output.SetObject(chunk, src); return; }

        Matrix dst = new Matrix(src.rect);

        for (int x=src.rect.offset.x; x<src.rect.offset.x+src.rect.size.x; x++)

           for (int z=src.rect.offset.z; z<src.rect.offset.z+src.rect.size.z; z++)

               dst[x,z] = 1 - src[x,z];

    }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

    }

}

We can speed up the code a bit to avoid summing of rect with offset using the Coord struct. Think of Coord like a Vector2, except that it uses ints instead of floats.

Coord min = src.rect.Min; Coord max = src.rect.Max;

for (int x=min.x; x<max.x; x++)

   for (int z=min.z; z<max.z; z++)

           dst[x,z] = 1 - src[x,z];

And the final step to make a generator work - it should store the generated result using the output.SetObject function. This function takes two arguments: the first one is a chunk to store an object and the second one is the result object itself.

But before storing an object check that the thread is not stopped. Just in case.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public override void Generate (MapMagic.MapMagic.Chunk chunk)

    {

        Matrix src = (Matrix)input.GetObject(chunk);

        if (src==null || chunk.stop) return;

            if (!enabled) { output.SetObject(chunk, src); return; }

        Matrix dst = new Matrix(src.rect);

        Coord min = src.rect.Min; Coord max = src.rect.Max;

        for (int x=min.x; x<max.x; x++)

           for (int z=min.z; z<max.z; z++)

                  dst[x,z] = 1 - src[x,z];

             if (chunk.stop) return;

            output.SetObject(chunk, dst);

    }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

    }

}

Now if we create this generator and connect it to the organic Voronoi Generator you will see that it produces bubbles instead of caverns - so it works the way we expect.

It has one drawback though - it creates a map somewhere at the top of the terrain which makes it hard to edit or blend the map with the other ones. This happens because we subtracted src values from 1 - the top terrain level. So if it was replaced with a custom value we will get a more adequate result. And note that we should monitor the dst value to prevent it from going below zero.

So let’s add a “level” variable and expose it on the GUI using the layout.SmartField. A smart field is a common MapMagic field with a text input and a draggable icon to the right. The SmartField function takes two required arguments: the ref of a value we want to expose and the text that should be displayed in front of it. And we will use two additional parameters: minimum value (min:0) and maximum value (max:1) because, obviously, the level could not be less than zero or higher than 1.

By default there is no need to create a new line using layout.Par() because this function is already called in a smart field unless the newLine parameter is set to false.

[System.Serializable]

[GeneratorMenu (menu="MyGenerators", name ="MyGenerator", disengageable = true)]

public class MyCustomGenerator : Generator

{

    //input and output properties

    public Input input = new Input("Input", InoutType.Map, mandatory:true);

    public Output output = new Output("Output", InoutType.Map);

    //including in enumerator

    public override IEnumerable<Input> Inputs() { yield return input; }

    public override IEnumerable<Output> Outputs() { yield return output; }

    public float level = 1;

    public override void Generate (MapMagic.MapMagic.Chunk chunk)

    {

        Matrix src = (Matrix)input.GetObject(chunk);

        if (src==null || chunk.stop) return;

            if (!enabled) { output.SetObject(chunk, src); return; }

        Matrix dst = new Matrix(src.rect);

        Coord min = src.rect.Min; Coord max = src.rect.Max;

        for (int x=min.x; x<max.x; x++)

           for (int z=min.z; z<max.z; z++)

              float val = level - src[x,z];

                   dst[x,z] = val>0? val : 0;

             if (chunk.stop) return;

            output.SetObject(chunk, dst);   

    }

    public override void OnGUI ()

    {

            layout.Par(20); input.DrawIcon(layout); output.DrawIcon(layout);

        layout.Field(ref level, "Level", min:0, max:1);

    }

}

Now we’ve got a nice looking generator, which has an input, output, and an adjustable parameter. It’s also seamless: It appears as if it’s been in MapMagic forever, but the really cool thing is that it is literally a plugin for MapMagic, we’ve done it without touching the main code. It could be removed by just deleting the file, sent to a colleague or published online.

Event system

OnApply

OnApply is a static event before applying any type of the output to the terrain. With it’s help you can pre-process terrains and what is going to be applied with your scripts.

Delegate: ApplyCallback (Terrain terrain, object obj)

Usage example (your custom script):

public void OnEnable ()

{

    MapMagic.OnApply -= MyPreProcess; //just in case it was not called on disable

    MapMagic.OnApply += MyPreProcess;

}

public void OnDisable ()

{

    MapMagic.OnApply -= MyPreProcess;

}

public void MyPreProcess (Terrain terrain, object obj)

{

    //processing heightmap

    if (obj is float[,]) //terrain.terrainData.SetHeights recieve float[,] as heightmap

    {

        float[,] heightmap = (float[,])obj;

        //do something with heightmap

        //or set the terrain layer

        //or assign some script to terrain

    }

    //processing splatmap

    if (obj is float[,,]) //alphamap is float[,,]

    {

        float[,,] splatmap = (float[,,])obj;

        //do something with splatmap

        //don't assign a script here if it is already assigned in heightmap

    }

    //processing grass

    if (obj is int[][,]) {}

    //etc

}

Generate Change Events

You can use the static generate change events to monitor current generate state and pre- or postprocess terrains before (or after) they have been modified with MapMagic:

Delegate: ChangeEvent (Terrain terrain)

Usage example (your custom script):

public void OnEnable ()

{

    MapMagic.OnGenerateStarted  -= MyGenerateNotify;

    MapMagic.OnGenerateStarted  += MyGenerateNotify;

}

public void OnDisable ()

{

    MapMagic.OnGenerateStarted -= MyGenerateNotify;

}

public void MyGenerateNotify (Terrain terrain)

{

    //notifying script that generate has started

}


[1] except Texture Output background layer