UMA 101视频教程备忘录

  1. UMA_DCS prefab 加上 UMA Dynamic Character Avatar, 选择一个race,然后设置好默认的animator controller就可以运行了。

    UMA 101 - Part 2 Up and Running

  2. base recipe –> 确定一个裸体的人物模型。
    wardrobe recipe –> 确定人物的衣服,鞋子,饰品等;这里可以设置A物体覆盖B物体。

    UMA 101 - Part 3 Introducing Recipes

  3. Dynamic Character Avatar中的Default Recipes可以为这个人物添加默认的衣服裤子等。每个Wardrobe recipe都只能和相对应的race一起才能正常工作。Wardrobe slot是设置这个物体是放在人身上的哪个部位的。还可以设置需要覆盖哪个部位的其他物件和base物件。

    UMA 101 - Part 4 Default Recipes

  4. Dynamic Character Avatar中的Character Colors可以重载recipes中的shared colors,然后在slot中可以用shared color来影响overlay的颜色。这样就可以通过设置Character Colors来方便的设置人物的整体颜色。

    UMA 101 - Part 5 Shared Colours

  5. 所有的recipes和他们所用到的components都需要到UMA Global Library中去注册才能使用。制作DCS->wardrobe recipe

    UMA 101 - Part 7 Creating Custom Recipes

  6. Misc->Mesh Hide Asset,可以用来隐藏某个slot上的某些三角形,已解决衣服和身体部分重叠的情况下的穿模问题。UMA内建工具可以编辑需要去掉那些三角形。在编辑的时候还可以把衣服覆盖在身体上,方便查看需要隐藏哪些三角形。

    UMA 101 - Part 8 Advanced Occlusion

  1. 通过代码来改变人物的外貌

    1. 得到DynamicCharacterAvatar实例:

      1
      2
      3
      4
      using UMA;
      using UMA.CharacterSystem;

      DynamicCharacterAvatar avatar = GetComponent<DynamicCharacterAvatar>();

      UMA 101 - Part 9 Modify Characters with Code

    2. 设置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();
    3. 改变种族:

      1
      2
      3
      4
      5
      // 把race改成HumanMaleDCS,改变后不需要调用BuildCharacter,
      // race改变后所对应的dnaSetter的字典也会改变。
      avatar.ChangeRace("HumanMaleDCS");
      // 得到当前种族
      avatar.activeRace;
    4. 监听avatar的事件:

      1
      2
      3
      // DynamicCharacterAvatar的一些时间可以通过下面类似的方法添加和删除。
      avatar.CharacterUpdated.AddListener()
      avatar.CharacterUpdated.RemoveListener()

      UMA 101 - Part 10 Simple Character Creator (1 of 3)

    5. 设置颜色:

      1
      2
      3
      4
      avatar.SetColor("Skin", Color.Black);// 设置名字为Skin的sharedColor的颜色为black
      // true就是函数内部马上调用BuildCharacter,颜色马上就显示出来
      // false则把新颜色缓存者,到下次BuildCharacter才会显示在人物上。
      avatar.UpdateColors(true);
    6. 得到颜色:

      1
      avatar.GetColor("Skin").color;
    7. 操作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)

  2. 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);

    UMA 101 - Part 12 Character Creator (3 of 3)

  3. Utility Recipes就是类似于wardrobe recipes的东西,但是他不是给人物添加衣服的,而是给人物添加一个功能性的脚本,这个脚本可以自定义做很多事情。UMA里面内建了两个比较有用的Utility Recipes。

    1. ForearmTwistRecipe,用来解决手腕在绕着手臂旋转时,手腕和手臂连接处会不自然的扭曲到一起的问题。用了这Recipe后,旋转手腕会和正常人那样自然。
    2. ExpressionsRecipe,用来解决人物下巴往下掉的问题。因为如果动画中没有下巴的动画,那么下巴会在建模时的位置,一般都是在下面,让嘴巴张开的。加了这个Recipe,就可以调整下巴,让嘴巴比起来。当然还可以调整很多五官的位置的。

    UMA 101 - Part 13 Utility Slots

  4. 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
    98
    using 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 101 - Part 14 Expression Player

  5. UMA和其他插件的整合

    1. 许多插件要求人物上必须有Animator组件,但是UMA的Aniamtor组件是运行时动态生成的,这就会有问题。但是其实可以先在人物上放置一个空的Animator组件,UMA运行后会找个这个Animator并正确初始化它。
    • 许多插件要求人物上必须有骨骼结构,但是UMA的骨骼结构同样是运行时才生成的,这又是个问题。但是现在可以通过UMA->Bone Builder菜单,预先生成静态的骨骼结构在人物上,然后运行时UMA会找到这个静态骨骼结构,并和UMA关联起来,而不会重新生成一个新的骨骼结构。
    • 有个小问题,这个预先生成的骨架是从UMA的原始FBX文件中得到的,和预览用的dummy人物不相符。UMA Bone Visualizer可以在没有运行的时候在scene中显示骨架。
    • 可以在这个预先生成的骨架上绑定新的骨头,用来绑定武器等附件,但是需要注意在骨架结构中不能出现相同名字的骨头,哪怕在不同的层次下。

    UMA 101 - Part 15 Integration Tips