T3D Generator

This is a page about the T3D generator subproject. The goal of T3D generator is to provide tools to create maps for UDK (and possibly other versions of Unreal) programaticcaly - e.g. to generate mazes. This page applies to Pogamut 3.2.5 in association with UDK (as of Beta 2011-05), but should work seamlessly with future versions. With minor modifications it is possible to use it for creating maps for other versions of Unreal than UDK.

There is automatically generated Javadoc for the latest version

There is a new version (3.4.1-SNAPSHOT) that supports CSG for UT2004. During the refactoring, some older classes have moved, so this tutorial is slightly outdated. Hope this gets fixed soon. Meanwhile, you can try to guess from javadoc or just use the older version….

Previous knowledge

Before trying this tutorial, you should be basically familiar with UDK level design and Maven.

Simple example

First a simple example usage.

Maven dependency

Enter a dependency on t3d generator to your project's pom:

        <dependency>
            <groupId>cz.cuni.amis.pogamut</groupId>
            <artifactId>pogamut-udk-t3dgenerator</artifactId>
            <version>3.2.5-SNAPSHOT</version>
        </dependency>

Creating a few static meshes

Let's code something small:

import cz.cuni.amis.pogamut.unreal.t3dgenerator.DefaultT3dGenerator;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.IT3dGenerator;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.T3dElementHelper;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.datatypes.Point3D;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.elements.AbstractUnrealActor;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.elements.map.MapElement;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.elements.map.PlayerStart;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.elements.map.PointLight;
import cz.cuni.amis.pogamut.unreal.t3dgenerator.elements.map.StaticMeshActor;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
 
public class ExampleApp {
 
    public static void main(String[] args) {
        StaticMeshActor staticMesh1 = new StaticMeshActor("FoliageDemo.Mesh.S_PavingBlocks_01", new Point3D(0, -50, -40));
        StaticMeshActor staticMesh2 = new StaticMeshActor("FoliageDemo.Mesh.RockMesa_06", new Point3D(0, 0, 400));
        PlayerStart playerStart = new PlayerStart(new Point3D(0, 0, 0));
        PointLight light = new PointLight(new Point3D(0, 50, 100));
 
        List<AbstractUnrealActor> actors = new ArrayList<AbstractUnrealActor>();
        actors.add(staticMesh1);
        actors.add(staticMesh2);
        actors.add(playerStart);
        actors.add(light);
 
        MapElement map = T3dElementHelper.wrapActorsIntoMap("My_First_Map", actors);
 
        IT3dGenerator generator = new DefaultT3dGenerator();
        try {
            OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("out.t3d"));
            generator.generateT3d(map, out);
            out.close();
        } catch (Exception ex) {
            System.out.println("Error writing to file" +  ex.getMessage());
        }
    }
}

Now, if you run this example, you'll get a file called “out.t3d” in project's working directory. Open UDK editor, choose File → Import and select this file. Now just press “Build all” and you'll see a simple level with a pad and a rock which you can save as an Unreal package (.upk).

Note: UDK Beta 2011-05 Crashes if you import T3D and currently open map is not empty. Always use File → New before importing.

Some notes to the code:

  • Creating a static mesh is done simply by creating an object of appropriate class
    • In general, T3D Generator contains ready-made classes for most commonly used objects
    • The only obligatory parameter to creating a static mesh is it's location and the mesh's name. You can use Right click → Copy full name in the Content Browser to get the name of selected static mesh, but strip it of the StaticMesh' prefix and of the trailing “'”
    • The meshes mentioned in this example should come with your UDK installation.
  • It is vital to create some light for your level, otherwise the player won't see anything
  • A PlayerStart is also useful, as it is a place for player to start :-)
  • Finally you wrap all your actors into a map using an utility function and generate the T3D to an OutputStreamWriter.

Concepts of T3d Generator

TODO

Using Prefabs

UDK allows you to create Prefabs, which are reusable groups of objects. This is great for common level creation and is sometimes handy even for generating levels with T3DGenerator. There is the obvious adventage, that you can change your Prefab and let the change propagate to your level without the need to regenerate it. But note, that programmatically generating Prefabs has some not very obvious caveats, especially if you need to reference the prefabs from other objects. See info about Kismet & Prefabs below for further discussion of this issue.

The source of most of the trouble is the fact, that if you instantiate prefab in your level a complete copy of the prefab is made. So far so good (it allows for adjusting a prefab instance individually) But the prefab object contains a mapping relationship between the original components of a prefab and their instances. Since map is not a datatype supported by T3D, you cannot express such relationship in T3D. To try this for yourself, create a prefab instance in your editor, export it into T3D and then import it again. You'll notice, that the child objects are no longer considered a part of the prefab.

Now what happens, if you instantiate a PrefabInstance object (without any children), generate T3D from it and import it into Unreal editor? You'll get an empty Prefab. But you can right click the prefab and choose “Reset instance from prefab XX” and voilá, we have a nice complete Prefab instance.

Let's sum it up:

  1. Using prefabs with T3D generator is possible, just create a PrefabInstance object with correct prefab name (name is in the form “YourPackage.PrefabName” as seen in the editor). Do not add any children.
  2. After you import T3D with prefabs into editor, select all prefabs (they should already be selected right after the import), right click and choose “Reset instance from prefab XX”

Creating Kismet Sequences

There are objects for creating kismet sequences. However there does not seem to be a way to add Kismet into the same T3D file as the rest of the map. So instead, create a separate T3D file containing your Kismet sequences. Importing Kismet to UDK editor is done by selecting the T3d text in your file and copy-pasting it into the Kismet window. And voilá - the object are there.

If you import your map T3D prior to importing Kismet, all references from Kismet to map objects are kept intact.

Copy pasting Kismet objects from kismet editor to text editor allows you to reverse-engineer the object structure so that you can create your own kismet objects.

In Kismet, objects are connected with links. There are three types of links: Input, Output and Variable links. Each link is assigned a name. But if you check out the T3D format, it uses only integer indices to access the links. To abstract from this, kismet objects are setup with link mapping (array of strings containing link names as a parameter to constructor). So you then just call one of addVariableLinkTarget, addOutputLinkTarget, setOutputLink, setVariableLink, setInputLink or setInputLinkTarget to control links by their name. Predefined Kismet objects also contain constants to access the links.

Using Prefabs AND Kismet Sequences

This is very tricky. TODO

Extending/adding new elements

Basically you just create a POJO (which usually extends AbstractUnrealActor, AbstractUnrealObject or AbstractPrimitiveComponent) and add all properties as java fields. The fields need public getters. If you want an object to be child object, annotate it with @UnrealChild or @UnrealComponent. An automatic translation of field names from Java naming convention to UDK convention occurs. If you want to override it use @FieldName. To make a child a property of the object as well use @UnrealProperty. For advanced uses you might want to use @UnrealHeaderField. If you need to add something that is neither actor, nor object, nor component, you might want to inherit from AbstractUnrealBean and annotate your class with @UnrealBean(“ObjectType”). To add a plaint text string to the T3D output of your object, annotate a String field with @StaticText.

Public methods with no arguments can be annotated @UnrealProperty - their return value is then treated as a property value. Name is either derived from the method name (in standard getter format) or from annotation value.

See usages of those classes in the code to get hang of it.

Try exporting your objects as T3D from the editor to reverse engineer the object structure. You can also explore http://wiki.beyondunreal.com/UE3:Object_%28UDK%29 and hierarchy below to understand the structure of objects.

TODO more detail

Datatypes

Datatypes are complex structures, that are not themselves elements of the T3D tree. They can only be assigned to properties. They somehow correspond to C++ structs withou pointers.

TODO

To add a datatype annotate your POJO with @UnrealDatatype, all it's properties will then be serialized automagically.

Known Issues

CSG Brushes

Issue: Some of my CSG objects (brushes) are not visible after building the map in UnrealEd! (UT2004)

Fix: Open “Build options” and change BSP optimazation to “Good” or “Lame”.

subprojects/t3dgenerator.txt · Last modified: 2013/01/21 12:50 by martin.cerny