InsaneMonster
Hosted Project: W3RR
- Joined
- Jul 20, 2011
- Messages
- 510
WARNING: This tutorial can be applied only to Warcraft 3 Reforged!
GOAL
To animate the portraits of any character, in any map, for any dialogue (consisting of a certain sound, a certain "spoken" text and a certain "speaker" name). Note that making a custom dialogue means to link togheter all that pieces in a custom way.
KNOWLEDGE REQUIRED
- Intermediate knowledge of the trigger editor
- Basic knowledge of JASS or Lua and GUI Custom Script action
- How to import external files into a map
- Warcraft 3 Reforged World Editor
- Ladik's Casc Viewer
- (optional) FaceFX (no save free edition)
If you prefer a video-tutorial instead, you can check the following video:
TABLE OF CONTENT
- BACKGROUND (OPTIONAL)
- EXPORTING ANIMATIONS USING CASC VIEWER
- IMPORTING ANIMATIONS INTO FACEFX (OPTIONAL)
- BLIZZARD FACIAL ANIMATIONS NAMING SCHEME
- CONVERSATION.JSON FILES AND RESTORING LIP-SYNC
- SETTING DIALOGUES PROPERTIES IN-EDITOR
BACKGROUND (OPTIONAL)
This section provides insights into the process of discovering the method explained in this tutorial and the technology used by Blizzard in Reforged. You can skip it if you are not interested.
From the Reforged beta itself, it was clear there was no easy way to customize or even keep animated potraits (with lip-sync) in unofficial built maps. Even re-saving a Blizzard's campaign map disabled the lip-sync of all characters (with some weirds exceptions) and there seemed to be no way to have them back. Not to mention the impossibility of adding custom dialogues with "some" lip-sync behind it. Even the texts of all dialogues seems to be somewhat hardcoded in the campaign.
In a post here, on Hive Workshop, Blizzard actually told lip-sync was a functionality not available to modders yet. This post is just some months old. Well, this is completely wrong, and the fact they hidden something inside their campaign files does not mean this something is not available to us, somehow. I find actually amazing how much misinformation they are spreading about their own game. This is very deterimental for it. For a game which is not going great... to say the least!
First of all, we need to understand why Reforged animated portraits are so much more complex than their Classic counterparts. Many think Blizzard should have added default "talking" animations, like in Classic.
Now, I think there is a reason they didn't. And that is they look bad. Reforged models are less cartoony and higher quality than Classic ones. Because of that, a "faked" lip-movement I feel it would look terrible.
I indeed think they did the right choice going with true lip-sync. And of course, they used FaceFX, which is the same tool they used for Starcraft 2, for the exact same purpose. With this tool, the process of lip-syncing is very easy, and it is a tool commonly used in the videogame industry. They have built some kind of in-engine integration for FaceFX. I believe it's like an "engine inside the engine", which is activated when some appropriate functions are called.
You may have noticed FaceFX to be very expensive. There is however a free version on their website, allowing only to inspect files. This version prevents you from saving anything. If you want to use FaceFX to inspect the facial animations of Reforged, you may be interested in downloading it. I'll show you how to use FaceFX to check the content of an animation file, but consider that in most cases this tutorial should contain all the information you need to skip that specific section, if you so wish.
The engine integration of FaceFX in Reforged seems to output a certain type of "log-files", the conversation.json files.
Note: actually, by talking directly with a Blizzard employee, it seems that conversation.json files are used by a Dialogue Editor which is not yet released inside the World Editor.
You can find conversation.json files inside almost any campaign map if you open the Asset Manager. Each one of these files is regenerated when the map is saved.
While Re-Reforging the Prologue campaign, I noticed that when exporting the same conversation.json file from an edited campaign map there were some differences with respect to the original one. The interesting fact is that the edited one uses a "relative" animation set file-path, while the original does not.
This is probably due to some kind of relation set by Blizzard in their campaign file which, sadly, is not currently accessible. By investingating this behaviour I found that re-importing all the animations in the map actually solved the problem everyone was having of lip-sync not working on edited campaign maps!
All the references for the dialogue, I mean the links to animations, sounds, texts, etc. were still there, but for some reason the engine was searching the files in a different place.
What about making custom dialogues? Many thinks there is no function in the editor to link togheter all components of a dialogue, and so there is no way to animate a portrait. Actually, this is what Blizzard itself said. And they all are wrong.This is probably due to some kind of relation set by Blizzard in their campaign file which, sadly, is not currently accessible. By investingating this behaviour I found that re-importing all the animations in the map actually solved the problem everyone was having of lip-sync not working on edited campaign maps!
All the references for the dialogue, I mean the links to animations, sounds, texts, etc. were still there, but for some reason the engine was searching the files in a different place.
User Solcius123, on the same post linked above, found that there are three strange identical functions in the trigger GUI editor. To find them you can do the following:
- Click on any trigger on any map of your choice.
- Right click inside the trigger and choose "New Condition"
- Select "Boolean Comparison"
- Select the left operand of the equality check and scroll up until you find three "Sound - Set Facial Animation Label"
EXPORTING ANIMATIONS USING CASC VIEWER
You can skip this section if you already know how to extract .animset_ingame files from Reforged CASC storage or you plan to link directly the files in the game folder. Note that CASC Viewer could still be necessary to see the exact name and path of each one of these files.
Reforged uses for lip-sync .animset_ingame files, which are stripped down versions of the .animset files, the ones FaceFX actually process. These files contains the facial animations of a given character, each animation identified by a specific "name" and usually grouped together under a "group name".
To extract the .animset_ingame files you can use Ladik's Casc Viewer or any other tool able to open Reforged CASC storage. This is just like extracting any other file from the game CASC storage. Anyway, if you don't know how to do so, you can do the following:
- Open Ladik's Casc Viewer.
- Click on Game Storage in the upper left portion of the screen.
- Click on Warcraft 3 Reforged in the dialog.
- Open war3.war3mod.
- Open _hd.war3mod.
- Open _locales.
- Open the locale you need, usually enus.war3mod, which is the english locale.
- Now, if you want a face-animation used by Blizzard in the campaign open the folder sound.
- Now open the folder dialogue.
- Now open the folder faceanimation.
- Select the mission of your choice and export the .animset_ingame file of the character you need.
I'll be exporting orcx03b/facialanimation/proudmoore.animset_ingame.
If you want instead to use a face-animation employed by Blizzard in the unit responses, open the folder units, then open the race and the unit of your choice. I'll be exporting human/heroarchmage/heroarchmage_portrait.sd.animset_ingame.
Note that the Archmage animset is also compatible with the High Elf Archmage model.
Once the files are extracted, you can import them into your map using the Asset Manager.
IMPORTING ANIMATIONS INTO FACEFX (OPTIONAL)
This section is optional. Use it only if the .animset_ingame files don't follow for some reason the naming scheme defined in the following section or, of course, if you are interested!
When the .animset_ingame files are extracted, you can import them into FaceFX to check their content. This is useful to understand animations names and group names, which are necessary to properly link them inside Reforged editor. I will show how to link all data inside the editor in the following sections. To import an .animset_ingame file inside FaceFX do the following:
- Open FaceFX.
- Click on Actor in the toolbar.
- Click on Mount Animation Set.
- Click yes in the dialog.
- Use explorer to find the .animset_ingame file you want to open. In my case proudmoore.animset_ingame. Remember to set the filter to "All files" otherwise the .animset_ingame files will not be visible.
- Now a warning appears. The warning is due to the fact .animset_ingame files are "stripped down" versions. Ignore the warning and press ok.
- Another warning appears, detecting some inconsistencies. I don't know what this is all about, probably the same as the warning before. Just ignore this one too and click ok.
- Now the file is opened. You cannot see anything, don't worry, it's normal. Just check the animation group name and you'll see now there is a name in the group name tab: Map-Proudmoore.
- Beside it, you can see a list of animations you can choose from the drop down menu. Each one of these animations is connected to a certain sound file you can find in that map of the campaign.
- Now we can do the same for the other animation set we've extracted. That is, heroarchmage_portrait.animset_ingame.
- Notice that the group name is now BaseSD and each animation has the name of the respective sound file for that unit response during the game.
BLIZZARD FACIAL ANIMATIONS NAMING SCHEME
This section contains the naming scheme followed by Blizzard to define all the facial animations.
There is a quite precise naming scheme followed by Blizzard in naming the animations. From my research it appears to be the following:
For all unit responses in-game:
GROUP NAME: BaseSD
ANIMATION NAME: the same as the respective sound file
For all campaign dialogues:
GROUP NAME: character related in the form "Map-NameOfCharacter" (for example: "Map-Thrall", "Map-Proudmoore", "Map-Grunt", etc)
ANIMATION NAME: the same as the respective sound file
I've not checked them all, of course, but these rules should be quite accurate to let you avoid the entire FaceFX steps of this tutorial in most cases.
CONVERSATION.JSON FILES AND RESTORING LIP-SYNC
This section allows you to understand what the conversation.json files contain and how to restore already linked lip-sync in official campaign maps when they are edited. Note that understanding conversation.json files is required to properly link all components inside the editor in the last section!
To see the content of a conversation.json file, open the Warcraft 3 Reforged editor on any Blizzard campaign map. Once it is loaded, open the Asset Manager and export the conversation.json file.
Once exported, open it with any text editor. You can see that the file uses a list of string (war3map.wts) and consists in a list of triggers in which dialogues appear in a certain order.
We need to inspect carefully the file to understand each component of the FaceFX integration. To my knowledge, the properties you can find inside each one of these files are the following:
Once exported, open it with any text editor. You can see that the file uses a list of string (war3map.wts) and consists in a list of triggers in which dialogues appear in a certain order.
We need to inspect carefully the file to understand each component of the FaceFX integration. To my knowledge, the properties you can find inside each one of these files are the following:
- "conversationOrder" is the order of the dialogue in the trigger. This is altered when you generate/reorder new dialogues in new or existing triggers.
- "speakerModel" is just the model of the speaker.
- "speakerNameId" is the id of the string contaning the name of the speaker (for example, Thrall). This is set with a specific function in the editor and not when calling the dialogue itself.
- "speakerUnitID" is the id in the object editor of the unit which is speaking, if any.
- "soundFile" is just the sound file used.
- "dialogueId" is the id of the string containing the dialogue, the text displayed in the subtitles.
This is also set using a specific function in the editor and not when calling the dialogue itself. - "animationLabel" is the name of the animation as you can check in FaceFX or using the naming scheme I've provided. This is set with an "hidden" function in the editor and not when calling the dialogue itself.
- "animationGroupLabel" is the group name of the animation as you can check in FaceFX or using the naming scheme I've provided. This is set with an "hidden" function in the editor too and not when calling the dialogue itself.
- "animationSetFilepath" is the path of the .animset_ingame file for this dialogue. If relative, it uses the map Asset Manager folder as the root directory.
- "animationSetFilepathMapRelative" is just a boolean (true, false) flag defining if the previous property uses a relative file-path or not.
Beside that, known that when an official campaign map is edited, more often than not the file-path is for some reason set to relative. Since there is no .animset_ingame file at the map relative path, the animations are broken even if all the links are still there. To restore them, just import in the edited map (using the Asset Manager) all the required .animset_ingame files at the path specified in the conversation.json. Remeber to export the conversation.json file once the map is already edited to get the proper file-path.
SETTING DIALOGUES PROPERTIES IN-EDITOR
This section explains the function used to properly link togheter all components of a dialogue in order to make custom dialogues. Note that making a custom dialogue is the necessary step to take in order to animate the portraits.
Now, we know what to link togheter to generate our custom dialogues but not how to do so.
The functions you need actually are just the following, written in JASS:
JASS:
call SetSoundFacialAnimationLabel(sound_variable, "animation_name (the animationLabel)")
call SetSoundFacialAnimationGroupLabel(sound_variable, "animation_group_name (the animationGroupLabel)")
call SetSoundFacialAnimationSetFilepath(sound_variable, "relative_path_to_animset OR game_asset_path")
call SetDialogueTextKey(sound_variable, "spoken_text")
call SetDialogueSpeakerNameKey(sound_variable, "speaker_name")
As you can see their names are quite intuitive. Also, all of them have a corrispective in the GUI trigger editor:
- SetSoundFacialAnimationLabel is displayed as Sound - Set Facial Animation Label under boolean function when creating conditions.
- SetSoundFacialAnimationGroupLabel is displayed as Sound - Set Facial Animation Label under boolean function when creating conditions.
- SetSoundFacialAnimationSetFilepath is displayed as Sound - Set Facial Animation Label under boolean function when creating conditions.
- SetDialogueTextKey is displayed as Cinematic - Set Dialogue Text ID under actions.
- SetDialogueSpeakerNameKey is displayed as Cinematic - Set Dialogue Text ID under actions.
This is an example of how I've organized all the functions in one of my maps, which is completely built from scratch and in which portraits are all fully animated. You can see I've made some additional helper functions to load what I need for each animation set:
JASS:
function LoadD20Proudmore08FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_D20Proudmoore08, "D20Proudmoore08")
call SetSoundFacialAnimationGroupLabel(gg_snd_D20Proudmoore08, "Map-Proudmoore")
call SetSoundFacialAnimationSetFilepath(gg_snd_D20Proudmoore08, "FacialAnimation/Proudmoore.animset")
call SetDialogueTextKey( gg_snd_D20Proudmoore08, "For Lordaeron!" )
call SetDialogueSpeakerNameKey( gg_snd_D20Proudmoore08, "Admiral Proudmoore" )
endfunction
function LoadD20Proudmore09FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_D20Proudmoore09, "D20Proudmoore09")
call SetSoundFacialAnimationGroupLabel(gg_snd_D20Proudmoore09, "Map-Proudmoore")
call SetSoundFacialAnimationSetFilepath(gg_snd_D20Proudmoore09, "FacialAnimation/Proudmoore.animset")
call SetDialogueTextKey( gg_snd_D20Proudmoore09, "For Sir Lothar!" )
call SetDialogueSpeakerNameKey( gg_snd_D20Proudmoore09, "Admiral Proudmoore" )
endfunction
function LoadD20Proudmore10FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_D20Proudmoore10, "D20Proudmoore10")
call SetSoundFacialAnimationGroupLabel(gg_snd_D20Proudmoore10, "Map-Proudmoore")
call SetSoundFacialAnimationSetFilepath(gg_snd_D20Proudmoore10, "FacialAnimation/Proudmoore.animset")
call SetDialogueTextKey( gg_snd_D20Proudmoore10, "Death to the Blackbloods!" )
call SetDialogueSpeakerNameKey( gg_snd_D20Proudmoore10, "Admiral Proudmoore" )
endfunction
function LoadO01Thrall22FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_O01Thrall22, "O01Thrall22")
call SetSoundFacialAnimationGroupLabel(gg_snd_O01Thrall22, "Map-Thrall")
call SetSoundFacialAnimationSetFilepath(gg_snd_O01Thrall22, "FacialAnimation/ThrallO01.animset")
call SetDialogueTextKey( gg_snd_O01Thrall22, "Lok'Tar! Lok'Tar!" )
call SetDialogueSpeakerNameKey( gg_snd_O01Thrall22, "Thrall" )
endfunction
function LoadO07Thrall38FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_O07Thrall38, "O07Thrall38")
call SetSoundFacialAnimationGroupLabel(gg_snd_O07Thrall38, "Map-ThrallMountless")
call SetSoundFacialAnimationSetFilepath(gg_snd_O07Thrall38, "FacialAnimation/ThrallO07.animset")
call SetDialogueTextKey( gg_snd_O07Thrall38, "What the hell is going on here?!" )
call SetDialogueSpeakerNameKey( gg_snd_O07Thrall38, "Thrall" )
endfunction
function LoadThrallPissed3FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_ThrallPissed3, "ThrallPissed3")
call SetSoundFacialAnimationGroupLabel(gg_snd_ThrallPissed3, "BaseSD")
call SetSoundFacialAnimationSetFilepath(gg_snd_ThrallPissed3, "FacialAnimation/Thrall_Portrait.animset")
call SetDialogueTextKey( gg_snd_ThrallPissed3, "The spirits will guide me..." )
call SetDialogueSpeakerNameKey( gg_snd_ThrallPissed3, "Thrall" )
endfunction
function LoadThrallYesAttack1FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_ThrallYesAttack1, "ThrallYesAttack1")
call SetSoundFacialAnimationGroupLabel(gg_snd_ThrallYesAttack1, "BaseSD")
call SetSoundFacialAnimationSetFilepath(gg_snd_ThrallYesAttack1, "FacialAnimation/Thrall_Portrait.animset")
call SetDialogueTextKey( gg_snd_ThrallYesAttack1, "... Lok'Narosh!" )
call SetDialogueSpeakerNameKey( gg_snd_ThrallYesAttack1, "Thrall" )
endfunction
function LoadHeroArchMageYesAttack3FacialAnimations takes nothing returns nothing
call SetSoundFacialAnimationLabel(gg_snd_HeroArchMageYesAttack3, "HeroArchMageYesAttack3")
call SetSoundFacialAnimationGroupLabel(gg_snd_HeroArchMageYesAttack3, "BaseSD")
call SetSoundFacialAnimationSetFilepath(gg_snd_HeroArchMageYesAttack3, "FacialAnimation/HeroArchmage_Portrait.animset")
call SetDialogueTextKey( gg_snd_HeroArchMageYesAttack3, "Infury frostaris sedaa!" )
call SetDialogueSpeakerNameKey( gg_snd_HeroArchMageYesAttack3, "Kelen the Seeker" )
endfunction
For completion, here there is a Lua version:
Lua:
-- Admiral Proudmoore
function LoadD20Proudmore08FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_D20Proudmoore08, "D20Proudmoore08")
SetSoundFacialAnimationGroupLabel(gg_snd_D20Proudmoore08, "Map-Proudmoore")
SetSoundFacialAnimationSetFilepath(gg_snd_D20Proudmoore08, "Sound/Dialogue/FaceAnimation/Orcx03b/FacialAnimation/Proudmoore.animset")
end
function LoadD20Proudmore09FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_D20Proudmoore09, "D20Proudmoore09")
SetSoundFacialAnimationGroupLabel(gg_snd_D20Proudmoore09, "Map-Proudmoore")
SetSoundFacialAnimationSetFilepath(gg_snd_D20Proudmoore09, "Sound/Dialogue/FaceAnimation/Orcx03b/FacialAnimation/Proudmoore.animset")
end
function LoadD20Proudmore10FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_D20Proudmoore10, "D20Proudmoore10")
SetSoundFacialAnimationGroupLabel(gg_snd_D20Proudmoore10, "Map-Proudmoore")
SetSoundFacialAnimationSetFilepath(gg_snd_D20Proudmoore10, "Sound/Dialogue/FaceAnimation/Orcx03b/FacialAnimation/Proudmoore.animset")
end
-- Thrall
function LoadO01Thrall22FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_O01Thrall22, "O01Thrall22")
SetSoundFacialAnimationGroupLabel(gg_snd_O01Thrall22, "Map-Thrall")
SetSoundFacialAnimationSetFilepath(gg_snd_O01Thrall22, "Sound/Dialogue/FaceAnimation/Orc01/FacialAnimation/Thrall.animset")
end
function LoadO07Thrall38FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_O07Thrall38, "O07Thrall38")
SetSoundFacialAnimationGroupLabel(gg_snd_O07Thrall38, "Map-ThrallMountless")
SetSoundFacialAnimationSetFilepath(gg_snd_O07Thrall38, "Sound/Dialogue/FaceAnimation/Orc07/FacialAnimation/ThrallMountless.animset")
end
function LoadThrallPissed3FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_ThrallPissed3, "ThrallPissed3")
SetSoundFacialAnimationGroupLabel(gg_snd_ThrallPissed3, "BaseSD")
SetSoundFacialAnimationSetFilepath(gg_snd_ThrallPissed3, "Units/Orc/Thrall/Thrall_Portrait.sd.animset")
end
function LoadThrallYesAttack1FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_ThrallYesAttack1, "ThrallYesAttack1")
SetSoundFacialAnimationGroupLabel(gg_snd_ThrallYesAttack1, "BaseSD")
SetSoundFacialAnimationSetFilepath(gg_snd_ThrallYesAttack1, "Units/Orc/Thrall/Thrall_Portrait.sd.animset")
end
-- Kelen the Seeker
function LoadHeroArchMageYesAttack3FacialAnimations()
SetSoundFacialAnimationLabel(gg_snd_HeroArchMageYesAttack3, "HeroArchMageYesAttack3")
SetSoundFacialAnimationGroupLabel(gg_snd_HeroArchMageYesAttack3, "BaseSD")
SetSoundFacialAnimationSetFilepath(gg_snd_HeroArchMageYesAttack3, "Units/Human/HeroArchmage/HeroArchmage_Portrait.sd.animset")
end
Please note that in this Lua version the two not bugged functions are called as GUI actions, because that allowed me to better use the localized facial animations in the game (for the Re-Reforged project). For the same reason, and to save memory space, I'm linking to path to the files contained in the game assets, instead to their relative path when such files are imported into the map. If you don't need localization, you can just stick to the custom script implementation provided above and easily converting the two missing functions to Lua by removing the call keyword. I would always suggest, though, to use the game file path instead of the relative ones, to avoid importing unnecessary assets.
To actually load the functions defined here, I've used a simple trigger during initalization in which, using the action Custom Script, I call each one of these functions to generate all the references in-game when I'm initializing the map:
And... this is it people!
You can now have animated portraits for all characters in any map. Now, I've used them with the respective sound files, but you can do whatever you want. It's a bit more complex than classic, I know, but the result is worth the effort in my opinion.
Hope you liked the tutorial, let me know if it's clear or not.
Have fun animating those characters!
Last edited: