├── Editor.meta ├── Editor ├── Assistant.meta ├── Assistant │ ├── Agent.meta │ ├── Agent │ │ ├── AIAgent.cs │ │ ├── AIAgent.cs.meta │ │ ├── AIAgentSettingsWindow.cs │ │ ├── AIAgentSettingsWindow.cs.meta │ │ ├── AIAgentWindow.cs │ │ └── AIAgentWindow.cs.meta │ ├── AssistantPromptWordWindow.cs │ ├── AssistantPromptWordWindow.cs.meta │ ├── AssistantRenameWindow.cs │ ├── AssistantRenameWindow.cs.meta │ ├── AssistantSettingsWindow.cs │ ├── AssistantSettingsWindow.cs.meta │ ├── AssistantWindow.cs │ ├── AssistantWindow.cs.meta │ ├── ImageGeneration.meta │ ├── ImageGeneration │ │ ├── ImageGenerationWindow.cs │ │ └── ImageGenerationWindow.cs.meta │ ├── Texture.meta │ └── Texture │ │ ├── AIAgentIcon.png │ │ ├── AIAgentIcon.png.meta │ │ ├── AssistantIcon.png │ │ ├── AssistantIcon.png.meta │ │ ├── OllamaIcon.png │ │ ├── OllamaIcon.png.meta │ │ ├── UserIcon.png │ │ └── UserIcon.png.meta ├── CharacterRecognition.meta ├── CharacterRecognition │ ├── EditorCharacterRecognitioner.cs │ └── EditorCharacterRecognitioner.cs.meta ├── HTFramework.AI.Editor.asmdef ├── HTFramework.AI.Editor.asmdef.meta ├── Pathfinding.meta ├── Pathfinding │ ├── AStarGridInspector.cs │ └── AStarGridInspector.cs.meta ├── Speech.meta ├── Speech │ ├── EditorSpeecher.cs │ └── EditorSpeecher.cs.meta ├── Utility.meta └── Utility │ ├── EditorGlobalToolsAI.cs │ ├── EditorGlobalToolsAI.cs.meta │ ├── EditorPrefsTableAI.cs │ └── EditorPrefsTableAI.cs.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── RunTime.meta └── RunTime ├── Assistant.meta ├── Assistant ├── AssistantUtility.cs ├── AssistantUtility.cs.meta ├── ChatSession.cs ├── ChatSession.cs.meta ├── PollinationsAI.cs └── PollinationsAI.cs.meta ├── CharacterRecognition.meta ├── CharacterRecognition ├── Base.meta ├── Base │ ├── CharacterRecognitionModeBase.cs │ └── CharacterRecognitionModeBase.cs.meta ├── CharacterRecognitioner.cs ├── CharacterRecognitioner.cs.meta ├── RecognitionMode.meta ├── RecognitionMode │ ├── OCRAccurateBasicRecognition.cs │ ├── OCRAccurateBasicRecognition.cs.meta │ ├── OCRAccurateRecognition.cs │ ├── OCRAccurateRecognition.cs.meta │ ├── OCRGeneralBasicRecognition.cs │ ├── OCRGeneralBasicRecognition.cs.meta │ ├── OCRGeneralRecognition.cs │ └── OCRGeneralRecognition.cs.meta ├── Response.meta └── Response │ ├── OCRResponse.cs │ └── OCRResponse.cs.meta ├── HTFramework.AI.RunTime.asmdef ├── HTFramework.AI.RunTime.asmdef.meta ├── Pathfinding.meta ├── Pathfinding ├── AStarGrid.cs ├── AStarGrid.cs.meta ├── AStarManhattan.cs ├── AStarManhattan.cs.meta ├── AStarNode.cs ├── AStarNode.cs.meta ├── Base.meta └── Base │ ├── AStarEvaluation.cs │ ├── AStarEvaluation.cs.meta │ ├── AStarRule.cs │ └── AStarRule.cs.meta ├── Speech.meta └── Speech ├── SpeechUtility.cs ├── SpeechUtility.cs.meta ├── Speecher.cs ├── Speecher.cs.meta ├── SynthesisRule.cs └── SynthesisRule.cs.meta /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20cd20e615f65454ba778d444b15f623 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Assistant.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38220b5a2356e164aaa68ff469931869 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Assistant/Agent.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c1653e9b0ffb0324d8a03b88b0431b2f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Assistant/Agent/AIAgent.cs: -------------------------------------------------------------------------------- 1 | namespace HT.Framework.AI 2 | { 3 | /// 4 | /// AI智能体 5 | /// 6 | public abstract class AIAgent 7 | { 8 | /// 9 | /// 智能体名称 10 | /// 11 | public abstract string Name { get; } 12 | /// 13 | /// 智能体的@提及列表 14 | /// 15 | public abstract string[] Ats { get; } 16 | 17 | /// 18 | /// 初始化智能体 19 | /// 20 | public abstract void InitAgent(); 21 | /// 22 | /// 向智能体发送指令 23 | /// 24 | /// 用户指令 25 | /// 代码内容 26 | /// 选择的文件夹路径 27 | /// @提及 28 | /// 智能体回复(智能体处理当前指令时可能会多次回复,参数1:当前指令是否处理完毕,参数2:回复文本内容) 29 | public abstract void SendInstruction(string content, string code, string folderPath, string at, HTFAction agentReply); 30 | } 31 | } -------------------------------------------------------------------------------- /Editor/Assistant/Agent/AIAgent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 333ea406d9d7a784da52cfa0d6da904f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/Agent/AIAgentSettingsWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace HT.Framework.AI 7 | { 8 | internal sealed class AIAgentSettingsWindow : HTFEditorWindow 9 | { 10 | public static void OpenWindow(AssistantWindow assistantWindow) 11 | { 12 | AIAgentSettingsWindow window = GetWindow(); 13 | window.titleContent.text = "AIAgent Settings"; 14 | window._assistantWindow = assistantWindow; 15 | window.minSize = new Vector2(300, 200); 16 | window.maxSize = new Vector2(300, 200); 17 | window.Show(); 18 | } 19 | 20 | private AssistantWindow _assistantWindow; 21 | private string _agent; 22 | private bool _permissionOpenURL; 23 | private bool _permissionOpenProgram; 24 | private bool _permissionRunCode; 25 | private bool _permissionReadFile; 26 | private bool _permissionWriteFile; 27 | 28 | protected override void OnEnable() 29 | { 30 | base.OnEnable(); 31 | 32 | _agent = EditorPrefs.GetString(EditorPrefsTableAI.AIAgent_Type, ""); 33 | _permissionOpenURL = EditorPrefs.GetBool(EditorPrefsTableAI.AIAgent_PermissionOpenURL, false); 34 | _permissionOpenProgram = EditorPrefs.GetBool(EditorPrefsTableAI.AIAgent_PermissionOpenProgram, false); 35 | _permissionRunCode = EditorPrefs.GetBool(EditorPrefsTableAI.AIAgent_PermissionRunCode, false); 36 | _permissionReadFile = EditorPrefs.GetBool(EditorPrefsTableAI.AIAgent_PermissionReadFile, false); 37 | _permissionWriteFile = EditorPrefs.GetBool(EditorPrefsTableAI.AIAgent_PermissionWriteFile, false); 38 | } 39 | protected override void OnBodyGUI() 40 | { 41 | base.OnBodyGUI(); 42 | 43 | GUILayout.BeginHorizontal(); 44 | GUILayout.Label("智能体类型", GUILayout.Width(120)); 45 | if (GUILayout.Button(_agent, EditorStyles.popup)) 46 | { 47 | GenericMenu gm = new GenericMenu(); 48 | gm.AddItem(new GUIContent(""), _agent == "", () => 49 | { 50 | _agent = ""; 51 | }); 52 | gm.AddSeparator(""); 53 | List types = ReflectionToolkit.GetTypesInAllAssemblies(type => 54 | { 55 | return type.IsSubclassOf(typeof(AIAgent)) && !type.IsAbstract; 56 | }, false); 57 | for (int i = 0; i < types.Count; i++) 58 | { 59 | int j = i; 60 | gm.AddItem(new GUIContent(types[j].FullName), _agent == types[j].FullName, () => 61 | { 62 | _agent = types[j].FullName; 63 | }); 64 | } 65 | gm.ShowAsContext(); 66 | } 67 | GUILayout.EndHorizontal(); 68 | 69 | GUILayout.BeginHorizontal(); 70 | GUILayout.Label("智能体权限:"); 71 | GUILayout.FlexibleSpace(); 72 | GUILayout.EndHorizontal(); 73 | 74 | GUILayout.BeginHorizontal(); 75 | GUILayout.Label(" 访问 Web 网站", GUILayout.Width(120)); 76 | _permissionOpenURL = EditorGUILayout.Toggle(_permissionOpenURL); 77 | GUILayout.EndHorizontal(); 78 | 79 | GUILayout.BeginHorizontal(); 80 | GUILayout.Label(" 访问外部程序", GUILayout.Width(120)); 81 | _permissionOpenProgram = EditorGUILayout.Toggle(_permissionOpenProgram); 82 | GUILayout.EndHorizontal(); 83 | 84 | GUILayout.BeginHorizontal(); 85 | GUILayout.Label(" 运行代码", GUILayout.Width(120)); 86 | _permissionRunCode = EditorGUILayout.Toggle(_permissionRunCode); 87 | GUILayout.EndHorizontal(); 88 | 89 | GUILayout.BeginHorizontal(); 90 | GUILayout.Label(" 读取文件", GUILayout.Width(120)); 91 | _permissionReadFile = EditorGUILayout.Toggle(_permissionReadFile); 92 | GUILayout.EndHorizontal(); 93 | 94 | GUILayout.BeginHorizontal(); 95 | GUILayout.Label(" 写入文件", GUILayout.Width(120)); 96 | _permissionWriteFile = EditorGUILayout.Toggle(_permissionWriteFile); 97 | GUILayout.EndHorizontal(); 98 | 99 | GUILayout.FlexibleSpace(); 100 | 101 | GUILayout.BeginHorizontal(); 102 | if (GUILayout.Button("保存", "ButtonLeft")) 103 | { 104 | EditorPrefs.SetString(EditorPrefsTableAI.AIAgent_Type, _agent); 105 | EditorPrefs.SetBool(EditorPrefsTableAI.AIAgent_PermissionOpenURL, _permissionOpenURL); 106 | EditorPrefs.SetBool(EditorPrefsTableAI.AIAgent_PermissionOpenProgram, _permissionOpenProgram); 107 | EditorPrefs.SetBool(EditorPrefsTableAI.AIAgent_PermissionRunCode, _permissionRunCode); 108 | EditorPrefs.SetBool(EditorPrefsTableAI.AIAgent_PermissionReadFile, _permissionReadFile); 109 | EditorPrefs.SetBool(EditorPrefsTableAI.AIAgent_PermissionWriteFile, _permissionWriteFile); 110 | Close(); 111 | } 112 | if (GUILayout.Button("取消", "ButtonRight")) 113 | { 114 | Close(); 115 | } 116 | GUILayout.EndHorizontal(); 117 | } 118 | private void Update() 119 | { 120 | if (_assistantWindow == null) 121 | { 122 | Close(); 123 | } 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /Editor/Assistant/Agent/AIAgentSettingsWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fb3536cad87e9db408e0504f62828f56 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/Agent/AIAgentWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace HT.Framework.AI 7 | { 8 | internal sealed class AIAgentWindow : HTFEditorWindow 9 | { 10 | public static void OpenWindow(AssistantWindow assistantWindow) 11 | { 12 | AIAgentWindow window = GetWindow(); 13 | window.titleContent.image = EditorGUIUtility.IconContent("ParticleSystemForceField Icon").image; 14 | window.titleContent.text = "AI Agent"; 15 | window._assistantWindow = assistantWindow; 16 | window.Show(); 17 | } 18 | 19 | private AssistantWindow _assistantWindow; 20 | private AIAgent _agent; 21 | private List _messages = new List(); 22 | private bool _isThinking = false; 23 | private bool _isReplying = false; 24 | private string _userCode; 25 | private string _userFolderPath; 26 | private string _userContent; 27 | private string _userAt = "@"; 28 | 29 | private Texture _aiAgentIcon; 30 | private GUIContent _aiAgentGC; 31 | private GUIContent _codeGC; 32 | private GUIContent _folderGC; 33 | private Vector2 _sessionScroll; 34 | private Rect _sessionRect; 35 | 36 | /// 37 | /// AI智能体 38 | /// 39 | private AIAgent Agent 40 | { 41 | get 42 | { 43 | if (_agent == null) 44 | { 45 | string agent = EditorPrefs.GetString(EditorPrefsTableAI.AIAgent_Type, ""); 46 | Type type = (agent == "") ? null : ReflectionToolkit.GetTypeInAllAssemblies(agent, false); 47 | if (type != null) 48 | { 49 | _agent = Activator.CreateInstance(type) as AIAgent; 50 | _agent.InitAgent(); 51 | } 52 | } 53 | return _agent; 54 | } 55 | } 56 | 57 | protected override void OnEnable() 58 | { 59 | base.OnEnable(); 60 | 61 | _aiAgentIcon = AssetDatabase.LoadAssetAtPath("Assets/HTFrameworkAI/Editor/Assistant/Texture/AIAgentIcon.png"); 62 | _aiAgentGC = new GUIContent(); 63 | _aiAgentGC.image = _aiAgentIcon; 64 | _codeGC = new GUIContent(); 65 | _codeGC.image = EditorGUIUtility.IconContent("d_cs Script Icon").image; 66 | _folderGC = new GUIContent(); 67 | _folderGC.image = EditorGUIUtility.IconContent("Folder Icon").image; 68 | } 69 | private void Update() 70 | { 71 | if (_assistantWindow == null) 72 | { 73 | Close(); 74 | } 75 | } 76 | protected override void OnTitleGUI() 77 | { 78 | base.OnTitleGUI(); 79 | 80 | GUILayout.FlexibleSpace(); 81 | 82 | if (GUILayout.Button(_assistantWindow._settingsGC, EditorStyles.iconButton)) 83 | { 84 | AIAgentSettingsWindow.OpenWindow(_assistantWindow); 85 | } 86 | } 87 | protected override void OnBodyGUI() 88 | { 89 | base.OnBodyGUI(); 90 | 91 | if (Agent != null) 92 | { 93 | EventHandle(); 94 | OnSessionGUI(); 95 | } 96 | else 97 | { 98 | OnErrorGUI(); 99 | } 100 | } 101 | private void OnSessionGUI() 102 | { 103 | GUILayout.BeginVertical(); 104 | 105 | GUILayout.BeginHorizontal(); 106 | GUILayout.FlexibleSpace(); 107 | GUILayout.Label(Agent.Name, "WarningOverlay", GUILayout.ExpandWidth(true)); 108 | GUILayout.FlexibleSpace(); 109 | GUILayout.EndHorizontal(); 110 | 111 | GUILayout.BeginHorizontal(); 112 | GUILayout.FlexibleSpace(); 113 | GUI.color = Color.gray; 114 | GUILayout.Label($"共{_messages.Count}条记录", GUILayout.ExpandWidth(true)); 115 | GUI.color = Color.white; 116 | GUILayout.FlexibleSpace(); 117 | GUILayout.EndHorizontal(); 118 | 119 | GUILayout.Space(5); 120 | 121 | _sessionScroll = GUILayout.BeginScrollView(_sessionScroll); 122 | for (int i = 0; i < _messages.Count; i++) 123 | { 124 | Message message = _messages[i]; 125 | if (message.Role == "user") 126 | { 127 | OnUserMessageGUI(message); 128 | } 129 | else 130 | { 131 | OnAssistantMessageGUI(message); 132 | } 133 | GUILayout.Space(5); 134 | } 135 | _sessionRect = _messages.Count > 0 ? GUILayoutUtility.GetLastRect() : Rect.zero; 136 | OnThinkingMessageGUI(); 137 | GUILayout.EndScrollView(); 138 | 139 | GUILayout.FlexibleSpace(); 140 | 141 | GUILayout.Space(10); 142 | 143 | GUILayout.BeginHorizontal(); 144 | GUI.color = Color.yellow; 145 | GUILayout.Label($"给【{Agent.Name}】发送指令:"); 146 | GUI.color = _userAt != "@" ? Color.yellow : Color.gray; 147 | if (GUILayout.Button(_userAt, EditorStyles.popup)) 148 | { 149 | GenericMenu gm = new GenericMenu(); 150 | for (int i = 0; i < Agent.Ats.Length; i++) 151 | { 152 | string at = Agent.Ats[i]; 153 | gm.AddItem(new GUIContent(at), at == _userAt, () => 154 | { 155 | _userAt = at; 156 | }); 157 | } 158 | gm.AddSeparator(""); 159 | gm.AddItem(new GUIContent(""), "@" == _userAt, () => 160 | { 161 | _userAt = "@"; 162 | }); 163 | gm.ShowAsContext(); 164 | } 165 | GUI.color = Color.white; 166 | GUILayout.FlexibleSpace(); 167 | 168 | GUI.color = string.IsNullOrEmpty(_userCode) ? Color.gray : Color.white; 169 | _codeGC.tooltip = string.IsNullOrEmpty(_userCode) ? "上传代码" : "上传代码(已上传)"; 170 | if (GUILayout.Button(_codeGC, EditorStyles.iconButton)) 171 | { 172 | StringValueEditor.OpenWindow(this, _userCode, "上传代码", (str) => 173 | { 174 | _userCode = string.IsNullOrEmpty(str) ? null : str.Trim(); 175 | }); 176 | } 177 | GUI.color = Color.white; 178 | 179 | GUI.color = string.IsNullOrEmpty(_userFolderPath) ? Color.gray : Color.white; 180 | _folderGC.tooltip = string.IsNullOrEmpty(_userFolderPath) ? "选择文件夹" : $"选择文件夹(已选择:{_userFolderPath})"; 181 | if (GUILayout.Button(_folderGC, EditorStyles.iconButton)) 182 | { 183 | string path = EditorUtility.OpenFolderPanel("选择文件夹", Application.dataPath, ""); 184 | if (!string.IsNullOrEmpty(path)) 185 | { 186 | _userFolderPath = "Assets" + path.Replace(Application.dataPath, ""); 187 | GUI.FocusControl(null); 188 | } 189 | } 190 | GUI.color = Color.white; 191 | 192 | GUILayout.Space(5); 193 | GUILayout.EndHorizontal(); 194 | 195 | if (_isReplying) 196 | { 197 | GUILayout.BeginHorizontal(); 198 | GUI.enabled = false; 199 | EditorGUILayout.TextArea($"{Agent.Name} 正在处理指令,请稍后......", GUILayout.MinHeight(40)); 200 | GUI.enabled = true; 201 | GUILayout.EndHorizontal(); 202 | } 203 | else 204 | { 205 | GUILayout.BeginHorizontal("textarea"); 206 | _userContent = EditorGUILayout.TextArea(_userContent, _assistantWindow._userInputStyle, GUILayout.MinHeight(40)); 207 | GUILayout.EndHorizontal(); 208 | } 209 | 210 | GUILayout.BeginHorizontal(); 211 | GUI.enabled = !string.IsNullOrEmpty(_userContent) && !_isReplying; 212 | if (GUILayout.Button("发送指令", EditorGlobalTools.Styles.LargeButton)) 213 | { 214 | SendInstruction(); 215 | ToSessionScrollBottom(); 216 | } 217 | GUI.enabled = true; 218 | GUILayout.EndHorizontal(); 219 | 220 | GUILayout.EndVertical(); 221 | } 222 | private void OnErrorGUI() 223 | { 224 | GUILayout.BeginVertical(); 225 | 226 | GUILayout.FlexibleSpace(); 227 | 228 | GUILayout.BeginHorizontal(); 229 | 230 | GUILayout.FlexibleSpace(); 231 | 232 | GUI.color = Color.yellow; 233 | GUILayout.Label("当前无法启用智能体,请设置有效的【智能体类型】。", EditorStyles.largeLabel); 234 | GUI.color = Color.white; 235 | 236 | GUILayout.FlexibleSpace(); 237 | 238 | GUILayout.EndHorizontal(); 239 | 240 | GUILayout.FlexibleSpace(); 241 | 242 | GUILayout.EndVertical(); 243 | } 244 | private void OnUserMessageGUI(Message message) 245 | { 246 | GUILayout.BeginHorizontal(); 247 | GUILayout.FlexibleSpace(); 248 | GUI.color = Color.gray; 249 | GUILayout.Label(message.Date, _assistantWindow._dateStyle, GUILayout.Height(40)); 250 | GUI.color = Color.white; 251 | GUILayout.Label(_assistantWindow._userGC, GUILayout.Width(40), GUILayout.Height(40)); 252 | GUILayout.EndHorizontal(); 253 | 254 | GUILayout.BeginHorizontal("textarea"); 255 | EditorGUILayout.TextArea(message.Content, _assistantWindow._userStyle); 256 | if (!string.IsNullOrEmpty(message.Code)) 257 | { 258 | _codeGC.tooltip = "上传了代码"; 259 | if (GUILayout.Button(_codeGC, EditorStyles.iconButton)) 260 | { 261 | StringValueEditor.OpenWindow(this, message.Code, "已上传的代码", null); 262 | } 263 | } 264 | if (!string.IsNullOrEmpty(message.FolderPath)) 265 | { 266 | _folderGC.tooltip = "选择了文件夹"; 267 | if (GUILayout.Button(_folderGC, EditorStyles.iconButton)) 268 | { 269 | Selection.activeObject = AssetDatabase.LoadAssetAtPath(message.FolderPath); 270 | } 271 | } 272 | GUILayout.EndHorizontal(); 273 | } 274 | private void OnAssistantMessageGUI(Message message) 275 | { 276 | GUILayout.BeginHorizontal(); 277 | GUILayout.Label(_aiAgentGC, GUILayout.Width(40), GUILayout.Height(40)); 278 | GUI.color = Color.gray; 279 | GUILayout.Label(message.Date, _assistantWindow._dateStyle, GUILayout.Height(40)); 280 | GUI.color = Color.white; 281 | GUILayout.FlexibleSpace(); 282 | GUILayout.EndHorizontal(); 283 | 284 | GUILayout.BeginHorizontal("textarea"); 285 | EditorGUILayout.TextArea(message.Content, _assistantWindow._assistantStyle); 286 | GUILayout.EndHorizontal(); 287 | } 288 | private void OnThinkingMessageGUI() 289 | { 290 | if (_isThinking) 291 | { 292 | GUILayout.BeginHorizontal(); 293 | GUILayout.Label(_aiAgentGC, GUILayout.Width(40), GUILayout.Height(40)); 294 | GUILayout.FlexibleSpace(); 295 | GUILayout.EndHorizontal(); 296 | 297 | GUILayout.BeginHorizontal("textarea"); 298 | EditorGUILayout.LabelField("思考中......", _assistantWindow._assistantStyle); 299 | GUILayout.EndHorizontal(); 300 | 301 | GUILayout.Space(5); 302 | } 303 | } 304 | private void EventHandle() 305 | { 306 | if (Event.current == null) 307 | return; 308 | 309 | switch (Event.current.rawType) 310 | { 311 | case EventType.KeyDown: 312 | switch (Event.current.keyCode) 313 | { 314 | case KeyCode.Return: 315 | case KeyCode.KeypadEnter: 316 | if (!string.IsNullOrEmpty(_userContent) && !_isReplying) 317 | { 318 | SendInstruction(); 319 | ToSessionScrollBottom(); 320 | GUI.FocusControl(null); 321 | } 322 | break; 323 | } 324 | break; 325 | } 326 | } 327 | private void ToSessionScrollBottom() 328 | { 329 | EditorApplication.delayCall += () => 330 | { 331 | _sessionScroll = _sessionRect.position; 332 | Repaint(); 333 | }; 334 | } 335 | 336 | /// 337 | /// 发送指令 338 | /// 339 | private void SendInstruction() 340 | { 341 | _isThinking = true; 342 | _isReplying = true; 343 | 344 | string at = _userAt != "@" ? $" {_userAt}" : ""; 345 | _messages.Add(new Message { Role = "user", Content = _userContent + at, Date = DateTime.Now.ToDefaultDateString(), Code = _userCode, FolderPath = _userFolderPath }); 346 | Agent.SendInstruction(_userContent, _userCode, _userFolderPath, _userAt, (done, reply) => 347 | { 348 | _isThinking = false; 349 | 350 | if (done) 351 | { 352 | _isReplying = false; 353 | } 354 | 355 | if (!string.IsNullOrEmpty(reply)) 356 | { 357 | _messages.Add(new Message { Role = "assistant", Content = reply, Date = DateTime.Now.ToDefaultDateString(), Code = null, FolderPath = null }); 358 | ToSessionScrollBottom(); 359 | } 360 | 361 | Focus(); 362 | }); 363 | _userContent = null; 364 | _userCode = null; 365 | _userFolderPath = null; 366 | } 367 | 368 | [Serializable] 369 | private class Message 370 | { 371 | public string Role; 372 | public string Content; 373 | public string Date; 374 | public string Code; 375 | public string FolderPath; 376 | } 377 | } 378 | } -------------------------------------------------------------------------------- /Editor/Assistant/Agent/AIAgentWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba11055176d981e4c9200d9ea36388d3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/AssistantPromptWordWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace HT.Framework.AI 5 | { 6 | internal sealed class AssistantPromptWordWindow : HTFEditorWindow 7 | { 8 | public static void OpenWindow(AssistantWindow assistantWindow, ChatSession chatSession) 9 | { 10 | AssistantPromptWordWindow window = GetWindow(); 11 | window.titleContent.text = "Assistant PromptWords"; 12 | window._assistantWindow = assistantWindow; 13 | window._chatSession = chatSession; 14 | window._content = chatSession.PromptWords.Content; 15 | window.Show(); 16 | } 17 | 18 | private AssistantWindow _assistantWindow; 19 | private ChatSession _chatSession; 20 | private string _content; 21 | private GUIContent _deleteGC; 22 | private Vector2 _scroll; 23 | 24 | protected override void OnEnable() 25 | { 26 | base.OnEnable(); 27 | 28 | _deleteGC = new GUIContent(); 29 | _deleteGC.image = EditorGUIUtility.IconContent("TreeEditor.Trash").image; 30 | _deleteGC.tooltip = "清空提示词"; 31 | } 32 | protected override void OnTitleGUI() 33 | { 34 | base.OnTitleGUI(); 35 | 36 | GUILayout.Label("【智能体提示词】" + _chatSession.Name); 37 | GUILayout.FlexibleSpace(); 38 | 39 | if (GUILayout.Button(_deleteGC, EditorStyles.iconButton)) 40 | { 41 | _content = null; 42 | } 43 | } 44 | protected override void OnBodyGUI() 45 | { 46 | base.OnBodyGUI(); 47 | 48 | _scroll = GUILayout.BeginScrollView(_scroll); 49 | _content = EditorGUILayout.TextArea(_content); 50 | GUILayout.EndScrollView(); 51 | 52 | GUILayout.FlexibleSpace(); 53 | 54 | GUILayout.BeginHorizontal(); 55 | if (GUILayout.Button("保存", "ButtonLeft")) 56 | { 57 | _chatSession.SetPromptWords(_content); 58 | Close(); 59 | } 60 | if (GUILayout.Button("取消", "ButtonRight")) 61 | { 62 | Close(); 63 | } 64 | GUILayout.EndHorizontal(); 65 | } 66 | private void Update() 67 | { 68 | if (_assistantWindow == null) 69 | { 70 | Close(); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /Editor/Assistant/AssistantPromptWordWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f79ca18ef84792a4ab6720a5c03f01de 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/AssistantRenameWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace HT.Framework.AI 5 | { 6 | internal sealed class AssistantRenameWindow : HTFEditorWindow 7 | { 8 | public static void OpenWindow(AssistantWindow assistantWindow, ChatSession chatSession) 9 | { 10 | AssistantRenameWindow window = GetWindow(); 11 | window.titleContent.text = "Assistant Rename"; 12 | window._assistantWindow = assistantWindow; 13 | window._chatSession = chatSession; 14 | window._name = chatSession.Name; 15 | window.minSize = new Vector2(300, 100); 16 | window.maxSize = new Vector2(300, 100); 17 | window.Show(); 18 | } 19 | 20 | private AssistantWindow _assistantWindow; 21 | private ChatSession _chatSession; 22 | private string _name; 23 | 24 | protected override void OnTitleGUI() 25 | { 26 | base.OnTitleGUI(); 27 | 28 | GUILayout.Label("【重命名】" + _chatSession.Name); 29 | GUILayout.FlexibleSpace(); 30 | } 31 | protected override void OnBodyGUI() 32 | { 33 | base.OnBodyGUI(); 34 | 35 | GUILayout.BeginHorizontal(); 36 | _name = EditorGUILayout.TextField(_name); 37 | GUILayout.EndHorizontal(); 38 | 39 | GUILayout.FlexibleSpace(); 40 | 41 | GUILayout.BeginHorizontal(); 42 | if (GUILayout.Button("保存", "ButtonLeft")) 43 | { 44 | _chatSession.Name = _name; 45 | Close(); 46 | } 47 | if (GUILayout.Button("取消", "ButtonRight")) 48 | { 49 | Close(); 50 | } 51 | GUILayout.EndHorizontal(); 52 | } 53 | private void Update() 54 | { 55 | if (_assistantWindow == null) 56 | { 57 | Close(); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Editor/Assistant/AssistantRenameWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3ad9e1c86ac297c43af4af4bd9c347e8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/AssistantSettingsWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace HT.Framework.AI 9 | { 10 | internal sealed class AssistantSettingsWindow : HTFEditorWindow 11 | { 12 | private const int KEYEVENTF_EXTENDEDKEY = 0x1; 13 | private const int KEYEVENTF_KEYUP = 0x2; 14 | private const byte VK_CONTROL = 0x11; 15 | private const byte VK_V = 0x56; 16 | private const byte VK_RETURN = 0x0D; 17 | private static string[] DeepSeekModels = new string[] { 18 | "deepseek-r1:8b" 19 | , "deepseek-r1:14b" 20 | , "deepseek-r1:32b" 21 | , "deepseek-r1:70b" 22 | , "deepseek-r1:671b" 23 | , "deepseek-coder-v2:16b" 24 | , "deepseek-coder-v2:236b" 25 | , "deepseek-v2:16b" 26 | , "deepseek-v2:236b" 27 | , "deepseek-v3:671b" 28 | , "deepseek-llm:7b" 29 | , "deepseek-llm:67b" 30 | }; 31 | private static string[] GLMModels = new string[] { 32 | "glm4:9b" 33 | }; 34 | private static string[] QwenModels = new string[] { 35 | "qwen:14b" 36 | , "qwen2:7b" 37 | , "qwen2.5:14b" 38 | }; 39 | private static string[] PhiModels = new string[] { 40 | "phi3:14b" 41 | , "phi4:14b" 42 | }; 43 | private static string[] LlamaModels = new string[] { 44 | "llama2:13b" 45 | , "llama3:8b" 46 | , "llama3.1:8b" 47 | , "llama3.3:70b" 48 | }; 49 | private static string[] MistralModels = new string[] { 50 | "mistral:7b" 51 | , "mistral-nemo:12b" 52 | }; 53 | private static string[] GemmaModels = new string[] { 54 | "gemma:7b" 55 | , "gemma2:9b" 56 | , "gemma2:27b" 57 | }; 58 | 59 | public static void OpenWindow(AssistantWindow assistantWindow) 60 | { 61 | AssistantSettingsWindow window = GetWindow(); 62 | window.titleContent.text = "Assistant Settings"; 63 | window._assistantWindow = assistantWindow; 64 | window.minSize = new Vector2(300, 300); 65 | window.maxSize = new Vector2(300, 300); 66 | window.Show(); 67 | } 68 | [DllImport("user32.dll")] 69 | private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo); 70 | 71 | private AssistantWindow _assistantWindow; 72 | private Texture _ollamaIcon; 73 | private GUIContent _ollamaGC; 74 | private string _model; 75 | private bool _stream; 76 | private string _baseAddress; 77 | private string _api; 78 | private int _timeout; 79 | private int _round; 80 | private bool _isLogInEditor; 81 | private bool _isShowThink; 82 | private AssistantSessionSavePath _savePath; 83 | private bool _isEnableAIAgent; 84 | 85 | protected override void OnEnable() 86 | { 87 | base.OnEnable(); 88 | 89 | _ollamaIcon = AssetDatabase.LoadAssetAtPath("Assets/HTFrameworkAI/Editor/Assistant/Texture/OllamaIcon.png"); 90 | _ollamaGC = new GUIContent(); 91 | _ollamaGC.image = _ollamaIcon; 92 | _model = EditorPrefs.GetString(EditorPrefsTableAI.Assistant_Model, "deepseek-coder-v2:16b"); 93 | _stream = EditorPrefs.GetBool(EditorPrefsTableAI.Assistant_Stream, true); 94 | _baseAddress = EditorPrefs.GetString(EditorPrefsTableAI.Assistant_BaseAddress, "http://localhost:11434"); 95 | _api = EditorPrefs.GetString(EditorPrefsTableAI.Assistant_API, "/api/chat"); 96 | _timeout = EditorPrefs.GetInt(EditorPrefsTableAI.Assistant_Timeout, 60); 97 | _round = EditorPrefs.GetInt(EditorPrefsTableAI.Assistant_Round, 7); 98 | _isLogInEditor = EditorPrefs.GetBool(EditorPrefsTableAI.Assistant_IsLogInEditor, false); 99 | _isShowThink = EditorPrefs.GetBool(EditorPrefsTableAI.Assistant_ShowThink, false); 100 | _savePath = (AssistantSessionSavePath)EditorPrefs.GetInt(EditorPrefsTableAI.Assistant_SavePath, 0); 101 | _isEnableAIAgent = EditorPrefs.GetBool(EditorPrefsTableAI.Assistant_EnableAIAgent, false); 102 | } 103 | protected override void OnBodyGUI() 104 | { 105 | base.OnBodyGUI(); 106 | 107 | GUILayout.BeginHorizontal(); 108 | GUILayout.Label("大模型", GUILayout.Width(120)); 109 | _model = EditorGUILayout.TextField(_model); 110 | if (GUILayout.Button("选择", EditorStyles.popup)) 111 | { 112 | GenericMenu gm = new GenericMenu(); 113 | SetGenericMenuItem(gm, "DeepSeek (深度求索)", DeepSeekModels); 114 | SetGenericMenuItem(gm, "GLM (智谱清言)", GLMModels); 115 | SetGenericMenuItem(gm, "Qwen (通义千问)", QwenModels); 116 | SetGenericMenuItem(gm, "Phi (Microsoft)", PhiModels); 117 | SetGenericMenuItem(gm, "Llama (Meta)", LlamaModels); 118 | SetGenericMenuItem(gm, "Mistral (Mistral AI)", MistralModels); 119 | SetGenericMenuItem(gm, "Gemma (Google)", GemmaModels); 120 | gm.AddSeparator(""); 121 | gm.AddItem(new GUIContent("Other......"), false, () => 122 | { 123 | Application.OpenURL("https://ollama.com/search"); 124 | }); 125 | gm.ShowAsContext(); 126 | } 127 | GUILayout.EndHorizontal(); 128 | 129 | GUILayout.BeginHorizontal(); 130 | GUILayout.Label("流式请求", GUILayout.Width(120)); 131 | _stream = EditorGUILayout.Toggle(_stream); 132 | GUILayout.EndHorizontal(); 133 | 134 | GUILayout.BeginHorizontal(); 135 | GUILayout.Label("大模型根地址", GUILayout.Width(120)); 136 | _baseAddress = EditorGUILayout.TextField(_baseAddress); 137 | GUILayout.EndHorizontal(); 138 | 139 | GUILayout.BeginHorizontal(); 140 | GUILayout.Label("API接口", GUILayout.Width(120)); 141 | _api = EditorGUILayout.TextField(_api); 142 | GUILayout.EndHorizontal(); 143 | 144 | GUILayout.BeginHorizontal(); 145 | GUILayout.Label("超时时长(秒)", GUILayout.Width(120)); 146 | _timeout = EditorGUILayout.IntField(_timeout); 147 | GUILayout.EndHorizontal(); 148 | 149 | GUILayout.BeginHorizontal(); 150 | GUILayout.Label("多轮对话最大轮数", GUILayout.Width(120)); 151 | _round = EditorGUILayout.IntField(_round); 152 | GUILayout.EndHorizontal(); 153 | 154 | GUILayout.BeginHorizontal(); 155 | GUILayout.Label("打印相关日志", GUILayout.Width(120)); 156 | _isLogInEditor = EditorGUILayout.Toggle(_isLogInEditor); 157 | GUILayout.EndHorizontal(); 158 | 159 | GUILayout.BeginHorizontal(); 160 | GUILayout.Label("显示推理过程", GUILayout.Width(120)); 161 | _isShowThink = EditorGUILayout.Toggle(_isShowThink); 162 | GUILayout.EndHorizontal(); 163 | 164 | GUILayout.BeginHorizontal(); 165 | GUILayout.Label("数据存储路径", GUILayout.Width(120)); 166 | _savePath = (AssistantSessionSavePath)EditorGUILayout.EnumPopup(_savePath); 167 | GUILayout.EndHorizontal(); 168 | 169 | GUILayout.BeginHorizontal(); 170 | GUI.color = _isEnableAIAgent ? Color.green : Color.gray; 171 | GUILayout.Label("启用通用智能体", GUILayout.Width(120)); 172 | _isEnableAIAgent = EditorGUILayout.Toggle(_isEnableAIAgent); 173 | GUI.color = Color.white; 174 | GUILayout.EndHorizontal(); 175 | 176 | GUILayout.Space(10); 177 | 178 | GUILayout.FlexibleSpace(); 179 | 180 | GUILayout.BeginHorizontal(); 181 | GUI.backgroundColor = Color.yellow; 182 | _ollamaGC.text = "Run In Ollama"; 183 | if (GUILayout.Button(_ollamaGC, GUILayout.Height(30))) 184 | { 185 | if (EditorUtility.DisplayDialog("Run In Ollama", $"是否确认在 Ollama 中启动大模型 [{_model}] ?如果该模型未下载,将自动下载。", "是的", "我再想想")) 186 | { 187 | RunOllamaInCMD($"ollama run {_model}"); 188 | } 189 | } 190 | GUI.backgroundColor = Color.white; 191 | GUILayout.EndHorizontal(); 192 | 193 | GUILayout.BeginHorizontal(); 194 | GUI.backgroundColor = Color.yellow; 195 | _ollamaGC.text = "View Ollama Model List"; 196 | if (GUILayout.Button(_ollamaGC, GUILayout.Height(30))) 197 | { 198 | RunOllamaInCMD("ollama list"); 199 | } 200 | GUI.backgroundColor = Color.white; 201 | GUILayout.EndHorizontal(); 202 | 203 | GUILayout.BeginHorizontal(); 204 | if (GUILayout.Button("保存", "ButtonLeft")) 205 | { 206 | EditorPrefs.SetString(EditorPrefsTableAI.Assistant_Model, _model); 207 | EditorPrefs.SetBool(EditorPrefsTableAI.Assistant_Stream, _stream); 208 | EditorPrefs.SetString(EditorPrefsTableAI.Assistant_BaseAddress, _baseAddress); 209 | EditorPrefs.SetString(EditorPrefsTableAI.Assistant_API, _api); 210 | EditorPrefs.SetInt(EditorPrefsTableAI.Assistant_Timeout, _timeout); 211 | EditorPrefs.SetInt(EditorPrefsTableAI.Assistant_Round, _round); 212 | EditorPrefs.SetBool(EditorPrefsTableAI.Assistant_IsLogInEditor, _isLogInEditor); 213 | EditorPrefs.SetBool(EditorPrefsTableAI.Assistant_ShowThink, _isShowThink); 214 | EditorPrefs.SetInt(EditorPrefsTableAI.Assistant_SavePath, (int)_savePath); 215 | EditorPrefs.SetBool(EditorPrefsTableAI.Assistant_EnableAIAgent, _isEnableAIAgent); 216 | _assistantWindow.ApplySettings(); 217 | Close(); 218 | } 219 | if (GUILayout.Button("取消", "ButtonRight")) 220 | { 221 | Close(); 222 | } 223 | GUILayout.EndHorizontal(); 224 | } 225 | private void Update() 226 | { 227 | if (_assistantWindow == null) 228 | { 229 | Close(); 230 | } 231 | } 232 | private void RunOllamaInCMD(string order) 233 | { 234 | GUIUtility.systemCopyBuffer = order; 235 | 236 | ProcessStartInfo startInfo = new ProcessStartInfo(); 237 | startInfo.FileName = "cmd.exe"; 238 | startInfo.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 239 | 240 | using Process process = new Process(); 241 | process.StartInfo = startInfo; 242 | process.Start(); 243 | 244 | Thread.Sleep(1000); 245 | 246 | keybd_event(VK_CONTROL, 0, KEYEVENTF_EXTENDEDKEY, 0); 247 | keybd_event(VK_V, 0, KEYEVENTF_EXTENDEDKEY, 0); 248 | 249 | Thread.Sleep(100); 250 | 251 | keybd_event(VK_V, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); 252 | keybd_event(VK_CONTROL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); 253 | 254 | Thread.Sleep(1000); 255 | 256 | keybd_event(VK_RETURN, 0x45, KEYEVENTF_EXTENDEDKEY, 0); 257 | keybd_event(VK_RETURN, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); 258 | } 259 | private void SetGenericMenuItem(GenericMenu gm, string group, string[] models) 260 | { 261 | for (int i = 0; i < models.Length; i++) 262 | { 263 | string model = models[i]; 264 | gm.AddItem(new GUIContent(group + "/" + model), model == _model, () => 265 | { 266 | _model = model; 267 | }); 268 | } 269 | } 270 | } 271 | 272 | /// 273 | /// 会话数据存储路径 274 | /// 275 | public enum AssistantSessionSavePath 276 | { 277 | LocalAppData = 0, 278 | Library = 1 279 | } 280 | } -------------------------------------------------------------------------------- /Editor/Assistant/AssistantSettingsWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e36ac7dc6b288e44bb088008cea13706 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/AssistantWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e848445f48abee444b9a818a13d25390 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/ImageGeneration.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f761925729780924f986e22d0b470693 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Assistant/ImageGeneration/ImageGenerationWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace HT.Framework.AI 8 | { 9 | internal sealed class ImageGenerationWindow : HTFEditorWindow 10 | { 11 | public static void OpenWindow(AssistantWindow assistantWindow) 12 | { 13 | ImageGenerationWindow window = GetWindow(); 14 | window.titleContent.image = EditorGUIUtility.IconContent("d_RawImage Icon").image; 15 | window.titleContent.text = "AI Draw"; 16 | window._assistantWindow = assistantWindow; 17 | window.Show(); 18 | } 19 | 20 | private AssistantWindow _assistantWindow; 21 | private List _messages = new List(); 22 | private List _seeds = new List(); 23 | private bool _isDrawing = false; 24 | private string _prompt; 25 | private int _width = 512; 26 | private int _height = 512; 27 | 28 | private GUIContent _redrawGC; 29 | private GUIContent _saveGC; 30 | private Vector2 _sessionScroll; 31 | private Rect _sessionRect; 32 | 33 | protected override void OnEnable() 34 | { 35 | base.OnEnable(); 36 | 37 | _redrawGC = new GUIContent(); 38 | _redrawGC.image = EditorGUIUtility.IconContent("d_editicon.sml").image; 39 | _redrawGC.tooltip = "重新画一张"; 40 | _saveGC = new GUIContent(); 41 | _saveGC.image = EditorGUIUtility.IconContent("d_SaveAs").image; 42 | _saveGC.tooltip = "保存图像"; 43 | } 44 | private void Update() 45 | { 46 | if (_assistantWindow == null) 47 | { 48 | Close(); 49 | } 50 | } 51 | private void OnDestroy() 52 | { 53 | for (int i = 0; i < _messages.Count; i++) 54 | { 55 | if (_messages[i].Image != null) 56 | { 57 | Main.KillImmediate(_messages[i].Image); 58 | } 59 | } 60 | _messages.Clear(); 61 | _seeds.Clear(); 62 | } 63 | protected override void OnBodyGUI() 64 | { 65 | base.OnBodyGUI(); 66 | 67 | EventHandle(); 68 | OnSessionGUI(); 69 | } 70 | private void OnSessionGUI() 71 | { 72 | GUILayout.BeginVertical(); 73 | 74 | GUILayout.BeginHorizontal(); 75 | GUILayout.FlexibleSpace(); 76 | GUILayout.Label("AI画图", "WarningOverlay", GUILayout.ExpandWidth(true)); 77 | GUILayout.FlexibleSpace(); 78 | GUILayout.EndHorizontal(); 79 | 80 | GUILayout.BeginHorizontal(); 81 | GUILayout.FlexibleSpace(); 82 | GUI.color = Color.gray; 83 | GUILayout.Label($"共{_messages.Count}条记录", GUILayout.ExpandWidth(true)); 84 | GUI.color = Color.white; 85 | GUILayout.FlexibleSpace(); 86 | GUILayout.EndHorizontal(); 87 | 88 | GUILayout.Space(5); 89 | 90 | _sessionScroll = GUILayout.BeginScrollView(_sessionScroll); 91 | for (int i = 0; i < _messages.Count; i++) 92 | { 93 | Message message = _messages[i]; 94 | if (message.Role == "user") 95 | { 96 | OnUserMessageGUI(message); 97 | } 98 | else 99 | { 100 | OnAssistantMessageGUI(message); 101 | } 102 | GUILayout.Space(5); 103 | } 104 | _sessionRect = _messages.Count > 0 ? GUILayoutUtility.GetLastRect() : Rect.zero; 105 | OnDrawingMessageGUI(); 106 | GUILayout.EndScrollView(); 107 | 108 | GUILayout.FlexibleSpace(); 109 | 110 | GUILayout.Space(10); 111 | 112 | GUILayout.BeginHorizontal(); 113 | GUI.color = Color.yellow; 114 | GUILayout.Label("给【AI画图】发送图像描述:"); 115 | GUI.color = Color.white; 116 | GUILayout.FlexibleSpace(); 117 | _width = EditorGUILayout.IntField(_width, GUILayout.Width(50)); 118 | GUILayout.Label("x", GUILayout.Width(10)); 119 | _height = EditorGUILayout.IntField(_height, GUILayout.Width(50)); 120 | GUILayout.EndHorizontal(); 121 | 122 | if (_isDrawing) 123 | { 124 | GUILayout.BeginHorizontal(); 125 | GUI.enabled = false; 126 | EditorGUILayout.TextArea("正在画图中,请稍后......", GUILayout.MinHeight(40)); 127 | GUI.enabled = true; 128 | GUILayout.EndHorizontal(); 129 | } 130 | else 131 | { 132 | GUILayout.BeginHorizontal("textarea"); 133 | _prompt = EditorGUILayout.TextArea(_prompt, _assistantWindow._userInputStyle, GUILayout.MinHeight(40)); 134 | GUILayout.EndHorizontal(); 135 | } 136 | 137 | GUILayout.BeginHorizontal(); 138 | GUI.enabled = !string.IsNullOrEmpty(_prompt) && !_isDrawing; 139 | if (GUILayout.Button("开始画图", EditorGlobalTools.Styles.LargeButton)) 140 | { 141 | SendImageGeneration(); 142 | ToSessionScrollBottom(); 143 | } 144 | GUI.enabled = true; 145 | GUILayout.EndHorizontal(); 146 | 147 | GUILayout.EndVertical(); 148 | } 149 | private void OnUserMessageGUI(Message message) 150 | { 151 | GUILayout.BeginHorizontal(); 152 | GUILayout.FlexibleSpace(); 153 | GUI.color = Color.gray; 154 | GUILayout.Label(message.Date, _assistantWindow._dateStyle, GUILayout.Height(40)); 155 | GUI.color = Color.white; 156 | GUILayout.Label(_assistantWindow._userGC, GUILayout.Width(40), GUILayout.Height(40)); 157 | GUILayout.EndHorizontal(); 158 | 159 | GUILayout.BeginHorizontal("textarea"); 160 | EditorGUILayout.TextArea(message.Content, _assistantWindow._userStyle); 161 | GUILayout.EndHorizontal(); 162 | } 163 | private void OnAssistantMessageGUI(Message message) 164 | { 165 | GUILayout.BeginHorizontal(); 166 | GUILayout.Label(_assistantWindow._assistantGC, GUILayout.Width(40), GUILayout.Height(40)); 167 | GUI.color = Color.gray; 168 | GUILayout.Label(message.Date, _assistantWindow._dateStyle, GUILayout.Height(40)); 169 | GUI.color = Color.white; 170 | GUILayout.FlexibleSpace(); 171 | GUILayout.EndHorizontal(); 172 | 173 | GUILayout.BeginVertical(); 174 | 175 | GUILayout.Space(2); 176 | 177 | if (message.Image) 178 | { 179 | GUILayout.BeginHorizontal(); 180 | GUILayout.Space(5); 181 | GUI.enabled = !_isDrawing; 182 | if (GUILayout.Button(_redrawGC, EditorGlobalTools.Styles.IconButton, GUILayout.Width(20), GUILayout.Height(20))) 183 | { 184 | _prompt = message.Content; 185 | SendImageGeneration(); 186 | ToSessionScrollBottom(); 187 | } 188 | if (GUILayout.Button(_saveGC, EditorGlobalTools.Styles.IconButton, GUILayout.Width(20), GUILayout.Height(20))) 189 | { 190 | string path = EditorUtility.SaveFilePanel("保存图像", Application.dataPath, "", "png"); 191 | if (!string.IsNullOrEmpty(path)) 192 | { 193 | File.WriteAllBytes(path, message.Image.EncodeToPNG()); 194 | if (path.StartsWith(Application.dataPath)) AssetDatabase.Refresh(); 195 | } 196 | } 197 | GUI.enabled = true; 198 | GUILayout.FlexibleSpace(); 199 | GUILayout.EndHorizontal(); 200 | 201 | GUILayout.BeginHorizontal(); 202 | int width = (int)Mathf.Min(message.Image.width, position.width - 10); 203 | int height = (int)((float)width / message.Image.width * message.Image.height); 204 | GUILayout.Label(message.Image, GUILayout.Width(width), GUILayout.Height(height)); 205 | GUILayout.EndHorizontal(); 206 | } 207 | 208 | GUILayout.EndVertical(); 209 | } 210 | private void OnDrawingMessageGUI() 211 | { 212 | if (_isDrawing) 213 | { 214 | GUILayout.BeginHorizontal(); 215 | GUILayout.Label(_assistantWindow._assistantGC, GUILayout.Width(40), GUILayout.Height(40)); 216 | GUILayout.FlexibleSpace(); 217 | GUILayout.EndHorizontal(); 218 | 219 | GUILayout.BeginHorizontal("textarea"); 220 | EditorGUILayout.LabelField("画图中......", _assistantWindow._assistantStyle); 221 | GUILayout.EndHorizontal(); 222 | 223 | GUILayout.Space(5); 224 | } 225 | } 226 | private void EventHandle() 227 | { 228 | if (Event.current == null) 229 | return; 230 | 231 | switch (Event.current.rawType) 232 | { 233 | case EventType.KeyDown: 234 | switch (Event.current.keyCode) 235 | { 236 | case KeyCode.Return: 237 | case KeyCode.KeypadEnter: 238 | if (!string.IsNullOrEmpty(_prompt) && !_isDrawing) 239 | { 240 | SendImageGeneration(); 241 | ToSessionScrollBottom(); 242 | GUI.FocusControl(null); 243 | } 244 | break; 245 | } 246 | break; 247 | } 248 | } 249 | private void ToSessionScrollBottom() 250 | { 251 | EditorApplication.delayCall += () => 252 | { 253 | _sessionScroll = _sessionRect.position; 254 | Repaint(); 255 | }; 256 | } 257 | 258 | /// 259 | /// 发送生成图像请求 260 | /// 261 | private async void SendImageGeneration() 262 | { 263 | _isDrawing = true; 264 | 265 | _messages.Add(new Message { Role = "user", Content = _prompt, Date = DateTime.Now.ToDefaultDateString(), Image = null }); 266 | byte[] bytes = await PollinationsAI.ImageGeneration(_prompt, _width, _height, GetSeed(_prompt)); 267 | 268 | if (bytes != null) 269 | { 270 | Texture2D texture = new Texture2D(_width, _height, TextureFormat.RGBA32, false); 271 | texture.LoadImage(bytes); 272 | _messages.Add(new Message { Role = "assistant", Content = _prompt, Date = DateTime.Now.ToDefaultDateString(), Image = texture }); 273 | ToSessionScrollBottom(); 274 | } 275 | 276 | Focus(); 277 | 278 | _isDrawing = false; 279 | _prompt = null; 280 | } 281 | /// 282 | /// 获取生成种子 283 | /// 284 | /// 生成图像的提示 285 | /// 生成种子 286 | private int GetSeed(string prompt) 287 | { 288 | Seed seed = _seeds.Find((s) => { return s.Prompt == prompt; }); 289 | if (seed == null) 290 | { 291 | seed = new Seed() 292 | { 293 | Prompt = prompt, 294 | SeedIndex = -1 295 | }; 296 | _seeds.Add(seed); 297 | } 298 | 299 | seed.SeedIndex += 1; 300 | return seed.SeedIndex; 301 | } 302 | 303 | [Serializable] 304 | private class Message 305 | { 306 | public string Role; 307 | public string Content; 308 | public string Date; 309 | public Texture2D Image; 310 | } 311 | [Serializable] 312 | private class Seed 313 | { 314 | public string Prompt; 315 | public int SeedIndex; 316 | } 317 | } 318 | } -------------------------------------------------------------------------------- /Editor/Assistant/ImageGeneration/ImageGenerationWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f7cf590c034424d4f85b91c2592f427d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Assistant/Texture.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87d77c74fd5b2f7458a4e03723d7bbb3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Assistant/Texture/AIAgentIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiTingHu/HTFrameworkAI/9b53a1ce4e25150bda459e008a427d3a63a65669/Editor/Assistant/Texture/AIAgentIcon.png -------------------------------------------------------------------------------- /Editor/Assistant/Texture/AIAgentIcon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6613fa0c7367984409158a603c4fb11c 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 1 41 | wrapV: 1 42 | wrapW: 0 43 | nPOTScale: 0 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 1 56 | spriteTessellationDetail: -1 57 | textureType: 2 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 1 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | spriteSheet: 123 | serializedVersion: 2 124 | sprites: [] 125 | outline: [] 126 | physicsShape: [] 127 | bones: [] 128 | spriteID: 129 | internalID: 0 130 | vertices: [] 131 | indices: 132 | edges: [] 133 | weights: [] 134 | secondaryTextures: [] 135 | nameFileIdTable: {} 136 | mipmapLimitGroupName: 137 | pSDRemoveMatte: 0 138 | userData: 139 | assetBundleName: 140 | assetBundleVariant: 141 | -------------------------------------------------------------------------------- /Editor/Assistant/Texture/AssistantIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiTingHu/HTFrameworkAI/9b53a1ce4e25150bda459e008a427d3a63a65669/Editor/Assistant/Texture/AssistantIcon.png -------------------------------------------------------------------------------- /Editor/Assistant/Texture/AssistantIcon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0dd17cd16bb09a34a9a5440b7c0c9c04 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 1 41 | wrapV: 1 42 | wrapW: 0 43 | nPOTScale: 0 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 1 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 1 56 | spriteTessellationDetail: -1 57 | textureType: 2 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 1 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | spriteSheet: 123 | serializedVersion: 2 124 | sprites: [] 125 | outline: [] 126 | physicsShape: [] 127 | bones: [] 128 | spriteID: 129 | internalID: 0 130 | vertices: [] 131 | indices: 132 | edges: [] 133 | weights: [] 134 | secondaryTextures: [] 135 | nameFileIdTable: {} 136 | mipmapLimitGroupName: 137 | pSDRemoveMatte: 0 138 | userData: 139 | assetBundleName: 140 | assetBundleVariant: 141 | -------------------------------------------------------------------------------- /Editor/Assistant/Texture/OllamaIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiTingHu/HTFrameworkAI/9b53a1ce4e25150bda459e008a427d3a63a65669/Editor/Assistant/Texture/OllamaIcon.png -------------------------------------------------------------------------------- /Editor/Assistant/Texture/OllamaIcon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a01d0ae6d09d0ab47926cfb3f158ff19 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 1 41 | wrapV: 1 42 | wrapW: 0 43 | nPOTScale: 0 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 1 56 | spriteTessellationDetail: -1 57 | textureType: 2 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 1 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | spriteSheet: 123 | serializedVersion: 2 124 | sprites: [] 125 | outline: [] 126 | physicsShape: [] 127 | bones: [] 128 | spriteID: 129 | internalID: 0 130 | vertices: [] 131 | indices: 132 | edges: [] 133 | weights: [] 134 | secondaryTextures: [] 135 | nameFileIdTable: {} 136 | mipmapLimitGroupName: 137 | pSDRemoveMatte: 0 138 | userData: 139 | assetBundleName: 140 | assetBundleVariant: 141 | -------------------------------------------------------------------------------- /Editor/Assistant/Texture/UserIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiTingHu/HTFrameworkAI/9b53a1ce4e25150bda459e008a427d3a63a65669/Editor/Assistant/Texture/UserIcon.png -------------------------------------------------------------------------------- /Editor/Assistant/Texture/UserIcon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 35dff195baf763647bd9c058af1e36a0 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 1 41 | wrapV: 1 42 | wrapW: 0 43 | nPOTScale: 0 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 1 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 1 56 | spriteTessellationDetail: -1 57 | textureType: 2 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 1 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | spriteSheet: 123 | serializedVersion: 2 124 | sprites: [] 125 | outline: [] 126 | physicsShape: [] 127 | bones: [] 128 | spriteID: 129 | internalID: 0 130 | vertices: [] 131 | indices: 132 | edges: [] 133 | weights: [] 134 | secondaryTextures: [] 135 | nameFileIdTable: {} 136 | mipmapLimitGroupName: 137 | pSDRemoveMatte: 0 138 | userData: 139 | assetBundleName: 140 | assetBundleVariant: 141 | -------------------------------------------------------------------------------- /Editor/CharacterRecognition.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ce140cf55acc454b9adf3b2c7955d4d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/CharacterRecognition/EditorCharacterRecognitioner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityEngine.Networking; 6 | 7 | namespace HT.Framework.AI 8 | { 9 | internal sealed class EditorCharacterRecognitioner : HTFEditorWindow 10 | { 11 | private readonly string[] Modes = new string[] { 12 | "通用", 13 | "通用(含位置信息)", 14 | "高精度", 15 | "高精度(含位置信息)" }; 16 | private readonly string[] APIs = new string[] { 17 | "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic", 18 | "https://aip.baidubce.com/rest/2.0/ocr/v1/general", 19 | "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic", 20 | "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate" }; 21 | private readonly string[] ContentTypes = new string[] { 22 | "application/x-www-form-urlencoded", 23 | "application/x-www-form-urlencoded", 24 | "application/x-www-form-urlencoded", 25 | "application/x-www-form-urlencoded" }; 26 | private readonly string TOKENAPI = "https://aip.baidubce.com/oauth/2.0/token"; 27 | private string APIKEY = ""; 28 | private string SECRETKEY = ""; 29 | private string TOKEN = ""; 30 | private Texture2D _texture; 31 | private OCRResponse _response; 32 | private string _result = ""; 33 | private Vector2 _resultScroll = Vector2.zero; 34 | private bool _isRecognition = false; 35 | 36 | private int _mode = 0; 37 | private string _api = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"; 38 | private string _contentType = "application/x-www-form-urlencoded"; 39 | private CharacterRecognitionModeBase.RecognizeGranularity _granularity = CharacterRecognitionModeBase.RecognizeGranularity.Big; 40 | private CharacterRecognitionModeBase.LanguageType _language = CharacterRecognitionModeBase.LanguageType.CHN_ENG; 41 | private bool _isDetectDirection = false; 42 | private bool _isDetectLanguage = false; 43 | 44 | protected override string HelpUrl => "https://wanderer.blog.csdn.net/article/details/103765003"; 45 | 46 | protected override void OnEnable() 47 | { 48 | base.OnEnable(); 49 | 50 | APIKEY = EditorPrefs.GetString(EditorPrefsTableAI.CR_APIKEY, ""); 51 | SECRETKEY = EditorPrefs.GetString(EditorPrefsTableAI.CR_SECRETKEY, ""); 52 | TOKEN = EditorPrefs.GetString(EditorPrefsTableAI.CR_TOKEN, ""); 53 | } 54 | protected override void OnTitleGUI() 55 | { 56 | base.OnTitleGUI(); 57 | 58 | GUILayout.FlexibleSpace(); 59 | if (GUILayout.Button("Console Login", EditorStyles.toolbarButton)) 60 | { 61 | Application.OpenURL(@"https://login.bce.baidu.com/"); 62 | } 63 | } 64 | protected override void OnBodyGUI() 65 | { 66 | base.OnBodyGUI(); 67 | 68 | RecognitionIdentityGUI(); 69 | RecognitionTexture(); 70 | ResultGUI(); 71 | RecognitionArgsGUI(); 72 | RecognitionButtonGUI(); 73 | } 74 | private void RecognitionIdentityGUI() 75 | { 76 | GUILayout.BeginHorizontal("DD HeaderStyle"); 77 | GUILayout.FlexibleSpace(); 78 | GUILayout.Label("Character Recognition in Editor"); 79 | GUILayout.FlexibleSpace(); 80 | GUILayout.EndHorizontal(); 81 | 82 | 83 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 84 | 85 | GUILayout.BeginHorizontal(); 86 | GUILayout.Label("API Key:", GUILayout.Width(80)); 87 | string apikey = EditorGUILayout.PasswordField(APIKEY); 88 | if (apikey != APIKEY) 89 | { 90 | APIKEY = apikey; 91 | EditorPrefs.SetString(EditorPrefsTableAI.CR_APIKEY, APIKEY); 92 | } 93 | GUILayout.EndHorizontal(); 94 | 95 | GUILayout.BeginHorizontal(); 96 | GUILayout.Label("Secret Key:", GUILayout.Width(80)); 97 | string secretkey = EditorGUILayout.PasswordField(SECRETKEY); 98 | if (secretkey != SECRETKEY) 99 | { 100 | SECRETKEY = secretkey; 101 | EditorPrefs.SetString(EditorPrefsTableAI.CR_SECRETKEY, SECRETKEY); 102 | } 103 | GUILayout.EndHorizontal(); 104 | 105 | GUILayout.BeginHorizontal(); 106 | GUILayout.Label("Token:", GUILayout.Width(80)); 107 | EditorGUILayout.TextField(TOKEN); 108 | GUI.enabled = (APIKEY != "" && SECRETKEY != ""); 109 | if (GUILayout.Button("Generate", EditorStyles.miniButton, GUILayout.Width(70))) 110 | { 111 | string uri = string.Format("{0}?grant_type=client_credentials&client_id={1}&client_secret={2}", TOKENAPI, APIKEY, SECRETKEY); 112 | UnityWebRequest request = UnityWebRequest.Get(uri); 113 | UnityWebRequestAsyncOperation async = request.SendWebRequest(); 114 | async.completed += GenerateTOKENDone; 115 | } 116 | GUI.enabled = true; 117 | GUILayout.EndHorizontal(); 118 | 119 | GUILayout.EndVertical(); 120 | } 121 | private void RecognitionTexture() 122 | { 123 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 124 | GUILayout.Label("Recognition Texture:"); 125 | _texture = EditorGUILayout.ObjectField(_texture, typeof(Texture2D), false) as Texture2D; 126 | GUILayout.EndVertical(); 127 | } 128 | private void ResultGUI() 129 | { 130 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 131 | 132 | GUILayout.BeginHorizontal(); 133 | GUILayout.Label("Result:"); 134 | GUILayout.FlexibleSpace(); 135 | if (GUILayout.Button("Clear", EditorStyles.miniButton)) 136 | { 137 | _response = null; 138 | _result = ""; 139 | } 140 | GUILayout.EndHorizontal(); 141 | 142 | _resultScroll = GUILayout.BeginScrollView(_resultScroll); 143 | EditorGUILayout.TextArea(_result); 144 | GUILayout.EndScrollView(); 145 | 146 | GUILayout.EndVertical(); 147 | } 148 | private void RecognitionArgsGUI() 149 | { 150 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 151 | 152 | GUILayout.BeginHorizontal(); 153 | GUILayout.Label("Recognition Mode:", GUILayout.Width(120)); 154 | int mode = EditorGUILayout.Popup(_mode, Modes); 155 | if (mode != _mode) 156 | { 157 | _mode = mode; 158 | _api = APIs[_mode]; 159 | _contentType = ContentTypes[_mode]; 160 | } 161 | GUILayout.EndHorizontal(); 162 | 163 | GUILayout.BeginHorizontal(); 164 | GUILayout.Label("Granularity:", GUILayout.Width(120)); 165 | if (GUILayout.Button(_granularity.GetRemark(), EditorGlobalTools.Styles.MiniPopup)) 166 | { 167 | GenericMenu gm = new GenericMenu(); 168 | foreach (var granularity in typeof(CharacterRecognitionModeBase.RecognizeGranularity).GetEnumValues()) 169 | { 170 | CharacterRecognitionModeBase.RecognizeGranularity g = (CharacterRecognitionModeBase.RecognizeGranularity)granularity; 171 | gm.AddItem(new GUIContent(g.GetRemark()), _granularity == g, () => 172 | { 173 | _granularity = g; 174 | }); 175 | } 176 | gm.ShowAsContext(); 177 | } 178 | GUILayout.EndHorizontal(); 179 | 180 | GUILayout.BeginHorizontal(); 181 | GUILayout.Label("Language:", GUILayout.Width(120)); 182 | if (GUILayout.Button(_language.GetRemark(), EditorGlobalTools.Styles.MiniPopup)) 183 | { 184 | GenericMenu gm = new GenericMenu(); 185 | foreach (var language in typeof(CharacterRecognitionModeBase.LanguageType).GetEnumValues()) 186 | { 187 | CharacterRecognitionModeBase.LanguageType l = (CharacterRecognitionModeBase.LanguageType)language; 188 | gm.AddItem(new GUIContent(l.GetRemark()), _language == l, () => 189 | { 190 | _language = l; 191 | }); 192 | } 193 | gm.ShowAsContext(); 194 | } 195 | GUILayout.EndHorizontal(); 196 | 197 | GUI.enabled = false; 198 | GUILayout.BeginHorizontal(); 199 | GUILayout.Label("Detect Direction:", GUILayout.Width(120)); 200 | _isDetectDirection = EditorGUILayout.Toggle(_isDetectDirection); 201 | GUILayout.EndHorizontal(); 202 | 203 | GUILayout.BeginHorizontal(); 204 | GUILayout.Label("Detect Language:", GUILayout.Width(120)); 205 | _isDetectLanguage = EditorGUILayout.Toggle(_isDetectLanguage); 206 | GUILayout.EndHorizontal(); 207 | GUI.enabled = true; 208 | 209 | GUILayout.EndVertical(); 210 | } 211 | private void RecognitionButtonGUI() 212 | { 213 | GUI.enabled = !_isRecognition && TOKEN != "" && _texture != null; 214 | 215 | GUILayout.BeginHorizontal(); 216 | if (GUILayout.Button("Recognition", EditorGlobalTools.Styles.LargeButton)) 217 | { 218 | RecognitionInEditor(); 219 | } 220 | GUILayout.EndHorizontal(); 221 | 222 | GUI.enabled = true; 223 | } 224 | 225 | private void RecognitionInEditor() 226 | { 227 | string url = string.Format("{0}?access_token={1}", _api, TOKEN); 228 | 229 | WWWForm form = new WWWForm(); 230 | form.AddField("image", Convert.ToBase64String(_texture.EncodeToJPG())); 231 | form.AddField("recognize_granularity", _granularity == CharacterRecognitionModeBase.RecognizeGranularity.Big ? "big" : "small"); 232 | form.AddField("language_type", _language.ToString()); 233 | form.AddField("detect_direction", _isDetectDirection ? "true" : "false"); 234 | form.AddField("detect_language", _isDetectLanguage ? "true" : "false"); 235 | 236 | UnityWebRequest request = UnityWebRequest.Post(url, form); 237 | request.SetRequestHeader("Content-Type", _contentType); 238 | UnityWebRequestAsyncOperation async = request.SendWebRequest(); 239 | async.completed += RecognitionDone; 240 | _isRecognition = true; 241 | } 242 | private void GenerateTOKENDone(AsyncOperation asyncOperation) 243 | { 244 | UnityWebRequestAsyncOperation async = asyncOperation as UnityWebRequestAsyncOperation; 245 | if (async != null) 246 | { 247 | if (async.webRequest.result == UnityWebRequest.Result.Success) 248 | { 249 | JsonData jsonData = JsonToolkit.StringToJson(async.webRequest.downloadHandler.text); 250 | if (jsonData != null) 251 | { 252 | TOKEN = jsonData.GetValueInSafe("access_token", ""); 253 | EditorPrefs.SetString(EditorPrefsTableAI.CR_TOKEN, TOKEN); 254 | Repaint(); 255 | } 256 | else 257 | { 258 | Log.Error("获取TOKEN失败:" + async.webRequest.downloadHandler.text); 259 | } 260 | } 261 | else 262 | { 263 | Log.Error("获取Token失败:" + async.webRequest.responseCode + " " + async.webRequest.error); 264 | } 265 | async.webRequest.Dispose(); 266 | } 267 | else 268 | { 269 | Log.Error("获取Token失败:错误的请求操作!"); 270 | } 271 | } 272 | private void RecognitionDone(AsyncOperation asyncOperation) 273 | { 274 | UnityWebRequestAsyncOperation async = asyncOperation as UnityWebRequestAsyncOperation; 275 | if (async != null) 276 | { 277 | if (async.webRequest.result == UnityWebRequest.Result.Success) 278 | { 279 | JsonData jsonData = JsonToolkit.StringToJson(async.webRequest.downloadHandler.text); 280 | if (jsonData != null) 281 | { 282 | if (jsonData.Keys.Contains("error_code")) 283 | { 284 | Log.Error("文字识别失败:" + jsonData.GetValueInSafe("error_code", "") + " " + jsonData.GetValueInSafe("error_msg", "")); 285 | } 286 | else 287 | { 288 | _response = new OCRResponse(jsonData); 289 | 290 | StringToolkit.BeginConcat(); 291 | for (int i = 0; i < _response.Words.Count; i++) 292 | { 293 | StringToolkit.Concat(_response.Words[i].Content, true); 294 | } 295 | _result = StringToolkit.EndConcat(); 296 | } 297 | } 298 | else 299 | { 300 | Log.Error("文字识别失败:" + async.webRequest.downloadHandler.text); 301 | } 302 | } 303 | else 304 | { 305 | Log.Error("文字识别失败:" + async.webRequest.responseCode + " " + async.webRequest.error); 306 | } 307 | async.webRequest.Dispose(); 308 | } 309 | else 310 | { 311 | Log.Error("文字识别失败:错误的请求操作!"); 312 | } 313 | _isRecognition = false; 314 | } 315 | } 316 | } -------------------------------------------------------------------------------- /Editor/CharacterRecognition/EditorCharacterRecognitioner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a05e684ffeac0a64d95a422d4978099d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/HTFramework.AI.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HTFramework.AI.Editor", 3 | "references": [ 4 | "GUID:7b65453a364e1774bb7db0e5a7454a24", 5 | "GUID:83f631a6180cc5a41982c9354b6cc704", 6 | "GUID:1c5ca69798d20074fb935732bf68b629" 7 | ], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [], 18 | "noEngineReferences": false 19 | } -------------------------------------------------------------------------------- /Editor/HTFramework.AI.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 196b59c9295fde74193f864619aed014 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Pathfinding.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f03b0ef84785d9541bec20f011071e49 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Pathfinding/AStarGridInspector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace HT.Framework.AI 7 | { 8 | [GiteeURL("https://gitee.com/SaiTingHu/HTFrameworkAI")] 9 | [CSDNBlogURL("https://wanderer.blog.csdn.net/article/details/103761142")] 10 | [GithubURL("https://github.com/SaiTingHu/HTFrameworkAI")] 11 | [CustomEditor(typeof(AStarGrid))] 12 | internal sealed class AStarGridInspector : HTFEditor 13 | { 14 | protected override void OnInspectorDefaultGUI() 15 | { 16 | base.OnInspectorDefaultGUI(); 17 | 18 | GUILayout.BeginHorizontal(); 19 | EditorGUILayout.LabelField("Evaluation Type", GUILayout.Width(LabelWidth)); 20 | if (GUILayout.Button(Target.EvaluationType, EditorGlobalTools.Styles.MiniPopup, GUILayout.Width(EditorGUIUtility.currentViewWidth - LabelWidth - 25))) 21 | { 22 | GenericMenu gm = new GenericMenu(); 23 | List types = ReflectionToolkit.GetTypesInRunTimeAssemblies(type => 24 | { 25 | return type.IsSubclassOf(typeof(AStarEvaluation)) && !type.IsAbstract; 26 | }, false); 27 | for (int i = 0; i < types.Count; i++) 28 | { 29 | int j = i; 30 | gm.AddItem(new GUIContent(types[j].FullName), Target.EvaluationType == types[j].FullName, () => 31 | { 32 | Undo.RecordObject(target, "Set Evaluation"); 33 | Target.EvaluationType = types[j].FullName; 34 | HasChanged(); 35 | }); 36 | } 37 | gm.ShowAsContext(); 38 | } 39 | GUILayout.EndHorizontal(); 40 | 41 | PropertyField(nameof(AStarGrid.Size), "Size"); 42 | PropertyField(nameof(AStarGrid.NodeRadius), "Node Radius"); 43 | PropertyField(nameof(AStarGrid.IsIgnoreOblique), "Ignore Oblique"); 44 | PropertyField(nameof(AStarGrid.IsAutoGenerate), "Auto Generate"); 45 | PropertyField(nameof(AStarGrid.IsHideFindFailedLog), "Hide Find Failed Log"); 46 | } 47 | protected override void OnInspectorRuntimeGUI() 48 | { 49 | base.OnInspectorRuntimeGUI(); 50 | 51 | PropertyField(nameof(AStarGrid.IsShowIndex), "Show Index"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Editor/Pathfinding/AStarGridInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 807f46728088b654d8db5ab1cab8a974 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Speech.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 993e260de4defc2448a8031508aa89df 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Speech/EditorSpeecher.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using UnityEngine.Networking; 6 | 7 | namespace HT.Framework.AI 8 | { 9 | internal sealed class EditorSpeecher : HTFEditorWindow 10 | { 11 | private readonly string TOKENAPI = "https://openapi.baidu.com/oauth/2.0/token"; 12 | private readonly string SynthesisAPI = "https://tsn.baidu.com/text2audio"; 13 | private string APIKEY = ""; 14 | private string SECRETKEY = ""; 15 | private string TOKEN = ""; 16 | private string _synthesisText = ""; 17 | private Vector2 _synthesisTextScroll = Vector2.zero; 18 | private string _savePath = ""; 19 | private string _saveFullPath = ""; 20 | private string _saveName = "NewAudio"; 21 | private SynthesisType _format = SynthesisType.MP3; 22 | private int _timeout = 60000; 23 | private Speaker _speaker = Speaker.Woman; 24 | private int _volume = 15; 25 | private int _speed = 5; 26 | private int _pitch = 5; 27 | private bool _isSynthesis = false; 28 | 29 | protected override string HelpUrl => "https://wanderer.blog.csdn.net/article/details/103764141"; 30 | 31 | protected override void OnEnable() 32 | { 33 | base.OnEnable(); 34 | 35 | APIKEY = EditorPrefs.GetString(EditorPrefsTableAI.Speech_APIKEY, ""); 36 | SECRETKEY = EditorPrefs.GetString(EditorPrefsTableAI.Speech_SECRETKEY, ""); 37 | TOKEN = EditorPrefs.GetString(EditorPrefsTableAI.Speech_TOKEN, ""); 38 | } 39 | protected override void OnTitleGUI() 40 | { 41 | base.OnTitleGUI(); 42 | 43 | GUILayout.FlexibleSpace(); 44 | if (GUILayout.Button("Console Login", EditorStyles.toolbarButton)) 45 | { 46 | Application.OpenURL(@"https://login.bce.baidu.com/"); 47 | } 48 | } 49 | protected override void OnBodyGUI() 50 | { 51 | base.OnBodyGUI(); 52 | 53 | SynthesisIdentityGUI(); 54 | SynthesisTextGUI(); 55 | SynthesisArgsGUI(); 56 | SynthesisButtonGUI(); 57 | } 58 | private void SynthesisIdentityGUI() 59 | { 60 | GUILayout.BeginHorizontal("DD HeaderStyle"); 61 | GUILayout.FlexibleSpace(); 62 | GUILayout.Label("Speech Synthesis in Editor"); 63 | GUILayout.FlexibleSpace(); 64 | GUILayout.EndHorizontal(); 65 | 66 | 67 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 68 | 69 | GUILayout.BeginHorizontal(); 70 | GUILayout.Label("API Key:", GUILayout.Width(80)); 71 | string apikey = EditorGUILayout.PasswordField(APIKEY); 72 | if (apikey != APIKEY) 73 | { 74 | APIKEY = apikey; 75 | EditorPrefs.SetString(EditorPrefsTableAI.Speech_APIKEY, APIKEY); 76 | } 77 | GUILayout.EndHorizontal(); 78 | 79 | GUILayout.BeginHorizontal(); 80 | GUILayout.Label("Secret Key:", GUILayout.Width(80)); 81 | string secretkey = EditorGUILayout.PasswordField(SECRETKEY); 82 | if (secretkey != SECRETKEY) 83 | { 84 | SECRETKEY = secretkey; 85 | EditorPrefs.SetString(EditorPrefsTableAI.Speech_SECRETKEY, SECRETKEY); 86 | } 87 | GUILayout.EndHorizontal(); 88 | 89 | GUILayout.BeginHorizontal(); 90 | GUILayout.Label("Token:", GUILayout.Width(80)); 91 | string token = EditorGUILayout.TextField(TOKEN); 92 | if (token != TOKEN) 93 | { 94 | TOKEN = token; 95 | EditorPrefs.SetString(EditorPrefsTableAI.Speech_TOKEN, TOKEN); 96 | } 97 | GUI.enabled = (APIKEY != "" && SECRETKEY != ""); 98 | if (GUILayout.Button("Generate", EditorStyles.miniButton, GUILayout.Width(70))) 99 | { 100 | string uri = string.Format("{0}?grant_type=client_credentials&client_id={1}&client_secret={2}", TOKENAPI, APIKEY, SECRETKEY); 101 | UnityWebRequest request = UnityWebRequest.Get(uri); 102 | UnityWebRequestAsyncOperation async = request.SendWebRequest(); 103 | async.completed += GenerateTOKENDone; 104 | } 105 | GUI.enabled = true; 106 | GUILayout.EndHorizontal(); 107 | 108 | GUILayout.EndVertical(); 109 | } 110 | private void SynthesisTextGUI() 111 | { 112 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 113 | GUILayout.Label("Synthesis Text:"); 114 | _synthesisTextScroll = GUILayout.BeginScrollView(_synthesisTextScroll); 115 | _synthesisText = EditorGUILayout.TextArea(_synthesisText); 116 | GUILayout.EndScrollView(); 117 | GUILayout.EndVertical(); 118 | } 119 | private void SynthesisArgsGUI() 120 | { 121 | GUILayout.BeginVertical(EditorGlobalTools.Styles.Box); 122 | 123 | GUILayout.BeginHorizontal(); 124 | GUILayout.Label("Save Path:", GUILayout.Width(80)); 125 | EditorGUILayout.TextField(_savePath); 126 | if (GUILayout.Button("Browse", EditorStyles.miniButton)) 127 | { 128 | string path = EditorUtility.OpenFolderPanel("Select Save Path", Application.dataPath, ""); 129 | if (path.Length != 0) 130 | { 131 | _savePath = path.Replace(Application.dataPath, ""); 132 | } 133 | } 134 | GUILayout.EndHorizontal(); 135 | 136 | GUILayout.BeginHorizontal(); 137 | GUILayout.Label("Save Name:", GUILayout.Width(80)); 138 | _saveName = EditorGUILayout.TextField(_saveName, GUILayout.Width(120)); 139 | GUILayout.Label("Format:"); 140 | _format = (SynthesisType)EditorGUILayout.EnumPopup(_format); 141 | GUILayout.EndHorizontal(); 142 | 143 | GUILayout.BeginHorizontal(); 144 | GUILayout.Label("Speaker:", GUILayout.Width(80)); 145 | if (GUILayout.Button(_speaker.GetRemark(), EditorGlobalTools.Styles.MiniPopup, GUILayout.Width(120))) 146 | { 147 | GenericMenu gm = new GenericMenu(); 148 | foreach (var speaker in typeof(Speaker).GetEnumValues()) 149 | { 150 | Speaker s = (Speaker)speaker; 151 | gm.AddItem(new GUIContent(s.GetRemark()), _speaker == s, () => 152 | { 153 | _speaker = s; 154 | }); 155 | } 156 | gm.ShowAsContext(); 157 | } 158 | GUILayout.Label("Timeout:"); 159 | _timeout = EditorGUILayout.IntField(_timeout); 160 | GUILayout.EndHorizontal(); 161 | 162 | GUILayout.BeginHorizontal(); 163 | GUILayout.Label("Volume:", GUILayout.Width(80)); 164 | _volume = EditorGUILayout.IntSlider(_volume, 0, 15); 165 | GUILayout.EndHorizontal(); 166 | 167 | GUILayout.BeginHorizontal(); 168 | GUILayout.Label("Speed:", GUILayout.Width(80)); 169 | _speed = EditorGUILayout.IntSlider(_speed, 0, 9); 170 | GUILayout.EndHorizontal(); 171 | 172 | GUILayout.BeginHorizontal(); 173 | GUILayout.Label("Pitch:", GUILayout.Width(80)); 174 | _pitch = EditorGUILayout.IntSlider(_pitch, 0, 9); 175 | GUILayout.EndHorizontal(); 176 | 177 | GUILayout.EndVertical(); 178 | } 179 | private void SynthesisButtonGUI() 180 | { 181 | GUI.enabled = (!_isSynthesis && TOKEN != "" && _synthesisText != "" && _saveName != ""); 182 | 183 | GUILayout.BeginHorizontal(); 184 | if (GUILayout.Button("Synthesis", EditorGlobalTools.Styles.LargeButton)) 185 | { 186 | _saveFullPath = string.Format("{0}{1}/{2}.{3}", Application.dataPath, _savePath, _saveName, _format); 187 | SynthesisInEditor(_synthesisText, _saveFullPath, _format, _timeout, _speaker, _volume, _speed, _pitch); 188 | } 189 | GUILayout.EndHorizontal(); 190 | 191 | GUI.enabled = true; 192 | } 193 | 194 | private void SynthesisInEditor(string text, string savePath, SynthesisType audioType = SynthesisType.MP3, int timeout = 60000, Speaker speaker = Speaker.DuYaYa, int volume = 15, int speed = 5, int pitch = 5) 195 | { 196 | if (string.IsNullOrEmpty(text) || Encoding.UTF8.GetByteCount(text) >= 1024) 197 | { 198 | Log.Error("合成语音失败:文本为空或长度超出了1024字节的限制!"); 199 | return; 200 | } 201 | if (File.Exists(savePath)) 202 | { 203 | Log.Error("合成语音失败:已存在音频文件 " + savePath); 204 | return; 205 | } 206 | 207 | string url = string.Format("{0}?tex='{1}'&tok={2}&cuid={3}&ctp={4}&lan={5}&spd={6}&pit={7}&vol={8}&per={9}&aue={10}", 208 | SynthesisAPI, text, TOKEN, SystemInfo.deviceUniqueIdentifier, 1, "zh", speed, pitch, volume, (int)speaker, (int)audioType); 209 | 210 | UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(url, audioType == SynthesisType.MP3 ? AudioType.MPEG : AudioType.WAV); 211 | UnityWebRequestAsyncOperation async = request.SendWebRequest(); 212 | async.completed += SynthesisDone; 213 | _isSynthesis = true; 214 | } 215 | private void GenerateTOKENDone(AsyncOperation asyncOperation) 216 | { 217 | UnityWebRequestAsyncOperation async = asyncOperation as UnityWebRequestAsyncOperation; 218 | if (async != null) 219 | { 220 | if (async.webRequest.result == UnityWebRequest.Result.Success) 221 | { 222 | JsonData jsonData = JsonToolkit.StringToJson(async.webRequest.downloadHandler.text); 223 | if (jsonData != null) 224 | { 225 | TOKEN = jsonData.GetValueInSafe("access_token", ""); 226 | EditorPrefs.SetString(EditorPrefsTableAI.Speech_TOKEN, TOKEN); 227 | Repaint(); 228 | } 229 | else 230 | { 231 | Log.Error("获取TOKEN失败:" + async.webRequest.downloadHandler.text); 232 | } 233 | } 234 | else 235 | { 236 | Log.Error("获取Token失败:" + async.webRequest.responseCode + " " + async.webRequest.error); 237 | } 238 | async.webRequest.Dispose(); 239 | } 240 | else 241 | { 242 | Log.Error("获取Token失败:错误的请求操作!"); 243 | } 244 | } 245 | private void SynthesisDone(AsyncOperation asyncOperation) 246 | { 247 | UnityWebRequestAsyncOperation async = asyncOperation as UnityWebRequestAsyncOperation; 248 | if (async != null) 249 | { 250 | if (async.webRequest.result == UnityWebRequest.Result.Success) 251 | { 252 | File.WriteAllBytes(_saveFullPath, async.webRequest.downloadHandler.data); 253 | AssetDatabase.Refresh(); 254 | Selection.activeObject = AssetDatabase.LoadAssetAtPath($"Assets{_savePath}/{_saveName}.{_format}", typeof(AudioClip)); 255 | } 256 | else 257 | { 258 | Log.Error("合成语音失败:" + async.webRequest.responseCode + " " + async.webRequest.error); 259 | } 260 | async.webRequest.Dispose(); 261 | } 262 | else 263 | { 264 | Log.Error("合成语音失败:错误的请求操作!"); 265 | } 266 | _isSynthesis = false; 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /Editor/Speech/EditorSpeecher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc6b52802fb34f74ab7c180df6dd4a25 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Utility.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1995510cdda635444b76e5befe804c3a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Utility/EditorGlobalToolsAI.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditor.SceneManagement; 3 | using UnityEngine; 4 | using UnityEngine.EventSystems; 5 | 6 | namespace HT.Framework.AI 7 | { 8 | /// 9 | /// AI编辑器全局工具 10 | /// 11 | public static class EditorGlobalToolsAI 12 | { 13 | #region AI 【优先级200】 14 | /// 15 | /// 打开 Assistant Window 16 | /// 17 | [MenuItem("HTFramework/★ AI/Assistant &A", false, 200)] 18 | private static void OpenAssistantWindow() 19 | { 20 | AssistantWindow.OpenWindow(null); 21 | } 22 | 23 | /// 24 | /// 打开 Editor Character Recognitioner 25 | /// 26 | [MenuItem("HTFramework/★ AI/Character Recognitioner", false, 220)] 27 | private static void OpenEditorCharacterRecognitioner() 28 | { 29 | EditorCharacterRecognitioner window = EditorWindow.GetWindow(); 30 | window.titleContent.text = "Character Recognitioner"; 31 | window.minSize = new Vector2(400, 400); 32 | window.maxSize = new Vector2(400, 400); 33 | window.Show(); 34 | } 35 | 36 | /// 37 | /// 打开 Editor Speecher 38 | /// 39 | [MenuItem("HTFramework/★ AI/Speecher", false, 221)] 40 | private static void OpenEditorSpeecher() 41 | { 42 | EditorSpeecher window = EditorWindow.GetWindow(); 43 | window.titleContent.text = "Speecher"; 44 | window.minSize = new Vector2(400, 400); 45 | window.maxSize = new Vector2(400, 400); 46 | window.Show(); 47 | } 48 | 49 | [MenuItem("CONTEXT/Component/★ Ask AI Assistant")] 50 | public static void AskAIAssistantComponent(MenuCommand cmd) 51 | { 52 | if (cmd.context is Component) 53 | { 54 | if (cmd.context is MonoBehaviour && cmd.context is not UIBehaviour) 55 | { 56 | Log.Warning("继承至 MonoBehaviour 的脚本不支持 Ask AI Assistant。"); 57 | } 58 | else 59 | { 60 | Component component = cmd.context as Component; 61 | AssistantWindow.OpenWindow($"Unity引擎中{component.GetType().Name}组件的参数及用法?"); 62 | } 63 | } 64 | } 65 | #endregion 66 | 67 | #region 层级视图新建菜单 【优先级200】 68 | /// 69 | /// 新建A*网格 70 | /// 71 | [MenuItem("GameObject/HTFramework/★ AI/A* Grid", false, 200)] 72 | private static void CreateAStarGrid() 73 | { 74 | GameObject[] objs = Selection.gameObjects; 75 | if (objs == null || objs.Length == 0) 76 | { 77 | GameObject aStar = new GameObject(); 78 | aStar.name = "New AStarGrid"; 79 | aStar.transform.localPosition = Vector3.zero; 80 | aStar.transform.localRotation = Quaternion.identity; 81 | aStar.transform.localScale = Vector3.one; 82 | aStar.AddComponent(); 83 | Selection.activeGameObject = aStar; 84 | EditorUtility.SetDirty(aStar); 85 | EditorSceneManager.MarkSceneDirty(aStar.scene); 86 | } 87 | else 88 | { 89 | for (int i = 0; i < objs.Length; i++) 90 | { 91 | objs[i].AddComponent(); 92 | EditorUtility.SetDirty(objs[i]); 93 | EditorSceneManager.MarkSceneDirty(objs[i].scene); 94 | } 95 | } 96 | } 97 | #endregion 98 | } 99 | } -------------------------------------------------------------------------------- /Editor/Utility/EditorGlobalToolsAI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d3609bf5553a4f4d952f0d119e660e5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Utility/EditorPrefsTableAI.cs: -------------------------------------------------------------------------------- 1 | namespace HT.Framework.AI 2 | { 3 | /// 4 | /// HT.Framework.AI编辑器配置表 5 | /// 6 | internal static class EditorPrefsTableAI 7 | { 8 | #region Editor PrefsKey 9 | /// 10 | /// APIKEY 11 | /// 12 | public static readonly string Speech_APIKEY = "HT.Framework.AI.Speech.APIKEY"; 13 | /// 14 | /// SECRETKEY 15 | /// 16 | public static readonly string Speech_SECRETKEY = "HT.Framework.AI.Speech.SECRETKEY"; 17 | /// 18 | /// TOKEN 19 | /// 20 | public static readonly string Speech_TOKEN = "HT.Framework.AI.Speech.TOKEN"; 21 | 22 | /// 23 | /// APIKEY 24 | /// 25 | public static readonly string CR_APIKEY = "HT.Framework.AI.CR.APIKEY"; 26 | /// 27 | /// SECRETKEY 28 | /// 29 | public static readonly string CR_SECRETKEY = "HT.Framework.AI.CR.SECRETKEY"; 30 | /// 31 | /// TOKEN 32 | /// 33 | public static readonly string CR_TOKEN = "HT.Framework.AI.CR.TOKEN"; 34 | 35 | /// 36 | /// AI助手的 Model 配置 37 | /// 38 | public static readonly string Assistant_Model = "HT.Framework.AI.Assistant.Model"; 39 | /// 40 | /// AI助手的 Stream 配置 41 | /// 42 | public static readonly string Assistant_Stream = "HT.Framework.AI.Assistant.Stream"; 43 | /// 44 | /// AI助手的 BaseAddress 配置 45 | /// 46 | public static readonly string Assistant_BaseAddress = "HT.Framework.AI.Assistant.BaseAddress"; 47 | /// 48 | /// AI助手的 API 配置 49 | /// 50 | public static readonly string Assistant_API = "HT.Framework.AI.Assistant.API"; 51 | /// 52 | /// AI助手的 Timeout 配置 53 | /// 54 | public static readonly string Assistant_Timeout = "HT.Framework.AI.Assistant.Timeout"; 55 | /// 56 | /// AI助手的 Round 配置 57 | /// 58 | public static readonly string Assistant_Round = "HT.Framework.AI.Assistant.Round"; 59 | /// 60 | /// AI助手的 IsLogInEditor 配置 61 | /// 62 | public static readonly string Assistant_IsLogInEditor = "HT.Framework.AI.Assistant.IsLogInEditor"; 63 | /// 64 | /// AI助手的 ShowThink 配置 65 | /// 66 | public static readonly string Assistant_ShowThink = "HT.Framework.AI.Assistant.ShowThink"; 67 | /// 68 | /// AI助手的 SavePath 配置 69 | /// 70 | public static readonly string Assistant_SavePath = "HT.Framework.AI.Assistant.SavePath"; 71 | /// 72 | /// AI助手的 EnableAIAgent 配置 73 | /// 74 | public static readonly string Assistant_EnableAIAgent = "HT.Framework.AI.Assistant.EnableAIAgent"; 75 | /// 76 | /// AI智能体的 Type 配置 77 | /// 78 | public static readonly string AIAgent_Type = "HT.Framework.AI.AIAgent.Type"; 79 | /// 80 | /// AI智能体的 Permission.OpenURL 配置 81 | /// 82 | public static readonly string AIAgent_PermissionOpenURL = "HT.Framework.AI.AIAgent.Permission.OpenURL"; 83 | /// 84 | /// AI智能体的 Permission.OpenProgram 配置 85 | /// 86 | public static readonly string AIAgent_PermissionOpenProgram = "HT.Framework.AI.AIAgent.Permission.OpenProgram"; 87 | /// 88 | /// AI智能体的 Permission.RunCode 配置 89 | /// 90 | public static readonly string AIAgent_PermissionRunCode = "HT.Framework.AI.AIAgent.Permission.RunCode"; 91 | /// 92 | /// AI智能体的 Permission.ReadFile 配置 93 | /// 94 | public static readonly string AIAgent_PermissionReadFile = "HT.Framework.AI.AIAgent.Permission.ReadFile"; 95 | /// 96 | /// AI智能体的 Permission.WriteFile 配置 97 | /// 98 | public static readonly string AIAgent_PermissionWriteFile = "HT.Framework.AI.AIAgent.Permission.WriteFile"; 99 | #endregion 100 | } 101 | } -------------------------------------------------------------------------------- /Editor/Utility/EditorPrefsTableAI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0275d69b3adacd499d39e95f60bb03c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 HuTao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab0ab6ea05ccf29488eb159e8981ef5e 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity HTFramework AI 2 | 3 | HTFramework的AI模块,必须依赖于HTFramework主框架使用。 4 | 5 | ## 环境 6 | 7 | - Unity版本:2022.3.34。 8 | 9 | - .NET API版本:.NET Framework。 10 | 11 | - [HTFramework(Latest version)](https://github.com/SaiTingHu/HTFramework)。 12 | 13 | ## 模块简介 14 | 15 | - [Assistant](https://wanderer.blog.csdn.net/article/details/145637201) - AI助手,支持在Unity中接入DeepSeek等AI语言大模型,支持AI画图,支持定制AI智能体(Agent)。 16 | 17 | - [Pathfinding](https://wanderer.blog.csdn.net/article/details/103761142) - 常规的A*寻路算法,目前支持两点间寻路,或只提供起点后根据行走值寻所有可行走节点。 18 | 19 | - [Speech](https://wanderer.blog.csdn.net/article/details/103764141) - 对接百度AI开放平台,封装的语音技术接口。 20 | 21 | - [OCR](https://wanderer.blog.csdn.net/article/details/103765003) - 对接百度AI开放平台,封装的文字识别接口。 22 | 23 | ## 使用方法 24 | 25 | - 1.拉取框架到项目中的Assets文件夹下(Assets/HTFramework/),或以添加子模块的形式。 26 | 27 | - 2.在入口场景的层级(Hierarchy)视图点击右键,选择 HTFramework -> Main Environment(创建框架主环境),并删除入口场景其他的东西(除了框架的主要模块,其他任何东西都应该是动态加载的)。 28 | 29 | - 3.拉取本模块到项目中的Assets文件夹下(Assets/HTFrameworkAI/),或以添加子模块的形式。 30 | 31 | - 4.参阅各个模块的帮助文档,开始开发。 32 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0a2541e4d24add4aa7fb8fd0ef20ede 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /RunTime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a443a48773d09eb498ecfc2160d16e23 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/Assistant.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c40db4f89fc7cec459fef2ab8e0b7f3c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/Assistant/AssistantUtility.cs: -------------------------------------------------------------------------------- 1 | namespace HT.Framework.AI 2 | { 3 | /// 4 | /// AI助手实用工具 5 | /// 6 | public static class AssistantUtility 7 | { 8 | /// 9 | /// 拆分AI助手回复的内容 10 | /// 11 | /// 完整内容 12 | /// 思考内容 13 | /// 正式内容 14 | public static void SplitContent(string fullContent, out string think, out string content) 15 | { 16 | string[] strs = fullContent.Split(""); 17 | if (strs.Length == 2) 18 | { 19 | think = strs[0].Replace("", "").Trim(); 20 | content = strs[1].Trim(); 21 | } 22 | else 23 | { 24 | think = null; 25 | content = fullContent.Trim(); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RunTime/Assistant/AssistantUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c9c0b66e64ddd1e45ab02303036dfaa6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Assistant/ChatSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace HT.Framework.AI 9 | { 10 | /// 11 | /// AI会话 12 | /// 13 | [Serializable] 14 | public class ChatSession 15 | { 16 | /// 17 | /// 会话ID 18 | /// 19 | public string ID; 20 | /// 21 | /// 会话名称 22 | /// 23 | public string Name; 24 | /// 25 | /// 远端大模型根地址 26 | /// 27 | public string BaseAddress = "http://localhost:11434"; 28 | /// 29 | /// 远端大模型接口 30 | /// 31 | public string API = "/api/chat"; 32 | /// 33 | /// 会话超时时长(秒) 34 | /// 35 | public int Timeout = 60; 36 | /// 37 | /// 多轮对话时,支持回传的最大轮数 38 | /// 39 | public int Round = 7; 40 | /// 41 | /// 是否打印相关日志(仅在编辑器中) 42 | /// 43 | public bool IsLogInEditor = true; 44 | /// 45 | /// 会话数据 46 | /// 47 | public ChatData Data = new ChatData(); 48 | /// 49 | /// 提示词 50 | /// 51 | public ChatMessage PromptWords = new ChatMessage() { Role = "system" }; 52 | /// 53 | /// 会话消息记录 54 | /// 55 | public List Messages = new List(); 56 | 57 | /// 58 | /// AI会话 59 | /// 60 | public ChatSession() 61 | { 62 | 63 | } 64 | /// 65 | /// AI会话 66 | /// 67 | public ChatSession(string id, string name) 68 | { 69 | ID = id; 70 | Name = name; 71 | } 72 | 73 | /// 74 | /// 设置提示词 75 | /// 76 | /// 提示词内容 77 | public void SetPromptWords(string content) 78 | { 79 | PromptWords.Role = "system"; 80 | PromptWords.Content = string.IsNullOrEmpty(content) ? content : content.Trim(); 81 | } 82 | /// 83 | /// 用户说话 84 | /// 85 | /// 说话内容 86 | /// AI回话时回调(如果是流式请求,则重复回调此委托) 87 | /// AI回话结束(参数为本次请求是否成功) 88 | public void UserSpeak(string content, HTFAction replyCallback, HTFAction endCallback) 89 | { 90 | Messages.Add(new ChatMessage { Role = "user", Think = null, Content = content, Date = DateTime.Now.ToDefaultDateString() }); 91 | 92 | ChatCompletionAsync(null, replyCallback, endCallback); 93 | } 94 | /// 95 | /// 用户说话 96 | /// 97 | /// 说话内容 98 | /// 自定义会话数据 99 | /// AI回话时回调(如果是流式请求,则重复回调此委托) 100 | /// AI回话结束(参数为本次请求是否成功) 101 | public void UserSpeak(string content, string customData, HTFAction replyCallback, HTFAction endCallback) 102 | { 103 | Messages.Add(new ChatMessage { Role = "user", Think = null, Content = content, Date = DateTime.Now.ToDefaultDateString() }); 104 | 105 | ChatCompletionAsync(customData, replyCallback, endCallback); 106 | } 107 | 108 | private async void ChatCompletionAsync(string customData, HTFAction replyCallback, HTFAction endCallback) 109 | { 110 | using HttpClient client = new HttpClient(); 111 | 112 | client.BaseAddress = new Uri(BaseAddress); 113 | client.Timeout = TimeSpan.FromSeconds(Timeout); 114 | 115 | string jsonContent = null; 116 | if (string.IsNullOrEmpty(customData)) 117 | { 118 | BuildChatMessages(); 119 | jsonContent = JsonToolkit.JsonToString(Data); 120 | } 121 | else 122 | { 123 | jsonContent = customData; 124 | } 125 | using StringContent stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json"); 126 | 127 | #if UNITY_EDITOR 128 | if (IsLogInEditor) Log.Info("[Send Request]: " + jsonContent); 129 | #endif 130 | 131 | StringBuilder buffer = new StringBuilder(); 132 | bool isSuccess = true; 133 | 134 | void RequestError(string detail) 135 | { 136 | isSuccess = false; 137 | buffer.Clear(); 138 | Log.Error("请求出错:" + detail); 139 | } 140 | 141 | try 142 | { 143 | using HttpResponseMessage response = await client.PostAsync(API, stringContent); 144 | 145 | if (Data.stream) 146 | { 147 | using Stream stream = await response.Content.ReadAsStreamAsync(); 148 | using StreamReader reader = new StreamReader(stream); 149 | 150 | while (!reader.EndOfStream) 151 | { 152 | string line = await reader.ReadLineAsync(); 153 | #if UNITY_EDITOR 154 | if (IsLogInEditor) Log.Info("[Receive Data (Stream)]: " + line); 155 | #endif 156 | ChatCompletion_ReceiveData receiveData = JsonToolkit.StringToJson(line); 157 | if (receiveData != null) 158 | { 159 | if (string.IsNullOrEmpty(receiveData.error) && receiveData.message != null) 160 | { 161 | replyCallback?.Invoke(receiveData.message.content); 162 | buffer.Append(receiveData.message.content); 163 | } 164 | else 165 | { 166 | RequestError(receiveData.error); 167 | break; 168 | } 169 | } 170 | else 171 | { 172 | RequestError(line); 173 | break; 174 | } 175 | await Task.Yield(); 176 | } 177 | } 178 | else 179 | { 180 | response.EnsureSuccessStatusCode(); 181 | 182 | string result = await response.Content.ReadAsStringAsync(); 183 | #if UNITY_EDITOR 184 | if (IsLogInEditor) Log.Info("[Receive Data (Non Stream)]: " + result); 185 | #endif 186 | ChatCompletion_ReceiveData receiveData = JsonToolkit.StringToJson(result); 187 | if (receiveData != null) 188 | { 189 | if (string.IsNullOrEmpty(receiveData.error) && receiveData.message != null) 190 | { 191 | replyCallback?.Invoke(receiveData.message.content); 192 | buffer.Append(receiveData.message.content); 193 | } 194 | else 195 | { 196 | RequestError(receiveData.error); 197 | } 198 | } 199 | else 200 | { 201 | RequestError(result); 202 | } 203 | await Task.Yield(); 204 | } 205 | } 206 | catch (HttpRequestException ex) 207 | { 208 | RequestError(ex.Message); 209 | } 210 | catch (Exception e) 211 | { 212 | RequestError(e.Message); 213 | } 214 | finally 215 | { 216 | string assistant = buffer.ToString(); 217 | if (!string.IsNullOrEmpty(assistant)) 218 | { 219 | AssistantUtility.SplitContent(assistant, out string think, out string content); 220 | Messages.Add(new ChatMessage { Role = "assistant", Think = think, Content = content, Date = DateTime.Now.ToDefaultDateString() }); 221 | } 222 | buffer.Clear(); 223 | 224 | endCallback?.Invoke(isSuccess); 225 | } 226 | } 227 | private void BuildChatMessages() 228 | { 229 | Data.messages.Clear(); 230 | if (!string.IsNullOrEmpty(PromptWords.Content)) 231 | { 232 | Data.messages.Add(new ChatData.Message() { role = PromptWords.Role, content = PromptWords.Content }); 233 | } 234 | int i = Messages.Count - Round; 235 | if (i < 0) i = 0; 236 | for (; i < Messages.Count; i++) 237 | { 238 | Data.messages.Add(new ChatData.Message() { role = Messages[i].Role, content = Messages[i].Content }); 239 | } 240 | } 241 | 242 | [Serializable] 243 | public class ChatData 244 | { 245 | public string model = "deepseek-coder-v2:16b"; 246 | public List messages = new List(); 247 | public bool stream = true; 248 | public Options options = new Options(); 249 | 250 | [Serializable] 251 | public class Message 252 | { 253 | public string role; 254 | public string content; 255 | } 256 | [Serializable] 257 | public class Options 258 | { 259 | public float temperature = 0.8f; 260 | } 261 | } 262 | [Serializable] 263 | public class ChatMessage 264 | { 265 | public string Role; 266 | public string Think; 267 | public string Content; 268 | public string Images; 269 | public string Date; 270 | public bool IsFold; 271 | } 272 | 273 | private class ChatCompletion_ReceiveData 274 | { 275 | public Message message; 276 | public string error; 277 | 278 | public class Message 279 | { 280 | public string role; 281 | public string content; 282 | } 283 | } 284 | } 285 | } -------------------------------------------------------------------------------- /RunTime/Assistant/ChatSession.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1c66362fd4e4a42468a3c5aada08ca4b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Assistant/PollinationsAI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace HT.Framework.AI 7 | { 8 | /// 9 | /// https://pollinations.ai/ 10 | /// 11 | public static class PollinationsAI 12 | { 13 | /// 14 | /// 获取图像生成的可用大模型列表 15 | /// 16 | public static async Task> GetImageGenerationModels() 17 | { 18 | using HttpClient client = new HttpClient(); 19 | client.Timeout = TimeSpan.FromSeconds(60); 20 | 21 | try 22 | { 23 | string url = "https://image.pollinations.ai/models"; 24 | using HttpResponseMessage response = await client.GetAsync(url); 25 | 26 | response.EnsureSuccessStatusCode(); 27 | 28 | string result = await response.Content.ReadAsStringAsync(); 29 | List models = new List(); 30 | JsonData jsonData = JsonToolkit.StringToJson(result); 31 | if (jsonData != null) 32 | { 33 | for (int i = 0; i < jsonData.Count; i++) 34 | { 35 | models.Add(jsonData[i].ToString()); 36 | } 37 | } 38 | return models; 39 | } 40 | catch (Exception e) 41 | { 42 | Log.Error("请求出错:" + e.Message); 43 | return null; 44 | } 45 | } 46 | /// 47 | /// 图像生成 48 | /// 49 | /// 需生成的图像描述 50 | /// 图像宽度 51 | /// 图像高度 52 | /// 生成种子 53 | /// 大模型 54 | /// 去掉LOGO 55 | /// 图像 56 | public static async Task ImageGeneration(string prompt, int width, int height, int seed, string model = "flux", bool nologo = true) 57 | { 58 | using HttpClient client = new HttpClient(); 59 | client.Timeout = TimeSpan.FromSeconds(60); 60 | 61 | try 62 | { 63 | string url = $"https://image.pollinations.ai/prompt/{prompt}?width={width}&height={height}&seed={seed}&model={model}&nologo={nologo}"; 64 | using HttpResponseMessage response = await client.GetAsync(url); 65 | 66 | response.EnsureSuccessStatusCode(); 67 | 68 | byte[] result = await response.Content.ReadAsByteArrayAsync(); 69 | return result; 70 | } 71 | catch (Exception e) 72 | { 73 | Log.Error("请求出错:" + e.Message); 74 | return null; 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /RunTime/Assistant/PollinationsAI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5c8640feccbaca44188c8fce588a4fab 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7bfcd7931139cb4dabeb5499cc4bcac 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/Base.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6b2822bbaec31146b3c65e030e92bab 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/Base/CharacterRecognitionModeBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using UnityEngine; 4 | 5 | namespace HT.Framework.AI 6 | { 7 | /// 8 | /// 文字识别方式 9 | /// 10 | public abstract class CharacterRecognitionModeBase 11 | { 12 | /// 13 | /// 接口 14 | /// 15 | public abstract string API { get; } 16 | /// 17 | /// ContentType 18 | /// 19 | public abstract string ContentType { get; } 20 | /// 21 | /// 图像源 22 | /// 23 | public Texture2D ImageSource; 24 | /// 25 | /// 识别粒度 26 | /// 27 | public RecognizeGranularity Granularity; 28 | /// 29 | /// 识别语言 30 | /// 31 | public LanguageType Language; 32 | /// 33 | /// 是否检测图像朝向 34 | /// 35 | public bool IsDetectDirection; 36 | /// 37 | /// 是否检测语言 38 | /// 39 | public bool IsDetectLanguage; 40 | /// 41 | /// 识别成功处理者 42 | /// 43 | public HTFAction SuccessHandler; 44 | /// 45 | /// 识别失败处理者 46 | /// 47 | public HTFAction FailHandler; 48 | 49 | public CharacterRecognitionModeBase(Texture2D image, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 50 | { 51 | ImageSource = image; 52 | Granularity = granularity; 53 | Language = language; 54 | IsDetectDirection = isDetectDirection; 55 | IsDetectLanguage = isDetectLanguage; 56 | } 57 | 58 | public CharacterRecognitionModeBase(string imagePath, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 59 | { 60 | if (File.Exists(imagePath)) 61 | { 62 | using (FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read)) 63 | { 64 | byte[] buffer = new byte[stream.Length]; 65 | stream.Read(buffer, 0, (int)stream.Length); 66 | 67 | ImageSource = new Texture2D(80, 80); 68 | ImageSource.LoadImage(buffer); 69 | } 70 | } 71 | else 72 | { 73 | ImageSource = null; 74 | } 75 | 76 | Granularity = granularity; 77 | Language = language; 78 | IsDetectDirection = isDetectDirection; 79 | IsDetectLanguage = isDetectLanguage; 80 | } 81 | 82 | public string GetImageSourceByBase64() 83 | { 84 | if (ImageSource != null) 85 | { 86 | return Convert.ToBase64String(ImageSource.EncodeToJPG()); 87 | } 88 | else 89 | { 90 | return ""; 91 | } 92 | } 93 | 94 | /// 95 | /// 识别粒度 96 | /// 97 | public enum RecognizeGranularity 98 | { 99 | [Remark("粗糙")] 100 | Big, 101 | [Remark("精细")] 102 | Small 103 | } 104 | 105 | /// 106 | /// 识别语言 107 | /// 108 | public enum LanguageType 109 | { 110 | /// 111 | /// 中英文混合 112 | /// 113 | [Remark("中英语混合")] 114 | CHN_ENG, 115 | /// 116 | /// 英文 117 | /// 118 | [Remark("英语")] 119 | ENG, 120 | /// 121 | /// 日语 122 | /// 123 | [Remark("日语")] 124 | JAP, 125 | /// 126 | /// 韩语 127 | /// 128 | [Remark("韩语")] 129 | KOR, 130 | /// 131 | /// 法语 132 | /// 133 | [Remark("法语")] 134 | FRE, 135 | /// 136 | /// 西班牙语 137 | /// 138 | [Remark("西班牙语")] 139 | SPA, 140 | /// 141 | /// 葡萄牙语 142 | /// 143 | [Remark("葡萄牙语")] 144 | POR, 145 | /// 146 | /// 德语 147 | /// 148 | [Remark("德语")] 149 | GER, 150 | /// 151 | /// 意大利语 152 | /// 153 | [Remark("意大利语")] 154 | ITA, 155 | /// 156 | /// 俄语 157 | /// 158 | [Remark("俄语")] 159 | RUS 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/Base/CharacterRecognitionModeBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 974e97c249f4b3e4bb268421691de66d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/CharacterRecognitioner.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | using UnityEngine.Networking; 4 | 5 | namespace HT.Framework.AI 6 | { 7 | /// 8 | /// 文字识别器 9 | /// 10 | public static class CharacterRecognitioner 11 | { 12 | private static readonly string TOKENAPI = "https://aip.baidubce.com/oauth/2.0/token"; 13 | private static string APIKEY = ""; 14 | private static string SECRETKEY = ""; 15 | private static string TOKEN = ""; 16 | 17 | /// 18 | /// 设置APIKEY 19 | /// 20 | /// APIKEY 21 | public static void SetAPIKEY(string apiKey) 22 | { 23 | APIKEY = apiKey; 24 | } 25 | /// 26 | /// 设置SECRETKEY 27 | /// 28 | /// SECRETKEY 29 | public static void SetSECRETKEY(string SecretKey) 30 | { 31 | SECRETKEY = SecretKey; 32 | } 33 | /// 34 | /// 设置TOKEN 35 | /// 36 | /// TOKEN 37 | public static void SetTOKEN(string token) 38 | { 39 | TOKEN = token; 40 | } 41 | 42 | /// 43 | /// 生成TOKEN 44 | /// 45 | /// 生成TOKEN的协程 46 | public static Coroutine GenerateTOKEN() 47 | { 48 | return Main.Current.StartCoroutine(GenerateTOKENCoroutine()); 49 | } 50 | private static IEnumerator GenerateTOKENCoroutine() 51 | { 52 | string url = string.Format("{0}?grant_type=client_credentials&client_id={1}&client_secret={2}", TOKENAPI, APIKEY, SECRETKEY); 53 | 54 | using (UnityWebRequest request = UnityWebRequest.Get(url)) 55 | { 56 | yield return request.SendWebRequest(); 57 | if (request.result == UnityWebRequest.Result.Success) 58 | { 59 | JsonData jsonData = JsonToolkit.StringToJson(request.downloadHandler.text); 60 | if (jsonData != null) 61 | { 62 | TOKEN = jsonData.GetValueInSafe("access_token", ""); 63 | } 64 | else 65 | { 66 | Log.Error("获取TOKEN失败:" + request.downloadHandler.text); 67 | } 68 | } 69 | else 70 | { 71 | Log.Error("获取TOKEN失败:" + request.responseCode + " " + request.error); 72 | } 73 | } 74 | } 75 | 76 | /// 77 | /// 文字识别 78 | /// 79 | /// 识别方式 80 | /// 文字识别的协程 81 | public static Coroutine Recognition(CharacterRecognitionModeBase recognitionMode) 82 | { 83 | if (recognitionMode == null || recognitionMode.ImageSource == null) 84 | { 85 | Log.Error("文字识别失败:识别方式和识别图像源均不能为空!"); 86 | return null; 87 | } 88 | 89 | return Main.Current.StartCoroutine(RecognitionCoroutine(recognitionMode)); 90 | } 91 | private static IEnumerator RecognitionCoroutine(CharacterRecognitionModeBase recognitionMode) 92 | { 93 | string url = string.Format("{0}?access_token={1}", recognitionMode.API, TOKEN); 94 | 95 | WWWForm form = new WWWForm(); 96 | form.AddField("image", recognitionMode.GetImageSourceByBase64()); 97 | form.AddField("recognize_granularity", recognitionMode.Granularity == CharacterRecognitionModeBase.RecognizeGranularity.Big ? "big" : "small"); 98 | form.AddField("language_type", recognitionMode.Language.ToString()); 99 | form.AddField("detect_direction", recognitionMode.IsDetectDirection ? "true" : "false"); 100 | form.AddField("detect_language", recognitionMode.IsDetectLanguage ? "true" : "false"); 101 | 102 | using (UnityWebRequest request = UnityWebRequest.Post(url, form)) 103 | { 104 | request.SetRequestHeader("Content-Type", recognitionMode.ContentType); 105 | yield return request.SendWebRequest(); 106 | if (request.result == UnityWebRequest.Result.Success) 107 | { 108 | JsonData jsonData = JsonToolkit.StringToJson(request.downloadHandler.text); 109 | if (jsonData != null) 110 | { 111 | if (jsonData.Keys.Contains("error_code")) 112 | { 113 | Log.Error("文字识别失败:" + jsonData.GetValueInSafe("error_code", "") + " " + jsonData.GetValueInSafe("error_msg", "")); 114 | 115 | recognitionMode.FailHandler?.Invoke(); 116 | } 117 | else 118 | { 119 | recognitionMode.SuccessHandler?.Invoke(new OCRResponse(jsonData)); 120 | } 121 | } 122 | else 123 | { 124 | Log.Error("文字识别失败:" + request.downloadHandler.text); 125 | 126 | recognitionMode.FailHandler?.Invoke(); 127 | } 128 | } 129 | else 130 | { 131 | Log.Error("文字识别失败:" + request.responseCode + " " + request.error); 132 | 133 | recognitionMode.FailHandler?.Invoke(); 134 | } 135 | } 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/CharacterRecognitioner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ca6c927daf94b6a459e1944d59b8fb6c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e93b0a24a8ab9494a8e453b3c03813bb 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRAccurateBasicRecognition.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// 通用文字识别(高精度版) 7 | /// 8 | public sealed class OCRAccurateBasicRecognition : CharacterRecognitionModeBase 9 | { 10 | public override string API 11 | { 12 | get 13 | { 14 | return "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic"; 15 | } 16 | } 17 | 18 | public override string ContentType 19 | { 20 | get 21 | { 22 | return "application/x-www-form-urlencoded"; 23 | } 24 | } 25 | 26 | public OCRAccurateBasicRecognition(Texture2D image, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 27 | : base(image, granularity, language, isDetectDirection, isDetectLanguage) 28 | { } 29 | 30 | public OCRAccurateBasicRecognition(string imagePath, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 31 | : base(imagePath, granularity, language, isDetectDirection, isDetectLanguage) 32 | { } 33 | } 34 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRAccurateBasicRecognition.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6201516ec2b792a478e43c22517f438a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRAccurateRecognition.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// 通用文字识别(高精度含位置版) 7 | /// 8 | public sealed class OCRAccurateRecognition : CharacterRecognitionModeBase 9 | { 10 | public override string API 11 | { 12 | get 13 | { 14 | return "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate"; 15 | } 16 | } 17 | 18 | public override string ContentType 19 | { 20 | get 21 | { 22 | return "application/x-www-form-urlencoded"; 23 | } 24 | } 25 | 26 | public OCRAccurateRecognition(Texture2D image, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 27 | : base(image, granularity, language, isDetectDirection, isDetectLanguage) 28 | { } 29 | 30 | public OCRAccurateRecognition(string imagePath, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 31 | : base(imagePath, granularity, language, isDetectDirection, isDetectLanguage) 32 | { } 33 | } 34 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRAccurateRecognition.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b97cf7028ec26045b814cf93047f4ed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRGeneralBasicRecognition.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// 通用文字识别 7 | /// 8 | public sealed class OCRGeneralBasicRecognition : CharacterRecognitionModeBase 9 | { 10 | public override string API 11 | { 12 | get 13 | { 14 | return "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"; 15 | } 16 | } 17 | 18 | public override string ContentType 19 | { 20 | get 21 | { 22 | return "application/x-www-form-urlencoded"; 23 | } 24 | } 25 | 26 | public OCRGeneralBasicRecognition(Texture2D image, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 27 | : base(image, granularity, language, isDetectDirection, isDetectLanguage) 28 | { } 29 | 30 | public OCRGeneralBasicRecognition(string imagePath, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 31 | : base(imagePath, granularity, language, isDetectDirection, isDetectLanguage) 32 | { } 33 | } 34 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRGeneralBasicRecognition.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 40ff7dc96fb80134897a4ea90ab99ae6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRGeneralRecognition.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// 通用文字识别(含位置信息版) 7 | /// 8 | public sealed class OCRGeneralRecognition : CharacterRecognitionModeBase 9 | { 10 | public override string API 11 | { 12 | get 13 | { 14 | return "https://aip.baidubce.com/rest/2.0/ocr/v1/general"; 15 | } 16 | } 17 | 18 | public override string ContentType 19 | { 20 | get 21 | { 22 | return "application/x-www-form-urlencoded"; 23 | } 24 | } 25 | 26 | public OCRGeneralRecognition(Texture2D image, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 27 | : base(image, granularity, language, isDetectDirection, isDetectLanguage) 28 | { } 29 | 30 | public OCRGeneralRecognition(string imagePath, RecognizeGranularity granularity = RecognizeGranularity.Big, LanguageType language = LanguageType.CHN_ENG, bool isDetectDirection = false, bool isDetectLanguage = false) 31 | : base(imagePath, granularity, language, isDetectDirection, isDetectLanguage) 32 | { } 33 | } 34 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/RecognitionMode/OCRGeneralRecognition.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 404b2d430d4474946930e44d842b0f5b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/Response.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6c8fbe359b2ffa74197260478142a25e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/Response/OCRResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace HT.Framework.AI 5 | { 6 | /// 7 | /// 文字识别响应数据 8 | /// 9 | public sealed class OCRResponse 10 | { 11 | /// 12 | /// ID 13 | /// 14 | public string LogID { get; private set; } 15 | /// 16 | /// 图像方向 17 | /// 18 | public DirectionType Direction { get; private set; } 19 | /// 20 | /// 文字数量 21 | /// 22 | public uint WordsCount { get; private set; } 23 | /// 24 | /// 文字语言 25 | /// 26 | public int Language { get; private set; } 27 | /// 28 | /// 所有文字 29 | /// 30 | public List Words { get; private set; } 31 | 32 | public OCRResponse(JsonData jsonData) 33 | { 34 | LogID = jsonData["log_id"].ToString(); 35 | Direction = jsonData.Keys.Contains("direction") ? (DirectionType)(int)jsonData["direction"] : DirectionType.None; 36 | WordsCount = uint.Parse(jsonData["words_result_num"].ToString()); 37 | Language = jsonData.Keys.Contains("language") ? (int)jsonData["language"] : -1; 38 | 39 | if (jsonData.Keys.Contains("words_result")) 40 | { 41 | Words = new List(); 42 | JsonData words = jsonData["words_result"]; 43 | for (int i = 0; i < words.Count; i++) 44 | { 45 | Word word = new Word(words[i]); 46 | Words.Add(word); 47 | } 48 | } 49 | } 50 | 51 | /// 52 | /// 文字 53 | /// 54 | public sealed class Word 55 | { 56 | /// 57 | /// 文字内容 58 | /// 59 | public string Content { get; private set; } 60 | /// 61 | /// 文字位置 62 | /// 63 | public Rect Location { get; private set; } 64 | /// 65 | /// 所有字符 66 | /// 67 | public List Chars { get; private set; } 68 | 69 | public Word(JsonData word) 70 | { 71 | Content = word["words"].ToString(); 72 | 73 | if (word.Keys.Contains("location")) 74 | { 75 | Location = new Rect((int)word["location"]["left"], (int)word["location"]["top"], (int)word["location"]["width"], (int)word["location"]["height"]); 76 | } 77 | 78 | if (word.Keys.Contains("chars")) 79 | { 80 | Chars = new List(); 81 | JsonData chars = word["chars"]; 82 | for (int i = 0; i < chars.Count; i++) 83 | { 84 | Char cha = new Char(chars[i]); 85 | Chars.Add(cha); 86 | } 87 | } 88 | } 89 | } 90 | 91 | /// 92 | /// 字符 93 | /// 94 | public sealed class Char 95 | { 96 | /// 97 | /// 字符内容 98 | /// 99 | public char Content { get; private set; } 100 | /// 101 | /// 字符位置 102 | /// 103 | public Rect Location { get; private set; } 104 | 105 | public Char(JsonData cha) 106 | { 107 | Content = char.Parse(cha["char"].ToString()); 108 | 109 | if (cha.Keys.Contains("location")) 110 | { 111 | Location = new Rect((int)cha["location"]["left"], (int)cha["location"]["top"], (int)cha["location"]["width"], (int)cha["location"]["height"]); 112 | } 113 | } 114 | } 115 | 116 | /// 117 | /// 图像方向类型 118 | /// 119 | public enum DirectionType 120 | { 121 | /// 122 | /// 未定义 123 | /// 124 | None = -1, 125 | /// 126 | /// 正向 127 | /// 128 | Forward = 0, 129 | /// 130 | /// 逆时针90度 131 | /// 132 | Anticlockwise90 = 1, 133 | /// 134 | /// 逆时针180度 135 | /// 136 | Anticlockwise180 = 2, 137 | /// 138 | /// 逆时针270度 139 | /// 140 | Anticlockwise270 = 3, 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /RunTime/CharacterRecognition/Response/OCRResponse.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 112ffd26f09e01240b46fa5ce412af39 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/HTFramework.AI.RunTime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HTFramework.AI.RunTime", 3 | "references": [ 4 | "GUID:83f631a6180cc5a41982c9354b6cc704" 5 | ], 6 | "includePlatforms": [], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /RunTime/HTFramework.AI.RunTime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1c5ca69798d20074fb935732bf68b629 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /RunTime/Pathfinding.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 40a651a97c564bf4f9d0a1e1805e4651 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/AStarGrid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using UnityEngine; 5 | 6 | [assembly: InternalsVisibleTo("HTFramework.AI.Editor")] 7 | 8 | namespace HT.Framework.AI 9 | { 10 | /// 11 | /// A*网格 12 | /// 13 | [DisallowMultipleComponent] 14 | [AddComponentMenu("HTFramework/★ AI/A* Grid")] 15 | public sealed class AStarGrid : HTBehaviour 16 | { 17 | /// 18 | /// 估价算法类型【请勿在代码中修改】 19 | /// 20 | [SerializeField] internal string EvaluationType = "HT.Framework.AI.AStarManhattan"; 21 | /// 22 | /// 网格平面的大小 23 | /// 24 | public Vector2 Size = new Vector2(10, 10); 25 | /// 26 | /// 节点的半径 27 | /// 28 | public float NodeRadius = 0.5f; 29 | /// 30 | /// 是否忽略斜对角 31 | /// 32 | public bool IsIgnoreOblique = false; 33 | /// 34 | /// 是否自动生成网格 35 | /// 36 | public bool IsAutoGenerate = true; 37 | /// 38 | /// 是否禁止显示寻路失败的日志提示 39 | /// 40 | public bool IsHideFindFailedLog = false; 41 | #if UNITY_EDITOR 42 | /// 43 | /// 是否显示节点索引 44 | /// 45 | public bool IsShowIndex = false; 46 | #endif 47 | 48 | //估价算法 49 | private AStarEvaluation _evaluation; 50 | //节点的直径 51 | private float _nodeDiameter; 52 | //网格节点组的宽度,高度 53 | private int _nodesWidth, _nodesHeight; 54 | //网格的所有节点 55 | private AStarNode[,] _nodes; 56 | //寻路的结果路径 57 | private List _resultPath = new List(); 58 | //寻可行走节点的结果组 59 | private List _resultNodes = new List(); 60 | //节点的相邻节点组 61 | private List _neighbors = new List(); 62 | //寻路的开启列表 63 | private List _openList = new List(); 64 | //寻路的开启列表 65 | private HashSet _openSet = new HashSet(); 66 | //寻路的关闭列表 67 | private HashSet _closeSet = new HashSet(); 68 | 69 | protected override void Awake() 70 | { 71 | base.Awake(); 72 | 73 | Type type = ReflectionToolkit.GetTypeInRunTimeAssemblies(EvaluationType, false); 74 | if (type != null) 75 | { 76 | _evaluation = Activator.CreateInstance(type) as AStarEvaluation; 77 | if (_evaluation == null) 78 | { 79 | Log.Error("A*:初始化网格失败,丢失了估价算法 " + EvaluationType); 80 | return; 81 | } 82 | } 83 | else 84 | { 85 | Log.Error("A*:初始化网格失败,丢失了估价算法 " + EvaluationType); 86 | return; 87 | } 88 | 89 | if (IsAutoGenerate) 90 | { 91 | GenerateGrid(); 92 | } 93 | } 94 | 95 | private void OnDrawGizmos() 96 | { 97 | #if UNITY_EDITOR 98 | Gizmos.DrawWireCube(transform.position, new Vector3(Size.x, 1, Size.y)); 99 | 100 | if (_nodes == null) 101 | return; 102 | 103 | if (IsShowIndex) 104 | { 105 | foreach (AStarNode node in _nodes) 106 | { 107 | UnityEditor.Handles.Label(node.WorldPoint, $"<{node.XIndex},{node.YIndex}>"); 108 | } 109 | } 110 | else 111 | { 112 | foreach (AStarNode node in _nodes) 113 | { 114 | Gizmos.color = node.IsCanWalk ? Color.white : Color.red; 115 | Gizmos.DrawCube(node.WorldPoint, Vector3.one * (_nodeDiameter - 0.1f)); 116 | } 117 | 118 | if (_resultPath != null) 119 | { 120 | for (int i = 0; i < _resultPath.Count; i++) 121 | { 122 | Gizmos.color = Color.cyan; 123 | Gizmos.DrawCube(_resultPath[i].WorldPoint, Vector3.one * (_nodeDiameter - 0.1f)); 124 | } 125 | } 126 | 127 | if (_resultNodes != null) 128 | { 129 | for (int i = 0; i < _resultNodes.Count; i++) 130 | { 131 | Gizmos.color = Color.yellow; 132 | Gizmos.DrawCube(_resultNodes[i].WorldPoint, Vector3.one * (_nodeDiameter - 0.1f)); 133 | } 134 | } 135 | } 136 | #endif 137 | } 138 | 139 | /// 140 | /// 生成网格 141 | /// 142 | public void GenerateGrid() 143 | { 144 | _nodeDiameter = NodeRadius * 2; 145 | _nodesWidth = Mathf.RoundToInt(Size.x / _nodeDiameter); 146 | _nodesHeight = Mathf.RoundToInt(Size.y / _nodeDiameter); 147 | _nodes = new AStarNode[_nodesWidth, _nodesHeight]; 148 | 149 | CreateGrid(); 150 | } 151 | 152 | /// 153 | /// 寻路 154 | /// 155 | /// 起点的索引 156 | /// 终点的索引 157 | /// 搜寻规则 158 | /// 结果路径 159 | public List Pathfinding(Vector2Int startIndex, Vector2Int endIndex, AStarRule rule = null) 160 | { 161 | if (startIndex.x < 0 || startIndex.x >= _nodesWidth || startIndex.y < 0 || startIndex.y >= _nodesHeight 162 | || endIndex.x < 0 || endIndex.x >= _nodesWidth || endIndex.y < 0 || endIndex.y >= _nodesHeight) 163 | { 164 | if (!IsHideFindFailedLog) Log.Warning("A*:寻路失败,起点或终点的索引超出了网格的大小!"); 165 | _resultPath.Clear(); 166 | return _resultPath; 167 | } 168 | 169 | if (rule != null) 170 | { 171 | foreach (AStarNode node in _nodes) 172 | { 173 | rule.Apply(node); 174 | } 175 | } 176 | 177 | return Pathfinding(_nodes[startIndex.x, startIndex.y], _nodes[endIndex.x, endIndex.y]); 178 | } 179 | 180 | /// 181 | /// 寻路 182 | /// 183 | /// 起点 184 | /// 终点 185 | /// 搜寻规则 186 | /// 结果路径 187 | public List Pathfinding(Vector3 startPoint, Vector3 endPoint, AStarRule rule = null) 188 | { 189 | if (rule != null) 190 | { 191 | foreach (AStarNode node in _nodes) 192 | { 193 | rule.Apply(node); 194 | } 195 | } 196 | 197 | return Pathfinding(GetNode(startPoint), GetNode(endPoint)); 198 | } 199 | 200 | /// 201 | /// 寻可行走节点 202 | /// 203 | /// 起点 204 | /// 可行走节点到起点的最大估价 205 | /// 搜寻规则 206 | /// 结果节点组 207 | public List WalkableNodefinding(Vector2Int startIndex, int cost, AStarRule rule = null) 208 | { 209 | if (startIndex.x < 0 || startIndex.x >= _nodesWidth || startIndex.y < 0 || startIndex.y >= _nodesHeight) 210 | { 211 | if (!IsHideFindFailedLog) Log.Warning("A*:寻可行走节点失败,起点的索引超出了网格的大小!"); 212 | _resultNodes.Clear(); 213 | return _resultNodes; 214 | } 215 | 216 | if (rule != null) 217 | { 218 | foreach (AStarNode node in _nodes) 219 | { 220 | rule.Apply(node); 221 | } 222 | } 223 | 224 | return WalkableNodefinding(_nodes[startIndex.x, startIndex.y], cost); 225 | } 226 | 227 | /// 228 | /// 寻可行走节点 229 | /// 230 | /// 起点 231 | /// 可行走节点到起点的最大估价 232 | /// 搜寻规则 233 | /// 结果节点组 234 | public List WalkableNodefinding(Vector3 startPoint, int cost, AStarRule rule = null) 235 | { 236 | if (rule != null) 237 | { 238 | foreach (AStarNode node in _nodes) 239 | { 240 | rule.Apply(node); 241 | } 242 | } 243 | 244 | return WalkableNodefinding(GetNode(startPoint), cost); 245 | } 246 | 247 | private void CreateGrid() 248 | { 249 | //从网格平面的左下角坐标开始 250 | Vector3 startPoint = transform.position - Size.x / 2 * Vector3.right - Size.y / 2 * Vector3.forward; 251 | 252 | for (int i = 0; i < _nodesWidth; i++) 253 | { 254 | for (int j = 0; j < _nodesHeight; j++) 255 | { 256 | Vector3 worldPoint = startPoint + Vector3.right * (i * _nodeDiameter + NodeRadius) + Vector3.forward * (j * _nodeDiameter + NodeRadius); 257 | _nodes[i, j] = new AStarNode(worldPoint, i, j); 258 | } 259 | } 260 | } 261 | 262 | private AStarNode GetNode(Vector3 worldPoint) 263 | { 264 | Vector3 pos = worldPoint - transform.position; 265 | 266 | float percentX = (pos.x + Size.x / 2) / Size.x; 267 | float percentY = (pos.z + Size.y / 2) / Size.y; 268 | 269 | percentX = Mathf.Clamp01(percentX); 270 | percentY = Mathf.Clamp01(percentY); 271 | 272 | int x = Mathf.RoundToInt((_nodesWidth - 1) * percentX); 273 | int y = Mathf.RoundToInt((_nodesHeight - 1) * percentY); 274 | 275 | return _nodes[x, y]; 276 | } 277 | 278 | private void GetNeighbor(AStarNode node) 279 | { 280 | _neighbors.Clear(); 281 | for (int i = -1; i <= 1; i++) 282 | { 283 | for (int j = -1; j <= 1; j++) 284 | { 285 | if (i == 0 && j == 0) continue; 286 | if (IsIgnoreOblique) 287 | { 288 | if (i == -1 && j == -1) continue; 289 | if (i == -1 && j == 1) continue; 290 | if (i == 1 && j == -1) continue; 291 | if (i == 1 && j == 1) continue; 292 | } 293 | 294 | int x = node.XIndex + i; 295 | int y = node.YIndex + j; 296 | 297 | if (x >= 0 && x < _nodesWidth && y >= 0 && y < _nodesHeight) 298 | { 299 | _neighbors.Add(_nodes[x, y]); 300 | } 301 | } 302 | } 303 | } 304 | 305 | private List Pathfinding(AStarNode startNode, AStarNode endNode) 306 | { 307 | //终点不可抵达 308 | if (!endNode.IsCanWalk) 309 | { 310 | if (!IsHideFindFailedLog) Log.Warning("A*:寻路失败,目标位置不可抵达!"); 311 | _resultPath.Clear(); 312 | return _resultPath; 313 | } 314 | 315 | //重置估价 316 | foreach (var node in _nodes) 317 | { 318 | node.GCost = 0; 319 | node.HCost = 0; 320 | } 321 | 322 | _openList.Clear(); 323 | _openSet.Clear(); 324 | _closeSet.Clear(); 325 | 326 | //将开始节点加入到开启列表中 327 | _openList.Add(startNode); 328 | _openSet.Add(startNode); 329 | while (_openList.Count > 0) 330 | { 331 | //从开启列表中找到总估价最低,且距离终点最近的点 332 | AStarNode currentNode = _openList[0]; 333 | for (int i = 1; i < _openList.Count; i++) 334 | { 335 | if (_openList[i].TotalCost <= currentNode.TotalCost && _openList[i].HCost < currentNode.HCost) 336 | { 337 | currentNode = _openList[i]; 338 | } 339 | } 340 | _openList.Remove(currentNode); 341 | _openSet.Remove(currentNode); 342 | _closeSet.Add(currentNode); 343 | 344 | //如果当前为终点节点,则创建最终路径 345 | if (currentNode == endNode) 346 | { 347 | return GeneratePath(startNode, endNode); 348 | } 349 | 350 | //遍历当前节点的邻居节点,更新估价并加入开启列表 351 | GetNeighbor(currentNode); 352 | for (int i = 0; i < _neighbors.Count; i++) 353 | { 354 | if (!_neighbors[i].IsCanWalk) 355 | continue; 356 | 357 | if (_closeSet.Contains(_neighbors[i])) 358 | continue; 359 | 360 | int gCost = currentNode.GCost + _evaluation.Evaluation(currentNode, _neighbors[i]) + _neighbors[i].OCost; 361 | if (gCost < _neighbors[i].GCost || !_openSet.Contains(_neighbors[i])) 362 | { 363 | _neighbors[i].GCost = gCost; 364 | _neighbors[i].HCost = _evaluation.Evaluation(_neighbors[i], endNode); 365 | _neighbors[i].Parent = currentNode; 366 | if (!_openSet.Contains(_neighbors[i])) 367 | { 368 | _openList.Add(_neighbors[i]); 369 | _openSet.Add(_neighbors[i]); 370 | } 371 | } 372 | } 373 | } 374 | 375 | if (!IsHideFindFailedLog) Log.Warning("A*:寻路失败,未找到合适的路径!"); 376 | _resultPath.Clear(); 377 | return _resultPath; 378 | } 379 | 380 | private List GeneratePath(AStarNode startNode, AStarNode endNode) 381 | { 382 | _resultPath.Clear(); 383 | AStarNode temp = endNode; 384 | while (temp != startNode) 385 | { 386 | _resultPath.Add(temp); 387 | temp = temp.Parent; 388 | } 389 | _resultPath.Reverse(); 390 | return _resultPath; 391 | } 392 | 393 | private List WalkableNodefinding(AStarNode startNode, int cost) 394 | { 395 | IsIgnoreOblique = true; 396 | 397 | if (cost <= 0) 398 | { 399 | _resultNodes.Clear(); 400 | return _resultNodes; 401 | } 402 | 403 | //重置估价 404 | foreach (var node in _nodes) 405 | { 406 | node.GCost = 0; 407 | node.HCost = 0; 408 | } 409 | 410 | _openList.Clear(); 411 | _openSet.Clear(); 412 | 413 | //将开始节点加入到开启列表中 414 | _openList.Add(startNode); 415 | _openSet.Add(startNode); 416 | while (_openList.Count > 0) 417 | { 418 | //从开启列表中任意取出一个节点 419 | AStarNode currentNode = _openList[0]; 420 | 421 | _openList.Remove(currentNode); 422 | _openSet.Remove(currentNode); 423 | 424 | //遍历当前节点的邻居节点,更新估价,估价低于限制估价的加入开启列表 425 | GetNeighbor(currentNode); 426 | for (int i = 0; i < _neighbors.Count; i++) 427 | { 428 | if (!_neighbors[i].IsCanWalk) 429 | continue; 430 | 431 | int gCost = currentNode.GCost + _evaluation.Evaluation(currentNode, _neighbors[i]) + _neighbors[i].OCost; 432 | if (_neighbors[i] != startNode && gCost <= cost && (gCost < _neighbors[i].GCost || _neighbors[i].GCost == 0)) 433 | { 434 | _neighbors[i].GCost = gCost; 435 | if (!_openSet.Contains(_neighbors[i])) 436 | { 437 | _openList.Add(_neighbors[i]); 438 | _openSet.Add(_neighbors[i]); 439 | } 440 | } 441 | } 442 | } 443 | 444 | //找到所有估价合格的节点 445 | _resultNodes.Clear(); 446 | foreach (AStarNode node in _nodes) 447 | { 448 | if (node.GCost != 0 && node.GCost <= cost) 449 | { 450 | _resultNodes.Add(node); 451 | } 452 | } 453 | return _resultNodes; 454 | } 455 | } 456 | } -------------------------------------------------------------------------------- /RunTime/Pathfinding/AStarGrid.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0625f951d63806344947c0a2bdcf49eb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/AStarManhattan.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// A*曼哈顿估价算法 7 | /// 8 | public sealed class AStarManhattan : AStarEvaluation 9 | { 10 | public override int Evaluation(AStarNode nodea, AStarNode nodeb) 11 | { 12 | return Mathf.Abs(nodea.XIndex - nodeb.XIndex) + Mathf.Abs(nodea.YIndex - nodeb.YIndex); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/AStarManhattan.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82e3a9b2b42fab64c87875b531ccde71 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/AStarNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// A*节点 7 | /// 8 | public sealed class AStarNode 9 | { 10 | /// 11 | /// 节点是否可以通行 12 | /// 13 | public bool IsCanWalk { get; set; } = true; 14 | /// 15 | /// 节点的世界坐标 16 | /// 17 | public Vector3 WorldPoint { get; private set; } 18 | /// 19 | /// 节点在网格上的X位置 20 | /// 21 | public int XIndex { get; private set; } 22 | /// 23 | /// 节点在网格上的Y位置 24 | /// 25 | public int YIndex { get; private set; } 26 | /// 27 | /// 当前节点自身的固定估价 28 | /// 29 | public int OCost { get; set; } = 0; 30 | /// 31 | /// 起始点到当前节点的估价 32 | /// 33 | public int GCost { get; internal set; } = 0; 34 | /// 35 | /// 当前节点到终点的估价 36 | /// 37 | public int HCost { get; internal set; } = 0; 38 | /// 39 | /// 父节点 40 | /// 41 | public AStarNode Parent { get; internal set; } 42 | 43 | /// 44 | /// 总估价 45 | /// 46 | public int TotalCost 47 | { 48 | get 49 | { 50 | return GCost + HCost + OCost; 51 | } 52 | } 53 | 54 | public AStarNode(Vector3 worldPoint, int xIndex, int yIndex) 55 | { 56 | WorldPoint = worldPoint; 57 | XIndex = xIndex; 58 | YIndex = yIndex; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/AStarNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c93d36b448f7cf4890cc5f335b45d5b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/Base.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4bf72c46bb89a2840b65023b891eba3c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/Base/AStarEvaluation.cs: -------------------------------------------------------------------------------- 1 | namespace HT.Framework.AI 2 | { 3 | /// 4 | /// A*估价算法 5 | /// 6 | public abstract class AStarEvaluation 7 | { 8 | /// 9 | /// 估价 10 | /// 11 | public abstract int Evaluation(AStarNode nodea, AStarNode nodeb); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/Base/AStarEvaluation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 26566bb04bcaa8b45883490dccb82592 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/Base/AStarRule.cs: -------------------------------------------------------------------------------- 1 | namespace HT.Framework.AI 2 | { 3 | /// 4 | /// A*搜寻规则 5 | /// 6 | public abstract class AStarRule 7 | { 8 | /// 9 | /// 应用规则 10 | /// 11 | public abstract void Apply(AStarNode node); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RunTime/Pathfinding/Base/AStarRule.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6bdbad4bddd71b4f8bbfbb2cb244d61 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Speech.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3be84d72097dc845b6faa39381394c3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /RunTime/Speech/SpeechUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Text; 3 | using System.IO; 4 | using System; 5 | 6 | namespace HT.Framework.AI 7 | { 8 | /// 9 | /// 语音管理实用工具 10 | /// 11 | public static class SpeechUtility 12 | { 13 | #region Calculation Method 14 | private const int BlockSize_16Bit = 2; 15 | 16 | private static float[] Convert8BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) 17 | { 18 | int wavSize = BitConverter.ToInt32(source, headerOffset); 19 | headerOffset += sizeof(int); 20 | if (wavSize <= 0 || wavSize != dataSize) 21 | { 22 | Log.Error("转换AudioClip失败:未获取到有效的WAV字节数据!"); 23 | return null; 24 | } 25 | 26 | float[] data = new float[wavSize]; 27 | sbyte maxValue = sbyte.MaxValue; 28 | int i = 0; 29 | while (i < wavSize) 30 | { 31 | data[i] = (float)source[i] / maxValue; 32 | i += 1; 33 | } 34 | return data; 35 | } 36 | private static float[] Convert16BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) 37 | { 38 | int wavSize = BitConverter.ToInt32(source, headerOffset); 39 | headerOffset += sizeof(int); 40 | if (wavSize <= 0 || wavSize != dataSize) 41 | { 42 | Log.Error("转换AudioClip失败:未获取到有效的WAV字节数据!"); 43 | return null; 44 | } 45 | 46 | int x = sizeof(short); 47 | int convertedSize = wavSize / x; 48 | 49 | float[] data = new float[convertedSize]; 50 | short maxValue = short.MaxValue; 51 | int offset = 0; 52 | int i = 0; 53 | while (i < convertedSize) 54 | { 55 | offset = i * x + headerOffset; 56 | data[i] = (float)BitConverter.ToInt16(source, offset) / maxValue; 57 | i += 1; 58 | } 59 | return data; 60 | } 61 | private static float[] Convert24BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) 62 | { 63 | int wavSize = BitConverter.ToInt32(source, headerOffset); 64 | headerOffset += sizeof(int); 65 | if (wavSize <= 0 || wavSize != dataSize) 66 | { 67 | Log.Error("转换AudioClip失败:未获取到有效的WAV字节数据!"); 68 | return null; 69 | } 70 | 71 | int x = 3; 72 | int convertedSize = wavSize / x; 73 | 74 | float[] data = new float[convertedSize]; 75 | int maxValue = int.MaxValue; 76 | byte[] block = new byte[sizeof(int)]; 77 | int offset = 0; 78 | int i = 0; 79 | while (i < convertedSize) 80 | { 81 | offset = i * x + headerOffset; 82 | Buffer.BlockCopy(source, offset, block, 1, x); 83 | data[i] = (float)BitConverter.ToInt32(block, 0) / maxValue; 84 | i += 1; 85 | } 86 | return data; 87 | } 88 | private static float[] Convert32BitByteArrayToAudioClipData(byte[] source, int headerOffset, int dataSize) 89 | { 90 | int wavSize = BitConverter.ToInt32(source, headerOffset); 91 | headerOffset += sizeof(int); 92 | if (wavSize <= 0 || wavSize != dataSize) 93 | { 94 | Log.Error("转换AudioClip失败:未获取到有效的WAV字节数据!"); 95 | return null; 96 | } 97 | 98 | int x = sizeof(float); 99 | int convertedSize = wavSize / x; 100 | 101 | float[] data = new float[convertedSize]; 102 | int maxValue = int.MaxValue; 103 | int offset = 0; 104 | int i = 0; 105 | while (i < convertedSize) 106 | { 107 | offset = i * x + headerOffset; 108 | data[i] = (float)BitConverter.ToInt32(source, offset) / maxValue; 109 | i += 1; 110 | } 111 | return data; 112 | } 113 | private static int WriteFileHeader(ref MemoryStream stream, int fileSize) 114 | { 115 | int count = 0; 116 | int total = 12; 117 | 118 | byte[] riff = Encoding.ASCII.GetBytes("RIFF"); 119 | count += WriteBytesToMemoryStream(ref stream, riff); 120 | 121 | int chunkSize = fileSize - 8; 122 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(chunkSize)); 123 | 124 | byte[] wave = Encoding.ASCII.GetBytes("WAVE"); 125 | count += WriteBytesToMemoryStream(ref stream, wave); 126 | 127 | if (count != total) 128 | { 129 | Log.Error("转换字节数组失败:写入的字节数组长度异常!"); 130 | } 131 | return count; 132 | } 133 | private static int WriteFileFormat(ref MemoryStream stream, int channels, int sampleRate, ushort bitDepth) 134 | { 135 | int count = 0; 136 | int total = 24; 137 | 138 | byte[] id = Encoding.ASCII.GetBytes("fmt "); 139 | count += WriteBytesToMemoryStream(ref stream, id); 140 | 141 | int subchunk1Size = 16; 142 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(subchunk1Size)); 143 | 144 | ushort audioFormat = 1; 145 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(audioFormat)); 146 | 147 | ushort numChannels = Convert.ToUInt16(channels); 148 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(numChannels)); 149 | 150 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(sampleRate)); 151 | 152 | int byteRate = sampleRate * channels * BytesPerSample(bitDepth); 153 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(byteRate)); 154 | 155 | ushort blockAlign = Convert.ToUInt16(channels * BytesPerSample(bitDepth)); 156 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(blockAlign)); 157 | 158 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(bitDepth)); 159 | 160 | if (count != total) 161 | { 162 | Log.Error("转换字节数组失败:写入的字节数组长度异常!"); 163 | } 164 | return count; 165 | } 166 | private static int WriteFileData(ref MemoryStream stream, AudioClip audioClip, ushort bitDepth) 167 | { 168 | int count = 0; 169 | int total = 8; 170 | 171 | float[] data = new float[audioClip.samples * audioClip.channels]; 172 | audioClip.GetData(data, 0); 173 | 174 | byte[] bytes = ConvertAudioClipDataToInt16ByteArray(data); 175 | 176 | byte[] id = Encoding.ASCII.GetBytes("data"); 177 | count += WriteBytesToMemoryStream(ref stream, id); 178 | 179 | int subchunk2Size = Convert.ToInt32(audioClip.samples * BlockSize_16Bit); 180 | count += WriteBytesToMemoryStream(ref stream, BitConverter.GetBytes(subchunk2Size)); 181 | 182 | if (count != total) 183 | { 184 | Log.Error("转换字节数组失败:写入的字节数组长度异常!"); 185 | } 186 | 187 | count += WriteBytesToMemoryStream(ref stream, bytes); 188 | 189 | if (bytes.Length != subchunk2Size) 190 | { 191 | Log.Error("转换字节数组失败:写入的字节数组长度异常!"); 192 | } 193 | return count; 194 | } 195 | private static int WriteBytesToMemoryStream(ref MemoryStream stream, byte[] bytes) 196 | { 197 | int count = bytes.Length; 198 | stream.Write(bytes, 0, count); 199 | return count; 200 | } 201 | private static byte[] ConvertAudioClipDataToInt16ByteArray(float[] data) 202 | { 203 | MemoryStream dataStream = new MemoryStream(); 204 | 205 | int x = sizeof(short); 206 | 207 | short maxValue = short.MaxValue; 208 | 209 | int i = 0; 210 | while (i < data.Length) 211 | { 212 | dataStream.Write(BitConverter.GetBytes(Convert.ToInt16(data[i] * maxValue)), 0, x); 213 | i += 1; 214 | } 215 | byte[] bytes = dataStream.ToArray(); 216 | 217 | if (data.Length * x != bytes.Length) 218 | { 219 | Log.Error("转换字节数组失败:写入的字节数组长度异常!"); 220 | } 221 | 222 | dataStream.Close(); 223 | 224 | return bytes; 225 | } 226 | 227 | private static int BytesPerSample(ushort bitDepth) 228 | { 229 | return bitDepth / 8; 230 | } 231 | private static string FormatCode(ushort code) 232 | { 233 | switch (code) 234 | { 235 | case 1: 236 | return "PCM"; 237 | case 2: 238 | return "ADPCM"; 239 | case 3: 240 | return "IEEE"; 241 | case 6: 242 | return "a-law"; 243 | case 7: 244 | return "μ-law"; 245 | case 49: 246 | return "GSM"; 247 | case 65534: 248 | return "WaveFormatExtensable"; 249 | default: 250 | Log.Warning("未知的WAV格式代码:" + code); 251 | return ""; 252 | } 253 | } 254 | #endregion 255 | 256 | /// 257 | /// 将WAV音频格式文件转换为AudioClip 258 | /// 259 | /// 文件路径 260 | /// 转换完成的AudioClip 261 | public static AudioClip ToAudioClip(string filePath) 262 | { 263 | if (!File.Exists(filePath)) 264 | { 265 | Log.Error("加载WAV文件失败:未找到 " + filePath + " 文件!"); 266 | return null; 267 | } 268 | byte[] fileBytes = File.ReadAllBytes(filePath); 269 | return ToAudioClip(fileBytes, 0); 270 | } 271 | /// 272 | /// 将字节数组转换为AudioClip 273 | /// 274 | /// 文件的字节数组 275 | /// 采样偏移 276 | /// 名称 277 | /// 转换完成的AudioClip 278 | public static AudioClip ToAudioClip(byte[] fileBytes, int offsetSamples = 0, string name = "wav") 279 | { 280 | int subchunk1Size = BitConverter.ToInt32(fileBytes, 16); 281 | ushort audioFormatCode = BitConverter.ToUInt16(fileBytes, 20); 282 | string audioFormat = FormatCode(audioFormatCode); 283 | 284 | if (audioFormatCode != 1 && audioFormatCode != 65534) 285 | { 286 | Log.Error("转换AudioClip失败:当前仅支持转换PCM或WAV的扩展格式!"); 287 | return null; 288 | } 289 | 290 | ushort channels = BitConverter.ToUInt16(fileBytes, 22); 291 | int sampleRate = BitConverter.ToInt32(fileBytes, 24); 292 | ushort bitsPerSample = BitConverter.ToUInt16(fileBytes, 34); 293 | int headerOffset = 16 + 4 + subchunk1Size + 4; 294 | int subchunk2Size = BitConverter.ToInt32(fileBytes, headerOffset); 295 | 296 | float[] data; 297 | switch (bitsPerSample) 298 | { 299 | case 8: 300 | data = Convert8BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2Size); 301 | break; 302 | case 16: 303 | data = Convert16BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2Size); 304 | break; 305 | case 24: 306 | data = Convert24BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2Size); 307 | break; 308 | case 32: 309 | data = Convert32BitByteArrayToAudioClipData(fileBytes, headerOffset, subchunk2Size); 310 | break; 311 | default: 312 | Log.Error("转换AudioClip失败:不支持 " + bitsPerSample + " 量化位数!"); 313 | return null; 314 | } 315 | 316 | if (data != null) 317 | { 318 | AudioClip audioClip = AudioClip.Create(name, data.Length, channels, sampleRate, false); 319 | audioClip.SetData(data, 0); 320 | return audioClip; 321 | } 322 | else 323 | { 324 | return null; 325 | } 326 | } 327 | 328 | /// 329 | /// 将AudioClip转换为字节数组 330 | /// 331 | /// AudioClip对象 332 | /// 转换完成的字节数组 333 | public static byte[] FromAudioClip(AudioClip audioClip) 334 | { 335 | return FromAudioClip(audioClip, false, string.Empty); 336 | } 337 | /// 338 | /// 将AudioClip转换为字节数组 339 | /// 340 | /// AudioClip对象 341 | /// 是否保存为文件 342 | /// 保存路径 343 | /// 转换完成的字节数组 344 | public static byte[] FromAudioClip(AudioClip audioClip, bool saveAsFile, string filepath) 345 | { 346 | MemoryStream stream = new MemoryStream(); 347 | 348 | int headerSize = 44; 349 | ushort bitDepth = 16; 350 | int fileSize = audioClip.samples * BlockSize_16Bit + headerSize; 351 | 352 | WriteFileHeader(ref stream, fileSize); 353 | WriteFileFormat(ref stream, audioClip.channels, audioClip.frequency, bitDepth); 354 | WriteFileData(ref stream, audioClip, bitDepth); 355 | 356 | byte[] bytes = stream.ToArray(); 357 | if (bytes.Length != fileSize) 358 | { 359 | Log.Error("转换字节数组失败:转换完成后的字节数组长度异常!"); 360 | return null; 361 | } 362 | 363 | if (saveAsFile) 364 | { 365 | Directory.CreateDirectory(Path.GetDirectoryName(filepath)); 366 | File.WriteAllBytes(filepath, bytes); 367 | } 368 | 369 | stream.Close(); 370 | return bytes; 371 | } 372 | } 373 | } -------------------------------------------------------------------------------- /RunTime/Speech/SpeechUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 59be6eafc97f04343843596e321afe72 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Speech/Speecher.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.IO; 3 | using System.Text; 4 | using UnityEngine; 5 | using UnityEngine.Networking; 6 | 7 | namespace HT.Framework.AI 8 | { 9 | /// 10 | /// 语音管理器 11 | /// 12 | public static class Speecher 13 | { 14 | private static readonly string TOKENAPI = "https://openapi.baidu.com/oauth/2.0/token"; 15 | private static readonly string SynthesisAPI = "https://tsn.baidu.com/text2audio"; 16 | private static readonly string RecognitionAPI = "https://vop.baidu.com/server_api"; 17 | private static string APIKEY = ""; 18 | private static string SECRETKEY = ""; 19 | private static string TOKEN = ""; 20 | 21 | /// 22 | /// 设置APIKEY 23 | /// 24 | /// APIKEY 25 | public static void SetAPIKEY(string apiKey) 26 | { 27 | APIKEY = apiKey; 28 | } 29 | /// 30 | /// 设置SECRETKEY 31 | /// 32 | /// SECRETKEY 33 | public static void SetSECRETKEY(string SecretKey) 34 | { 35 | SECRETKEY = SecretKey; 36 | } 37 | /// 38 | /// 设置TOKEN 39 | /// 40 | /// TOKEN 41 | public static void SetTOKEN(string token) 42 | { 43 | TOKEN = token; 44 | } 45 | 46 | /// 47 | /// 生成TOKEN 48 | /// 49 | /// 生成TOKEN的协程 50 | public static Coroutine GenerateTOKEN() 51 | { 52 | return Main.Current.StartCoroutine(GenerateTOKENCoroutine()); 53 | } 54 | private static IEnumerator GenerateTOKENCoroutine() 55 | { 56 | string url = string.Format("{0}?grant_type=client_credentials&client_id={1}&client_secret={2}", TOKENAPI, APIKEY, SECRETKEY); 57 | 58 | using (UnityWebRequest request = UnityWebRequest.Get(url)) 59 | { 60 | yield return request.SendWebRequest(); 61 | if (request.result == UnityWebRequest.Result.Success) 62 | { 63 | JsonData jsonData = JsonToolkit.StringToJson(request.downloadHandler.text); 64 | if (jsonData != null) 65 | { 66 | TOKEN = jsonData.GetValueInSafe("access_token", ""); 67 | } 68 | else 69 | { 70 | Log.Error("获取TOKEN失败:" + request.downloadHandler.text); 71 | } 72 | } 73 | else 74 | { 75 | Log.Error("获取TOKEN失败:" + request.responseCode + " " + request.error); 76 | } 77 | } 78 | } 79 | 80 | /// 81 | /// 合成语音 82 | /// 83 | /// 合成文本 84 | /// 合成完毕后的处理者 85 | /// 合成失败的处理者 86 | /// 超时时长 87 | /// 发音人 88 | /// 音量 89 | /// 音速 90 | /// 音调 91 | /// 合成语音的协程 92 | public static Coroutine Synthesis(string text, HTFAction handler, HTFAction failHandler, int timeout = 60000, Speaker speaker = Speaker.DuYaYa, int volume = 15, int speed = 5, int pitch = 5) 93 | { 94 | if (string.IsNullOrEmpty(text) || Encoding.UTF8.GetByteCount(text) >= 1024) 95 | { 96 | Log.Error("合成语音失败:文本为空或长度超出了1024字节的限制!"); 97 | return null; 98 | } 99 | 100 | return Main.Current.StartCoroutine(SynthesisCoroutine(text, handler, failHandler, timeout, speaker, volume, speed, pitch)); 101 | } 102 | /// 103 | /// 合成语音 104 | /// 105 | /// 合成文本 106 | /// 合成规则 107 | /// 合成完毕后的处理者 108 | /// 合成失败的处理者 109 | /// 超时时长 110 | /// 发音人 111 | /// 音量 112 | /// 音速 113 | /// 音调 114 | /// 合成语音的协程 115 | public static Coroutine Synthesis(string text, SynthesisRule rule, HTFAction handler, HTFAction failHandler, int timeout = 60000, Speaker speaker = Speaker.DuYaYa, int volume = 15, int speed = 5, int pitch = 5) 116 | { 117 | if (string.IsNullOrEmpty(text) || Encoding.UTF8.GetByteCount(text) >= 1024) 118 | { 119 | Log.Error("合成语音失败:文本为空或长度超出了1024字节的限制!"); 120 | return null; 121 | } 122 | 123 | text = rule.Apply(text); 124 | return Main.Current.StartCoroutine(SynthesisCoroutine(text, handler, failHandler, timeout, speaker, volume, speed, pitch)); 125 | } 126 | private static IEnumerator SynthesisCoroutine(string text, HTFAction handler, HTFAction failHandler, int timeout, Speaker speaker, int volume, int speed, int pitch) 127 | { 128 | string url = string.Format("{0}?tex='{1}'&tok={2}&cuid={3}&ctp={4}&lan={5}&spd={6}&pit={7}&vol={8}&per={9}&aue={10}", 129 | SynthesisAPI, text, TOKEN, SystemInfo.deviceUniqueIdentifier, 1, "zh", speed, pitch, volume, (int)speaker, 6); 130 | 131 | using (UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.WAV)) 132 | { 133 | yield return request.SendWebRequest(); 134 | if (request.result == UnityWebRequest.Result.Success) 135 | { 136 | string value = Encoding.UTF8.GetString(request.downloadHandler.data); 137 | JsonData jsonData = JsonToolkit.StringToJson(value); 138 | if (jsonData != null) 139 | { 140 | Log.Error("合成语音失败:" + value); 141 | 142 | handler?.Invoke(null); 143 | } 144 | else 145 | { 146 | AudioClip audioClip = DownloadHandlerAudioClip.GetContent(request); 147 | 148 | handler?.Invoke(audioClip); 149 | } 150 | } 151 | else 152 | { 153 | Log.Error("合成语音失败:" + request.responseCode + " " + request.error); 154 | 155 | failHandler?.Invoke(); 156 | } 157 | } 158 | } 159 | 160 | /// 161 | /// 合成语音,并保存语音文件 162 | /// 163 | /// 合成文本 164 | /// 语音文件保存路径 165 | /// 超时时长 166 | /// 音频文件格式 167 | /// 发音人 168 | /// 音量 169 | /// 音速 170 | /// 音调 171 | /// 合成语音的协程 172 | public static Coroutine Synthesis(string text, string savePath, SynthesisType audioType = SynthesisType.MP3, int timeout = 60000, Speaker speaker = Speaker.DuYaYa, int volume = 15, int speed = 5, int pitch = 5) 173 | { 174 | if (string.IsNullOrEmpty(text) || Encoding.UTF8.GetByteCount(text) >= 1024) 175 | { 176 | Log.Error("合成语音失败:文本为空或长度超出了1024字节的限制!"); 177 | return null; 178 | } 179 | 180 | return Main.Current.StartCoroutine(SynthesisCoroutine(text, savePath, audioType, timeout, speaker, volume, speed, pitch)); 181 | } 182 | /// 183 | /// 合成语音,并保存语音文件 184 | /// 185 | /// 合成文本 186 | /// 合成规则 187 | /// 语音文件保存路径 188 | /// 超时时长 189 | /// 音频文件格式 190 | /// 发音人 191 | /// 音量 192 | /// 音速 193 | /// 音调 194 | /// 合成语音的协程 195 | public static Coroutine Synthesis(string text, SynthesisRule rule, string savePath, SynthesisType audioType = SynthesisType.MP3, int timeout = 60000, Speaker speaker = Speaker.DuYaYa, int volume = 15, int speed = 5, int pitch = 5) 196 | { 197 | if (string.IsNullOrEmpty(text) || Encoding.UTF8.GetByteCount(text) >= 1024) 198 | { 199 | Log.Error("合成语音失败:文本为空或长度超出了1024字节的限制!"); 200 | return null; 201 | } 202 | 203 | text = rule.Apply(text); 204 | return Main.Current.StartCoroutine(SynthesisCoroutine(text, savePath, audioType, timeout, speaker, volume, speed, pitch)); 205 | } 206 | private static IEnumerator SynthesisCoroutine(string text, string savePath, SynthesisType audioType, int timeout, Speaker speaker, int volume, int speed, int pitch) 207 | { 208 | string url = string.Format("{0}?tex='{1}'&tok={2}&cuid={3}&ctp={4}&lan={5}&spd={6}&pit={7}&vol={8}&per={9}&aue={10}", 209 | SynthesisAPI, text, TOKEN, SystemInfo.deviceUniqueIdentifier, 1, "zh", speed, pitch, volume, (int)speaker, (int)audioType); 210 | 211 | using (UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(url, audioType == SynthesisType.MP3 ? AudioType.MPEG : AudioType.WAV)) 212 | { 213 | yield return request.SendWebRequest(); 214 | if (request.result == UnityWebRequest.Result.Success) 215 | { 216 | File.WriteAllBytes(savePath, request.downloadHandler.data); 217 | } 218 | else 219 | { 220 | Log.Error("合成语音失败:" + request.responseCode + " " + request.error); 221 | } 222 | } 223 | } 224 | 225 | /// 226 | /// 语音识别 227 | /// 228 | /// 语音音频 229 | /// 识别成功处理者 230 | /// 识别失败处理者 231 | /// 语音识别的协程 232 | public static Coroutine Recognition(AudioClip clip, HTFAction handler, HTFAction failHandler) 233 | { 234 | if (clip == null) 235 | { 236 | Log.Error("语音识别失败:语音内容为空!"); 237 | return null; 238 | } 239 | 240 | return Main.Current.StartCoroutine(RecognitionCoroutine(SpeechUtility.FromAudioClip(clip), handler, failHandler)); 241 | } 242 | /// 243 | /// 语音识别 244 | /// 245 | /// 语音音频数据 246 | /// 识别成功处理者 247 | /// 识别失败处理者 248 | /// 语音识别的协程 249 | public static Coroutine Recognition(byte[] data, HTFAction handler, HTFAction failHandler) 250 | { 251 | if (data == null || data.Length <= 0) 252 | { 253 | Log.Error("语音识别失败:语音内容为空!"); 254 | return null; 255 | } 256 | 257 | return Main.Current.StartCoroutine(RecognitionCoroutine(data, handler, failHandler)); 258 | } 259 | private static IEnumerator RecognitionCoroutine(byte[] data, HTFAction handler, HTFAction failHandler) 260 | { 261 | string url = string.Format("{0}?cuid={1}&token={2}", RecognitionAPI, SystemInfo.deviceUniqueIdentifier, TOKEN); 262 | 263 | WWWForm form = new WWWForm(); 264 | form.AddBinaryData("audio", data); 265 | 266 | using (UnityWebRequest request = UnityWebRequest.Post(url, form)) 267 | { 268 | request.SetRequestHeader("Content-Type", "audio/pcm;rate=16000"); 269 | yield return request.SendWebRequest(); 270 | if (request.result == UnityWebRequest.Result.Success) 271 | { 272 | JsonData jsonData = JsonToolkit.StringToJson(request.downloadHandler.text); 273 | string no = jsonData != null ? jsonData.GetValueInSafe("err_no", "") : ""; 274 | string msg = jsonData != null ? jsonData.GetValueInSafe("err_msg", "") : ""; 275 | 276 | if (no == "0") 277 | { 278 | string text = null; 279 | if (jsonData.ContainsKey("result") && jsonData["result"].Count > 0 && jsonData["result"][0] != null) 280 | { 281 | text = jsonData["result"][0].ToString(); 282 | } 283 | handler?.Invoke(text); 284 | } 285 | else 286 | { 287 | Log.Error("语音识别失败:" + msg); 288 | 289 | failHandler?.Invoke(); 290 | } 291 | } 292 | else 293 | { 294 | Log.Error("语音识别失败:" + request.responseCode + " " + request.error); 295 | 296 | failHandler?.Invoke(); 297 | } 298 | } 299 | } 300 | } 301 | 302 | /// 303 | /// 发音人 304 | /// 305 | public enum Speaker 306 | { 307 | /// 308 | /// 普通女声 309 | /// 310 | [Remark("普通女声")] 311 | Woman = 0, 312 | /// 313 | /// 普通男声 314 | /// 315 | [Remark("普通男声")] 316 | Man = 1, 317 | /// 318 | /// 度丫丫 319 | /// 320 | [Remark("度丫丫")] 321 | DuYaYa = 4, 322 | /// 323 | /// 度逍遥 324 | /// 325 | [Remark("度逍遥")] 326 | DuXiaoYao = 3, 327 | /// 328 | /// 度小娇 329 | /// 330 | [Remark("【精品发音人】度小娇")] 331 | DuXiaoJiao = 5, 332 | /// 333 | /// 度博文 334 | /// 335 | [Remark("【精品发音人】度博文")] 336 | DuBoWen = 106, 337 | /// 338 | /// 度小童 339 | /// 340 | [Remark("【精品发音人】度小童")] 341 | DuXiaoTong = 110, 342 | /// 343 | /// 度小萌 344 | /// 345 | [Remark("【精品发音人】度小萌")] 346 | DuXiaoMeng = 111, 347 | /// 348 | /// 度米朵 349 | /// 350 | [Remark("【精品发音人】度米朵")] 351 | DuMiDuo = 103, 352 | } 353 | 354 | /// 355 | /// 合成的音频格式 356 | /// 357 | public enum SynthesisType 358 | { 359 | /// 360 | /// MP3格式 361 | /// 362 | MP3 = 3, 363 | /// 364 | /// WAV格式 365 | /// 366 | WAV = 6, 367 | } 368 | } -------------------------------------------------------------------------------- /RunTime/Speech/Speecher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82855b148570260449d72574816953c5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /RunTime/Speech/SynthesisRule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HT.Framework.AI 4 | { 5 | /// 6 | /// 语音合成规则 7 | /// 8 | public sealed class SynthesisRule 9 | { 10 | private Dictionary _customTones = new Dictionary(); 11 | 12 | /// 13 | /// 添加自定义音标 14 | /// 15 | /// 文字 16 | /// 拼音 17 | /// 音标 18 | /// 拼音对应的文字索引 19 | public void AddCustomTone(string word, string pinyin, int tone, int index = 0) 20 | { 21 | if (_customTones.ContainsKey(word)) 22 | { 23 | CustomTone ct = _customTones[word]; 24 | ct.Word = word; 25 | ct.Pinyin = pinyin; 26 | ct.Tone = tone; 27 | ct.Index = index; 28 | } 29 | else 30 | { 31 | _customTones.Add(word, Main.m_ReferencePool.Spawn().Fill(word, pinyin, tone, index)); 32 | } 33 | } 34 | /// 35 | /// 移除自定义音标 36 | /// 37 | /// 文字 38 | public void RemoveCustomTone(string word) 39 | { 40 | if (_customTones.ContainsKey(word)) 41 | { 42 | Main.m_ReferencePool.Despawn(_customTones[word]); 43 | _customTones.Remove(word); 44 | } 45 | } 46 | /// 47 | /// 清空所有自定义音标 48 | /// 49 | public void ClearCustomTone() 50 | { 51 | foreach (KeyValuePair tone in _customTones) 52 | { 53 | Main.m_ReferencePool.Despawn(tone.Value); 54 | } 55 | _customTones.Clear(); 56 | } 57 | /// 58 | /// 应用规则 59 | /// 60 | /// 待合成文本 61 | /// 应用规则后的待合成文本 62 | public string Apply(string synthesisText) 63 | { 64 | foreach (KeyValuePair customTone in _customTones) 65 | { 66 | synthesisText = synthesisText.Replace(customTone.Key, customTone.Value.ToString()); 67 | } 68 | return synthesisText; 69 | } 70 | 71 | /// 72 | /// 自定义音标 73 | /// 74 | public sealed class CustomTone : IReference 75 | { 76 | /// 77 | /// 文字 78 | /// 79 | public string Word; 80 | /// 81 | /// 汉语拼音 82 | /// 83 | public string Pinyin; 84 | /// 85 | /// 音调,对应1、2、3、4声 86 | /// 87 | public int Tone; 88 | /// 89 | /// 拼音对应的文字索引(当文字为词语时) 90 | /// 91 | public int Index; 92 | 93 | public CustomTone() 94 | { 95 | } 96 | 97 | public CustomTone Fill(string word, string pinyin, int tone, int index) 98 | { 99 | Word = word; 100 | Pinyin = pinyin; 101 | Tone = tone; 102 | Index = index; 103 | return this; 104 | } 105 | 106 | public void Reset() 107 | { 108 | Word = ""; 109 | Pinyin = ""; 110 | Tone = 1; 111 | Index = 0; 112 | } 113 | 114 | public override string ToString() 115 | { 116 | if (Index < 0 || Index >= Word.Length) 117 | { 118 | Index = 0; 119 | } 120 | return Word.Insert(Index + 1, "(" + Pinyin + Tone.ToString() + ")"); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /RunTime/Speech/SynthesisRule.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d154a1c9f7cd8144fb67d3cf1e8b681f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | --------------------------------------------------------------------------------