Once I get that upgrade to 36-hour days, I will tackle that. – Mychaeel
Typecasting
Typecasting or type conversion is the concept of converting one data type into another, for example the turning the string "1" into the numeric value 1.
Type conversions from or to primitive data types, including conversions between the struct types vector and rotator and from a reference type, are called primitive casts. Type conversions between different classes of object references are called dynamic casts. Type conversions between different class limiter types or from an object reference to a class limiter type are called metacasts.
Contents
Explicit conversion syntax[edit]
To explicitly convert a value of one type to another, use the following syntax:
typename(value)
Typename is the type to convert the value to. As you can see, this syntax looks like a function call and is a good reason why your functions should not have the same name as any built-in or user-defined types. If a function and a type of the same name are found, the function call takes precedence over the typecast.
Note: The compiler will throw an error message if the typecast is redundant, for example because the target type is identical to the value's type, or will always fail, for example when casting between classes from unrelated branches of the class tree.
Implicit type conversion[edit]
Certain type conversions are done automatically.
Numeric types[edit]
The types int, float and byte are automatically converted into each other when an assignment, operator or function call require it. Especially for byte values this is essential, because only the increment, decrement and combined assignment operators are defined for the type byte. All other operators, such as + or - actually are int operators returning int values. The reason why you can still use them as expected is implicit type conversion:
local byte A, B, C; C = A + B; // actually compiled as: C = byte(int(A) + int(B));
Keep in mind that these implicit conversions have the same effects like explicit conversions. The *= and /= operators for the int type actually expect a float value as the second operand, so x *= 2
is not exactly the same as x = x * 2
, because the the latter performs actual integer multiplication, while the former internally performs floating point multiplication, which may produce rounding errors for very large values.
Object and class limiter types[edit]
Another kind of implicit type conversion happens when assigning an object reference of a certain class to a variable whose type is a parent class of the object's class. This is a direct consequence of object-oriented programming and does not alter the referenced object itself in any way, only the view on the object through the reference. This kind of conversion is done implicitly because it is a common task and will never fail. In fact, the compiler refuses any explicit type conversion to a super class because this is how OOP works.
Similarly, a class limiter type of a certain metaclass is implicitly converted to a class limiter type whose metaclass is a parent class of the first type's metaclass. Like with object typecasts, the compiler actually refuses explicit typecasts to a class limiter for a parent class.
For engine implementations that support interfaces, object references can be implicitly converted to all implemented interfaces when assigning the reference to a variable of the interface type. Note that this does not apply to class limiters, those strictly follow the class hierarchy without respecting the "cross connections" created by implementing interfaces!
Parameters defined with coerce[edit]
Functions and operators can apply the keyword coerce to their parameter declarations to instruct the compiler to automatically perform type conversion on values passed when the function or operator is called. That is only possible if an explicit type conversion exists from the value's type to the parameter's type. See the following section for a list of possible type conversions.
Usually the coerce keyword is only used for string parameters. It is widely used in the built-in string functions and operators and in the logging functions.
Primitive casts[edit]
Not all possible combinations of type conversions can actually be performed. For example, converting a floating point value to a class or a struct value to a name does not provide any meaningful result. This section lists all built-in primitive casts. These conversions will happen automatically when passing values to function parameters declared with the coerce modifier, unless the conversion requires casting to an enum type.
Note that it might be a good idea to specify literals of the target type directly instead of typecasting from a different type. For example rotator(vect(0.0,1.0,0.0))
should be written as rot(0,16384,0)
instead.
bool to...[edit]
- ...byte, int or float
- Returns 1 (or 1.0) for the value True and 0 (or 0.0) for the value False.
- ...string
- Returns the localized version of the strings "True" or "False" for the values True and False respectively.
byte to...[edit]
- ...bool
- Returns False for the byte value 0 and True for any other value.
- ...enum types
- Returns the enum constant corresponding to the numeric value. (0 -> first value, 1 -> second value, etc.)
- ...float
- Byte to float conversion is also lossless and returns a float with the same numeric value as the byte value.
- ...int
- Byte to int conversion is lossless and simply returns an int with the same numeric value as the byte value.
- ...string
- Returns the decimal string representation of the byte value.
All byte conversions also apply to values of enum types, with the first enum constant corresponding to 0, second constant to 1, etc.
int to...[edit]
- ...bool
- Returns False for the int value 0 and True for any other value.
- ...byte
- Int to byte conversion truncates the binary representation of the int value to the least significant eight bits. Negative int values are stored in two's complement, so for example converting -1 to byte results in the value 255.
- ...float
- Int to float conversion returns the nearest possible float value. This is unproblematic for values between -16777216 (0xFF000000) and +16777216 (0x1000000), but for larger positive or smaller negative values the float data type is not precise enough, so rounding will occur.
- ...enum types
- Performs conversion to byte, then returns the enum constant corresponding to the resulting numeric value. (0 -> first value, 1 -> second value, etc.)
- ...string
- Returns the decimal string representation of the int value. Negative values are preceded by a minus sign, positive values do not have a sign in their string representation.
float to...[edit]
- ...bool
- Returns False for the float value 0.0 and True for any other value.
- ...byte
- Float to byte conversion has the same result as float to int conversion, followed by int to byte conversion.
- ...enum types
- Performs conversion to byte, then returns the enum constant corresponding to the resulting numeric value. (0 -> first value, 1 -> second value, etc.)
- ...int
- Float to int conversion truncates any digits after the decimal point. If the value before the decimal point exceeds the range of valid int values, the binary (or hexadecimal) representation of the value is truncated to the least significant 32 bits (8 hex digits) and the resulting value is the two's complement representation of the resulting int value. An important side effect of this conversion is that the resulting int value might no longer resemble the magnitude or even sign of the original float value! NaN and infinity values are always converted to 0.
- ...string
- Returns the decimal string representation of the float value with a fixed number of digits after the decimal point and as many digits before the decimal point as necessary. Negative values are preceded by a minus sign. The number of digits after the decimal point is hard-coded and varies from game to game. For example, UT emits six digits, UT2003 and UT2004 only two. For obvious reasons this greatly limits the accuracy for small values. Scientific notation is not supported.
name to...[edit]
- ...bool
- The result is False for the name value
'None'
and True for any other name. - ...string
- The result is the normalized string representation of the name value.
string to...[edit]
- ...bool
- The result is True for the string "true", both in the English and localized version, as well as any string that, when converted to int, stands for a number other than zero. Any other string results in the value False.
- ...byte
- Has the same result as string to int conversion, followed by int to byte conversion.
- ...int
- Expects a decimal representation of the numeric value, potentially preceded by a plus or minus sign. If the resulting number exceeds the int range, the 32 least significant bits of its binary representation are used. Binary, octal or hexadecimal string representations are not supported. Leading spaces are ignored. Number recognition stops at the end of the string or the first non-digit character.
- ...name
- This conversion is only allowed in Unreal Engine 3 and returns a name value that case-insensitively equals the input string. If the name value was not used before, its capitalization is the same as the original string's, otherwise capitalization may differ.
- ...rotator
- Expects a string containing at least two commas, which are used to divide the string. All three parts are converted as described for string to int conversion above. The first value becomes the rotator value's Pitch component, the second the Yaw component and the third the Roll component. If the string contains no or only one comma, only Pitch or Pitch and Yaw are filled, while the remaining components stay 0.
- ...vector
- Expects a string containing at least two commas, which are used to divide the string. All three parts are converted as described for string to float conversion above. The values become the X, Y and Z components of the vector. If the string contains no or only one comma, only the X or X and Y components are filled, while the remaining components stay 0.
Note that strings can't be converted to object references or name values (except for Unreal Engine 3) using the typecasting syntax. To load or find an object reference via the object's string name, use the DynamicLoadObject() function.
rotator to...[edit]
- ...bool
- Returns False if Pitch, Yaw and Roll components are zero, otherwise True.
- ...string
- Returns the string representations of the Pitch, Yaw and Roll components of the rotator, separated by commas. Each component value is normalized to the range of 0 to 65535. If you want the string representations of actual integer values, you need to typecast the components separately.
- ...vector
- Returns a unit vector (i.e. VSize() == 1.0) pointing in the direction described by the Pitch and Yaw components of the rotator value. The zero rotator
rot(0,0,0)
corresponds to the unit vector pointing in positive X-axis directionvect(1.0,0.0,0.0)
. The result is the same as the out parameter X of the GetAxes() function.
vector to...[edit]
- ...bool
- Returns False for the zero vector, otherwise True.
- ...string
- Returns the string representations of the X, Y and Z components, separated by commas. Each component value is converted as described for the float to string conversion above, so small values may suffer from rounding errors.
- ...rotator
- Returns a rotator whose Pitch and Yaw components represent the same orientation as the vector. The Roll component is always zero. Since the zero vector does not have any orientation, it is defined to result in the zero rotator
rot(0,0,0)
.
Object to...[edit]
- ...bool
- Returns False for the value None and True for any object reference.
- ...string
- Returns the string representation of the object. In Unreal Engine 3 only the string representation of the object's name is returned. Previous engine generations also include the object's package and group(s), separated by dots.
These conversions also apply to class limiter type values.
Reference type conversions[edit]
Unlike primitive casts, type conversions between different reference types do not modify the reference in any way. They only act as a kind of "gate" for accessing objects so you don't accidently access variables or call functions of objects that don't have those variables or functions.
There are two types of reference typecasts, dynamic casts and metacasts. Dynamic casts are used to convert to a different type of object, and metacasts are used to convert to a different type of class. Both have in common that they don't actually change the reference being typecasted. That means, after a successful dynamic or metacast, the result is still a reference to the same object. However, if the typecast was not successful, the result is the null reference None. This means, whenever you can't be 100% sure the typecast was successful, you need to perform an additional check on the result to make sure you're not accidently accessing None.
Dynamic casts[edit]
A dynamic cast typecasts an object reference of a certain class, for example Actor, to a subclass, for example Inventory. Attempting to typecast to a class that is not a subclass of the original object reference type will be refused by the compiler, either for being redundant (i.e. casting to te same class or a parent class) or because the conversion would always fail. Examples of conversions that will always fail include Pawn to Inventory and Weapon to Ammo, or more generally all conversions between unrelated branches of the class tree.
Examples[edit]
Controllers have a reference to their currently controlled Pawn, but it's of type "Pawn". Now neither UT200x nor UT3 actually use pawns of type Pawn directly, instead they use the class xPawn and UTPawn by default, which are (indirect) subclasses of Pawn.
local Controller C; // getting Pawn's health: log(C.Pawn.Health); // yup, no typecasting at all - Health is defined in Pawn // is the Pawn currently invisible? log(xPawn(C.Pawn).bInvis);
Vehicles are Pawn subclasses as well:
local Controller C; // currently driving a vehicle? log(Vehicle(C.Pawn) != None); // sitting in a turret seat? log(ONSWeaponPawn(C.Pawn) != None); // accessing the vehicle driver: log(Vehicle(C.Pawn).Driver); // accessing vehicle health and driver health: log(C.Pawn.Health @ Vehicle(C.Pawn).Driver.Health); // accessing an Onslaught vehicle's active driver weapon (might change, e.g. for Leviathan): log(ONSVehicle(C.Pawn).Weapons[ONSVehicle(C.Pawn).ActiveWeapon]); // Ready for some serious typecasting action? // The Hellbender side turret stores the last fired sky mine projectile as combo target for bots. // Let's find its location, but with all the usual checks to prevent Accessed Nones: if (ONSWeaponPawn(C.Pawn) != None && ONSPRVSideGun(ONSWeaponPawn(C.Pawn).Gun) != None && ONSPRVSideGun(ONSWeaponPawn(C.Pawn).Gun).ComboTarget != None) log(ONSPRVSideGun(ONSWeaponPawn(C.Pawn).Gun).ComboTarget.Location);
Note: Usually you would avoid complex typecasting expressions and instead store part in variables. But if you only access things once, you might actually do it like in the last example above.
Metacasts[edit]
If the result of a type conversion is a class limiter type, the conversion is also called metacast. Metacasts are not limited to class references, but can also be used to convert from the most-generic type Object. Class references are special object references after all. Like for dynamic casts, metacasts between different class limiter types will only be accepted by the compiler if the target limiter metaclass is a subclass of the casted class limiter's metaclass. Typecasting to the same metaclass or one of its parent classes is refused for being redundant and typecasting to a metaclass in an unrelated branch of the class tree is refused because it will always fail.
Examples[edit]
The most common use for metacasts is when loading classes with the DynamicLoadObject() function:
local class<Actor> ActorClass< SEMI > ActorClass = class<Actor>(DynamicLoadObject(ActorClassName, class'Class')); Spawn(ActorClass);
The above example only works because of the metacast. DynamicLoadObject() only returns a reference of type Object, so a typecast is definitely required. A simple dynamic cast to type Class (which would actually be a metacast to class<Object>
)will not work here, because the Spawn() function requires an Actor class as its parameter.
Just for fun - weapon class roundtrip:
log(class<WeaponDamageType>(class<InstantFire>(class'AssaultRifle'.default.FireModeClass[0]).default.DamageType).default.WeaponClass); // logs XWeapons.AssaultRifle
What happened there? First we access the FireModeClass[0] of class AssaultRifle, which returns class AssaultFire. That's an InstantFire subclass and its DamageType property returns class DamTypeAssaultBullet. That, in turn, is a WeaponDamageType with its WeaponClass set to class AssaultRifle. Note that all of these are classes, not instances, so we always need to access default values via ".default.
".
Other conversions[edit]
Some type conversions are not allowed by the compiler. For example typecasting a string to a reference type will not work. If you need to perform such conversions, you will have to go a different route.
string to name[edit]
The string to name typecast is not allowed before Unreal Engine 3. Instead you have to rely on the SetPropertyText() function and a name-type variable declared at the class scope, optionally encapsulating the conversion in a function:
var name NameConversionHack; function name StringToName(string str) { SetPropertyText("NameConversionHack", str); return NameConversionHack; }
So what's happening here is that you let the engine parse your input string and assign it to a variable with the name you specified. The usual conventions for name values apply, so capitalization might get lost if the name was used before already.
string to object reference[edit]
To convert a string to an object or class reference, you can use the DynamicLoadObject() function. This will attempt to load the object, if it does not already exist. Such a load operation may require considerable amount of time if the engine needs to load the containing package first. Remember that UnrealScript is executed between rendering frames, so long load operations will cause the game to stop until loading either finished successfully or failed. Note that the DynamicLoadObject() function may emit a log warning if loading failed. See the article on the DynamicLoadObject() function for more details.
As an alternative for already loaded objects you can use the FindObject() function in Unreal Engine 2 and later. Its usage is similar to that of DynamicLoadObject(), but it will never tryto load objects from disk if they do not already exist.
string to struct[edit]
While there is not direct support for typecasting strings to a struct value or vice versa, you might want to try using the SetPropertyText() and GetPropertyText() functions on class variables of the desired struct type. The string to struct conversion would be similar to the string to name conversion mentioned above. The other way, struct to string, works as follows:
var YourStructType StructVariable; function string YourStructToString(YourStructType val) { StructVariable = val; // GetPropertyText() only operates on class variables! return GetPropertyText("StructVariable"); }
You can use this example to find out the required string format for the string to struct conversion.
Declarations | Preprocessor • Classes • Interfaces • Cpptext • Constants • Enums • Structs • Variables (Metadata) • Replication block • Operators • Delegates • Functions • States • Defaultproperties (Subobjects) |
---|---|
Types | bool • byte • float • int • name • string • Object • Class • Enums • Structs (Vector ⋅ Rotator ⋅ Quat ⋅ Color) • Static arrays • Dynamic arrays • Delegates • Typecasting |
Literals | Boolean • Float • Integer • Names • Objects (None ⋅ Self) • Vectors • Rotators • Strings |
Flow | GoTo • If • Assert • Return • Stop • Switch • While • Do...Until • For • ForEach • Break • Continue |
Specifiers | Super • Global • Static • Default • Const |
UnrealScript | Syntax • .UC • .UCI • .UPKG • Comments • #directives • Native |