├── 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 |
--------------------------------------------------------------------------------