UnrealSmall.gif (411 bytes)
Unreal Technology

Resources
Announcements
Downloads
Community

Unreal Tournament
Home Page
Known Issues and Bugs
Performance Tweaks
Running UT  Servers

Console Commands
AI

Licensing
Partners
Features
Licensing FAQ
Company Info

Level Design
UnrealEd Quick Notes
Overview of Zones

Programming
Mod Development
UnrealScript Reference
UnrealScript Black Magic
Networking Architecture
Server Querying
Objects in C++
Engine Redistribution
Localization
True-type Fonts

Other Content Creation
Package Files
Package Commandlet
Audio
Textures
Fire & Water
Music
Mesh LOD Tech
Mesh 3ds2unr Tool
Skeletal Animation

Unreal 1
Unreal 1 Server Tips
Unreal URL's
Master Servers
Console Commands
AI





unrealcomposite.jpg (19818 bytes)

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?

  • UnObjBas.h: FObjectManager (the global object manager, GObj) definition. UObject definition. Helper macros.
  • UnCorObj.h: Look here for definitions of some of Unreal's simpler object classes.

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:

  • Import tools for importing data from other file formats (graphics, models, etc) into the engine. For this, you'll want to write a new factory object (see the UFactory class, and UnEdFact.cpp for many examples). A factory is an object responsible for importing data from one or more files and constructing one or more UObject's from that data.
  • Export tools. Code for these can go into the game or editor. The typical approach is to traverse whatever Unreal objects you want and use fopen, fclose, fread, fprintf, etc. to write it out to a file.
  • New kinds of non-actor Unreal objects -- new algorithmic texture types, etc. The best approach for extending Unreal objects or creating new kinds of UObject derived classes is C++ subclassing. Here you take a base class like UObject, UTexture, etc. and you create a child class, and write whatever functions are appropriate for that class including, if your object is savable, a serialization function.
  • New kinds of actors -- the best approach is UnrealScript subclassing. You write a new .uc file. In this case, the engine takes care of all the details of serialization for you as long as you write a valid script.

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):

  • If you don't specify a name, Unreal creates a guaranteed unique name for you:
    • The guaranteed unique name is generated by appending a unique number onto the class name, for example Pawn23.
  • If you specify a name, and the name is unique, a new object is created.
  • If you specify a name, and the name matches that of another object of the SAME class, the other object is replace with the new one.
    • The old object's C++ destructor (and its UObject Destroy() function) are called.
    • The new object is constructed at the same memory location as the one it replaces.
    • Therefore any pointers to the replaced object are still valid, since they still point to the same class object.
    • The new object's C++ constructor is called.
  • If you specify a name, and the name matches that of another object of a DIFFERENT class.
    • You get a critical error message because this situation cannot be resolved safely.

End