Making a *.map based Level in Blender: opening .map into level editor (pt6)
In previous chapters we learnt to make a simple .map based level from a series of prefabs that contained some curved architecture.
We then UWV unwrapped the level, applied textures, added some basic entity 'helpers' and exported the results.
In this penultimate chapter we'll be optimising the open map file in to GtkRadiant 1.4, carrying out some essential in-editor amendments to the level making it ready for BSP compilation. We'll learn about some of the broader principles behind making efficient .map based levels which typically means having to return back to Blender to re-edit specific aspects of a scene, ensuring everything works the way it should.
Please note: a basic understanding of GtkRadiant 1.4 or similar level editor is helpful. In addition some instructions may vary depending on the version being used.
Optimising brush volumes ^
At this point we do have a usable level albeit in a slightly raw state. If we wanted to it could be compiled and run in-game after a few minor changes, namely replacing textures and entities. Being able to do this doesn't necessarily mean the level is in its most optimal condition however, i.e. it may not be performing to the best of its abilities, so some degree of additional work is likely to be needed before this can be achieved. Primarily this will be through the judicial use of the "structural" and "detail" surface/object properties.
Important Note: not all .map based level editors or games make use of the "structural" and/or "detail" properties so assigning them will, at best, have no effect. Check feature support beforehand.
The level open in GtkRadiant 1.4 in it's 'raw' state - "Shader Image Missing" is applied to all brushwork due to the way the export script truncates the path info used by Blender to assign shaders to brushes. N.B. front and ceiling sections are hidden from view for clarity [see *.map "40"]
The same level after some basic changes have been made, namely replacing 'null' for caulk and entities. N.B. front and ceiling sections are hidden from view for clarity [see *.map "45"]
Structural and Detail volumes ^
Within the context of optimising a level, "structural" and "detail" properties are not references to the physical complexity or architectural characteristics of a brush, i.e. its shaped. Instead it's a surface or object 'flag', a 'property' or 'identification' of sorts on the 'type' or 'state' of a particular volume. By default brushes are "structural" in nature and have a direct effect on BSP (the data format .map files are compiled to). "Detail" on the other hand doesn't. Using a combination of both properties we can change the resulting compiled map and make levels more efficient by affecting how brushwork determines BSP structure and organisation during compile.
For general level design then, this means it's not necessary for every brush volume be, or remain, a "structural" volume; ideally we only need the bare essentials to be appropriately flagged leaving the bulk of a map and any small, fiddly and relatively complex or non-essential shapes to be 'flagged' as "Detail". There is an important caveat however. Because "detail" is "non-structural" (not structural) any objects placed against, or used as, the outer walls of a level will cause maps to leak so we need to be mindful of this despite being able to address the problem through the use of a "caulk hull" (see below).
Design note: changing the 'property' assigned to a brush volume between "structural" or "detail" can only be done in a level editor, it's currently not possible to do this in Blender.
To set the "detail" property in GtkRadiant 1.4, "Shift+LMB" select a brush and then RMB click anywhere in a grid view to access the main menu, select "Make Detail" from the list ("Selection » Make Detail") or use the "Ctrl+M" shortcut. Nothing will happen to the brush to indicate the property is assigned so to check, press "Ctrl+D" - this toggles the display of 'detail' flagged volumes 'on/off', which will disappear from view if correctly set.
Design note: conversely to set an object as "Structural", select the item and RMB click the grid view, select "Make Structural" from the list ("Selection » Make Structural") or use the "Shift+Ctrl+S" shortcut. Note: shortcut and menu options differ with the version of GtkRadiant being used.
Once exported and opened into a level editor (GtkRadiant 1.4 shown above), brushes can be selected and 'flagged' as "Detail" which will reduce their impact on BSP once the map is finally compiled for use
Setting brushes as "Detail" however, will cause a level to 'leak'; because the property is "non-structural" in nature it won't seal the map. This problem can be addressed in Blender or the level editor being used to finalise the map. (N.B. front and ceiling sections hidden from view for clarity) [see *.map "42"]
Caulk hull in Blender ^
The general purpose behind using a "caulk hull" is to remove all extraneous material from the compile process that does not, in of itself, determine how the level is presented to the player as they move through it. This is typically done through the use of a simplified 'caulk' textured "hull" that sections the map, on a room-by-room basis, so the net result is a series of interlinking 'boxes' the contents of which can then be safely and appropriately flagged as "detail". This solves two problems at the same time; it optimises the level, and stops leaks due to the 'detail' property.
Design note: the term "box" is used here to refer to the way a particular area of a level can be 'contained' in the simplest manner possible. It should not to be confused with a "box hull", where the entire contents of a level are placed inside an all encompassing container. This is something that should never be done when making levels or correcting problems.
In practice there are generally two techniques we can use to built the necessary hull like structure; 1) we can flag all brushwork as "detail" relying entirely on a fully sectioned caulk hull to contain the bulk of material and prevent leaks. Or 2) we limit "detail" assignments to the bare essentials, 'hulling' the map through a combination of the levels original structure and the judicial placement of caulk brushes to close any "detail" related 'virtual' gaps. Because we don't have the same amount of control over what we do in Blender as we would in a level editor, the latter option is a much more practical approach to use.
The archway and corner are 'non-essential' objects that do nothing relative to the efficiencies of the level and will be made "detail" in the level editor (N.B. the front wall and ceiling sections hidden for clarity)
Back in Blender then, we should first move all the objects we'll subsequently flag in the editor into a separate layer, helping to better manage the scene. This will naturally reveal a number of holes in walls, locations that correspond to the aforementioned 'virtual' gaps that will occur as a result of assigning the "detail" property to brushes. Using a combination of RMB and "Shift+RMB" select the appropriate objects, press "M", then click one of the "Move to Layer" buttons in the pop-up to move the items to the selected layer.
Moving meshes to another layer that will eventually be flagged as detail helps to highlight problem areas where 'virtual gaps' will appear in the resulting level, causing it to 'leak'. This problem needs to be fixed. (N.B. the front wall and ceiling sections are hidden for clarity)
Next, as we did when making the original room for the level, we can now add a series of new objects; either "Shift+A", select "Mesh » Cube" to add a new primitive; or we can simply "Shift+D" duplicate blocks that are already available. Once done and as before, we can then reshaped the new items using the widget on selected faces in Edit mode, and/or changing the "X", "Y" and "Z" values of the objects "Dimension:" from "View Properties" ("N"). We need to box-in or enclose the problematic areas so the sum of all the parts creates a completely sealed level as shown below.
Using strategically placed mesh objects, the additional shapes close and fill any gaps, sealing the hull and preventing leaks [see *.blend "41"]
To check the position of the new additions "Shift+LMB" click the layer or layers holding the 'detail' objects to display them alongside the rest of the level. They should appear fitting snugly inside the new brush work, confirming their proper containment by the hull sections. If we were to then re-export the level and open it into GtkRadiant, we should see a completely intact level - this would then mean being able to continue with flagging the appropriate brush volumes without adversely affecting the maps integrity (shown below).
Design note: two points to note; 1) these types of objects typically sit behind other objects, as such there is no real need for materials, textures or UVW maps to be assigned in Blender as they will be automatically caulk or 'null' textured. And 2) blocks should only occupy the exact amount of space used by the objects they sit behind. Both points ensure that, because the new objects will be left as "structural" volumes once exported, these additions don't cause clipping, z-fighting or other build and/or compile issues later on.
The arch and corner section of the level in place with the 'hull' blocks behind or backing them sealing the map [see *.blend "42"]
Map shown in Radiant with 'detail' flagged brushes hidden from view which reveal the blocks placed to seal the hull behind (note that front wall and ceiling sections hidden for clarity) [see *.map "42"]
BSP, Portals and Visibility ^
When a map is compiled it's essentially broken down in to a series of smaller areas called "Leaf Nodes". Their number and complexity is defined by "structural" brush volumes and other leaf nodes. Where one node meets another neighbouring node a "Portal" surface is created that acts, as the name suggests, like a gateway between each volume; over an entire level they form interlinking relationships which are important for determining "visibility" and how the contents of each "Leaf Node" is rendered to screen as the player navigates though the map - the loading/rendering sequence is based on where the player is and the accessibility of other node areas from that position, so a properly optimised level is only loading nodes that are reasonably 'localised' to any other given node (the player should only ever see into areas that are nearby and local to their position).
Each of the multi-coloured 2D planes shown above is a "Portal" which is the result of a shared surface between "Leaf Node" volumes - the areas bound by either a "Portal" on their 'open' and shared sides, or "structural" brushwork on their 'closed'.
If we were to leave the entire contents of the tutorial map in its default 'raw' state for example, where everything retains their "structural" property, the process ends up generating a disproportionate number of nodes and portals which then have the potential to adversely effect runtime performance. We use optimisation to mitigate this problem by physically structuring the map to produce an optimal number of "leaf nodes" and "portals" per the levels overall complexity; on one hand we don't want too many - loading too many smaller units in close proximity to larger ones; on the other we don't want too few - loading larger portions of the level into fewer.
Design note: when we talk about 'splitting' or 'breaking' a level up during compile, this is meant in a 'virtual' sense because the resulting areas do not always correspond to the logical layout of a level as we see it in a level editor like GtkRadiant.
The Blender made level compiled and showing the resulting "Portal" splits in GtkRadiant via the "Portal Viewer". Each of the zones the portals border, "Leaf Nodes", is independent of, but drawn to screen if, neighbouring node areas can be seen. In large levels the distribution of these node is important manage as best as possible [see *.map & *.prt "40"]
To assist with the levels optimisation we can use the "Portal Viewer" available in GtkRadiant 1.4 to load the respective maps "*.prt" file. This displays a series of differentially coloured 2D planes representing each "Portal" (and "Leaf Node" they are the boundary of) so as we adjust the level, brushwork and their property assignment, the layout, size and pattern of the portals and nodes also change as a reflection of the effectiveness of their distribution around the map. In the essentially reductive process of optimisation we ideally want as few as possible.
Design note: the "*.prt" file becomes available once a level has been compiled, usually a simple "-BSP" will do (or "-BSP -meta" in GtkRadiant). The availability of the "Portal Viewer" may vary depending on the version GtkRadiant being used.
Following the logic of this process, in the images below we can see how altering the tutorial level changes the distribution and number of portals and leaf nodes with respect to their efficiency within the confines of the .map. The first image shows the resulting portals/nodes when the level is compiled 'as is'. The second image, what happens after non-essential brushwork is flagged as 'detail' (the archway and corner wall - second image below). We can then change the physical position of the level in Blender along the the vertical axis (up/down) so the inner face of the floor volumes align to the "0" "XY" grid axis (third image below). And finally in the fourth image, readjusting the level so the inner wall of first room aligns to another of the primary "0" "XZ"/"YZ" axis points, which results in the fewest number of portals.
Whilst the end result of manipulating the map may be an optimal arrangement of "Portals" and "Leaf Nodes" in this instance, it's not always practical or possible to do this, especially on a large complex map. It does however highlight how a .maps build process has a number of 'hidden' consequences that may not be readily apparent when we use Blender to make levels, and one of the reasons we should use tools like the "Portal Viewer" when optimising.
If the map is compiled with everything left as "structural" brush work this creates 10 separate node areas bounded by 13 Portals (including those that split across the horizontal). Nodes "1" through "4" should generally be avoided in this context as they're either small complex areas or thin, long volumes, both can cause secondary issues, AI navigation in particular [see *.map/*.prt "42"]
Flagging non-essential brush volumes as "detail" typically reduces the number of portals[1, 2] and node areas but, as in this instance, can also inadvertently introduce additional elements that may or may not be inside the level and useful (as shown above - these are usually culled) [see *.map/*.prt "42a"]
By adjusting the vertical position on the level relative to the 0,0,0 grid axis the 'floor' surface is aligned to the grid which means Portal and Leaf Node area numbers can be reduced further, here only 4 of each. Although repositioning in this instance was done in Radiant, this type of adjustment can also be done in Blender to the same effect [see *.map/*.prt "42b"]
By moving the level again so inside faces align to the 0,0,0 grid axis the number of Portals and Leaf Nodes can be reduce to their absolute bare minimum. Whilst this is possible for a simple level, on move complex constructions it's increasingly difficult to build with this level of control over Portal and Leaf Node placements in the level [see *.map/*.prt "42c"]
Readjusting the position of the level in Blender by moving the objects so they are better placed and aligned to the grid for optimal Portal and Leaf Node creation when the level is compiled [see *.blend "42c"]
The same finalised level open in GtkRadiant showing the same adjustments made in Blender which produced the most optimal layout. Note that in both instances objects are positioned so their inner surfaces align to the grid and not one of the outer edge - as these are generally discarded we should endeavour to properly locate internal, textured faces and surfaces to the grid for optimisation
Compiling the level ^
Once the level has been fully prepared in GtkRadiant it needs to be compiled. Before doing so it's a good idea to go through a quick check list to make sure we've not missed anything obvious in the process. We need to make sure that;
Textures are applied only to surfaces as needed.
Hidden and/or unused surfaces should be caulked.
Make sure the level is sealed - this is irrespective of using a 'caulk hull'.
Entity 'helpers' have been replaced with proper game entities.
Curves are made from a collection of mesh objects (or replaced with "Patch Mesh" objects as per editor availability).
How the level is actually compiled will depend on tool availability; GtkRadiant has a number of "BSP" options to do this from within the application itself for instance (using the included version of Q3Map2). To compile the level, with the .map file open in the editor simply click "BSP" in the main application menu and select an option from the list. To run a 'test' compile for example, select "Q3Map2: (test) BSP -meta, -vis, -light -fast -filter" - this produces a 'rough' or 'approximate' version of the file that allows the level to be tested quickly in game (especially where numerous iterations are needed) using a 'basic' quality level but using 'full' structural rendering - BSP and VIS are fully calculated so we can gauge their impact on the scene. On the other hand if a 'final' quality compile is needed, select "Q3Map2: (final) BSP -meta, -vis, -light -fast -filter -super 2 -bounce 8" or similar - this is usually done when wanting a 'final' quality level where BSP, VIS, lighting (lightmaps) etc., are all calculated to 'final' quality, i.e. the level-of-quality usually associated with the 'public release' of a map or project.
Each type of compile will take varying amounts of time to complete depending on the complexity of the level and the quality of the render required (this is an additional advantage of employing "detail" and "structural" property assignments - because "detail" doesn't affect BSP in the same way as "structural", less time is taken to compile the level), this can be anywhere from a few seconds or minutes to tens of minutes to hours for extremely large terrain based levels. Once done it's then just a matter of loading the level into the game the .map was for; if this were an idtech 3 based game for example, that would mean dropping the in-game console and typing "/devmap [mapname]" (where "[mapname]" is the name of the compiled level "mapname.bsp" - note the file extension "*.bsp" does not need to be typed when loading a map from the console).
Design note: it's typical that a level may need to be compiled several times and adjustments made through progressive versions, perhaps to reduce problems with AI navigation, or to better optimise 'tris count' (the number of polygons drawn on-screen at any given moment), and so on. It's generally a given that this needs to be done, maps are rarely completed on their first run.
The finalised level ready to be compiled. Entities and textures replaced, unused surfaces caulked, leaving only the inside of the level assigned shaders (shown using Return to Castle Wolfenstein default assets - textures would be replaced with whatever was available per-project set-up) [see *.map "42d"]
Designing and making a .map based level in Blender does require a certain amount of forethought and/or pre-planning if we're to get the most from using the application in that context. Although the tutorial level is relatively simply we can build much more complex structures, and so long as that is done using the same kind of considerations and construction techniques as would be used in a level editor like GtkRadiant, very few problems will arise. Those that do will be relatively straightforward to fix in Blender or a level editor exactly because of the way the map was built. This is key to making game levels based around the peculiarities of the .map format, which relies heavily on simple blocks (volumes), convex shapes and forms. In the final chapter we'll look at some of the underlying information pertaining to the format and it's use.