Adding and changing art assets - XCOM:EU 2012

From Nexus Mods Wiki
Jump to: navigation, search


The material in this article has been derived from the Nexus XCOM Mod Talk thread R&D Changing Item Meshes.

A common use of mods is to add or change the appearance of items in the game world. Knowledge of this process has been largely lacking for XCOM so far, but this article will capture what has been discovered.

A few points of clarification first.

  • Technically the word "model" in this context refers to a "3D model", which is a representation of any three-dimensional surface via specialized software. Everything in a computer game is constructed of such "models", so it is often used as a generic, collective term.
  • This "3D model" representation consists of:
    • a "mesh": the common term for a "polygon mesh", which is a collection of vertices, edges and faces that defines the shape of a polyhedral object generated by software. When viewed by itself, a "mesh" can easily been seen as the structural framework of the model defining it's shape and outline as an open lattice framework.
    • a "texture": refers to the surface appearance generated by software, which when applied to "3D models" means a bitmapped image applied to a "mesh". The "texture" is the surface material stretched over the framework of the model.
    • various types of "maps": applied to the texture which affect the way light interacts with the surface. Some provide "aging" effects, some embellishments like tattoos or scars, some how the object appears in the absence of a light source, etc.

There is a technique already in use for changing "textures" via TFC files, but it uses a "memory replacement" technique instead of replacing art assets in the manner described here. (See the Modifying Textures article for that technique.) This article is focused on making such complete "model" (rather than simply "texture") changes directly to the "code" in the files used by the game engine.

Within the Unreal Game Engine world, these are collectively known as "art assets". This is not a tutorial on how to create such art assets, but rather on how to incorporate them into the game.

Programs and Tools


Essentially there are two different things that could be done in regard to adding or changing the appearance of items in the game world:

  • Changing any existing model into something else
  • Adding additional models (new items, new aliens, etc)

The good news is that the Unreal Engine is actually designed to be fairly moddable, but its moddability is really designed around the "core market" of 3D First Person Shooters (FPS Games).

Pretty much all "Unreal Development Kit (UDK)" creations use the format of putting all unreal assets into a single folder with "<name>Game" for a name. XCOM is no exception, as most all of the assets are in the "XComGame" folder, with the notable exception of the binaries. This appears to be consistent with other UDK creations.

When the game launches, most all UPK assets are loaded, scanning all subfolders of <Steam install path>\XCom-Enemy-Unknown\XComGame. Note that for the "expansion" Enemy Within there is actually a separate <Steam install path>\XCom-Enemy-Unknown\XEW\XComGame folder which duplicates most of the unReal assets. It is an expansion in the sense that the Enemy Within code was built on the existing Enemy Unknown code, but install-wise it functions as a completely separate game.

Changing any existing model reference

To see how to add new or utilize different art assets, it's most useful to look at the DLC for Enemy Unknown. Most of the regular assets reside in the CookedPCConsole folder, including the bulk of the unRealScript in XComGame.UPK and XComStrategyGame.UPK, but also including all of the model/animation/sound assets. However there is also content added in the XComGame\DLC folder.

Digging into the Slingshot DLC, it contains its own CookedPCConsole and Config folder, which contains separately "cooked" UPKs and config files. This is part of the reason for the distinction between the "Default<config>.ini" files in the main config folder and the "XCom<config>.ini" files in the My Games folder -- the "XCom<config>.ini" files are combined forms of the various config files scattered throughout the XComGame root folder.

For example, the Slingshot (Day60DLC) folder has a DefaultContent.ini that includes various deco assets, new hair/helmet options, as well as Zhang's "civilian" body for the "Low Places" mission: +BodyPackageInfo=(Id=309,CustomTag="Zhang",ArchetypeName="Body_Zhang.ARC_Body_Zhang", Gender=eGender_Male, Character=eChar_Civilian, Race=eRace_Asian, Type=eCivilian_Cold)

The DLC_Day60\CookedPCConsole folder contains these art assets:

  • "Body_Zhang_SF.upk",
  • "CharTextures_DLC_Day060.tfc",
  • "Lighting_DLC_Day060.tfc",
  • "Textures_DLC_Day060.tfc".

(Presumably Firaxis used the UDK to create this separately cooked DLC which can reside in a separate folder within the main XComGame folder, yet still be loaded. Unfortunately I'm not very proficient with either UDK or 3D modelling in general.)

Calling assets

However, there is a further step beyond simply creating new content via UDK and getting the Unreal Engine to load the assets -- the assets have to actually be called during the game.

To illustrate this I'm going to describe the steps by which we are converting the Genemod Armors included in Enemy Within to new armors (which in vanilla are visually distinct but have no other gameplay purpose other than to designate genemodded soldiers) for the "Long War" mod. In principle if a 3D modeller were able to create a brand new armor model/animation set, this same technique could be used to swap/insert it into the game.

  • The mappings from "pawntype" to "model" for all of the genemod armors had already been done in DefaultContent.ini. For example:

This performs the mapping from byte values (e.g. ePawnType_Male_2_Skeleton_GM = 78) into the actual model (e.g. Soldier_MaleSkeleton_GM_SF.upk is a seek-free unreal packed in the CookedPCConsole folder, and is distinct from the Soldier_MaleSkeleton_SF.upk package). I'm presuming that ARC_MaleSkeleton_GM is a particular asset within the packagefile, but I'm not very familiar with how Unreal 3D models are defined.
  • I rewrote the XGBattleDesc.MapSoldierToPawn function to handle new item IDs.
    We handle new items typically be taking over "marker" positions within the existing EItemType enumeration. For example, eItem_END_WEAPONS = 57 is in EItemType but doesn't represent an existing item in the game. Since we've figured out how to expand enumerations, in general adding new items could be a little simpler. However replaceing existing items is even easier.
    Following the Skeleton GM armor model example, I put in the code:
case 58:  // eItem_BEGIN_ARMOR, light skeleton / Skeleton Suit
return(iGender == 1 ? 78 : 71)  // ePawnType_Male_2_Skeleton_GM,  ePawnType_Female_2_Skeleton_GM

This creates the mapping from the new EItemID 58 (eItem_BEGIN_ARMOR) to either pawn type for the Skeleton GM (either male or female, depending). The mapping from ePawnType to the actual model was accomplished in step 1.
  • Added Deco armor options for the new armor type. This is also handled in the DefaultContent.ini, via the new line:
ArmorDeco=(Armor=eItem_BEGIN_ARMOR,ArmorKit=eKit_Deco_Skeleton0)  ; light skeleton / Banshee

In this case I mapped the new item type to use the existing set of Skeleton armor decos.

Essentially this is all that was done. Adding the new armor types was actually quite straightforward, requiring far fewer hex changes than something like the psionic rework or officer system in "Long War" (like 2 or 3 hex changes for new armors, but 40-50 for each of psionics/officer systems).

Adding additional models

In principle I think that to change out an existing model for a new one would require only the following :

  1. Create the new model, which has to include all necessary animations -- not having an animation generally results in the game hanging
  2. Compile the model (apparently UE3 can work with uncooked models on the PC) into a separate subfolder inside XComGame.
  3. Either create new config files that override the UnitPackageInfo settings in the core game (to map an existing pawn or itemtype to the new art asset), or modify the DefaultContent.ini in the core game itself.

The different types of PackageInfos in DefaultContent.ini appear to be:

  • UnitPackageInfo -- defines armors -- this doesn't include the head, as the head as another attached "item"
  • HeadPackageInfo -- defines possible heads that can be attached to the armor
  • WeaponPackageInfo -- defines all possible weapons in the game -- basically anything "equippable" is considered a weapon (e.g. SCOPE, nano-fiber vest)
  • ArmorKitPackageInfo -- armor kits, including both weapon kits (e.g. Shotgun or LMG, or deco kits)
  • HairPackageInfo -- this is hair or helmet, and is an attachment to a head

As an example of a particular HairPackageInfo: HairPackageInfo=(Id=422,CustomTag="Annette",ArchetypeName="Hair_FemHair_DLC0.FemHair_DLC0",Gender=eGender_Female) is how the Annette special character custom hair is configured. This corresponds to the "Hair_FemHair_DLC0_SF.upk" file in CookedPCConsole.

Unfortunately I can't easily test much of this out since I'm not familiar with either 3D modeling in general or UDK in particular.

Accessing existing assets

Gildor's UE Viewer/uModel tool can extract all of the skeleton/mesh/texture/animation components from an existing UPK, and Gildor's ActorX plug-in for 3DS Max will allow the importing of these files into 3DS Max.

From Gildor's page:

Epic Games has stopped work on the ActorX Exporter in favor of a new advanced FBX format, but Gildor got permission to compile his own version, available as Gildor's ActorX All.

So probably the best starting point would be:

  1. Extract PSK/PSA files from an XCOM UPK with UE Viewer/uModel.
  2. Import PSK/PSA files into 3DS Max with Gildor's ActorX plug-in for 3DS Max.
  3. Use existing animations/skeletons/wireframes as template to create new model.
  4. Import 3DS Max object into the UDK.
  5. Use UDK to create new "plug in" UPK/TFC files.
  6. Place the new UPK/TFC files in a new subfolder of XComGame.
  7. Alter config files to load new art assets from the appropriate UPK/TFC files.

See the wiki article Modding with the UDK - XCOM:EU 2012 for instructions on how to incorporate "new" (or at least modified vanilla as "new") art asset files into the game.

Additional information has come to light regarding the possible range and use of values for colors and tints. See the wiki articles Color Palettes - XCOM:EU 2012, Changing colors and tints - XCOM:EU 2012, and Initial soldier appearance - XCOM:EU 2012.

Code Breakdown


Low-complexity things like hats/helmets would be the ideal first test subjects, even better than hair (since hair has animations, which hats/helmets don't AFAIK). Hair models are also generally usable on civilians, also this might be configurable (I say "might" because Firaxis has sometimes set up similar flags that had their functionality removed). Recall that a lack of expected animations will cause a CTD error.

However, both hair and helmet options are configured together (unsurprisingly, since they are put together in a single spinner).

The basic structure that is filled out is:

struct native XComHairPackageInfo extends XComPackageInfo
   var config int Id;
   var config name CustomTag;
   var config XGTacticalGameCoreNativeBase.EGender Gender;
   var config XGGameData.ECharacter Character;
   var config XGTacticalGameCoreNativeBase.ECharacterRace Race;
   var config XGGameData.EPawnType Pawn;
   var config bool bCanUseOnCivilian;
   var config bool bIsHelmet;

The parent class XComPackageInfo contains:

struct native XComPackageInfo
   var init config string ArchetypeName;
   var transient Object Archetype;
   var init array<init ArchetypeLoadedCallback> LoadedCallbacks;

Not all structure elements need be filled out -- any undefined structure elements get the default values (e.g. 0, false, "", none)

All of the existing XComHairPackageInfo configuration is done in DefaultContent.ini. For Enemy Within this looks like:

HairPackageInfo=(Id=1,ArchetypeName="Hair_FemHair_A.ARC_Hair_FemHair_A", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=2,CustomTag="EscapedAbductee",ArchetypeName="Hair_FemHair_B.ARC_Hair_FemHair_B", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=3,ArchetypeName="Hair_FemHair_C.ARC_Hair_FemHair_C", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=4,ArchetypeName="Hair_FemHair_D.ARC_Hair_FemHair_D", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=5,ArchetypeName="Hair_FemHair_E.ARC_Hair_FemHair_E", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=6,ArchetypeName="Hair_FemHair_F.ARC_Hair_FemHair_F", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=7,ArchetypeName="Hair_FemHair_G.ARC_Hair_FemHair_G", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=8,ArchetypeName="Hair_FemHair_H.ARC_Hair_FemHair_H", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=9,ArchetypeName="Hair_FemHair_I.ARC_Hair_FemHair_I", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=10,ArchetypeName="Hair_FemHair_J.ARC_Hair_FemHair_J", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=11,ArchetypeName="Hair_FemHair_K.ARC_Hair_FemHair_K", Gender=eGender_Female)
;HairPackageInfo=(Id=12,ArchetypeName="Hair_FemHair_L.ARC_Hair_FemHair_L", Gender=eGender_Female, bCanUseOnCivilian=true)
;HairPackageInfo=(Id=13,ArchetypeName="Hair_FemHair_M.ARC_Hair_FemHair_M", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=14,CustomTag="JoeKelly",ArchetypeName="Hair_MaleHair_A.ARC_Hair_MaleHair_A", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=15,CustomTag="Bodyguard1",ArchetypeName="Hair_MaleHair_B.ARC_Hair_MaleHair_B", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=16,CustomTag="Bodyguard",ArchetypeName="Hair_MaleHair_C.ARC_Hair_MaleHair_C", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=17,ArchetypeName="Hair_MaleHair_D.ARC_Hair_MaleHair_D", Gender=eGender_Male)
HairPackageInfo=(Id=18,CustomTag="Scientist",ArchetypeName="Hair_MaleHair_E.ARC_Hair_MaleHair_E", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=19,ArchetypeName="Hair_MaleHair_F.ARC_Hair_MaleHair_F", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=20,CustomTag="Engineer",ArchetypeName="Hair_MaleHair_G.ARC_Hair_MaleHair_G", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=21,CustomTag="General",ArchetypeName="Hair_MaleHair_H.ARC_Hair_MaleHair_H", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=22,CustomTag="MinisterThorne",ArchetypeName="Hair_MaleHair_I.ARC_Hair_MaleHair_I", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=23,CustomTag="MinisterHutch",ArchetypeName="Hair_MaleHair_J.ARC_Hair_MaleHair_J", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=24,ArchetypeName="Hair_MaleHair_K.ARC_Hair_MaleHair_K", Gender=eGender_Male, bCanUseOnCivilian=true)
;HairPackageInfo=(Id=25,ArchetypeName="Hair_MaleHair_L.ARC_Hair_MaleHair_L", Gender=eGender_Male)

for the base-game hair styles. Note that different hairstyles are locked to different genders (in general), and that most of them are apparently configured to be usable to construct civilians for terror missions.

Much further down in the same file is the "extra content" section, which configures new hair styles from the DLCs:

HairPackageInfo=(Id=307,CustomTag="Zhang",ArchetypeName="Hair_MaleHair_N.ARC_Hair_MaleHair_N", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=325,CustomTag=,ArchetypeName="Hair_MaleHair_O.ARC_Hair_MaleHair_O", Gender=eGender_Male, bCanUseOnCivilian=true)
HairPackageInfo=(Id=326,CustomTag=,ArchetypeName="Hair_FemHair_O.ARC_Hair_FemHair_O", Gender=eGender_Female, bCanUseOnCivilian=true)
HairPackageInfo=(Id=327,CustomTag=,ArchetypeName="Hair_FemHair_N.ARC_Hair_FemHair_N", Gender=eGender_Female, bCanUseOnCivilian=true)

as well as the helmets:


Back with Enemy Unknown, DLC content was made available in separate DLC versions of the *Content.ini files, both named XComContent.ini but placed within their respective DLC subfolders.

All of this hair/helmet/hat configuration data appears to be read in via the native function XComContentManager.GetContentInfo_Hair:

native final function bool GetContentInfo_Hair(int Id, out XComHairPackageInfo HairInfo);

which apparently retrieves a particular XComHairPackageInfo based on supplied Id.

This is called (along with similar other functions for other types of content) in XGBattleDesc.BuildSoldierContent:

if(m_kAppearance.iHaircut != -1)
    kRequest.eCategory = 6;
    kRequest.iID = m_kAppearance.iHaircut;
        if(XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentInfo_Hair(m_kAppearance.iHaircut, kHairInfo))
                    kRequest.iID = 2;
                    kRequest.iID = 18;

The setting up of the hair spinner for the Customize UI in the strategy game appears to use a XComUnitPawn.PossibleHairs function which is configured in XComHumanPawn.state'InHQ'.GetPossibleCustomParts:

XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForRace(6, byte(m_kAppearance.iRace), RaceHairs,, true);
XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForGender(6, byte(m_kAppearance.iGender), GenderHairs,, true);
XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForCharacter(6, byte(inCharacter.iType), CharacterHairs,, true);
XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).GetContentIdsForPawn(6, PawnType, PawnHairs, true);
XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).IntersectContentIds(RaceHairs, GenderHairs, RaceGenderHairs);
XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).IntersectContentIds(CharacterHairs, PawnHairs, CharacterPawnHairs);
XComContentManager(class'Engine'.static.GetEngine().GetContentManager()).IntersectContentIds(RaceGenderHairs, CharacterPawnHairs, PossibleHairs);

It first generates lists based on the current soldiers' race, gender, type and pawn. These are then intersected to form the final possible list.

Consequently to insert a new hair/helmet option, it should be as simple as adding a new line of the format:

HairPackageInfo=(Id=<NewID>,ArchetypeName="<PackageName>.<AssetName>", Gender=<Gender>, bCanUseOnCivilian=<true/false>)

The ID= and ArchetypeName= fields are required, but other fields are optional:

  • Gender=<eGender_Male/eGender_Female> -- defaults to eGender_None, so could be used by either
  • bCanUseOnCivilian=<true/false> - defaults to false, not usable by civilians
  • bIsHelmet=<true/false> - defaults to false
  • Race=<eRace_Caucasian/eRace_African/eRace_Asian/eRace_Hispanic> -- unused by vanilla, so unsure if/how this works
  • Character=<???> -- unsure how this could be used
  • Pawn=<???> -- could in theory be used to restrict certain types of hair/helmets to certain armors (based on pawn), but isn't used in vanilla

The Character here isn't referring to something like special characters or easter egg characters -- ECharacter differentiates civilians from soldiers from various alien types (e.g. eChar_Soldier, eChar_Sectoid, eChar_eChar_MutonBerserker). However this is all located inside XComHumanPawn, so none of it can be applied to aliens.

Of course the tricky part is creating the new UPK asset file with the UPK.

For some initial testing opened up Helmet_Kevlar0_SF.upk in umodel. It's a pretty straightforward object -- no bones, no animations, no attachment sockets. There are four different versions (2 male and 2 female) with from 1630-1690 vertices each.


Referred to by this article:

That refer to this article: