Unity editor文件数UI(支持勾选框)

news2025/5/22 22:07:23

unity editor文件数(支持勾选框)

使用的时候new一个box即可

using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class TGFileTreeViewBox<T> where T : OdinMenuEditorWindow
{

    public TGFileTreeView fileTreeView;
    private Vector2 scrollPosition;

    public TGFileTreeViewBox(bool ShowCheckboxes = true)
    {
        fileTreeView = new TGFileTreeView(new List<string>(), ThisRepaint);
        fileTreeView.ShowCheckboxes = ShowCheckboxes;
        //fileTreeView = new TGFileTreeView(GetFilePathsTest(), ThisRepaint);
        fileTreeView.SelectAll();
    }

    public TGFileTreeViewBox(List<string> paths, bool ShowCheckboxes = true)
    {
        //for (int i = 0; i < paths.Count; i++)
        //{
        //    Logger.Log($"文件夹树视图:{paths[i]}");
        //}
        fileTreeView = new TGFileTreeView(paths, ThisRepaint);
        fileTreeView.ShowCheckboxes = ShowCheckboxes;
        //fileTreeView = new TGFileTreeView(GetFilePathsTest(), ThisRepaint);
        //fileTreeView.ToggleExpandAll(true);
        fileTreeView.SelectAll();
    }

    [OnInspectorGUI]
    private void DrawCustomInspector()
    {
        float contentHeight = fileTreeView.GetHeight();
        float maxHeight = Mathf.Min(contentHeight, 250f);
        scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(maxHeight));

        fileTreeView.DrawTreeView();

        //if (GUILayout.Button("打印选中路径"))
        //{
        //    foreach (var path in fileTreeView.CheckedPaths)
        //    {
        //        Logger.Log($"选中了--{path}");
        //    }
        //}

        //if (GUILayout.Button("打印未选中路径"))
        //{
        //    foreach (var path in fileTreeView.UncheckedPaths)
        //    {
        //        Logger.Log($"未选中--{path}");
        //    }
        //}

        //if (GUILayout.Button("打印选中文件路径"))
        //{
        //    foreach (var path in fileTreeView.CheckedFilePaths)
        //    {
        //        Logger.Log($"选中文件了--{path}");
        //    }
        //}

        GUILayout.EndScrollView();
    }

    // 刷新界面的方法
    public static void ThisRepaint()
    {
        var window = UnityEditor.EditorWindow.GetWindow<T>();
        window?.Repaint();
    }

    private static List<string> GetFilePathsTest()
    {
        // 直接定义路径数据
        return new List<string>
        {
            "Assets/_Test/sedan.prefab",
            "Assets/_Test/mat/New Material.mat",
            "Assets/_Test/mat/New Material 1.mat",
            "Assets/_Test/mat/New Material 2.mat",
            "Assets/_Test/mat/New Material 3.mat",
            "Assets/_Test/New Material 1.mat",
            "Assets/_Test/New Material 2.mat",
            "Assets/_Test/New Material 3.mat",
            "Assets/_Test/New Material 4.mat",
            "Assets/_Test/New Material.mat",
            "Assets/_Test/source/sedan.fbx",
            "Assets/_Test/source/sedan 1.fbx",
            "Assets/_Test/TestNull",
            "Assets/_Test/textures/internal_ground_ao_texture.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 1.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 2.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 3.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 4.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 5.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 6.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 7.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 8.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 9.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture 10.jpeg",
            "Assets/_Test/textures/internal_ground_ao_texture.tga"
        };
    }
}

using Sirenix.OdinInspector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

public class TGFileTreeView
{
    private List<string> allfilePaths = new List<string>(); // 传入的路径
    private List<string> filePaths = new List<string>(); // 当前有效路径,经过过滤
    private List<string> checkedPaths = new List<string>(); // 勾选的路径
    private List<string> uncheckedPaths = new List<string>(); // 未勾选的路径
    private List<string> checkedFilePaths = new List<string>(); // 选中的文件路径列表

    private Dictionary<string, bool> foldoutStates = new Dictionary<string, bool>();
    private Dictionary<string, bool> checkboxStates = new Dictionary<string, bool>();

    private string selectedFilePath = "";
    private Action repaintAction;
    private int treeHeight;
    private bool isExpandAll = true;

    public List<string> CheckedPaths => checkedPaths;
    public List<string> UncheckedPaths => uncheckedPaths;
    public List<string> AllPaths => allfilePaths; // 传入的路径列表
    public List<string> CurrentValidPaths => filePaths; // 当前有效路径
    public List<string> CheckedFilePaths //选中的文件路径列表
    {
        get
        {
            checkedFilePaths.Clear();
            foreach (var item in checkedPaths)
            {
                if (Path.HasExtension(item))
                {
                    checkedFilePaths.Add(item);
                }
            }

            return checkedFilePaths;
        }
    }

    public Action<string, bool> OnCheckStateChanged; // path 和状态
    public bool ShowCheckboxes = true; // 全局控制是否显示勾选框


    public TGFileTreeView(List<string> paths, System.Action repaintAction)
    {
        allfilePaths = paths;
        this.repaintAction = repaintAction;
        InitializePaths();
    }

    // 初始化时计算有效路径,并根据勾选状态更新 CheckedPaths 和 UncheckedPaths
    private void InitializePaths()
    {
        // 过滤出有效路径
        filePaths = allfilePaths.Where(path => File.Exists(path) || Directory.Exists(path)).ToList();
        // 初始化勾选与取消勾选的路径
        checkedPaths = new List<string>();
        uncheckedPaths = new List<string>();

        foreach (var path in filePaths)
        {
            checkboxStates[path] = false;
            uncheckedPaths.Add(path);
        }
    }

    public void DrawTreeView()
    {
        CheckForDeletedFiles();

        if (filePaths.Count == 0)
        {
            EditorGUILayout.LabelField("没有可用的文件路径!!");
            return;
        }

        DrawFileTree(filePaths);
    }

    private void CheckForDeletedFiles()
    {
        filePaths.Clear();
        foreach (var path in allfilePaths)
        {
            if (File.Exists(path) || Directory.Exists(path))
            {
                filePaths.Add(path);
            }
        }
    }

    private void DrawFileTree(List<string> paths)
    {
        TGTreeNode root = BuildTree(paths);
        treeHeight = GetTotalHeight(root);
        DrawNode(root, 0);
    }

    private void DrawNode(TGTreeNode node, int indentLevel)
    {
        EditorGUILayout.BeginHorizontal();

        if (!foldoutStates.ContainsKey(node.Path))
            foldoutStates[node.Path] = isExpandAll;

        if (!checkboxStates.ContainsKey(node.Path))
        {
            checkboxStates[node.Path] = false;
            UpdateCheckboxLists(node.Path, false);
        }

        float lineHeight = 20f;
        float indentX = indentLevel * 20f;
        float foldoutWidth = 14f;
        float spacingA = 20f; // 箭头和勾选框之间
        float toggleWidth = 18f;
        float spacingB = 4f; // 勾选框和图标之间
        float iconWidth = 20f;
        float spacingC = 4f; // 图标和文件名之间

        Rect lineRect = GUILayoutUtility.GetRect(0, lineHeight, GUILayout.ExpandWidth(true));
        float x = lineRect.x + indentX;

        // 折叠箭头
        if (node.IsFolder && node.Children.Count > 0)
        {
            Rect foldoutRect = new Rect(x, lineRect.y + 3, foldoutWidth, 14f);
            foldoutStates[node.Path] = EditorGUI.Foldout(foldoutRect, foldoutStates[node.Path], GUIContent.none, false);
            x += foldoutWidth + spacingA;
        }
        else
        {
            x += foldoutWidth + spacingA;
        }

        // 勾选框
        if (ShowCheckboxes)
        {
            // 勾选框
            Rect toggleRect = new Rect(x, lineRect.y + 1, toggleWidth, 18f);
            bool oldChecked = checkboxStates[node.Path];
            bool newChecked = GUI.Toggle(toggleRect, oldChecked, GUIContent.none);
            if (oldChecked != newChecked)
            {
                checkboxStates[node.Path] = newChecked;
                UpdateCheckboxLists(node.Path, newChecked);
            }
            x += toggleWidth + spacingB;
        }
        else
        {
            x += spacingB; // 保留缩进对齐
        }


        // 图标
        Texture icon = GetIconForPath(node.Path, node.IsFolder);
        if (icon != null)
        {
            Rect iconRect = new Rect(x, lineRect.y, iconWidth, 20f);
            GUI.DrawTexture(iconRect, icon);
            x += iconWidth + spacingC;
        }

        // 文件名
        Rect labelRect = new Rect(x, lineRect.y, lineRect.width - (x - lineRect.x), lineHeight);
        GUIContent labelContent = new GUIContent(GetFileName(node.Path));

        if (selectedFilePath == node.Path)
            EditorGUI.DrawRect(lineRect, new Color(0.24f, 0.49f, 0.90f, 0.3f));

        HandleClick(labelRect, node.Path);
        GUI.Label(labelRect, labelContent);

        EditorGUILayout.EndHorizontal();

        if (node.IsFolder && foldoutStates[node.Path])
        {
            foreach (var child in node.Children)
            {
                DrawNode(child, indentLevel + 1);
            }
        }
    }

    private void UpdateCheckboxLists(string path, bool isChecked)
    {
        checkboxStates[path] = isChecked;

        if (isChecked)
        {
            if (!checkedPaths.Contains(path)) checkedPaths.Add(path);
            uncheckedPaths.Remove(path);
        }
        else
        {
            if (!uncheckedPaths.Contains(path)) uncheckedPaths.Add(path);
            checkedPaths.Remove(path);
        }

        // 更新子项状态
        var keys = checkboxStates.Keys.ToList();
        foreach (var kvp in keys)
        {
            if (kvp != path && kvp.StartsWith(path + "/"))
            {
                checkboxStates[kvp] = isChecked;

                if (isChecked)
                {
                    if (!checkedPaths.Contains(kvp)) checkedPaths.Add(kvp);
                    uncheckedPaths.Remove(kvp);
                }
                else
                {
                    if (!uncheckedPaths.Contains(kvp)) uncheckedPaths.Add(kvp);
                    checkedPaths.Remove(kvp);
                }
            }
        }
        OnCheckStateChanged?.Invoke(path, isChecked);
        UpdateParentCheckboxStates(path);
    }

    private void UpdateParentCheckboxStates(string path)
    {
        string parentPath = GetParentPath(path);
        if (string.IsNullOrEmpty(parentPath)) return;

        var childPaths = checkboxStates.Keys
            .Where(k => GetParentPath(k) == parentPath)
            .ToList();

        bool anyChildChecked = childPaths.Any(k => checkboxStates.ContainsKey(k) && checkboxStates[k]);

        checkboxStates[parentPath] = anyChildChecked;

        if (anyChildChecked)
        {
            if (!checkedPaths.Contains(parentPath)) checkedPaths.Add(parentPath);
            uncheckedPaths.Remove(parentPath);
        }
        else
        {
            if (!uncheckedPaths.Contains(parentPath)) uncheckedPaths.Add(parentPath);
            checkedPaths.Remove(parentPath);
        }

        UpdateParentCheckboxStates(parentPath);
    }

    private string GetParentPath(string path)
    {
        if (string.IsNullOrEmpty(path)) return null;
        int lastSlashIndex = path.LastIndexOf('/');
        if (lastSlashIndex <= 0) return null;
        return path.Substring(0, lastSlashIndex);
    }

    private void HandleClick(Rect rect, string path)
    {
        Event e = Event.current;
        if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
        {
            if (e.clickCount == 1)
            {
                selectedFilePath = path;
                repaintAction?.Invoke();
            }
            else if (e.clickCount == 2)
            {
                if (File.Exists(path) || Directory.Exists(path))
                {
                    EditorUtility.FocusProjectWindow();
                    UnityEngine.Object asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
                    if (asset != null)
                    {
                        EditorGUIUtility.PingObject(asset);
                        Selection.activeObject = asset;
                    }
                }
            }
            e.Use();
        }
    }

    private TGTreeNode BuildTree(List<string> paths)
    {
        TGTreeNode root = new TGTreeNode("Assets", true);

        foreach (string path in paths)
        {
            string[] parts = path.Split('/');
            TGTreeNode current = root;

            for (int i = 1; i < parts.Length; i++)
            {
                string part = parts[i];
                string fullPath = string.Join("/", parts, 0, i + 1);
                bool isFile = (i == parts.Length - 1) && Path.HasExtension(part);

                if (!current.HasChild(part))
                {
                    current.Children.Add(new TGTreeNode(fullPath, !isFile));
                }
                current = current.GetChild(part);
            }
        }

        return root;
    }

    private string GetFileName(string path)
    {
        return Path.GetFileName(path);
    }

    private int GetTotalHeight(TGTreeNode node)
    {
        int totalHeight = 25;

        bool isFolded = !foldoutStates.ContainsKey(node.Path) || !foldoutStates[node.Path];
        if (node.IsFolder && !isFolded)
        {
            foreach (var child in node.Children)
            {
                totalHeight += GetTotalHeight(child);
            }
        }

        return totalHeight;
    }

    public int GetHeight()
    {
        return treeHeight;
    }

    public void ToggleExpandAll(bool _isExpandAll)
    {
        isExpandAll = _isExpandAll;
        foldoutStates.Clear();
        repaintAction?.Invoke();
    }

    // 全选
    public void SelectAll()
    {
        foreach (var path in filePaths)
        {
            if (!checkboxStates.ContainsKey(path)) continue;
            if (!checkboxStates[path]) // 只有未勾选的才修改
            {
                checkboxStates[path] = true;
                UpdateCheckboxLists(path, true);
            }
        }
        repaintAction?.Invoke(); // 更新界面
    }

    // 全不选
    public void DeselectAll()
    {
        foreach (var path in filePaths)
        {
            if (!checkboxStates.ContainsKey(path)) continue;
            if (checkboxStates[path]) // 只有勾选的才修改
            {
                checkboxStates[path] = false;
                UpdateCheckboxLists(path, false);
            }
        }
        repaintAction?.Invoke(); // 更新界面
    }


    private Texture GetIconForPath(string path, bool isFolder)
    {
        if (isFolder) return EditorGUIUtility.IconContent("Folder Icon").image;

        string extension = Path.GetExtension(path).ToLower();
        switch (extension)
        {
            case ".prefab": return EditorGUIUtility.IconContent("Prefab Icon").image;
            case ".fbx":
            case ".obj":
            case ".blend": return EditorGUIUtility.IconContent("Mesh Icon").image;
            case ".mat": return EditorGUIUtility.IconContent("Material Icon").image;
            case ".shader":
            case ".compute": return EditorGUIUtility.IconContent("Shader Icon").image;
            case ".png":
            case ".jpg": return EditorGUIUtility.IconContent("Texture Icon").image;
            case ".anim": return EditorGUIUtility.IconContent("Animation Icon").image;
            case ".controller": return EditorGUIUtility.IconContent("AnimatorController Icon").image;
            case ".unity": return EditorGUIUtility.IconContent("SceneAsset Icon").image;
            case ".ttf":
            case ".otf": return EditorGUIUtility.IconContent("Font Icon").image;
            default: return EditorGUIUtility.IconContent("DefaultAsset Icon").image;
        }
    }
}

public class TGTreeNode
{
    public string Path { get; private set; }
    public bool IsFolder { get; private set; }
    public List<TGTreeNode> Children { get; private set; }

    public TGTreeNode(string path, bool isFolder)
    {
        Path = path;
        IsFolder = isFolder;
        Children = new List<TGTreeNode>();
    }

    public bool HasChild(string path)
    {
        return Children.Exists(child => child.Path.EndsWith(path));
    }

    public TGTreeNode GetChild(string path)
    {
        return Children.Find(child => child.Path.EndsWith(path));
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2383415.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Node.js】Web开发框架

个人主页&#xff1a;Guiat 归属专栏&#xff1a;node.js 文章目录 1. Node.js Web框架概述1.1 Web框架的作用1.2 Node.js主要Web框架生态1.3 框架选择考虑因素 2. Express.js2.1 Express.js概述2.2 基本用法2.2.1 安装Express2.2.2 创建基本服务器 2.3 路由2.4 中间件2.5 请求…

使用Vite创建一个动态网页的前端项目

1. 引言 虽然现在的前端更新换代的速度很快&#xff0c;IDE和工具一批批的换&#xff0c;但是我们始终要理解一点基本的程序构建的思维&#xff0c;这些环境和工具都是为了帮助我们更快的发布程序。笔者还记得以前写前端代码的时候&#xff0c;只使用文本编辑器&#xff0c;然…

系统架构设计师案例分析题——web篇

软考高项系统架构设计师&#xff0c;其中的科二案例分析题为5选3&#xff0c;总分75达到45分即合格。本贴来归纳web设计题目中常见的知识点即细节&#xff1a; 目录 一.核心知识 1.常见英文名词 2.私有云 3.面向对象三模型 4.计网相关——TCP和UDP的差异 5.MQTT和AMQP协…

MySQL--day5--多表查询

&#xff08;以下内容全部来自上述课程&#xff09; 多表查询 1. 为什么要用多表查询 # 如果不用多表查询 #查询员工名为Abel的人在哪个城市工作? SELECT* FROM employees WHERE last_name Abel;SELECT * FROM departments WHERE department_id 80;SELECT * FROM locati…

leetcode hot100刷题日记——7.最大子数组和

class Solution { public:int maxSubArray(vector<int>& nums) {//方法一&#xff1a;动态规划//dp[i]表示以i下标结尾的数组的最大子数组和//那么在i0时&#xff0c;dp[0]nums[0]//之后要考虑的就是我们要不要把下一个数加进来&#xff0c;如果下一个数加进来会使结…

基于Spring Boot和Vue的在线考试系统架构设计与实现(源码+论文+部署讲解等)

源码项目获取联系 请文末卡片dd我获取更详细的演示视频 系统介绍 基于Spring Boot和Vue的在线考试系统。为学生和教师/管理员提供一个高效、便捷的在线学习、考试及管理平台。系统采用前后端分离的架构&#xff0c;后端基于成熟稳定的Spring Boot框架&#xff0c;负责数据处理…

Android 绘制折线图

用了一段时间的 Jetpack Compose ,感觉写 UI 的效率确实会提升不少 。 配合 AI 编程绘制了一个折线图。供大家学习参考! @Composable fun TemperatureChart() {val timeLabels = listOf("7:00", "8:00", "9:00", "10:00", "11:…

自建srs实时视频服务器支持RTMP推流和拉流

文章目录 一、整体示意图二、服务器端1.srs简介及架构2.docker方式安装3.k8s方式安装4.端口 三、推流端1.OBS Studio2.ffmpeg推流3.streamlabs苹果手机4.twire安卓手机5.网络推流摄像头 四、拉流端1.vlc2.srs 参考awesome系列&#xff1a;https://github.com/juancarlospaco/aw…

Spring IOCDI————(2)

DI详解 我们之前讲了控制反转IOC&#xff0c;也就是bean的存&#xff0c;那么我们还需要Bean的取&#xff0c;就是DI了&#xff0c;DI翻译过来就是依赖注入&#xff0c;啥意思呢&#xff0c;就是我们通过IOC容器&#xff0c;把所有的对象交给Spring管理&#xff0c;我们指定哪…

融云 uni-app IMKit 上线,1 天集成,多端畅行

融云 uni-app IMKit 正式上线&#xff0c;支持一套代码同时运行在 iOS、Android、H5、小程序主流四端&#xff0c;集成仅需 1 天&#xff0c;并可确保多平台的一致性体验。 融云 uni-app IMKit 在 Vue 3 的高性能加持下开发实现&#xff0c;使用 Vue 3 Composition API&#x…

篇章五 项目创建

目录 1.创建一个SpringBoot项目 2.创建核心类 2.1 Exchange类 2.2 MessageQueue类 2.3 Binding类 2.4 Message类 1.Message的组成 2.逻辑删除 3.工厂方法 4.序列化与反序列化 5.offsetBeg和offsetEnd 1.创建一个SpringBoot项目 1.点击 2.填写表单 3.添加依赖 2.创建…

aws平台s3存储桶夸域问题处理

当我们收到开发反馈s3存在跨域问题 解决步骤&#xff1a; 配置 S3 存储桶的 CORS 设置&#xff1a; 登录到 AWS 管理控制台。转到 S3 服务。选择你存储文件的 存储桶。点击 权限 标签页。在 跨域资源共享&#xff08;CORS&#xff09;配置 部分&#xff0c;点击 编辑。 登陆…

【vue-text-highlight】在vue2的使用教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、下载二、使用步骤1.引入库2.用法 效果速通 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不断发…

pycharm无法正常调试问题

pycharm无法正常调试问题 1.错误代码 已连接到 pydev 调试器(内部版本号 231.8109.197)Traceback (most recent call last):File "E:\Python\pycharm\PyCharm 2023.1\plugins\python\helpers\pydev\_pydevd_bundle\pydevd_comm.py", line 304, in _on_runr r.deco…

Leetcode百题斩-哈希

看来面试前还是要老老实实刷leetcode为好&#xff0c;今天看到一个题库&#xff0c;leetcode百题斩&#xff0c;刚好最近面试的这两题全在里面。瞄了一眼&#xff0c;也有不少题之前居然也刷过。那么&#xff0c;冲冲冲&#xff0c;看多久能把这百题刷完。 第一天&#xff0c;先…

EXIST与JOIN连表比较

结论 1&#xff1a;EXIST可以用于链表&#xff0c;且可以利用到索引2&#xff1a;当join无法合理利用到索引&#xff0c;可以尝试EXIST链表3&#xff1a;EXIST在某些情况下可以更好地利用到索引4&#xff1a;大数据量时&#xff0c;要考虑EXIST的使用 EXIST SQL: EXPLAN JOIN…

【Linux】利用多路转接epoll机制、ET模式,基于Reactor设计模式实现

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f152; C 语言 | &#x1f310; 计算机网络 上篇文章&#xff1a;多路转接epoll&#xff0c;实现echoserver 至此&#xff0c;Linux与…

react中运行 npm run dev 报错,提示vite.config.js出现错误 @esbuild/win32-x64

在React项目中运行npm run dev时&#xff0c;如果遇到vite.config.js报错&#xff0c;提示esbuild/win32-x64在另一个平台中被使用&#xff0c;通常是由于依赖冲突或缓存问题导致的。解决方法是删除node_modules文件夹&#xff0c;并重新安装依赖。 如下图&#xff1a; 解决办…

鸿蒙UI开发——Builder与LocalBuilder对比

1、概 述 在ArkUI中&#xff0c;有的朋友应该接触过Builder和LocalBuilder。其中有了LocalBuilder的存在&#xff0c;是为了解决组件的父子关系和状态管理的父子关系保持一致的问题。 这里面最直观的表现则是this的指向问题与组件刷新问题&#xff0c;本文对Builder与LocalBu…

关于光谱相机的灵敏度

一、‌灵敏度的核心定义‌ ‌光谱灵敏度&#xff08;单色灵敏度&#xff09;‌ 描述光谱相机对单色辐射光的响应能力&#xff0c;即探测器对特定波长入射光的输出信号强度与入射光功率的比值。 例如&#xff0c;若在680nm波长下的光谱灵敏度较高&#xff0c;则表示该相机对此…