Skip to content

About

Unity的Avatar换装第二版

Resources

Stars

9 stars

Watchers

1 watching

Forks

Latest commit

 

History

30 Commits

Folders and files

Repository files navigation

Unity的Avatar换装第二版

Demo示例

github

第一版换装链接

UnityDemo_Avatar

优化内容,使在实际项目中使用

资源准备

github

模型prefab拆分成基础的的prefab组件

  • eyes.prefab
  • face.prefab
  • hair.prefab
  • pants.prefab
  • shoes.prefab
  • top.prefab

以一个prefab为例

github

prefab只有 Transform 和 SkinnedMeshRenderer 组件,对于SkinnedMeshRenderer组件,内部Mesh,Root Bone, Materials全部置空,资源通过动态加载

骨骼prefab

female_skeleton.prefab

github

骨骼的prefab只保存骨架信息

骨骼资源asset

bonenames.asset

github

[Serializable]
public class TransInfo
{
    public Vector3 localPos;
    public Quaternion localRot;
    public Vector3 localScale;
}

[Serializable]
public struct Info
{
    public string partName;
    public string rootBoneName;
    public string[] boneNames;

    public Bounds bounds;
    public TransInfo trans;
}

记录 partName, rootBoneName, boneNames, bounds, trans信息, 然后通过查询的方式,填充进入相应的prefab中

重点代码

public void ChangePart(Mesh mesh, List<Material> matList)
{
    if (m_Character == null || m_SkinMesh == null)
        return;

    var partAsset = m_Character.partAsset;
    if (partAsset == null)
        return;

    var partName = mesh.name;

    // 设置Mesh
    m_SkinMesh.sharedMesh = mesh;

    // 设置材质 (真实项目里面,这里动态加载材质路径列表,然后填充进入列表)
    // 例如 mat1path|mat2path|mat3path, 加载这些资源,然后加入列表里面
    // 例如眼睛等部件,可能会有多个材质,所以这里需要是材质列表
    m_MatList.Clear();
    m_MatList.AddRange(matList);
    m_SkinMesh.sharedMaterials = m_MatList.ToArray();

    // 设置transform
    var trans = partAsset.GetTransInfo(partName);
    if (trans != null)
    {
        m_SkinMesh.transform.localPosition = trans.localPos;
        m_SkinMesh.transform.localRotation = trans.localRot;
        m_SkinMesh.transform.localScale = trans.localScale;
    }

    // 设置bounds
    m_SkinMesh.localBounds = partAsset.GetBounds(partName);

    // 设置骨骼列表和根骨骼
    m_SkinMesh.bones = m_Character.GetBones(partName, out Transform rootBone);
    m_SkinMesh.rootBone = rootBone;
}

脸贴花

github

贴花矩阵计算代码

public static Matrix4x4 TransformUV(float rotation, Vector2 translate, float scale)
{
    Matrix4x4 matrix = Matrix4x4.identity;

    // rotate
    float radian = rotation * Mathf.Deg2Rad;
    float cosRadian = Mathf.Cos(radian);
    float sinRadian = Mathf.Sin(radian);
    Matrix4x4 rotateM = new Matrix4x4(new Vector4(cosRadian, sinRadian, 0, 0),
                                        new Vector4(-sinRadian, cosRadian, 0, 0),
                                        new Vector4(0, 0, 1, 0),
                                        new Vector4(0, 0, 0, 1));

    if (scale == 0)
    {
        scale = 1;
    }

    float invScale = 1 / scale;
    float offestX = -translate.x * invScale - 0.5f * invScale;
    float offsetY = -translate.y * invScale - 0.5f * invScale;

    // anchor translate
    Matrix4x4 preTranslateM = new Matrix4x4(new Vector4(1, 0, 0.5f, 0),
                                            new Vector4(0, 1, 0.5f, 0),
                                            new Vector4(0, 0, 1, 0),
                                            new Vector4(0, 0, 0, 1));

    // translate
    Matrix4x4 translateM = new Matrix4x4(new Vector4(1, 0, offestX, 0),
                                        new Vector4(0, 1, offsetY, 0),
                                        new Vector4(0, 0, 1, 0),
                                        new Vector4(0, 0, 0, 1));

    // scale
    Matrix4x4 scaleM = new Matrix4x4(new Vector4(invScale, 0, 0, 0),
                                    new Vector4(0, invScale, 0, 0),
                                    new Vector4(0, 0, 1, 0),
                                    new Vector4(0, 0, 0, 1));

    matrix = scaleM * translateM * rotateM * preTranslateM;

    return matrix;
}

贴花shader计算

half4 CalcDecal(float2 uv)
{
    half3x3 calMatrix = half3x3(_FaceDecalUVMatrixM0.xyz,
        _FaceDecalUVMatrixM1.xyz,
        _FaceDecalUVMatrixM2.xyz);

    uv = saturate(mul(calMatrix, half3(uv, 1)).xy);
    half4 decalColor = tex2D(_FaceDecalTex, uv) * _FaceDecalColor;

    return decalColor;
}

WebGL 打包适配

问题说明

原始 CreateAllAvatarRes 在 WebGL 打包后资源全部加载失败,原因有三:

问题 原因
DirectoryInfo / System.IO WebGL 运行在浏览器沙箱,无本地文件系统访问
FunctionUtil.CollectAll<T> 方法体被 #if UNITY_EDITOR 包裹,打包后返回空列表
Mesh / Material / Anim 不在 Resources 下 原路径在 Assets/Meshs/、Assets/Materials/、Assets/Anims/,打包时不会被包含

代码改动(Assets/Scripts/Main.cs)

1. 移除 System.IO 依赖

// 删除
using System;
using System.IO;

2. AvatarRes 路径常量改为 Resources 子路径

// 修改前
public const string MESH_PATH = "Assets/Meshs/";
public const string ANIM_PATH = "Assets/Anims/";
public const string Prefab_PATH = "Assets/Resources/";
public const string MAT_PATH = "Assets/Materials/";

// 修改后
public const string MESH_SUBPATH = "Meshs";
public const string ANIM_SUBPATH = "Anims";
public const string MAT_SUBPATH = "Materials";
public static readonly string[] CHAR_NAMES = { "Female", "Male" };

3. CreateAllAvatarRes 改用 Resources.LoadAll

// 修改前:使用 DirectoryInfo 扫描文件系统 + FunctionUtil.CollectAll(Editor Only)
void CreateAllAvatarRes()
{
    DirectoryInfo dir = new DirectoryInfo(AvatarRes.Prefab_PATH);
    foreach(var subdir in dir.GetDirectories())
    {
        string dirname = subdir.Name;
        List<Mesh> meshArray = FunctionUtil.CollectAll<Mesh>(AvatarRes.MESH_PATH + dirname + "/");
        List<Material> matArray = FunctionUtil.CollectAll<Material>(AvatarRes.MAT_PATH + dirname + "/");
        List<AnimationClip> clips = FunctionUtil.CollectAll<AnimationClip>(AvatarRes.ANIM_PATH + dirname + "/");
        // ...
    }
}

// 修改后:使用静态角色列表 + Resources.LoadAll(全平台可用)
void CreateAllAvatarRes()
{
    foreach (string dirname in AvatarRes.CHAR_NAMES)
    {
        List<Mesh> meshArray = new List<Mesh>(Resources.LoadAll<Mesh>($"{dirname}/{AvatarRes.MESH_SUBPATH}"));
        List<Material> matArray = new List<Material>(Resources.LoadAll<Material>($"{dirname}/{AvatarRes.MAT_SUBPATH}"));
        List<AnimationClip> clips = new List<AnimationClip>(Resources.LoadAll<AnimationClip>($"{dirname}/{AvatarRes.ANIM_SUBPATH}"));
        // ...
    }
}

资源目录迁移(需在 Unity Editor 中操作)

将 Mesh、Material、AnimationClip 移入 Assets/Resources/ 对应子目录:

迁移前:
Assets/Meshs/Female/        → Assets/Resources/Female/Meshs/
Assets/Materials/Female/    → Assets/Resources/Female/Materials/
Assets/Anims/Female/        → Assets/Resources/Female/Anims/
Assets/Meshs/Male/          → Assets/Resources/Male/Meshs/
Assets/Materials/Male/      → Assets/Resources/Male/Materials/
Assets/Anims/Male/          → Assets/Resources/Male/Anims/

迁移后完整结构:
Assets/Resources/
├── Female/
│   ├── bonenames.asset
│   ├── female_skeleton.prefab
│   ├── eyes/face/hair/pants/shoes/top .prefab
│   ├── Meshs/        ← Mesh 资源
│   ├── Materials/    ← Material 资源
│   └── Anims/        ← AnimationClip 资源
└── Male/
    └── ...(同上)

新增角色时:在 AvatarRes.CHAR_NAMES 数组中追加角色目录名,并按上述结构放置资源即可。

About

Unity的Avatar换装第二版

Resources

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages