Creating a level

From OniGalore
Revision as of 21:32, 12 May 2024 by Iritscen (talk | contribs) (wording)
Jump to navigation Jump to search
This page covers level creation from scratch. See Modifying an existing level if you want to change a vanilla level in the game.


Level releases and resources

Fan-made levels

Date Name Level number
TBA Oni 2 Angel Studios Tour 32
TBA Green Room 26
2019/09/16 Omega Tournament 31
2016/02/13 AI Battle 7
2014/01/19 Wilderness Preserve 17
2013/09/07 Island 29
2013/07/12 City 28
2013/06/01 Fight Club 25
2012/05/12 Old China 24
2012/03/18 Boss Battle 23
2012/02/12 De Dust Deluxe 22
2012/01/23 Parkour Challenge 2 21
2012/01/16 Parkour Challenge 21
2011/07/25 Mini-Partenon 5
2011/10/03 Muro's Lair - Preview 20
2011/07/03 Hexagon 16
2011/06/23 Junkyard 15
2011/05/01 Arena of Hurt (OZG) 7
2010/11/08 Arena of Hurt (OTA) 7
2010/11/06 Martian Colony AKA "Silly World" 5
2010/02/15 Maze 5
2008/09/16 Arena of Pain 30

Forum tutorials on level creation

Wiki pages on level creation

Free asset sources

Engine limitations

First you should be aware of the limitations that Oni will place on your level.

  • The triangle limit of a game level is approx. 520,000 (consider it 500,000 to be on the safe side).
  • (formerly "XSI") Mod Tool can save DAE files with a maximum of 64,000 triangles per object.
  • When polygons are too tightly packed, the camera will look at too many of them and glitches will appear. The limit on visible GQs (Gunk Quads) is 8,192 (213), and higher numbers will cause render bugs and produce the console error message "Exceeded max visible GQs number". There are limited solutions to this:
    • Decrease the gs_farclipplane_set value.
    • Hide objects via env_show.
    • Hide objects by level design. For example, departments in a building could be quite detailed because the view of the other departments/rooms is blocked by walls.
  • The camera can look at 2,049 transparent textures at once; one more, and Oni crashes.
  • Characters are visible only within world coordinates {-4,099, -4,099, -4,099} to {4,099, 4,099, 4,099}.
  • Geometry stops outside of (roughly) world coordinates -4,228 to 4,228 on the X and Z axes (vertical axis not tested).
  • chr_debug_spheres = 1 visualizes the collision spheres of characters, so you can check if they will fit through an entrance.

3D tools

Problems when using multiple different tools

Re-saving a DAE file in Mod Tool which was originally made in SketchUp can result in a surprising change in size within Oni. This is due to a difference in the specification of a scale unit.

For example, the SketchUp DAE file may have: <unit meter="0.0254000" name="inch" />
And the re-saved Mod Tool DAE file may have: <unit meter="0.1" name="decimetre"></unit>

In that case you'll need to open the new DAE in a text editor and change the length specification back.

Sketchup

If you are working with Sketchup check out this pages:

Blender

Scaling

Same as Mod Tool and SketchUp: Caution with scaling: See here

Pathfinding

Be sure to uncheck automatic triangle creation when exporting (BNV) geometry used for pathfinding. Ghost (doors) must be quads.

Setting up a workflow

If not noted differentely you need OniSplit v0.9.82.0 or a newer version. Onisplit can be downloaded HERE (it also comes with the AE and is found in the Tools directory of the installation).

The level import demo files can be downloaded HERE.

First, put OniSplit.exe into the second lab folder alongside the XML files.

Look at the file build.cmd. These are the two important lines:

OniSplit.exe -create:level out lab.xml
OniSplit.exe -import:pc out level3_Final.dat

The first line creates the *.oni files from a master.xml file, in this case lab.xml. Once finalized, the resulting .oni files can be put into a new AE package.

The second line creates level archives which can be used for fast tests. They don't require you to install a package through the AE Installer.

Instead of "-nosep" you can now also use "-pc" for the import. Macs, however, do use "-sep".

Troubleshooting

If the game crashes while loading at ~90% progress, be sure that the texture's x and y dimensions are powers of two: 64, 128, 256, 512….

If the game crashes while loading at ~50-60% progress, be sure that the BINACJBOCharacters or AISA file has a player character defined.

If the game crashes while loading ~5-10% progress, check if all your textures were inside the "out" folder.

You can extract the AKEV file to XML, then search for the TXMP Array.
THIS script checks which textures are missing by comparing the AKEV*.xml with its *.oni files folder. Change the two paths so that it works for you: Alt+4, insert code, then F5.

"TXMP_marker_door" and "TXMP_marker_ghost" will be missing in the demo's "out" folder.

You can add this between the other two lines in build.cmd to prevent a crash.

OniSplit.exe -create:txmp out env/markers/*.tga

Terminology

Polygon
A polygon is a shape defined by points (vertices) in space. Polygons are the most fundamental building block in games. Oni uses only shapes with 3 and 4 points (triangles and quads). Other games might also use N-gons, shapes with more points.
Face
The surface of a polygon is also named face. The direction of a face in defined by its normal. If an object seems to have a hole, it might be that the polygon just has a flipped normal and is therefore wrongly rendered (displayed on screen).
Normal
Faces and even vertices themselves have directions. The normals of vertices determine how smooth or sharp edges will appear.
Vertex (plural: vertices)
A point within a polygon.
Vertex colors radiate from one vertex to another vertex, whereby their different color values blend into each other so that gradients can appear.
Edge
A line of two points within a polygon.
Tris (triangles)
Polygons made of 3 points.
Quads
Polygons made of 4 points. Planes. Rectangles.
Internally, all quads get converted into two triangles and will affect the maximum triangle count accordingly.
Level
The term "level" is often used to shorten "level map" (buildings and terrain).
Geometry
Level geometry can be objects made of tris and/or quads.
Oni differentiates between basically 3 types of level geometry:
  • Static objects (sometimes also named AKEV core geometry)
  • Animated objects
  • Pushable objects (not used in the original game)
Static geometry
  • Unique level geometry with no functions (AKEV core geometry such as buildings and streets).
  • Repeating objects are flagged as furniture (FURN) although they can be outdoor objects such as boxes and containers. FURN objects were used to be imported by an object collection, an CJBO. Nowadays, furniture collections can be used (again) for imports, but the individual objects are stored as OFGA files. This modularity is known in many game editors (like the Unreal Engine Editor). So, OFGA are nothing else than "game assets". The drawback of OFGA is that they will not import with any vertex shading. Only their geometries, attached particle and gunk flags get copied into final (baked) level geometry.
  • Objects with actual functions (CJBO such as doors, consoles, triggers)
    • Some types of CJBO (spawn lists of weapons, characters, powerups and of course flags and patrol paths) are not considered as level geometry.
Gunk flags
Properties directly associated with the polygons. Among others, these can be: Transparent, TwoSided, NoCollision, Invisible, NoObjectCollision, NoCharacterCollision, NoDecals, Furniture, Impassable.
Baking
When a level gets baked, all geometries get merged.
  • Unique level geometry will have vertex painting.
  • Objects derived from already compiled oni-files (shared classes) will have no vertex painting.
  • Animated and pushable objects will have no vertex shading.
Vertex shading
Vertex colors (vector data) can be painted onto core geometry.
They are used as a rudimentary form of lightmapping (pixel data).
Having vertex shading is still better than having no vertex shading at all. It makes the level appear more lifelike.
Lightmapping
In other games, lights and shadows are added to level geometry with lightmapping.
There were experiments to bring lightmaps back to Oni. The technique come with a major drawback though. It doubles the triangle count and adds many textures.
In-game
When you are "inside" the game, running or playing it.

Level construction

Visible environment

  • A level map consists of visible geometry: terrain and buildings. This is also named the environment or for short env.
    • Use quads of approximately 10 up to 20 meters in size to construct env geometry.
  • Anything other than floors and simple walls will be made of triangles.
    • Geometry from CJBO works best with triangles. Quads might show holes in-game.

Invisible pathfinding

  • AIs (or non-player characters, NPCs) need pathfinding data, usually shortened as bnv.
    • Pathfinding data can be described as geometry, therefore as rooms. These bnv rooms are made of floors and are connected by ghost quads which work like doors. AIs can move from one room to another room only by passing a ghost. Therefore, ghosts have the same width as rooms in open terrain.
      • Use floor quads of approximately 10 meters in size to construct BNV floors.
      • Put ghost quads exactly on the edges of BNV floors with an angle of 90 degree.

Support for effects

  • Effects such as decals that indicate damaged environment need level textures to be registered in TMBD. Make sure not to replace existing texture entries but to add your own so that the other levels remain unaffected.

Level logic

BSL scripts bring levels fully to life. The logic is mostly event driven whereby different checkpoints and conditions must be reached.

  • Checkpoints are actually trigger volumes that can recognize entering and exiting characters.
  • The second most important source of events are character (script) functions, especially the die function of enemies.
    • To support good and less good players alike, NPC characters can feature the UpgradeDifficulty flag. Also, BSL scripts can detect the current difficulty level.
  • Laser triggers are mostly used to control turrets. Consoles, triggers and door unlocking are used to set up very simple "riddles”.
  • The creation of cut scenes is time-consuming quality work. Due to missing tools for streamlined workflows, it is totally okay for you to skip this.
    • New synchronized FILM, OBAN and TRAM files are required for cut scenes. Not to mention sound files for dialog.

The master XML file

The project file for level creation.

The master XML file's name determines the BSL folder name and the AKEV and ONLV file names in the output folder. File paths in the XML can be either absolute or relative.

Mandatory files for level import:

  • level_environment.dae
  • level_bnv.dae
  • level_textures.tga/jpg/png
  • Character.xml or AISA with player
  • ONSK name in <Sky> (in Windows, this can be a fake name; does the Mac need actual ONSK files?)
  • ONLD file for the new level; read HERE about making sure your level is unlocked (accessible on the Load Game screen)
XML tag Content type Description
<Level SharedPath="..."> link The master XML file can link to other XML files. Most of them will be BINACJBO files. The file type is declared inside the file, so the file name itself doesn't have to contain the type. Ergo, "BINACJBOCharacter.xml" can be given a simpler name like "Character.xml".

Some resources used by those XML files can be found in a shared folder. OniSplit copies textures and "physics" objects into the output folder; furniture objects will be integrated into the AKEV. Unused resources in the shared folder will be ignored. That way the final level archive or AE package contains only the necessary files, saving disk space for the end user.

In the demo are various subfolders in the shared folder: consoles, doors, furniture, triggers and turrets. These file types do not need their file prefixes: CONS, DOOR, TRIG and OFGA. OniSplit will recognize them nonetheless.

Common mistake:

CJBO files need relative paths to the resource instead of just file names. Taking consoles, for example: a standard export to XML gives you "console_data". But to use the shared resources these must be paths like "consoles/console_data.oni" as they are stored in shared/console/. Note how the demoed shared folder is placed next to the project ("lab") folder and the XML master file points to it using "../shared".

<Environment> -
<Model> - Used to import level geometry and static objects. See HERE for detailed information.
<Import Path="..."/> link
<Node Id="..."> string informational (space for notes)
<ScriptId> integer Can be used with BSL commands such as ...
env_show ID 1 (use 0 to hide)
env_broken ID1 [ID2] (if a second ID is supplied then objects in the range ID1..ID2 get counted; "3001 3018" would cover 18 objects)
env_texswap ID texture (no file prefix/suffix allowed)
env_shade ID ID R G B

Static objects store their ID in one of AKEV's IDXA instances.

<GunkFlags> flag
AGQG (Gunk) flags (trimmed list)

see OniSplit -help enums for more flags

None
NoCollision
NoObjectCollision
NoCharacterCollision
NoDecal
TwoSided
Invisible
<Rooms> - For detailed information see HERE.

For a Google SketchUp BNV tutorial see HERE.

<Import Path="..."/> link File path to *.dae file. BNV data, used to create pathfinding.
<Textures> - With OniSplit v0.9.94.0+ you can use attributes to set the default import formats and max dimensions. For example:
<Textures Format="BGR" AlphaFormat="RGBA" MaxSize="512">

Textures with a dimension that isn't a power of two (2x) and textures larger than 512px on a side will be resized. For example, 2003x2000 will become 512x512. (What would 257x255 become?)

<Texture Name="..."> string TXMP file name to generate.
<Flags> flag Optional tag. For list of flags see HERE.
<Format> flag
DXT1
BGR
RGBA
BGR555
BGRA5551
BGRA4444
<GunkFlags> flag
AGQG (Gunk) flags (trimmed list)

see OniSplit -help enums for more flags

None
NoCollision
NoObjectCollision
NoCharacterCollision
NoDecal
TwoSided
Invisible
<Image> link File path to *.tga/jpg file.
<Sky> string ONSKfile.oni (without file pre- and suffix)

The import doesn't work with an empty tag. <Sky>clear</Sky> helps here; any other fictional name will also do.

For detailed information see HERE.

<Objects> -
<Import> link File path to an OBJC (collection) file. Supported collection files for <Import> are:
Character.xml (has to contain player character if there's no AISA file with it)
Console.xml
Door.xml
Flag.xml
Furniture.xml
Neutral.xml
Particle.xml
PatrolPath.xml
Physics.xml
PowerUp.xml
Sound.xml
Trigger.xml
TriggerVolume.xml
Weapon.xml

The following two collection files are NOT supported. You need to copy them as regular pre-compiled *.oni files into your mod package:

Melee.xml (overrides global MELE; not used in vanilla levels)
Combat.xml (overrides global CMBT?; not used in vanilla levels)

Inside the following three supported collection files, provide links to pre-compiled (*.oni) class files: console, door and furniture collections. Use relative paths such as:

consoles/console_data.oni
doors/TCdouble.oni
furniture/V_tctf_bigvan.oni

When you are unsure about the folder structure refer back to the level import demo.

<Films> -
<Import> link File path to *.xml file. OniSplit creates a FILM file from it. Used for characters in cutscenes.
<Cameras> -
<Camera Path="..."> link File path to *.dae file. OniSplit creates an OBAN file from it. Used for cutscenes.
<Animation Name="..."/> string Name for the OBAN file.


This is a trimmed-down version of the master file lab.xml from the demo. It's meant to give you a first impression. We might upload a smaller demo someday with all features nonetheless.

<?xml version="1.0" encoding="utf-8" ?>
<Oni>
   <Level SharedPath="../shared">
       <Environment>
           <Model>
               <Import Path="env/lab_env.dae"/>
               <Import Path="env/lab_bomber_window.dae"/>
               <Import Path="env/lab_motorcycle.dae">
                   <Node Id="motorcycle">
                       <ScriptId>9</ScriptId>
                       <GunkFlags>NoCollision</GunkFlags>
                   </Node>
               </Import>
           </Model>
           <Rooms>
               <Import Path="env/lab_bnv.dae"/>
           </Rooms>
           <Textures>
               <Texture Name="GOO">
                   <Format>bgra4444</Format>
                   <GunkFlags>NoCollision</GunkFlags>
                   <Image>env/images/GOO.tga</Image>
               </Texture>
           </Textures>
       </Environment>
       <Sky>sunset</Sky>
       <Objects>
           <Import>Character.xml</Import>
           <Import>Furniture.xml</Import>
           <Import>TriggerVolume.xml</Import>
           <Import>Physics.xml</Import>
       </Objects>
       <Films>
           <Import>films/BomberKonRun01.xml</Import>
           <Import>films/BomberKonRun02.xml</Import>
       </Films>
       <Cameras>
           <Camera Path="cameras/BomberCam01.dae">
               <Animation Name="BomberCam01"/>
           </Camera>
           <Camera Path="cameras/BomberCam02.dae">
               <Animation Name="BomberCam02"/>
           </Camera>
       </Cameras>
   </Level>
</Oni>


<Model>

This tag is mandatory (it must be present to avoid errors); the model section contains one import path to the AKEV (level) geometry (*.dae) and can contain additional import paths for exceptions (*.dae).

Data from the demo:

               <Import Path="env/lab_env.dae"/>
               <Import Path="env/lab_motorcycle.dae">
                   <Node Id="motorcycle">
                       <ScriptId>9</ScriptId>
                       <GunkFlags>NoCollision</GunkFlags>
                   </Node>
               </Import>


env_show

The motorcycle has here scripting ID 9. You can show and hide the object with the BSL command env_show, where the second parameter means true or false. Never use 0 as an ID because it won't work.

env_show 9 1
env_show 9 0

EdT demonstrates env_show here. Note how the objects have collision.

The last vanilla level (Syndicate Mountain Compound) has a big satellite dish platform that can be made hidden. Obviously, such objects/areas can be made to have pathfinding too.

More possible applications of env_show
  • ground/ceiling/objects for cutscenes that become partially destroyed
  • destructible walls like in Deus Ex: Human Revolution (trigger volume, replacing punch anim with punch-through-wall anim)
  • grids in front of ventilation shafts (a small door object could be an alternative)
    • at the moment this can't be done because characters can't sneak under the top edge of the opening (the characters collision spheres are too big)
  • managing object groups with trigger volumes (save multiple objects in one DAE file)


Import of object groups

Mod Tool versus SketchUp

The meaning and behavior of "groups" greatly differs in these two programs.

In SketchUp you can group objects which enables you to easily duplicate all objects inside a group and to scale/rotate/translate them all together.
In Mod Tool grouped objects are still loose, and duplicating a group won't duplicate the objects. To achieve the same effect as in SketchUp, you have to create a hierarchy where any one object is the "parent" and all others the "children".


Google SketchUp

To create a group, select two or more objects, right-click on the selection and choose "make group".

Mod Tool can import objects in hierarchies to Oni.

Mod Tool

Either use the Explorer (A) or the Schematics (B) to create a hierarchy.

(A) In the Explorer window, drag and drop one or more objects onto another one.
(B) Open the constrain tab at the right side. Select parent object, click "Parent", click the child object(s). Check the result in the Schematics window.
Mod Tool: caution when saving to DAE!
A normal selection won't do. You have to select the object tree to successfully save a hierarchy. This is the same way that you do it with Oni characters.

Breakable glass with BSL recognition

Broken environmental objects can be recognized by the BSL command env_broken (ID_1, ID_N). However, this requires additional code to work.

An example is the training level. Let's determine how we can set up such a thing.

First we need a trigger volume like in level 1.

       <TRGV Id="11495">
           <Header>
               <Flags>Locked</Flags>
               <Position>-714.6615 -298 -555.2073</Position>
               <Rotation>0 0 0</Rotation>
           </Header>
           <OSD>
               <Name>tv75</Name>
               <Scripts>
                   <Entry></Entry>
                   <Inside>targets_gone</Inside>
                   <Exit></Exit>
               </Scripts>
               <Teams>255</Teams>
               <Size>400 31 270</Size>
               <TriggerVolumeId>75</TriggerVolumeId>
               <ParentId>0</ParentId>
               <Notes></Notes>
               <Flags>PlayerOnly</Flags>
           </OSD>
       </TRGV>

Now we need some BSL code.

var int inside_target_function;

func void enter_target_function(void)
{
   dprint enter_target_function
   inside_target_function = 1;
}

func void exit_target_function(void)
{
   dprint exit_target_function
   inside_target_function = 0;
}

func void targets_are_not_gone(void)
{
	# CB: turn off the trigger volume and sleep for a second
	# so as not to cause hideous performance loss
	trigvolume_enable tv75 0
	sleep 60
	trigvolume_enable tv75 1
}

func void targets_gone(string ai_name)
{
	if (inside_target_function eq 0)
	{	
		enter_target_function() # catch other "targets_gone" functions to let them do nothing

		var int num_broken = env_broken(3001, 3018);
		# if you only one target use scheme: env_broke(3001, 3001)

		if (num_broken eq 18)
		{
			targets_are_gone();
		}
	
		if (num_broken < 18)
		{
			targets_are_not_gone(); # to set check interval to one second
		}

		exit_target_function
	}
}

func targets_are_gone
{
	trigvolume_enable tv75 0
	# [...]
}
Explanation

Player enters the TV, targets_gone() gets triggered. The variable "inside_target_function" should be 0 by default, so we will enter the "if" block. Next, we can assume that the player didn't destroy all glass objects, so "num_broken" will be less than 18; thus, targets_are_not_gone() gets called.

The TV function targets_gone() would be triggered every frame, but targets_are_not_gone() creates a delay between checks: the TV gets deactivated for 60 frames. Then the TV becomes enabled again and will start anew until all glass objects got destroyed or player left the TV.

In this case the targets_gone() function will do nothing because "inside_target_function" was set to 1 by the first call.

targets_are_gone() eventually disables the TV to prevent memory overflow; this function also contains all the things ("[...]") that you want to happen after the glass target is broken.

Texture exchange

BSL command supported on Windows and Mac:

env_texswap ID texture

Might be useful to switch on/off static and animated textures. (News broadcast screen: running or off or smashed. Lava stream: flowing or stagnating or cooled down. Etc.)

A similar effect can be achieved by showing/hiding geometries with different textures as EdT has demonstrated in a private test. That way it should also be possible to have different vertex coloring for an area.

Vertex coloring

Vertex colors can be edited manually in Blender by setting up following 3 things:

  • Properties Panel > Data > Color Attributes
  • 3D viewport (left top) > change work mode to Vertex Paint
  • 3D viewport (right top) > change to Viewport Shading to Solid mode (under the dropdown menu you can choose "flat" to have full saturation)
env_shade

If you use env_shade, use it with caution. It completely overwrites the vertex coloring.

# BSL command
# env_shade obj_id obj_id R G B

# examples
env_shade 7 7			# object 7 will be black
env_shade 7 7 0 0 0		# object 7 will be black
env_shade 7 7 .31 .999 .5	# object 7 will be quite green
env_shade 7 10 1 1 1		# object 7, 8, 9, 10 lose all their shading

Baked lightmaps

A drawback of this method, AKA shadow maps, is the high number of TMBD changes and new textures for every wall, requiring a lot of memory. How to create shadow maps:

Notes on map options:

  • "Surface color and illumination": outputs used texture with added shadows
    • Can be used to replace original texture
  • "Illumination only": output a texture that only contains the shadows
    • Can be used on a new overlying, transparent surface

Standalone lightmaps

With this approach there are two meshes, whereby the shadow effects on one mesh get drawn in front of the regular level texture.

Compared to the "baked lightmaps" method, this needs more polygons of course. And these polygons are transparent, which is a problem with Oni's limited rendering ability for such textures. However, standalone lightmaps only need a few new textures if used wisely.

Read Lightmapping levels to learn more.

Oni Lightmaps double polygon method.jpg

<Rooms>

The Rooms tag is mandatory. It contains an import path to BNV and ghosts (*.dae) which are used to create pathfinding grids. For information on the binary data behind this, see HERE.

Sample code from the master XML file:

<Import Path="env/lab_bnv.dae"/>


Fundamentals

  • BNVs are volumes (sometimes also named rooms) that have a pathfinding grid assigned to them.
  • For practical reasons you only need floors and ramps to build a BNV. (As seen HERE.)
    • A BNV polygon can consist of 3 or more points, but it's recommended to use shapes with 4 points because the other shapes usually waste more grid space. At the moment you can import only convex shapes.
  • Secondly, there are so called ghosts (vertical quads) that connect neighboring BNVs. AI can only transit from BNV to another through ghosts.
OniSplit gives error messages for ghosts that don't have 2 neighboring BNVs, for example:
BNV Builder: Ghost 'grid2' has no adjacencies at <-72,41 1,999 -166,6> and <-71 1,999 -168,21>, ignoring

Tolerance values

  • Horizontal distance from room to ghost: 1
  • Vertical distance from room to ghost: 18
  • Ghost's horizontal dimension must be greater than 0
  • Under optimal circumstances, pathfinding works on a plane that is +4 world units above or -0.5 below the pathfinding grid
  • Normally characters can't pass over low vertical walls such as stairs/curbs if those don't have a ramp under them; near ground level (Y=0) is an exception where characters are allowed to step on a plane that is 4 world units above pathfinding grid; higher planes need a ramp
  • Maximum angle of ramp you can go on is 70°; higher angles will make characters fall through to their death (in those cases you might want to add invisible walls/boxes to prevent them from touching the ramp)

More notes on PF processing

  • Actually, BNV and ghosts can have any name and don't need any texture.
  • During pathfinding calculation, OniSplit creates a new folder called "temp" and a subfolder "grids". There is a file named "levelname_grids.dae". That file contains rooms with textures called "bnv_grid_N.tga" which mark obstacles in the pathfinding grid. Ghosts will have a transparent texture "_marker_ghost.tga". You also get these when you export a level. Transparent ghosts make it easier to see the rest of your level if you have many of those planes.
  • In Mod Tool, ghosts and rooms can be grouped under a null object. Right-click the null object to select all its children, then use "Selection Only [x]" as your saving option. That way all the planes can be quickly saved to a *.dae file.
  • You can influence the pathfinding creation of dangerous zones using danger quads. Read about those HERE.
  • Another kind of influence is the gunk flag IgnoreGrid. It can be used to tell OniSplit to ignore the object during pathfinding creation. The grids in that place will be white.
ai2_showgrids = 1 in action

BSL commands

  • chr_show_bnv = 1
shows number of BNVs the player is standing on
  • ai2_showgrids = 1
shows pathfinding grids
  • ai2_chump
spawns a friendly Striker that follows the player if possible, makes also pathfinding grids visible
  • ai2_chump_stop = 1
Striker stops following you
  • ai2_chump_stop = 0
Striker follows you again

Pathfinding on uneven ground

Pathfinding works on slightly uneven ground as long as the polygons are 0.5 world units beneath or 4 world units above the BNV.

For greater surface irregularities, we can use a trick:

  • The problematic polygons must be saved to a DAE file and then referenced in the model section as an individual object.
  • Then the <GunkFlags> tag needs to be GridIgnore.
  • Additionally we create a plane (invisible if necessary) under the object so that Oni doesn't think there's a hole.
  • The ghosts must be taller than the highest polygon to be walked on. (This has been tested up to a height of 2,000 world units.)

Symptoms of too-small ghosts

A) AI can't run at all.

B) AI loses PF ability if the tracked enemy performs animations that are outside of the pathfinding volume. Here's a video (https://youtu.be/vfKPzP5hiCo, dead link) showing these symptoms.

Missing boundaries

Let's say you have a BNV, tall ghosts, and a GridIgnore-flagged hill with trees and bushes and stuff.

Why do AIs run up against the trees?

Problem: There are no red/orange grids that limit the AI's movement. Normally, objects will produce red pathfinding squares around them. But objects that are 20 world units above the BNV are ignored just like objects with the IgnoreGrid flag.
Solution: Add simple geometries like _marker_impassable fences or danger quads near the BNV.

Disappearing AIs

The ground is sometimes not solid, and the player and AIs are falling through it.

Problem: On GridIgnore-flagged uneven ground, AIs can fall through it when they are out of the player's view.
Solution: Use either chr_lock_active AI_name or chr_all_active = 1

Debugging

(OniSplit v0.9.93.0+)

OniBrowser displaying level geometry plus PF and ghosts.
  • env_show_ghostgqs = 1 makes the ghost quads visible
    • The original ghost quads can't be seen; per Neo, "they are not included in the octtree and bsp tree"
    • To see them you must use the -debug option in the level creation command (available with OniSplit v0.9.93.0+)
OniSplit.exe -create:level output_folder -debug input_folder/master_xml_file.xml

Not all debugging/testing needs to be done in Oni. There is also a program (Windows only) which can display the whole level.

For example you can load an AKEV*.oni, select the level name, and use the hotkeys [B] and [P] to display BNVs and ghosts.

Bug prevention:

After all testing is done, you should compile the level a last time without the -debug option.

AIs appear to have trouble shooting through ghost quads that got registered in the octtree/BSP tree as seen here (https://www.youtube.com/watch?v=-ZugVBgBVKc, dead link).

When OniSplit fails to connect PF quads

Fixed PF in compound level after underground construction...

It can help to merge polygons to one object and merge their edges. These become white. (Outer edges are blue.)

<Textures>

...used for exceptions

The textures tag is mandatory.

AKEV textures will be imported by the master XML file automatically; you don't need to add them to that file.

With OniSplit v0.9.82-93.0, the textures' formats will be DXT1 and (for transparent ones) BGRA4444.
With OniSplit v0.9.94.0+ the textures' formats will be BGR and (for transparent ones) RGBA.

To change the default texture import format, use the Textures tag. (Possible with OniSplit v0.9.94.0+.)

To change the format/flag/gunk flag of a specific texture, use the Texture tag.

An example:

         <Textures Format="BGR" AlphaFormat="RGBA" MaxSize="512">
               <Texture Name="GOO">
                   <Flags>TwoSided</Flags>
                   <Format>BGRA4444</Format>
                   <GunkFlags>NoCollision</GunkFlags>
                   <Image>env/images/GOO.tga</Image>
               </Texture>
         </Textures>
  • Use the gunk flag "TwoSided" to make surfaces visible from both sides.
  • If the texture comes with the gunk flag "NoCollision" then all objects with that texture will have no collision. Characters would fall through it.

Transparency and no collision together would make sense for a water-like substance. The Vago Biotech level uses this approach for the area with green toxic waste.

Figure 1
Figure 2
one object with multiple textures, one per polygon cluster
Figure 3
blue: two-sided
glass: two-sided and transparent
random metal: no flag (one-sided)


TwoSided and transparent objects

It's also possible to set this in Mod Tool. For TwoSided-ness, use one image source for diffuse and transparency. (It won't work with two image sources even if both sources use the same image.)

For transparency you must additionally use the three checkboxes:

  • Transparency: Enable
  • Transparency: Use Alpha
  • Transparency: Invert

See Figure 1.

Regular textures

OniSplit automatically imports textures of objects in the <Model> section. Those objects could be considered as AKEV core geometry.


One texture per object

Furniture and Physics objects are allowed to have only one texture per object. AKEV core geometry can have one texture or multiple textures – that's up to you to decide.


Multiple textures per object

Question: How do we apply multiple textures to an AKEV core object?

Answer: Assuming you are using Mod Tool the procedure will be as follows. Repeat this for each cluster you create (except for projection). See also Figure 2 and 3.

  • Select some polygons
  • Go to Material > Phong
  • Hit [7] to open Render Tree
  • Add a texture and connect "Image" with "Phong", choose "diffuse" as illumination mode
  • Double-click "Image" to open Material page
  • In the Texture Projection section, click on "New" and choose a suitable projection
    • Do this only if there isn't a projection in the "Texture_Coordinates_AUTO" cluster yet
    • OniSplit processes only one texture projection, see Figure 4
  • Choose the texture and UV, then fine-tune the UV via Alt + 7
Figure 4
use only one texture projection per object

Caution: textures will be fixed to one set of flags. You can't use the same texture A for a one-sided cluster/object B and a two-sided cluster/object C. You would need to create a differently-named clone of that texture and apply that to cluster/object C.

Marker textures

Marker textures are used to import certain objects with special flags. Import those objects alongside AKEV core geometry.

Marker textures aren't imported automatically. Add those textures to the final package or plugin yourself, e.g. with some sort of script.

OniSplit.exe -create:txmp out env/markers/*.tga

The flags Transparent, TwoSided and NoOcclusion are only set if the same texture source is used for diffuse and transparency and if transparency setting are enabled. See the subsection under "...used for exceptions" called "TwoSided and transparent objects".

Danger quads

Pathfinding grids have different colored squares with different meanings.

  • white/gray - clear space
  • green light - near wall
  • green - semi-passable
  • green dark - stairs
  • blue very light - border 1
  • blue light - border 2
  • blue - border 3
  • blue dark - border 4
  • orange - danger
  • red - impassable

For detailed information on colors, see HERE.

Normally, OniSplit decide how to color the grids. But it's possible to add your own danger quads to fine-tune zones of higher danger. Those orange areas will be generated with the 4 different blue borders around it.

Danger quads must have "_marker_danger" as texture name.

Results in flags: Transparent TwoSided NoCollision Invisible NoOcclusion Danger


_marker_barrier

Results in flags: Transparent TwoSided(double-check) Invisible NoObjectCollision NoOcclusion


COLLISION (boxes)

AIs can get stuck in detailed geometry. This can be prevented with collision boxes. In-game the box will be invisible, but has collision so characters can't get through. The pathfinding grid will be red where it comes in contact with the box; the red is followed by a light green and then regular green.

OFGAs use the COLLISION texture. Onisplit doesn't automatically apply gunk flags to it. You need to use XML tags for that. Typically it would be <GunkFlags>Invisible NoObjectCollision</GunkFlags>


Stairs

Texture: _marker_stairs

Normally, characters can't move on stairs because they contain vertical rises. In this case you can use a sloping quad that is above or inside the staircases. It's possible to set the desired flags in the <Model> section, but doing so and exporting these self-made stair ramps is wasted time. Simply apply _marker_stairs to that quad and characters will be able to move on that surface.

Resulting flags: Stairs Transparent TwoSided Invisible NoObjectCollision NoOcclusion

This has no effect on pathfinding creation. Regularly-textured stair geometry (which intersects the marker quad) will be flagged as NoCharacterCollision.


Impassable

texture: _marker_impassable

Resulting flags: Transparent TwoSided (<-double-check this) Invisible NoOcclusion

Impassable walls are very similar to collision boxes except that particles can also collide with them. The pathfinding grids will be also red, light green, and green.


Blackness a.k.a. jello fix

Texture: _marker_blackness

Resulting flags: TwoSided NoCollision

Simply black walls. No effect on pathfinding creation. The purpose of these walls in Oni is to block out areas outside of a level's outermost wall so that the Jello-cam cannot show the player other parts of the level when it escapes a wall. See Jello-cam for details.


Doors

Texture: _marker_door

Resulting flags: DoorFrame Transparent TwoSided NoCollision NoOcclusion

Don't use this.

<Objects>

Information on regular object lists can be looked up here:

Character.xml (has to contain player character if there's no AISA file with it)
Console.xml
Door.xml
Flag.xml
Neutral.xml
Particle.xml
PatrolPath.xml
PowerUp.xml
Sound.xml
Trigger.xml
TriggerVolume.xml
Weapon.xml

Furniture.xml

The AKEV core geometry is more or less the overall structure of the level while this furniture file adds standard objects (e.g. crates and desks) to it.

XML tag Content type Description
<Objects> - This tag marks the file as BINACJBO.
<Furniture> - This tag marks the file as FURN.
<Header>
<Flags> flag Optional tag. Ignore it. Those flags were used in the past.
None
Locked
PlacedInGame
Temporary
Gunk
<Position> float x3 Optional tag. X Y Z position.
<Rotation> float x3 Optional tag. X Y Z rotation.
<OSD> -
<Class> link File path to a pre-compiled OFGA (*.oni) file. Use it as part of a relative path. Make sure that the OFGA comes with the files it needs: M3GM and TXMP.

An OFGA can hold multiple M3GMs. That 3D content will then be integrated into the AKEV.

<Particle> string This name gets written into <Tag> inside ONLV (ENVP section) and can be used with BSL commands, e.g. you can take control over a particle via particle BSL_name start. For more commands, see HERE.

Take care with the naming of new OFGA files. The particle name will be composed of 2 parts separated by an "_" underscore. When one tag is empty, the final name will contain an underscore nonetheless. Examples:

FURN <Particle>test</Particle> + OFGA <Tag>streetlight</Tag> = test_streetlight
FURN <Particle></Particle> + OFGA <Tag>streetlight</Tag> = _streetlight
FURN <Particle>test</Particle> + OFGA <Tag></Tag> = test_


Example
<?xml version="1.0" encoding="utf-8"?>
<Oni>
   <Objects>
       [...]
       <Furniture>
           <Header>
               <Flags>Gunk</Flags>
               <Position>-62.692 -29 108.35</Position>
               <Rotation>181.522 356.021 178.114</Rotation>
           </Header>
           <OSD>
               <Class>furniture/V_tctf_bigvan.oni</Class>
               <Particle></Particle>
           </OSD>
       </Furniture>
       [...]
   </Objects>
</Oni>

Physics.xml

This file is for objects with "physics". Unlike AKEV core geometry or furniture, these objects can be animated.

A small video tutorial on how to create a simple animated object
XML tag Content type Description
<Physics> -
<Object Name="..."> string Use a unique name for each object.
<ScriptId> integer Tested with BSL command...
obj_create ID [ID]
obj_kill ID [ID]
env_anim ID [ID]
env_setanim ID [ID]

The scripting ID will be stored in the ONLV's OBOA instance.

<Flags> flag
Object setup flags
None
InUse
NoCollision
NoGravity
FaceCollision
<Physics> flag Optional tag for an Object.
None
Static
Linear
Animated
Newton (used with <Flags> FaceCollision to make an object pushable)
For a pushable object don't use import sub-tags such as animation name, flags, etc. Just use <Import Path="..." /> or <Import Url="..." />
<Position> float x3 Optional tag of Object. Only used for non-animated objects. X Y Z position.
<Rotation> float x3 Optional tag of Object. Only used for non-animated objects. X Y Z rotation.
<Scale> float Optional tag of Object. Only used for non-animated objects.

<Import Path="...">

<Import Url="...">

link This tag comes in two variants. It doesn't matter what variant you use.
<Animation Name="..."> link File path to *.dae file.
<Flags> flag Optional tag of Animation. For more information on those flags look OBAN page.
Object animation flags
None
Loop
PingPong
RandomStart
AutoStart
Local
<End> integer Optional tag of Animation. Frame number. Used to define the end of an OBAN from a *.dae file.
<Start> integer Optional tag of Animation. Frame number. Used to define the start of an OBAN from a *.dae file.

The XML code for physics can be written in two styles.

  • The first style mimics the original structure found in extracted ONLV instances. This makes it easier to edit old levels and reimport them into the game.
  • The second style was created with structured data (nested objects) in mind and is suited for totally new levels where animated objects must be created in a 3D editor at first.


Code style 1

<?xml version="1.0" encoding="utf-8"?>
<Oni>
   <Physics>
       <Object>
           <Geometry>M3GMDishBase.oni</Geometry>
           <Animation>OBANDishBase.oni</Animation>
           <Physics>Animated</Physics>
           <ScriptId>201</ScriptId>
           <Position>-197.729141 128.46283 -2501.46387</Position>
           <Rotation>0.7071067 -1.545431E-08 1.545431E-08 0.7071068</Rotation>
           <Scale>2.109375</Scale>
           <Name>object_DishBase</Name>
       </Object>
   </Physics>
</Oni>

Code style 2

For documentation purposes, the file here has been trimmed down.
<?xml version="1.0" encoding="utf-8"?>
<Oni>
   <Physics>
       [...]
       <Object Name="motorcycle">
           <ScriptId>8</ScriptId>
           <Flags>FaceCollision</Flags>
           <Import Url="motorcycle/export.dae">
               <Animation Name="motorcycle02">
                   <Flags>AutoStart</Flags>
                   <End>880</End>
               </Animation>
               <Animation Name="motorcycle02_stop">
                   <Start>881</Start>
               </Animation>
           </Import>
       </Object>
       [...]
   </Physics>
</Oni>

In this example, OniSplit takes the file "export.dae", looks at the parts it contains, and creates geometry files from it.

  • M3GMhubs_rear.oni
  • M3GMhubs.oni
  • M3GMmotorcycle.oni

It's convenient that hierarchies are supported here.

The motorcycle is made of 3 parts so 3 object animations (OBAN) will be created up to frame 880. Then OniSplit creates 3 more OBANs starting from frame 881.

The reason for the interruption at frame 880 is probably to give Barabas a little more camera time in the cutscene.

Notes about BSL usage

The Vago Lab cutscene script uses the motorcycle objects and animations like this:

	env_show 8 0		# hide static motorcycle parts or else they would be visible
	env_show 9 0		# while Konoko is still riding her animated motorcycle
	env_show 10 0		# the static motorcycle is separated into objects 8, 9 and 10

	# Static and animated objects are totally independent from each other.
	# They are stored in different arrays so their script IDs can use the same numbers.

	obj_create 8 10		# create all animation-ready motorcycle parts
	env_anim 8 10		# animate those parts (not necessary if OBAN flag is "AutoStart")

	# The first animations would loop because of "AutoStart", so get the
	# right timing to apply the other animations. "motorcycle02_stop"
	# and the others will not loop because they don't have "AutoStart".
	env_setanim 8 hubs_stop
	env_setanim 9 hubs_rear_stop
	env_setanim 10 motorcycle02_stop

	obj_kill 8 10		# delete all animated parts
	env_show 9 1		# show static motorcycle model where Konoko parks her bike

Corpses.xml

An empty Corpse element (<Corpse />) is treated as unused and placed at the end of the array. If no such empty elements are provided, OniSplit automatically adds 5. OniSplit also ensures that there are at least 20 corpses in the array.

XML tag Content type Description
<Corpses>
<Corpse> -
<Class> link ONCCname
<Transforms> - There are 19 <Matrix> for each <Transforms> (which represents a body part).
<Matrix> matrix Float 3x3 + float x3 for the last 3 values (X Y Z position). For more information about the matrix, see OBD:CRSA.
<BoundingBox> - Bounding box of the whole corpse. Optional; if not provided, OniSplit will generate an approximate one. Currently it doesn't try to locate the ONCC to compute an exact bounding box.
<Min> float x3
<Max> float x3

Trimmed example

<?xml version="1.0" encoding="utf-8"?>
<Oni>
   <Corpses>
       <Corpse>
           <Class>generic_male_1</Class>
           <Transforms>
               <Matrix>-0.36488995 0.04620622 -0.9299034 -0.05580434 -0.9980576 -0.02769538 -0.9293768 0.04178688 0.366759717 -215.667755 1.603917 92.85312</Matrix>
               <Matrix>0.2961557 -0.185628444 0.936928 -0.368288517 -0.9272724 -0.06730227 0.8812806 -0.32512787 -0.342981815 -216.51889 1.64218426 93.189</Matrix>
               <Matrix>0.407847315 0.156575248 0.8995248 -0.238757968 -0.9326179 0.2705892 0.8812805 -0.3251278 -0.342982143 -215.239883 0.8405094 97.23532</Matrix>
               <Matrix>0.439697325 0.467113584 0.767118752 0.0248355754 -0.8601105 0.5095029 0.8978026 -0.204975232 -0.389789373 -213.6234 1.46109009 100.80056</Matrix>
               <Matrix>0.717241049 -0.179043233 0.673430443 0.4761817 -0.579636 -0.6612664 0.5087397 0.794962764 -0.3304816 -214.81662 1.56564879 92.5172348</Matrix>
               <Matrix>0.3398023 0.1672905 0.925498843 0.7910236 -0.5831365 -0.185023025 0.5087395 0.7949628 -0.330481827 -211.71907 0.792413354 95.42558</Matrix>
               <Matrix>0.1283634 0.227430075 0.965297 0.804142356 -0.59352535 0.032904774 0.5804117 0.7720126 -0.259073079 -210.372269 1.45546341 99.0937653</Matrix>
               <Matrix>-0.285799921 0.06851995 -0.955836535 -0.240357026 -0.970681965 0.00228381017 -0.927656651 0.230394751 0.2938901 -216.284851 1.5839076 91.29168</Matrix>
               <Matrix>-0.312039882 0.04875436 -0.948817253 -0.176752359 -0.9842264 0.00755515043 -0.9334825 0.1700632 0.31573534 -216.689789 1.68287766 89.9359055</Matrix>
               <Matrix>-0.356412441 -0.5390179 -0.763170958 0.0404523835 -0.82494843 0.5637587 -0.933452547 0.170058519 0.315826446 -217.422012 1.7979821 87.70907</Matrix>
               <Matrix>-0.101782769 -0.247171819 -0.9636112 0.5426916 -0.8256094 0.154451028 -0.8337422 -0.507223248 0.2181709 -217.747284 1.30605173 87.0125656</Matrix>
               <Matrix>-0.8154478 -0.150626346 0.558888733 -0.1200991 0.9885643 0.09119747 -0.566234052 0.00724477554 -0.8242127 -217.7402 2.065678 88.15346</Matrix>
               <Matrix>-0.4593738 -0.326781839 0.825947642 0.8874048 -0.1284554 0.442732215 -0.0385793857 0.9363295 0.348996729 -219.093979 1.81561315 89.08131</Matrix>
               <Matrix>-0.9545983 -0.13777709 0.26412034 0.295387268 -0.322961926 0.899134 -0.0385792255 0.9363296 0.348996431 -220.3681 0.9092429 91.37218</Matrix>
               <Matrix>-0.9715769 -0.0362956524 0.233924776 0.0276728421 -0.9988148 -0.0400396362 0.2351007 -0.0324283168 0.9714299 -222.775482 0.56178683 92.03825</Matrix>
               <Matrix>0.979860842 -0.192433611 0.0533096045 0.193336874 0.9810555 -0.0122905634 -0.0499345176 0.0223497637 0.9985025 -216.8387 1.90143847 87.84845</Matrix>
               <Matrix>0.863898 -0.399280578 0.3070101 0.171117 -0.340619326 -0.9244986 0.4737078 0.851207137 -0.225936681 -215.211975 1.58196676 87.93695</Matrix>
               <Matrix>0.4673251 -0.0255063027 0.883717656 0.746463835 -0.524209559 -0.4098731 0.4737075 0.851207256 -0.225936785 -212.815842 0.47451216 88.78848</Matrix>
               <Matrix>0.216871262 -0.007893051 0.976168454 0.3751846 0.9238389 -0.0758833 -0.9012231 0.3827002 0.203315422 -211.637314 0.410188764 91.0171051</Matrix>
           </Transforms>
           <BoundingBox>
               <Min>-219.731216 -2.258905 84.47974</Min>
               <Max>-207.518463 4.49535131 104.023239</Max>
           </BoundingBox>
       </Corpse>
   </Corpses>
</Oni>