Unreal C++ Objects

Tim Sweeney
Epic MegaGames, Inc.
http://www.epicgames.com/

Audience: Licensee programmers who have access to the Unreal C++ source.
WORK IN PROGRESS -- This document is far from complete.
Last Updated: 07/21/99

Where do I look for info?

When do I need to write a C++ Serialize function?

C++ UObject-derived classes for which you write a script (a .uc file) automatically are able to serialize themselves completely. In this case, serialization simply involves the engine automatically going through all of the variables defined in the script (bytes, floats, object pointers, etc) and serializing them. Since the script defines the layout of variables, you don't need to write any serialization code because the engine already knows what to serialize.

If you don't have a .uc script for your class (meaning it's purely written and implemented in C++), you need to write a serialize function that overrides UObject::Serialize, to serialize the new variables you've added. Here' is a code fragment example.

class UWhatever : public UObject
{
	DECLARE_CLASS(UWhatever,UObject);

	// New variables.
	INT A;
	FLOAT B;
	FString C;

// UObject interface.
void Serialize( FArchive& Ar )
{
	UObject::Serialize( Ar ); // Must route to superclass to serialize the superclass variables and stuff.
	Ar << A << B << C; // Serialize these variables.
	if( Ar.IsLoading() )
	{
		// If you have any special cleanup code you want to execute when loading, stick it here (optional).
	}
}

How does saving a pointer to an object causes that object to be saved?

When you are saving a package (UPackage* Pkg) and during serialization to the archive (FArchive& Ar) you save a pointer to an object (UObject* Obj) by passing it to the archive (Ar << Obj), Unreal automatically saves two things: (1) the reference to the other object, so that it is automatically linked up with the proper object when you load it, and (2) the other object, *IF* it's in the package that is being saved. If the other object resides in another package, it only saves the reference to it as an "import".

Unreal serialization works like MFC serialization and Java serialization in that you save one base object (for example the ULevel) and the framework automatically serializes the entire graph of objects that are reachable to the base object. This is the standard object-oriented way of loading and saving stuff.

However, Unreal (unlike MFC and Java) has the concept of packages which enables saving only the objects in the graph which reside in a certain package. This approach combines the benefits of the traditional OOP serialization patter (being able to automatically save complex interrelated objects) with the benefits of Windows DLL style dynamic linking (being able to develop stuff as a bunch of modules, which publically export certain objects, and import other objects) which enables object-code modularization.

What resides in the Unreal binary package files?

An Unreal package file on disk corresponds to one top-level package (i.e. UnrealI.u corresponds to the UnrealI package, MyLevel.unr corresponds to a level named MyLevel).

From a high level point of view, an Unreal binary package file contains four distinct things:

  1. The name table (a table of C++ FName's) which maps unique text strings to indices.
  2. The import table, which lists all of the external objects, in other packages, that this package depends on. These are just described by name, just as the symbol table in a .DLL file lists functions imported from other DLL's.
  3. The export table, which lists all of the objects that reside in the package file, and tells where to find it within the current package file.
  4. The export data, which contains a bunch of raw serialized data for each exported object.

How do I write an external utility to read and write Unreal binary files?

The data in Unreal binary files describes highly complex interrelated objects whose characteristics are totally dependent on Unreal's object framework, so trying to read and write them outside of the engine is not a fruitful activity. When you need to get data in and out of the engine, the best way to do that is through plug-ins, either written in C++ (for editor tools, data importers, or performance critical plug-in code) or UnrealScript (for game stuff; UnrealScript doesn't execute in the editor).

The main categories of plug-ins of interest are:

Are package names hardcoded into files?

No! If you have a package named "MyPackage" and you save it to the file "NewName.u", the next time you load it, it will be called "NewName". Alternatively, when you load a package you can optionally specify its name (which may differ from the filename). Within a binary package file, there are absolutely no hardcoded references to the package's name.

How do "packages within packages" work?

Explain.

How can I best manage importing complex game data into new classes of C++ objects?

We've found that the easiest way to manage complex game objects (like your conversations) is to describe them in one or more UnrealScript classes residing in .uc files, add in a few new "#exec" commands in UnEdSrv.cpp to import your custom format data, and build those .uc files into a .u binary files using "unreal -make". For example, look at the humongous UnrealI.u file which contains a ton of scripts, sounds, textures, and meshes imported from a huge number of source files. With this approach:

1. You can create your basic content in text files (or other industry standard files) or your own simple, proprietary format.
2. You can change your UObject-derived object formats without completely breaking your content -- you just do another "unreal -make". This is important. I've gone through over 300 breaks of the .u file format!

What you will probably want to do is create one package containing ALL of your DConEvents, to avoid tying them to individual level.s One easy way to do this is:

1. Create a new directory (for example) \unreal\con, and \unreal\con\classes.
2. Create a script \unreal\con\AllConEvents.uc that looks something like this:

//=============================================================================
// AllConEvents.
//=============================================================================
class AllConEvents expands Object
	abstract;

// A variable to contain references to the sounds we're importing.
var object Refs[10];

// Import the sounds. Note: #exec commands are only executed when you rebuild from the
// command line using "unreal -make". See Packages.htm for more info.
#exec AUDIO IMPORT FILE="Sounds\Skaarj\amb1sk.WAV" NAME="amb1sk" GROUP="Skaarj"
#exec AUDIO IMPORT FILE="Sounds\Skaarj\syl07.WAV" NAME="syl07sk" GROUP="Skaarj"
#exec AUDIO IMPORT FILE="Sounds\Skaarj\syl09.WAV" NAME="syl09sk" GROUP="Skaarj"

// Force this class to reference the sounds we imported, so that they'll be saved
// in this package file.
defaultproperties
{
	Refs(0)=amb1sk
	Refs(1)=skl07
	Refs(2)=syl09
}

3. Add "Con" to the "EditPackages" section of Unreal.ini so it will be properly rebuild when you do an "unreal -make". (It searches thru all directories listed in the EditPackages section and rebuilds packages whose .u files have been deleted).

The above example assumes I want to stick a bunch of sound files in the package (stored as .wav's on disk, brought into Unreal as USound objects). If you want to import a bunch of conversations, you need to write a new "#exec" handler in UnEdSrv.cpp to import your data from your custom file format into some UObject derived class like DConversation.

How are objects unique identified and created within a package?

Within a package (which defines a namespace):

End