根据目前模型资源平均面数预算进行脚本制作,自动化校验模型面数是否符合规范。
*注:文件格式为.cs。需要放置在unity资源文件夹Assets>Editor下。
测试效果(拖一个fbx文件进unity时自动检测):

以下为完整代码
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
public class FBXFaceBudgetValidator : AssetPostprocessor
{
    // 配置:不同前缀对应的面数预算(不区分大小写)
    private static readonly Dictionary<string, int> FACE_BUDGETS = new Dictionary<string, int>(
        StringComparer.OrdinalIgnoreCase
    )
    {
        {"hair_", 4000},
        {"hat_", 5000},
        {"headdress_", 5000},
        {"caps_", 5000},
        {"clothing_", 6500},
        {"shoe_", 2000},
        {"glasses_", 1000},
        {"mask_", 1500},
        {"glove_", 1000},
        {"necklace_", 1000},
        {"scarf_", 1000},
        {"bracelet_", 2500},
        {"waist_", 1000},
        {"satchel_", 1000},
        {"backpack_", 2500},
        {"wing_", 2500},
        {"cape_", 1000},
        {"earring_", 300},
        {"instrument_",3000}
    };
    // 配置:忽略校验的文件夹路径
    private static readonly string[] IGNORE_PATHS = { "Assets/Models/LowPoly" };
    void OnPostprocessModel(GameObject model)
    {
        try
        {
            string assetPath = assetImporter.assetPath;
            //Debug.Log($"[校验器] 开始处理: {assetPath}");
            if (IsInIgnorePath(assetPath))
            {
                Debug.Log($"[校验器] 已忽略路径: {assetPath}");
                return;
            }
            string fileName = Path.GetFileNameWithoutExtension(assetPath);
            var sortedPrefixes = FACE_BUDGETS.Keys.OrderByDescending(p => p.Length);
            int maxTriangles = -1;
            string matchedPrefix = "";
            foreach (var prefix in sortedPrefixes)
            {
                if (fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                {
                    maxTriangles = FACE_BUDGETS[prefix];
                    matchedPrefix = prefix;
                    break;
                }
            }
            if (maxTriangles == -1)
            {
                string error = $"<color=red>命名错误</color>: {fileName}\n允许的前缀列表:\n{string.Join("\n", FACE_BUDGETS.Keys)}";
                Debug.LogError(error, model);
                return;
            }
            int totalTriangles = CalculateTriangleCount(model);
            Debug.Log($"[校验器] 总面数合规: {totalTriangles}");
            if (totalTriangles > maxTriangles)
            {
                string error = $"<color=red>面数超标</color>: {fileName} ({matchedPrefix})\n预算: {maxTriangles}, 实际: {totalTriangles}";
                Debug.LogError(error, model);
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[校验器] 发生异常: {ex}");
        }
    }
    private bool IsInIgnorePath(string assetPath)
    {
        try
        {
            string fullAssetPath = Path.GetFullPath(assetPath)
                .Replace('\\', '/')
                .TrimEnd('/');
            foreach (var path in IGNORE_PATHS)
            {
                string fullIgnorePath = Path.GetFullPath(path)
                    .Replace('\\', '/')
                    .TrimEnd('/') + "/";
                if (fullAssetPath.StartsWith(fullIgnorePath, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }
        catch
        {
            return false;
        }
    }
    private int CalculateTriangleCount(GameObject model)
    {
        int triangles = 0;
        try
        {
            foreach (var renderer in model.GetComponentsInChildren<Renderer>(true))
            {
                Mesh mesh = null;
                // 处理SkinnedMeshRenderer
                if (renderer is SkinnedMeshRenderer skinnedRenderer)
                {
                    mesh = skinnedRenderer.sharedMesh;
                }
                // 处理普通MeshRenderer
                else if (renderer is MeshRenderer)
                {
                    var filter = renderer.GetComponent<MeshFilter>();
                    if (filter != null)
                    {
                        mesh = filter.sharedMesh;
                    }
                }
                if (mesh != null && mesh.triangles != null)
                {
                    triangles += mesh.triangles.Length / 3;
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[面数计算] 发生异常: {ex}");
        }
        return triangles;
    }
}

















