UMA_DCS prefab 加上 UMA Dynamic Character Avatar, 选择一个race,然后设置好默认的animator controller就可以运行了。
base recipe –> 确定一个裸体的人物模型。
wardrobe recipe –> 确定人物的衣服,鞋子,饰品等;这里可以设置A物体覆盖B物体。Dynamic Character Avatar中的Default Recipes可以为这个人物添加默认的衣服裤子等。每个Wardrobe recipe都只能和相对应的race一起才能正常工作。Wardrobe slot是设置这个物体是放在人身上的哪个部位的。还可以设置需要覆盖哪个部位的其他物件和base物件。
Dynamic Character Avatar中的Character Colors可以重载recipes中的shared colors,然后在slot中可以用shared color来影响overlay的颜色。这样就可以通过设置Character Colors来方便的设置人物的整体颜色。
所有的recipes和他们所用到的components都需要到UMA Global Library中去注册才能使用。制作DCS->wardrobe recipe
Misc->Mesh Hide Asset,可以用来隐藏某个slot上的某些三角形,已解决衣服和身体部分重叠的情况下的穿模问题。UMA内建工具可以编辑需要去掉那些三角形。在编辑的时候还可以把衣服覆盖在身体上,方便查看需要隐藏哪些三角形。
通过代码来改变人物的外貌
得到DynamicCharacterAvatar实例:
1
2
3
4using UMA;
using UMA.CharacterSystem;
DynamicCharacterAvatar avatar = GetComponent<DynamicCharacterAvatar>();设置Dna:
1
2
3
4
5
6
7
8//得到所有的DnaSetter
Dictionary<string, DnaSetter> dna = avatar.GetDNA();
//设置height dna
dna["height"].Set(1f);
//得到height dna
dna["height"].Get();
//重建人物
avatar.BuildCharacter();改变种族:
1
2
3
4
5// 把race改成HumanMaleDCS,改变后不需要调用BuildCharacter,
// race改变后所对应的dnaSetter的字典也会改变。
avatar.ChangeRace("HumanMaleDCS");
// 得到当前种族
avatar.activeRace;监听avatar的事件:
1
2
3// DynamicCharacterAvatar的一些时间可以通过下面类似的方法添加和删除。
avatar.CharacterUpdated.AddListener()
avatar.CharacterUpdated.RemoveListener()设置颜色:
1
2
3
4avatar.SetColor("Skin", Color.Black);// 设置名字为Skin的sharedColor的颜色为black
// true就是函数内部马上调用BuildCharacter,颜色马上就显示出来
// false则把新颜色缓存者,到下次BuildCharacter才会显示在人物上。
avatar.UpdateColors(true);得到颜色:
1
avatar.GetColor("Skin").color;
操作slot:
1
2
3
4
5
6
7
8
9
10
11
12// 把名字为Hair的slot设置成MaleHair1这个配件。
avatar.SetSlot("Hair", "MaleHair1");
//重建人物
avatar.BuildCharacter();
// 把名字为Hair的slot清空
avatar.ClearSlot("Hair");
//重建人物
avatar.BuildCharacter();
// 得到名字为Hair的slot的item的名字
avatar.GetWardrobeItemName("Hair");UMA 101 - Part 11 Simple Character Creator (2 of 3)
UMA 101 - Part 11a Simple Character Creator (2a of 3)
Save/Load一个人物
1
2
3
4
5
6
7// 这里可以得到一个json编码的字符串,保存了所有和当前人物相关的配置信息。
string recipeToBeSaved = avatar.GetCurrentRecipe();
// load之前需要清空所有的slots,不然如果load出来的人物没有相对应的slot的信息,
// 那么之前的slot将会被laod出来的人物保留着。
avatar.ClearSlots();
// 从一个string的recipe中load一个人物
avatar.LoadFromRecipeString(recipeToBeSaved);Utility Recipes就是类似于wardrobe recipes的东西,但是他不是给人物添加衣服的,而是给人物添加一个功能性的脚本,这个脚本可以自定义做很多事情。UMA里面内建了两个比较有用的Utility Recipes。
- ForearmTwistRecipe,用来解决手腕在绕着手臂旋转时,手腕和手臂连接处会不自然的扭曲到一起的问题。用了这Recipe后,旋转手腕会和正常人那样自然。
- ExpressionsRecipe,用来解决人物下巴往下掉的问题。因为如果动画中没有下巴的动画,那么下巴会在建模时的位置,一般都是在下面,让嘴巴张开的。加了这个Recipe,就可以调整下巴,让嘴巴比起来。当然还可以调整很多五官的位置的。
Expression Player可以控制人物的面部表情。内建的Enable Blinking可以让人物随机间隔的眨眼,Enable Saccades可以让眼球像真人那样转动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98using UMA;
using UMA.CharacterSystem;
using UMA.PoseTools; // for Expression Player
private ExpressionPlayer expression;
// OnUMACreated
expression = GetComponent<ExpressionPlayer>();
expression.enableBlinking = true; // 激活眨眼
expression.enableSaccades = true; // 激活眼球运动
// void Update,实现各种表情
float delta = 10 * Time.deltaTime;
switch(mood)
{
case 0: // Normal
expression.leftMouthSmile_Frown = Mathf.Lerp(expression.leftMouthSmile_Frown,0,delta);
expression.rightMouthSmile_Frown = Mathf.Lerp(expression.rightMouthSmile_Frown,0,delta);
expression.midBrowUp_Down = Mathf.Lerp(expression.midBrowUp_Down,0,delta);
expression.leftBrowUp_Down = Mathf.Lerp(expression.leftBrowUp_Down,0,delta);
expression.rightBrowUp_Down = Mathf.Lerp(expression.rightBrowUp_Down,0,delta);
expression.rightUpperLipUp_Down = Mathf.Lerp(expression.rightUpperLipUp_Down,0,delta);
expression.leftUpperLipUp_Down = Mathf.Lerp(expression.leftUpperLipUp_Down,0,delta);
expression.rightLowerLipUp_Down = Mathf.Lerp(expression.rightLowerLipUp_Down,0,delta);
expression.leftLowerLipUp_Down = Mathf.Lerp(expression.leftLowerLipUp_Down,0,delta);
expression.mouthNarrow_Pucker = Mathf.Lerp(expression.mouthNarrow_Pucker,0,delta);
expression.jawOpen_Close = Mathf.Lerp(expression.jawOpen_Close,0,delta);
expression.noseSneer = Mathf.Lerp(expression.noseSneer,0,delta);
expression.leftEyeOpen_Close = Mathf.Lerp(expression.leftEyeOpen_Close,0,delta);
expression.rightEyeOpen_Close = Mathf.Lerp(expression.rightEyeOpen_Close,0,delta);
break;
case 1: // Happy
expression.leftMouthSmile_Frown = Mathf.Lerp(expression.leftMouthSmile_Frown,0.7f,delta);
expression.rightMouthSmile_Frown = Mathf.Lerp(expression.rightMouthSmile_Frown,0.7f,delta);
expression.midBrowUp_Down = Mathf.Lerp(expression.midBrowUp_Down,-0.7f,delta);
expression.leftBrowUp_Down = Mathf.Lerp(expression.leftBrowUp_Down,0f,delta);
expression.rightBrowUp_Down = Mathf.Lerp(expression.rightBrowUp_Down,0f,delta);
expression.rightUpperLipUp_Down = Mathf.Lerp(expression.rightUpperLipUp_Down,0f,delta);
expression.leftUpperLipUp_Down = Mathf.Lerp(expression.leftUpperLipUp_Down,0f,delta);
expression.rightLowerLipUp_Down = Mathf.Lerp(expression.rightLowerLipUp_Down,-0f,delta);
expression.leftLowerLipUp_Down = Mathf.Lerp(expression.leftLowerLipUp_Down,-0f,delta);
expression.mouthNarrow_Pucker = Mathf.Lerp(expression.mouthNarrow_Pucker,0f,delta);
expression.jawOpen_Close = Mathf.Lerp(expression.jawOpen_Close,0f,delta);
expression.noseSneer = Mathf.Lerp(expression.noseSneer,0.1f,delta);
expression.leftEyeOpen_Close = Mathf.Lerp(expression.leftEyeOpen_Close,-0.2f,delta);
expression.rightEyeOpen_Close = Mathf.Lerp(expression.rightEyeOpen_Close,-0.2f,delta);
break;
case 2: // Sad
expression.leftMouthSmile_Frown = Mathf.Lerp(expression.leftMouthSmile_Frown,-0.8f,delta);
expression.rightMouthSmile_Frown = Mathf.Lerp(expression.rightMouthSmile_Frown,-0.8f,delta);
expression.midBrowUp_Down = Mathf.Lerp(expression.midBrowUp_Down,0.7f,delta);
expression.leftBrowUp_Down = Mathf.Lerp(expression.leftBrowUp_Down,-0.3f,delta);
expression.rightBrowUp_Down = Mathf.Lerp(expression.rightBrowUp_Down,-0.3f,delta);
expression.rightUpperLipUp_Down = Mathf.Lerp(expression.rightUpperLipUp_Down,0f,delta);
expression.leftUpperLipUp_Down = Mathf.Lerp(expression.leftUpperLipUp_Down,0,delta);
expression.rightLowerLipUp_Down = Mathf.Lerp(expression.rightLowerLipUp_Down,0f,delta);
expression.leftLowerLipUp_Down = Mathf.Lerp(expression.leftLowerLipUp_Down,0f,delta);
expression.mouthNarrow_Pucker = Mathf.Lerp(expression.mouthNarrow_Pucker,-0.7f,delta);
expression.jawOpen_Close = Mathf.Lerp(expression.jawOpen_Close,0f,delta);
expression.noseSneer = Mathf.Lerp(expression.noseSneer,-0.1f,delta);
expression.leftEyeOpen_Close = Mathf.Lerp(expression.leftEyeOpen_Close,0.5f,delta);
expression.rightEyeOpen_Close = Mathf.Lerp(expression.rightEyeOpen_Close,0.5f,delta);
break;
case 3: // Angry
expression.leftMouthSmile_Frown = Mathf.Lerp(expression.leftMouthSmile_Frown,-0.3f,delta);
expression.rightMouthSmile_Frown = Mathf.Lerp(expression.rightMouthSmile_Frown,-0.3f,delta);
expression.midBrowUp_Down = Mathf.Lerp(expression.midBrowUp_Down,-1f,delta);
expression.leftBrowUp_Down = Mathf.Lerp(expression.leftBrowUp_Down,1f,delta);
expression.rightBrowUp_Down = Mathf.Lerp(expression.rightBrowUp_Down,1f,delta);
expression.rightUpperLipUp_Down = Mathf.Lerp(expression.rightUpperLipUp_Down,0.7f,delta);
expression.leftUpperLipUp_Down = Mathf.Lerp(expression.leftUpperLipUp_Down,0.7f,delta);
expression.rightLowerLipUp_Down = Mathf.Lerp(expression.rightLowerLipUp_Down,-0.7f,delta);
expression.leftLowerLipUp_Down = Mathf.Lerp(expression.leftLowerLipUp_Down,-0.7f,delta);
expression.mouthNarrow_Pucker = Mathf.Lerp(expression.mouthNarrow_Pucker,0.7f,delta);
expression.jawOpen_Close = Mathf.Lerp(expression.jawOpen_Close,-0.3f,delta);
expression.noseSneer = Mathf.Lerp(expression.noseSneer,0.3f,delta);
expression.leftEyeOpen_Close = Mathf.Lerp(expression.leftEyeOpen_Close,-0.2f,delta);
expression.rightEyeOpen_Close = Mathf.Lerp(expression.rightEyeOpen_Close,-0.2f,delta);
break;
case 4: // Surprised
expression.leftMouthSmile_Frown = Mathf.Lerp(expression.leftMouthSmile_Frown,0f,delta);
expression.rightMouthSmile_Frown = Mathf.Lerp(expression.rightMouthSmile_Frown,0f,delta);
expression.midBrowUp_Down = Mathf.Lerp(expression.midBrowUp_Down,1f,delta);
expression.leftBrowUp_Down = Mathf.Lerp(expression.leftBrowUp_Down,1f,delta);
expression.rightBrowUp_Down = Mathf.Lerp(expression.rightBrowUp_Down,1f,delta);
expression.rightUpperLipUp_Down = Mathf.Lerp(expression.rightUpperLipUp_Down,0f,delta);
expression.leftUpperLipUp_Down = Mathf.Lerp(expression.leftUpperLipUp_Down,0f,delta);
expression.rightLowerLipUp_Down = Mathf.Lerp(expression.rightLowerLipUp_Down,-0f,delta);
expression.leftLowerLipUp_Down = Mathf.Lerp(expression.leftLowerLipUp_Down,-0f,delta);
expression.mouthNarrow_Pucker = Mathf.Lerp(expression.mouthNarrow_Pucker,-1f,delta);
expression.jawOpen_Close = Mathf.Lerp(expression.jawOpen_Close,0.8f,delta);
expression.noseSneer = Mathf.Lerp(expression.noseSneer,-0.3f,delta);
expression.leftEyeOpen_Close = Mathf.Lerp(expression.leftEyeOpen_Close,1f,delta);
expression.rightEyeOpen_Close = Mathf.Lerp(expression.rightEyeOpen_Close,1f,delta);
break;
default:
break;
}在AssetStore上有个插件LipSync Pro可以更好利用ExpressionPlayer来控制表情
UMA和其他插件的整合
- 许多插件要求人物上必须有Animator组件,但是UMA的Aniamtor组件是运行时动态生成的,这就会有问题。但是其实可以先在人物上放置一个空的Animator组件,UMA运行后会找个这个Animator并正确初始化它。
- 许多插件要求人物上必须有骨骼结构,但是UMA的骨骼结构同样是运行时才生成的,这又是个问题。但是现在可以通过UMA->Bone Builder菜单,预先生成静态的骨骼结构在人物上,然后运行时UMA会找到这个静态骨骼结构,并和UMA关联起来,而不会重新生成一个新的骨骼结构。
- 有个小问题,这个预先生成的骨架是从UMA的原始FBX文件中得到的,和预览用的dummy人物不相符。UMA Bone Visualizer可以在没有运行的时候在scene中显示骨架。
- 可以在这个预先生成的骨架上绑定新的骨头,用来绑定武器等附件,但是需要注意在骨架结构中不能出现相同名字的骨头,哪怕在不同的层次下。