├── .gitignore
├── .idea
└── .idea.CommandConsole
│ └── .idea
│ ├── .gitignore
│ ├── encodings.xml
│ ├── indexLayout.xml
│ └── vcs.xml
├── .vscode
└── settings.json
├── Assets
├── CommandLine.meta
├── CommandLine
│ ├── Assets.meta
│ ├── Assets
│ │ ├── FIXEDSYS-EXCELSIOR-301.TTF
│ │ ├── FIXEDSYS-EXCELSIOR-301.TTF.meta
│ │ ├── terminal-default-theme @v0.12.prefab
│ │ └── terminal-default-theme @v0.12.prefab.meta
│ ├── Scripts.meta
│ └── Scripts
│ │ ├── Console.meta
│ │ ├── Console
│ │ ├── Attributes.cs
│ │ ├── Attributes.cs.meta
│ │ ├── CommandSystem.cs
│ │ ├── CommandSystem.cs.meta
│ │ ├── ConsoleSystem.cs
│ │ ├── ConsoleSystem.cs.meta
│ │ ├── Interfaces.cs
│ │ ├── Interfaces.cs.meta
│ │ ├── Utils.cs
│ │ └── Utils.cs.meta
│ │ ├── Core.meta
│ │ ├── Core
│ │ ├── Exception.cs
│ │ ├── Exception.cs.meta
│ │ ├── Lexer.cs
│ │ ├── Lexer.cs.meta
│ │ ├── SuggestionQuery.cs
│ │ ├── SuggestionQuery.cs.meta
│ │ ├── SyntaxAnalyzer.cs
│ │ ├── SyntaxAnalyzer.cs.meta
│ │ ├── Utils.cs
│ │ ├── Utils.cs.meta
│ │ ├── VirtualMachine.cs
│ │ └── VirtualMachine.cs.meta
│ │ ├── Helper.meta
│ │ ├── Helper
│ │ ├── DebugHelper.cs
│ │ └── DebugHelper.cs.meta
│ │ ├── UnityImpl.meta
│ │ └── UnityImpl
│ │ ├── GameConsole.cs
│ │ ├── GameConsole.cs.meta
│ │ ├── GameConsoleAlternativeOptionsPanel.cs
│ │ ├── GameConsoleAlternativeOptionsPanel.cs.meta
│ │ ├── GameConsoleHeader.cs
│ │ ├── GameConsoleHeader.cs.meta
│ │ ├── GameConsoleInput.cs
│ │ ├── GameConsoleInput.cs.meta
│ │ ├── GameConsoleRenderer.cs
│ │ └── GameConsoleRenderer.cs.meta
├── Example.meta
└── Example
│ ├── CommandExample.cs
│ ├── CommandExample.cs.meta
│ ├── ConsoleExampleScene.unity
│ ├── ConsoleExampleScene.unity.meta
│ ├── Example.cs
│ └── Example.cs.meta
├── LICENSE
├── Packages
├── manifest.json
└── packages-lock.json
├── ProjectSettings
├── AudioManager.asset
├── AutoStreamingSettings.asset
├── ClusterInputManager.asset
├── DynamicsManager.asset
├── EditorBuildSettings.asset
├── EditorSettings.asset
├── GraphicsSettings.asset
├── InputManager.asset
├── MemorySettings.asset
├── NavMeshAreas.asset
├── PackageManagerSettings.asset
├── Packages
│ └── com.unity.testtools.codecoverage
│ │ └── Settings.json
├── Physics2DSettings.asset
├── PresetManager.asset
├── ProjectSettings.asset
├── ProjectVersion.txt
├── QualitySettings.asset
├── SceneTemplateSettings.json
├── ShaderGraphSettings.asset
├── TagManager.asset
├── TimeManager.asset
├── URPProjectSettings.asset
├── UnityConnectSettings.asset
├── VFXManager.asset
├── VersionControlSettings.asset
├── XRSettings.asset
└── boot.config
├── README-ch.md
├── README.md
├── Res
├── Untitled (1).png
├── Untitled.png
├── screen-shot.png
├── usage-part-1.png
├── usage.mp4
└── 屏幕截图 2024-01-12 173800.png
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Uu]ser[Ss]ettings/
12 |
13 | # MemoryCaptures can get excessive in size.
14 | # They also could contain extremely sensitive data
15 | /[Mm]emoryCaptures/
16 |
17 | # Recordings can get excessive in size
18 | /[Rr]ecordings/
19 |
20 | # Uncomment this line if you wish to ignore the asset store tools plugin
21 | # /[Aa]ssets/AssetStoreTools*
22 |
23 | # Autogenerated Jetbrains Rider plugin
24 | /[Aa]ssets/Plugins/Editor/JetBrains*
25 |
26 | # Visual Studio cache directory
27 | .vs/
28 |
29 | # Gradle cache directory
30 | .gradle/
31 |
32 | # Autogenerated VS/MD/Consulo solution and project files
33 | ExportedObj/
34 | .consulo/
35 | *.csproj
36 | *.unityproj
37 | *.sln
38 | *.suo
39 | *.tmp
40 | *.user
41 | *.userprefs
42 | *.pidb
43 | *.booproj
44 | *.svd
45 | *.pdb
46 | *.mdb
47 | *.opendb
48 | *.VC.db
49 |
50 | # Unity3D generated meta files
51 | *.pidb.meta
52 | *.pdb.meta
53 | *.mdb.meta
54 |
55 | # Unity3D generated file on crash reports
56 | sysinfo.txt
57 |
58 | # Builds
59 | *.apk
60 | *.aab
61 | *.unitypackage
62 | *.app
63 |
64 | # Crashlytics generated file
65 | crashlytics-build.properties
66 |
67 | # Packed Addressables
68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
69 |
70 | # Temporary auto-generated Android Assets
71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta
72 | /[Aa]ssets/[Ss]treamingAssets/aa/*
73 |
--------------------------------------------------------------------------------
/.idea/.idea.CommandConsole/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /contentModel.xml
6 | /.idea.CommandConsole.iml
7 | /projectSettingsUpdater.xml
8 | /modules.xml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/.idea/.idea.CommandConsole/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/.idea.CommandConsole/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/.idea.CommandConsole/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude":
3 | {
4 | "**/.DS_Store":true,
5 | "**/.git":true,
6 | "**/.gitmodules":true,
7 | "**/*.booproj":true,
8 | "**/*.pidb":true,
9 | "**/*.suo":true,
10 | "**/*.user":true,
11 | "**/*.userprefs":true,
12 | "**/*.unityproj":true,
13 | "**/*.dll":true,
14 | "**/*.exe":true,
15 | "**/*.pdf":true,
16 | "**/*.mid":true,
17 | "**/*.midi":true,
18 | "**/*.wav":true,
19 | "**/*.gif":true,
20 | "**/*.ico":true,
21 | "**/*.jpg":true,
22 | "**/*.jpeg":true,
23 | "**/*.png":true,
24 | "**/*.psd":true,
25 | "**/*.tga":true,
26 | "**/*.tif":true,
27 | "**/*.tiff":true,
28 | "**/*.3ds":true,
29 | "**/*.3DS":true,
30 | "**/*.fbx":true,
31 | "**/*.FBX":true,
32 | "**/*.lxo":true,
33 | "**/*.LXO":true,
34 | "**/*.ma":true,
35 | "**/*.MA":true,
36 | "**/*.obj":true,
37 | "**/*.OBJ":true,
38 | "**/*.asset":true,
39 | "**/*.cubemap":true,
40 | "**/*.flare":true,
41 | "**/*.mat":true,
42 | "**/*.meta":true,
43 | "**/*.prefab":true,
44 | "**/*.unity":true,
45 | "build/":true,
46 | "Build/":true,
47 | "Library/":true,
48 | "library/":true,
49 | "obj/":true,
50 | "Obj/":true,
51 | "ProjectSettings/":true,
52 | "temp/":true,
53 | "Temp/":true
54 | }
55 | }
--------------------------------------------------------------------------------
/Assets/CommandLine.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 135230210c8dde14382abbefd36f2b80
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Assets.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0015d6c4e186ce6439de8d6f33078122
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Assets/FIXEDSYS-EXCELSIOR-301.TTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Assets/CommandLine/Assets/FIXEDSYS-EXCELSIOR-301.TTF
--------------------------------------------------------------------------------
/Assets/CommandLine/Assets/FIXEDSYS-EXCELSIOR-301.TTF.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7c17a58733c11a643a27f5f4f0a36359
3 | TrueTypeFontImporter:
4 | externalObjects: {}
5 | serializedVersion: 4
6 | fontSize: 16
7 | forceTextureCase: -2
8 | characterSpacing: 0
9 | characterPadding: 1
10 | includeFontData: 1
11 | fontNames:
12 | - Fixedsys Excelsior 3.01
13 | fallbackFontReferences: []
14 | customCharacters:
15 | fontRenderingMode: 0
16 | ascentCalculationMode: 1
17 | useLegacyBoundsCalculation: 0
18 | shouldRoundAdvanceValue: 1
19 | userData:
20 | assetBundleName:
21 | assetBundleVariant:
22 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Assets/terminal-default-theme @v0.12.prefab.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 27240d217acc77844a8e410e5841b762
3 | PrefabImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0adedea0fa4d0564398db8bbac09d1f1
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a6d813add122f92438773e75740b8d59
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/Attributes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RedSaw.CommandLineInterface{
4 |
5 | ///
6 | /// command attribute
7 | /// any static method has attach this attribute would be treated
8 | /// as a command method, and it woule be collected by command system automatically
9 | ///
10 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
11 | public class CommandAttribute : Attribute{
12 |
13 | /// name of command
14 | public string Name { get; private set; }
15 |
16 | /// description of command
17 | public string Desc { get; set; }
18 |
19 | /// tag of command, for you can query commands by tag, or constraint commands by tag
20 | public string Tag { get; set; }
21 |
22 | public CommandAttribute(string name){
23 |
24 | this.Name = name;
25 | this.Tag = null;
26 | this.Desc = "command has no description";
27 | }
28 | public CommandAttribute(){
29 |
30 | this.Name = null;
31 | this.Tag = null;
32 | this.Desc = "command has no description";
33 | }
34 | }
35 |
36 | ///
37 | /// a static method which defined this attribute would be treated as
38 | /// parameter parser.
39 | ///
40 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
41 | public class CommandValueParserAttribute : Attribute{
42 |
43 | public readonly Type type;
44 | public string Alias { get; set; }
45 |
46 | public CommandValueParserAttribute(Type type){
47 |
48 | this.type = type;
49 | this.Alias = null;
50 | }
51 | }
52 |
53 | ///
54 | /// any static property or field marked by CommandPropertyAttribute could be visit directly by @ in command line
55 | ///
56 | [AttributeUsage(AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
57 | public class CommandPropertyAttribute : Attribute{
58 |
59 | public readonly string Name;
60 | public readonly string Tag;
61 | public readonly string Desc;
62 |
63 | public CommandPropertyAttribute(string name){
64 |
65 | this.Name = name;
66 | this.Desc = "property has no description";
67 | this.Tag = null;
68 | }
69 | public CommandPropertyAttribute(){
70 |
71 | this.Name = null;
72 | this.Desc = "property has no description";
73 | this.Tag = null;
74 | }
75 | }
76 |
77 |
78 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/Attributes.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 676d2bd05e7b75a40a6d7afef6396e51
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/CommandSystem.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3009c9077360c214385554b3a9f3e2f3
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/ConsoleSystem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 |
5 | namespace RedSaw.CommandLineInterface{
6 |
7 |
8 |
9 | /// use to record command input history
10 | class InputHistory{
11 |
12 | readonly List history;
13 | readonly int capacity;
14 | int lastIndex;
15 |
16 | public InputHistory(int capacity){
17 |
18 | this.lastIndex = 0;
19 | this.capacity = capacity;
20 | this.history = new List();
21 | }
22 | /// record input string
23 | public void Record(string input){
24 |
25 | if(history.Count == capacity){
26 | history.RemoveAt(0);
27 | }
28 | history.Add(input);
29 | lastIndex = history.Count - 1;
30 | }
31 |
32 | /// get last input string
33 | public string Last{
34 | get{
35 | if(history.Count == 0)return string.Empty;
36 | if(lastIndex < 0 || lastIndex >= history.Count)lastIndex = history.Count - 1;
37 | return history[lastIndex --];
38 | }
39 | }
40 |
41 | /// get next input string
42 | public string Next{
43 | get{
44 | if(history.Count == 0)return string.Empty;
45 | if(lastIndex >= history.Count || lastIndex < 0)lastIndex = 0;
46 | return history[lastIndex ++];
47 | }
48 | }
49 | }
50 |
51 |
52 | /// command selector
53 | class LinearSelector{
54 |
55 | public event Action OnSelectionChanged;
56 | readonly List optionsBuffer;
57 | int currentIndex;
58 |
59 | int TotalCount{
60 | get{
61 | if(optionsBuffer == null)return 0;
62 | return optionsBuffer.Count;
63 | }
64 | }
65 |
66 | ///
67 | /// the current selection of the alternative options
68 | /// if value is -1, it means there's no alternative options choosed
69 | ///
70 | public int SelectionIndex => currentIndex;
71 |
72 | public LinearSelector(){
73 |
74 | this.optionsBuffer = new();
75 | this.currentIndex = -1;
76 | }
77 |
78 | public void LoadOptions(List options){
79 |
80 | this.optionsBuffer.Clear();
81 | this.optionsBuffer.AddRange(options);
82 | this.currentIndex = -1;
83 | }
84 |
85 | public bool GetCurrentSelection(out string selection){
86 |
87 | selection = string.Empty;
88 | if(currentIndex == -1)return false;
89 | selection = optionsBuffer[currentIndex];
90 | return true;
91 | }
92 |
93 | /// move to next alternative option
94 | public void MoveNext(){
95 |
96 | if(TotalCount == 0)return;
97 | if(currentIndex == -1){
98 | currentIndex = 0;
99 | OnSelectionChanged?.Invoke(currentIndex);
100 | return;
101 | }
102 | currentIndex = currentIndex < TotalCount - 1 ? currentIndex + 1 : 0;
103 | OnSelectionChanged?.Invoke(currentIndex);
104 | }
105 |
106 | /// move to last alternative option
107 | public void MoveLast(){
108 |
109 | if(TotalCount == 0)return;
110 | if(currentIndex == -1){
111 | currentIndex = TotalCount - 1;
112 | OnSelectionChanged?.Invoke(currentIndex);
113 | return;
114 | }
115 | currentIndex = currentIndex > 0 ? currentIndex - 1 : TotalCount - 1;
116 | OnSelectionChanged?.Invoke(currentIndex);
117 | }
118 | }
119 |
120 |
121 | public class LogManager where T : Enum{
122 | /* about output logs */
123 |
124 | /// console log
125 | public readonly struct Log{
126 |
127 | public static Log Empty => new(string.Empty, default);
128 |
129 | public readonly string message;
130 | public readonly string color;
131 | public readonly T logType;
132 |
133 | public readonly bool HasColor =>
134 | color != null && color.Length > 0;
135 |
136 | public Log(string message, string color, T logType = default){
137 |
138 | this.message = message;
139 | this.color = color;
140 | this.logType = logType;
141 | }
142 |
143 | public Log(string message, T logType = default){
144 |
145 | this.message = message;
146 | this.color = null;
147 | this.logType = logType;
148 | }
149 | public override string ToString(){
150 | return color != null ? $"{message}" : message;
151 | }
152 | }
153 |
154 | ///
155 | /// this event would triggered while receive messages
156 | ///
157 | public event Action OnReceivedMessage;
158 | readonly int logCapacity;
159 | readonly bool hasLogCapacity;
160 |
161 | readonly List logs = new();
162 |
163 | ///
164 | /// initialize log manager
165 | ///
166 | /// the capacity of logs, -1 as infinite
167 | public LogManager(int logCapacity = -1){
168 |
169 | this.logCapacity = logCapacity;
170 | this.hasLogCapacity = logCapacity > 0;
171 | }
172 |
173 | ///
174 | /// output a debug log on console, if info is null, then the tag would work on it
175 | ///
176 | public void Output(string info, T type = default){
177 |
178 | if( info == null || info.Length == 0 ){
179 | Output(Log.Empty);
180 | return;
181 | }
182 | Output(new(info, type));
183 | }
184 | ///
185 | /// output a debug log on console, if info is null, then the tag would work on it,
186 | /// the color should in format of '#ffffff';
187 | ///
188 | public void Output(string info, string color, T type = default){
189 |
190 | if( color == null || color.Length == 0 ){
191 | Output(info, type);
192 | return;
193 | }
194 | if( info == null || info.Length == 0 ){
195 | Output(Log.Empty);
196 | return;
197 | }
198 | Output(new(info, color, type));
199 | }
200 |
201 | void Output(Log log){
202 | /* trigger event */
203 |
204 | logs.Add(log);
205 | if( hasLogCapacity && logs.Count >= logCapacity ){
206 | logs.RemoveAt(0);
207 | }
208 | OnReceivedMessage?.Invoke(log);
209 | }
210 |
211 | ///
212 | /// get total logs of current console
213 | ///
214 | public IEnumerable AllLogs => logs;
215 |
216 | ///
217 | /// get last logs
218 | ///
219 | public IEnumerable GetLastLogs( int count ){
220 |
221 | if( count == 0 )yield break;
222 | count = Math.Min(count, logs.Count);
223 | int L = logs.Count - count;
224 | int R = L + count;
225 | for( int i = L; i < R; i ++ ){
226 | yield return logs[i];
227 | }
228 | }
229 |
230 | ///
231 | /// get last logs with target tag
232 | ///
233 | public IEnumerable GetLastLogs( int count, T type = default ){
234 |
235 | if( count == 0 )yield break;
236 | foreach(var log in logs){
237 | if( log.logType.Equals(type) ){
238 | yield return log;
239 | }
240 | if( -- count <= 0 )break;
241 | }
242 | }
243 | }
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | /// command console
262 | public class ConsoleController where TLog : Enum{
263 |
264 | readonly IConsoleRenderer renderer;
265 | readonly IConsoleInput userInput;
266 | readonly CommandSystem commandSystem;
267 | readonly LogManager logManager;
268 |
269 | public event Action OnFocusOut;
270 | public event Action OnFocus;
271 |
272 | readonly int alternativeCommandCount;
273 | readonly bool shouldRecordFailedCommand;
274 | readonly bool outputWithTime;
275 | readonly bool outputStackTraceOfCommandExecution;
276 |
277 | readonly InputHistory inputHistory;
278 | readonly LinearSelector selector;
279 | bool ignoreTextChanged;
280 |
281 | /// initialize console
282 | /// the renderer of console
283 | /// the input of console
284 |
285 | #region About Command System
286 | /// the capacity of command query cache, 20 as default
287 | ///
288 | /// should output stack trace of command excution, it maybe too long..
289 | /// only use for debug
290 | #endregion
291 |
292 | /// the capacity of input history
293 | /// the count of alternative command options
294 | /// should record failed command input
295 | /// the capacity of output panel
296 | /// should output with time information of [HH:mm:ss]
297 |
298 | ///
299 | public ConsoleController(
300 | IConsoleRenderer renderer,
301 | IConsoleInput userInput,
302 |
303 | int logCapacity = -1,
304 | int inputHistoryCapacity = 20,
305 | int commandQueryCacheCapacity = 20,
306 | int alternativeCommandCount = 8,
307 | bool shouldRecordFailedCommand = true,
308 | bool outputWithTime = true,
309 | bool outputStackTraceOfCommandExecution = true
310 | ){
311 | // about renderer
312 | this.renderer = renderer;
313 | renderer.BindOnSubmit(OnSubmit);
314 | renderer.BindOnTextChanged(OnTextChanged);
315 |
316 | // about user input
317 | this.userInput = userInput;
318 |
319 | // about command system
320 | commandSystem = new CommandSystem(
321 | commandQueryCacheCapacity:commandQueryCacheCapacity
322 | );
323 |
324 | logManager = new LogManager(logCapacity);
325 | logManager.OnReceivedMessage += log => {
326 |
327 | if(log.HasColor){
328 | renderer.Output(log.message, log.color);
329 | return;
330 | }
331 | renderer.Output(log.message);
332 | };
333 |
334 | // other things - input history
335 | inputHistory = new InputHistory(Math.Max(inputHistoryCapacity, 2));
336 |
337 | // other things - alternative options
338 | selector = new LinearSelector();
339 | this.selector.OnSelectionChanged += idx => {
340 | renderer.AlternativeOptionsIndex = idx;
341 | renderer.MoveCursorToEnd();
342 | };
343 |
344 | this.alternativeCommandCount = Math.Max(alternativeCommandCount, 1);
345 | this.shouldRecordFailedCommand = shouldRecordFailedCommand;
346 | this.outputWithTime = outputWithTime;
347 | this.outputStackTraceOfCommandExecution = outputStackTraceOfCommandExecution;
348 | }
349 | public void Update(){
350 |
351 | if(userInput.ShowOrHide){
352 | renderer.IsVisible = !renderer.IsVisible;
353 | }
354 | if(!renderer.IsVisible)return;
355 |
356 | if(renderer.IsInputFieldFocus){
357 |
358 | // quit focus
359 | if(userInput.QuitFocus){
360 |
361 | renderer.QuitFocus();
362 | OnFocusOut?.Invoke();
363 | }
364 | else if(userInput.MoveDown){
365 |
366 | if(renderer.IsAlternativeOptionsActive){
367 | selector.MoveNext();
368 | }else{
369 | ignoreTextChanged = true;
370 | renderer.InputText = inputHistory.Next;
371 | renderer.MoveCursorToEnd();
372 | }
373 | }else if(userInput.MoveUp){
374 |
375 | if(renderer.IsAlternativeOptionsActive){
376 | selector.MoveLast();
377 | }else{
378 | ignoreTextChanged = true;
379 | renderer.InputText = inputHistory.Last;
380 | renderer.MoveCursorToEnd();
381 | }
382 | }
383 | return;
384 | }
385 | if(userInput.Focus){
386 | renderer.Focus();
387 | renderer.ActivateInput();
388 | OnFocus?.Invoke();
389 | }
390 | }
391 |
392 | string AddTimeInfo(string msg){
393 | if( outputWithTime ){
394 | return CLIUtils.TimeInfo + msg;
395 | }
396 | return msg;
397 | }
398 | string[] AddTimeInfo(string[] msgs){
399 |
400 | if( outputWithTime ){
401 | var timeInfo = CLIUtils.TimeInfo;
402 | for(int i = 0; i < msgs.Length; i ++){
403 | msgs[i] = timeInfo + msgs[i];
404 | }
405 | return msgs;
406 | }
407 | return msgs;
408 | }
409 |
410 | /// Output message on console
411 | public void Output(string msg) =>
412 | logManager.Output(AddTimeInfo(msg));
413 |
414 | /// Output message on console with given color
415 | public void Output(string msg, string color = "#ffffff") => logManager.Output(AddTimeInfo(msg), color);
416 |
417 | /// Output messages on console
418 | public void Output(string[] msgs, string color = "#ffffff"){
419 |
420 | foreach(var m in AddTimeInfo(msgs))
421 | logManager.Output(m, color);
422 | }
423 |
424 | /// Output messages on console
425 | public void Output(string[] msgs){
426 |
427 | foreach(var m in AddTimeInfo(msgs))
428 | logManager.Output(m);
429 | }
430 |
431 | /// clear current console
432 | public void ClearOutputPanel() => renderer.Clear();
433 |
434 | public void OnTextChanged(string text){
435 | /* when input new string, should query commands from commandSystem */
436 |
437 | if(ignoreTextChanged){
438 | ignoreTextChanged = false;
439 | return;
440 | }
441 |
442 | string queryText = renderer.InputTextToCursor;
443 | var result = commandSystem.GetCurrentSuggestions(queryText, alternativeCommandCount, CLIUtils.FindSimilarity);
444 |
445 | if(result.Length == 0){
446 | if(renderer.IsAlternativeOptionsActive){
447 | renderer.IsAlternativeOptionsActive = false;
448 | }
449 | return;
450 | }
451 |
452 | /* show options panel */
453 | var optionsBuffer = new List();
454 | var list = new List();
455 | foreach(var elem in result){
456 | optionsBuffer.Add(elem.primary);
457 | list.Add(elem.ToString());
458 | }
459 | if(!renderer.IsAlternativeOptionsActive){
460 | renderer.IsAlternativeOptionsActive = true;
461 | }
462 | selector.LoadOptions(optionsBuffer);
463 | renderer.AlternativeOptions = list;
464 | renderer.AlternativeOptionsIndex = selector.SelectionIndex;
465 | }
466 |
467 | /// input string into current console
468 | public void OnSubmit(string text){
469 |
470 | if(renderer.IsAlternativeOptionsActive && selector.GetCurrentSelection(out string selection)){
471 | renderer.InputTextToCursor = commandSystem.TakeSuggestion(renderer.InputTextToCursor, selection);
472 | renderer.IsAlternativeOptionsActive = false;
473 | renderer.ActivateInput();
474 | renderer.SetInputCursorPosition(renderer.InputText.Length);
475 | return;
476 | }
477 |
478 | if(renderer.IsAlternativeOptionsActive)
479 | renderer.IsAlternativeOptionsActive = false;
480 |
481 | Output(text);
482 | if(text.Length > 0){
483 | var ex = commandSystem.Execute(text, out object executeResult);
484 | if( ex == null ){
485 | inputHistory.Record(text);
486 | OutputResult(executeResult);
487 | }else{
488 | if(shouldRecordFailedCommand)inputHistory.Record(text);
489 | Output(ex.Message, "#f27a5f");
490 | if( outputStackTraceOfCommandExecution )
491 | Output(ex.StackTrace, "#f27a5f");
492 | }
493 | renderer.InputText = string.Empty;
494 | }
495 | renderer.MoveScrollBarToEnd();
496 | renderer.ActivateInput();
497 | }
498 |
499 | void OutputResult(object instance){
500 |
501 | if( instance == null )return;
502 | var type = instance.GetType();
503 | var attr = type.GetCustomAttribute();
504 | if( attr == null ){
505 | Output(instance.ToString());
506 | return;
507 | }
508 | var debugInfos = DebugHelper.GetDebugInfos(instance);
509 | if (debugInfos.Length == 0)return;
510 | Output($"---------- {instance} start ----------");
511 | foreach(var (message, color) in debugInfos){
512 | Output(message, color);
513 | }
514 | Output($"---------- {instance} end ----------");
515 | }
516 | }
517 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/ConsoleSystem.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 031211d5272c2a74290c0e943108bbb4
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/Interfaces.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | /*
5 | 使用接口描述上层逻辑的功能,目的是为了使得该模块能够保持较低的成本移植到其他引擎或者平台
6 | use interface between the real implemetations to decoupling the whole system's components
7 | */
8 |
9 | namespace RedSaw.CommandLineInterface{
10 |
11 | ///
12 | /// define the user input of CommandConsole, cause Unity has two different
13 | /// input solutions.
14 | ///
15 | public interface IConsoleInput{
16 |
17 | /// push up to get history input or change selection of alternative options
18 | bool MoveUp{ get; }
19 |
20 | /// push down to get history input or change selection of alternative options
21 | bool MoveDown{ get; }
22 |
23 | /// push ctrl+c or other keys to focus on console
24 | bool Focus{ get; }
25 |
26 | /// push esc or other keys to quit focus on console
27 | bool QuitFocus{ get; }
28 |
29 | /// push F1 or other keys to show or hide console
30 | bool ShowOrHide{ get; }
31 | }
32 |
33 | /// define the interface of console renderer
34 | public interface IConsoleRenderer{
35 |
36 | ///
37 | /// the console renderer would only show the last logs on console
38 | /// for unity text cannot render so much content, if you use another
39 | /// implementation, you can set it as you wish.
40 | ///
41 | int OutputPanelCapacity { get; }
42 |
43 | bool IsVisible{ get; set; }
44 |
45 | #region InputField
46 |
47 | /// check if input field is focused
48 | bool IsInputFieldFocus{ get; }
49 |
50 | /// input text
51 | string InputText{ get; set; }
52 |
53 | /// input text to selection
54 | string InputTextToCursor{ get; set; }
55 |
56 | /// focus on input field
57 | void Focus();
58 |
59 | /// activate input field
60 | void ActivateInput();
61 |
62 | /// quit focus on input field
63 | void QuitFocus();
64 |
65 | /// add listener on input field
66 | void BindOnTextChanged(Action callback);
67 |
68 | /// add listener on input field
69 | void BindOnSubmit(Action callback);
70 |
71 | /// set cursor position
72 | void SetInputCursorPosition(int pos);
73 |
74 | /// make scrollbar position at last position
75 | void MoveScrollBarToEnd();
76 |
77 | void MoveCursorToEnd(){
78 | if(InputText != null)SetInputCursorPosition(InputText.Length);
79 | }
80 |
81 | #endregion
82 |
83 | #region OutputPanel
84 |
85 | /// output message
86 | void Output(string msg);
87 |
88 | /// output message
89 | void Output(string[] msg);
90 |
91 | /// output message with given color
92 | void Output(string msg, string color = "#ffffff");
93 |
94 | /// output message
95 | void Output(string[] msg, string color = "#ffffff");
96 |
97 | /// clear current console
98 | void Clear();
99 |
100 | #endregion
101 |
102 | #region AlternativeOptions
103 |
104 | /// is current alternative panel showing now
105 | bool IsAlternativeOptionsActive{ get; set; }
106 |
107 | ///
108 | /// render current alternative options
109 | ///
110 | List AlternativeOptions{ set; }
111 |
112 | ///
113 | /// highlight this alternative options index
114 | ///
115 | int AlternativeOptionsIndex{ set; }
116 | #endregion
117 | }
118 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/Interfaces.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 51027716d5725d24f89f30909e369711
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RedSaw.CommandLineInterface{
4 |
5 | public static class CLIUtils{
6 |
7 | static int GetEditDistance(string X, string Y)
8 | {
9 | int m = X.Length;
10 | int n = Y.Length;
11 |
12 | int[][] T = new int[m + 1][];
13 | for (int i = 0; i < m + 1; ++i) {
14 | T[i] = new int[n + 1];
15 | }
16 |
17 | for (int i = 1; i <= m; i++) {
18 | T[i][0] = i;
19 | }
20 | for (int j = 1; j <= n; j++) {
21 | T[0][j] = j;
22 | }
23 |
24 | int cost;
25 | for (int i = 1; i <= m; i++) {
26 | for (int j = 1; j <= n; j++) {
27 | cost = X[i - 1] == Y[j - 1] ? 0: 1;
28 | T[i][j] = Math.Min(Math.Min(T[i - 1][j] + 1, T[i][j - 1] + 1),
29 | T[i - 1][j - 1] + cost);
30 | }
31 | }
32 |
33 | return T[m][n];
34 | }
35 |
36 | /// find similarity of two strings
37 | /// string x
38 | /// string y
39 | public static float FindSimilarity(string x, string y) {
40 | if (x == null || y == null) return 0;
41 |
42 | float maxLength = Math.Max(x.Length, y.Length);
43 | if (maxLength > 0) {
44 | // optionally ignore case if needed
45 | return (maxLength - GetEditDistance(x, y)) / maxLength;
46 | }
47 | return 1.0f;
48 | }
49 |
50 | /// get time information of [HH:mm:ss]
51 | public static string TimeInfo{
52 | get{
53 | var time = DateTime.Now;
54 | return $"[{PadZero(time.Hour)}:{PadZero(time.Minute)}:{PadZero(time.Second)}] ";
55 | }
56 | }
57 | static string PadZero(int value){
58 | return value < 10 ? $"0{value}" : $"{value}";
59 | }
60 | }
61 |
62 |
63 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Console/Utils.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a21e6653311bfe54083cb7f5fbf57228
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 294d31a4eef8f9a4297329d8569f2f01
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/Exception.cs:
--------------------------------------------------------------------------------
1 | namespace RedSaw.CommandLineInterface{
2 |
3 |
4 | ///
5 | /// command system exception
6 | ///
7 | public class CommandSystemException : System.Exception{
8 | public CommandSystemException(string message) : base(message){}
9 | }
10 |
11 | ///
12 | /// command lexer exception
13 | ///
14 | public class CommandLexerException : CommandSystemException{
15 | public CommandLexerException(string message) : base(message){}
16 | }
17 |
18 | ///
19 | /// command syntax exception
20 | ///
21 | public class CommandSyntaxException : CommandSystemException{
22 | public CommandSyntaxException(string message) : base(message){}
23 | }
24 |
25 | ///
26 | /// command execute exception
27 | ///
28 | public class CommandExecuteException : CommandSystemException{
29 | public CommandExecuteException(string message) : base(message){}
30 | }
31 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/Exception.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 20c0df9feca1c794a94666ba9ad7fe8b
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/Lexer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace RedSaw.CommandLineInterface{
5 |
6 | ///
7 | /// token type of lexer
8 | ///
9 | public enum TokenType{
10 |
11 | TK_NONE, // nothing
12 | TK_EOF, // end of file
13 | TK_VAR, // @
14 | TK_INPUT, // input
15 | TK_STRING, // "string"
16 | TK_TRUE, // true
17 | TK_FALSE, // false
18 | TK_NULL, // null
19 | TK_INT, // 123
20 | TK_FLOAT, // 1.3
21 | TK_ASSIGN, // =
22 | TK_DOT, // .
23 | TK_COMMA, // ,
24 | TK_COLON, // :
25 | TK_L_BRACKET, // [
26 | TK_R_BRACKET, // ]
27 | TK_L_PAREN, // (
28 | TK_R_PAREN, // )
29 | }
30 |
31 |
32 | ///
33 | /// lexer token
34 | ///
35 | public readonly struct Token{
36 |
37 | public readonly TokenType type;
38 | public readonly int sourceIdx;
39 | public readonly int startIdx;
40 | public readonly int endIdx;
41 |
42 | public Token(TokenType type, int sourceId, int startIdx, int endIdx){
43 |
44 | this.type = type;
45 | this.sourceIdx = sourceId;
46 | this.startIdx = startIdx;
47 | this.endIdx = endIdx;
48 | }
49 | }
50 |
51 | ///
52 | /// lexer result, contains a group of LexerResult
53 | ///
54 | public readonly struct LexerResult{
55 |
56 | public readonly string source;
57 | public readonly Token[] tokens;
58 | public readonly string[] sourceBuffer;
59 | public readonly Token this[int index] => tokens[index];
60 | public readonly Token LastToken => tokens[^1];
61 |
62 | public LexerResult(string source, Token[] tokens, string[] sourceBuffer){
63 |
64 | this.source = source;
65 | this.tokens = tokens;
66 | this.sourceBuffer = sourceBuffer;
67 | }
68 | public readonly void Debug(Action log){
69 |
70 | foreach(var tk in tokens){
71 | var src = tk.sourceIdx >= 0 ? sourceBuffer[tk.sourceIdx] : string.Empty;
72 | log($"<{tk.type}> {src}");
73 | }
74 | }
75 | public readonly string NearInfo(Token token, int radius = 7){
76 |
77 | int left = Math.Max(0, token.startIdx - radius);
78 | int right = Math.Min(token.endIdx + radius, source.Length - 1);
79 | return source[left..right];
80 | }
81 | public readonly string NearInfo(int LB, int RB){
82 |
83 | int left = Math.Max(0, LB);
84 | int right = Math.Min(RB, source.Length - 1);
85 | return source[left..right];
86 | }
87 | }
88 |
89 | public partial class Lexer{
90 |
91 | ///
92 | /// check if a string value is an id
93 | /// which starts with '_' or letter and contains only '_' or letter or digit
94 | ///
95 | public static bool IsId(string value){
96 |
97 | if(value == null || value.Length == 0)return false;
98 | char head = value[0];
99 | if(head == UNDERLINE || char.IsLetter(head)){
100 | for(int i = 1; i < value.Length; i ++){
101 | char c = value[i];
102 | if(c == UNDERLINE || char.IsLetterOrDigit(c))continue;
103 | }
104 | return true;
105 | }
106 | return false;
107 | }
108 |
109 | // SPECIAL CHARACTERS
110 | public const char EOL = '\n';
111 | public const char EOF = '\0';
112 | public const char TAB = '\t';
113 | public const char CR = '\r';
114 | public const char WHITE_SPACE = ' ';
115 |
116 | // SYMBOLS
117 | public const char VAR = '@';
118 | public const char DOT = '.';
119 | public const char COLON = ':';
120 | public const char COMMA = ',';
121 | public const char ASSIGN = '=';
122 | public const char L_PAREN = '(';
123 | public const char R_PAREN = ')';
124 | public const char L_BRACKET = '[';
125 | public const char R_BRACKET = ']';
126 | public const char SINGLE_QUOTE = '\'';
127 | public const char DOUBLE_QUOTE = '"';
128 | public const char UNDERLINE = '_';
129 | public const char NEGATIVE = '-';
130 | public const char E = 'e';
131 |
132 |
133 | // KEYWORDS
134 | public const string TRUE = "true";
135 | public const string FALSE = "false";
136 | public const string NULL = "null";
137 |
138 | ///
139 | /// use to preprocess the source input, the source input could only contains
140 | /// 'EOF' and 'WHITE_SPACE'
141 | ///
142 | public static char[] WHITE_SPACE_CHARS = { EOL, EOF, TAB, CR };
143 | public static char[] SYMBOLS = { VAR, DOT, COLON, COMMA, ASSIGN, L_PAREN, R_PAREN, L_BRACKET, R_BRACKET, SINGLE_QUOTE, DOUBLE_QUOTE };
144 | public static char[] ID_TERMINATORS = { WHITE_SPACE, EOF, DOT, COLON, COMMA, ASSIGN, L_PAREN, R_PAREN, L_BRACKET, R_BRACKET, SINGLE_QUOTE, DOUBLE_QUOTE };
145 |
146 | public static bool IsWhiteSpace(char c){
147 |
148 | foreach(char cc in WHITE_SPACE_CHARS){
149 | if( c == cc )return true;
150 | }
151 | return char.IsWhiteSpace(c);
152 | }
153 |
154 | bool IsInputTerminator(char c){
155 |
156 | foreach(char t in ID_TERMINATORS)if(c == t)return true;
157 | return false;
158 | }
159 | /// remove all white space in target source input
160 | string PreProcess(string input){
161 |
162 | foreach(char c in WHITE_SPACE_CHARS)input = input.Replace(c, WHITE_SPACE);
163 | return input + EOF;
164 | }
165 | }
166 |
167 | ///
168 | /// lexer main logic
169 | ///
170 | public partial class Lexer{
171 |
172 | int index;
173 | string src;
174 | readonly List tokenValues = new();
175 | readonly List tokens = new();
176 | bool HasMore => index < src.Length;
177 |
178 | /// parse input string to tokens as LexerResult
179 | /// input string
180 | /// LexerResult
181 | public LexerResult Parse(string input){
182 |
183 | // check if target input is null or empty
184 | if(input == null || input.Length == 0)
185 | throw new CommandLexerException("Input string is empty");
186 |
187 | // preprocess to remove all white space, and recheck if target input is empty
188 | var pp = PreProcess(input);
189 | if(pp.Length == 0)
190 | throw new CommandLexerException("Input string is invalid");
191 |
192 | // start to parse
193 | return Walk(pp);
194 | }
195 | LexerResult Walk(string input){
196 |
197 | src = input;
198 |
199 | index = 0;
200 | tokens.Clear();
201 | tokenValues.Clear();
202 |
203 | while(HasMore){
204 | int startIdx = index;
205 | char c = src[ index ++ ];
206 | switch(c){
207 |
208 | // skip white space
209 | case WHITE_SPACE:break;
210 |
211 | // stop at the end of file
212 | case EOF:
213 | tokens.Add(new Token(TokenType.TK_EOF, -1, startIdx, index));
214 | break;
215 |
216 | /*
217 | while start with '@', it would be treated as a property which
218 | could be visit directly by '@' in command line
219 | */
220 | case VAR:
221 | string varName = NextInput(index);
222 | tokens.Add(new Token(TokenType.TK_VAR, tokenValues.Count, startIdx, index));
223 | tokenValues.Add(varName);
224 | break;
225 |
226 | // string of double quote
227 | case DOUBLE_QUOTE:
228 | case SINGLE_QUOTE:
229 | string str = NextString(index, c);
230 | tokens.Add(new Token(TokenType.TK_STRING, tokenValues.Count, startIdx, index));
231 | tokenValues.Add(str);
232 | break;
233 |
234 | // :
235 | case COLON:
236 | tokens.Add(new Token(TokenType.TK_COLON, -1, startIdx, index));
237 | break;
238 |
239 | // ,
240 | case COMMA:
241 | startIdx = index - 1;
242 | tokens.Add(new Token(TokenType.TK_COMMA, -1, startIdx, index));
243 | break;
244 |
245 | // .
246 | case DOT:
247 | tokens.Add(new Token(TokenType.TK_DOT, -1, startIdx, index));
248 | break;
249 |
250 | // =
251 | case ASSIGN:
252 | tokens.Add(new Token(TokenType.TK_ASSIGN, -1, startIdx, index));
253 | break;
254 |
255 | // (
256 | case L_PAREN:
257 | tokens.Add(new Token(TokenType.TK_L_PAREN, -1, startIdx, index));
258 | break;
259 |
260 | // )
261 | case R_PAREN:
262 | tokens.Add(new Token(TokenType.TK_R_PAREN, -1, startIdx, index));
263 | break;
264 |
265 | // [
266 | case L_BRACKET:
267 | tokens.Add(new Token(TokenType.TK_L_BRACKET, -1, startIdx, index));
268 | break;
269 |
270 | // ]
271 | case R_BRACKET:
272 | tokens.Add(new Token(TokenType.TK_R_BRACKET, -1, startIdx, index));
273 | break;
274 |
275 | // input string
276 | default:
277 | // number
278 | if(c == NEGATIVE || char.IsDigit(c)){
279 | string number = NextNumber(index, out bool isFloat);
280 | tokens.Add(new Token(isFloat ? TokenType.TK_FLOAT : TokenType.TK_INT, tokenValues.Count, startIdx, index));
281 | tokenValues.Add(number);
282 | break;
283 | }
284 |
285 | var nextInput = NextInput(index - 1);
286 |
287 | // keywords
288 | switch(nextInput){
289 | case TRUE:
290 | tokens.Add(new Token(TokenType.TK_TRUE, -2, startIdx, index));
291 | break;
292 | case FALSE:
293 | tokens.Add(new Token(TokenType.TK_FALSE, -3, startIdx, index));
294 | break;
295 | case NULL:
296 | tokens.Add(new Token(TokenType.TK_NULL, -4, startIdx, index));
297 | break;
298 | default:
299 | tokens.Add(new Token(TokenType.TK_INPUT, tokenValues.Count, startIdx, index));
300 | tokenValues.Add(nextInput);
301 | break;
302 | }
303 | break;
304 | }
305 | }
306 |
307 | return new LexerResult(input, tokens.ToArray(), tokenValues.ToArray());
308 | }
309 | string NextInput(int startIdx){
310 |
311 | while(HasMore){
312 | char c = src[index ++ ];
313 | if(IsInputTerminator(c)){
314 | index -- ;
315 | break;
316 | }
317 | }
318 | return src[startIdx..index];
319 | }
320 | string NextString(int startIdx, char quoteChar){
321 |
322 | while(HasMore){
323 | char c = src[index ++ ];
324 | if(c == quoteChar){
325 | return src[startIdx..(index - 1)];
326 | }
327 | }
328 | var nearStr = src[startIdx..index];
329 | throw new CommandLexerException($"String not terminated near <{nearStr}>");
330 | }
331 | string NextNumber(int startIdx, out bool isFloat){
332 |
333 | while(HasMore){
334 | char c = src[index ++];
335 | if(c == DOT){
336 | isFloat = true;
337 | return NextFloat(startIdx);
338 | }
339 | if(char.IsDigit(c))continue;
340 | index -- ;
341 | break;
342 | }
343 | isFloat = false;
344 | return src[(startIdx - 1)..index];
345 | }
346 | string NextFloat(int startIdx){
347 |
348 | while(HasMore){
349 | char c = src[index ++ ];
350 | if(char.IsDigit(c))continue;
351 | if(c == E)return NextExponent(startIdx);
352 | index -- ;
353 | break;
354 | }
355 | return src[(startIdx - 1)..index];
356 | }
357 | string NextExponent(int startIdx){
358 |
359 | while(HasMore){
360 | char c = src[index ++ ];
361 | if(char.IsDigit(c))continue;
362 | index -- ;
363 | break;
364 | }
365 | return src[(startIdx - 1)..index];
366 | }
367 |
368 | }
369 |
370 |
371 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/Lexer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dc5f5b26b04358745bc29ddb76c4594e
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/SuggestionQuery.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 |
5 | namespace RedSaw.CommandLineInterface
6 | {
7 |
8 | public enum SuggestionType
9 | {
10 |
11 | /// no suggestion
12 | None,
13 |
14 | /// search property or local variable
15 | Variable,
16 |
17 | /// search member of a type
18 | Member,
19 |
20 | /// search command
21 | Command,
22 | }
23 |
24 | /// save current suggestion query information, provide to
25 | /// current command system to get some suggestion back
26 | public readonly struct SuggestionQuery
27 | {
28 |
29 | public static readonly SuggestionQuery None =
30 | new(SuggestionType.None, string.Empty);
31 |
32 | public readonly SuggestionType suggestionType;
33 | public readonly string queryStr;
34 | public readonly Type queryType;
35 |
36 | public SuggestionQuery(SuggestionType suggestionType, string queryStr, Type queryType)
37 | {
38 |
39 | this.suggestionType = suggestionType;
40 | this.queryStr = queryStr;
41 | this.queryType = queryType;
42 | }
43 | public SuggestionQuery(SuggestionType suggestionType, string queryStr) :
44 | this(suggestionType, queryStr, null)
45 | { }
46 |
47 | public override string ToString()
48 | {
49 | return suggestionType switch
50 | {
51 | SuggestionType.Variable => $"Variable -> \"{queryStr}\"",
52 | SuggestionType.Command => $"Command -> \"{queryStr}\"",
53 | SuggestionType.Member => $"Member -> \"{queryType.Name}.{queryStr}\"",
54 | _ => "No Suggestions",
55 | };
56 | }
57 | }
58 |
59 | public delegate bool QueryVariableType(string name, out Type type);
60 |
61 | ///
62 | /// use to analyze input string and provide suggestions type
63 | ///
64 | class InputBehaviourStateMachine
65 | {
66 |
67 | public readonly Stack stateStack;
68 | private readonly Func getVariableType;
69 | private readonly Func getCallableType;
70 | private InputBehaviourState currentState;
71 |
72 | public string CurrentStatus
73 | {
74 | get
75 | {
76 | if (currentState == null) return "";
77 | return currentState.ToString();
78 | }
79 | }
80 |
81 | /// current suggestion
82 | public SuggestionQuery CurrentSuggestionQuery => currentState.GetSuggestion();
83 |
84 | public InputBehaviourStateMachine(Func getVariableType, Func getCallableType)
85 | {
86 |
87 | this.getVariableType = getVariableType;
88 | this.getCallableType = getCallableType;
89 | stateStack = new Stack();
90 | currentState = new IBS_Ready(this);
91 | }
92 | public InputBehaviourState TryGetVariableMemberState(string variableName)
93 | {
94 |
95 | var type = getVariableType(variableName);
96 | if (type == null) return new IBS_Unknown(this);
97 | return new IBS_Member(this, type);
98 | }
99 | public InputBehaviourState TryGetCallableMemberState(string callableName)
100 | {
101 |
102 | var type = getCallableType(callableName);
103 | if (type == null) return new IBS_Unknown(this);
104 | return new IBS_Member(this, type);
105 | }
106 |
107 | public void StepForward(char c)
108 | {
109 |
110 | var nextState = currentState.StepForward(c);
111 | if (nextState != currentState)
112 | {
113 | if (nextState.ShouldCollapse)
114 | {
115 | stateStack.Pop();
116 | }
117 | stateStack.Push(currentState);
118 | currentState = nextState;
119 | }
120 | }
121 | public void StepBackward()
122 | {
123 |
124 | if (currentState.StepBackward())
125 | {
126 | currentState = stateStack.Pop();
127 | }
128 | }
129 | public void Reset()
130 | {
131 |
132 | stateStack.Clear();
133 | currentState = new IBS_Ready(this);
134 | }
135 | public void Reset(string input)
136 | {
137 |
138 | Reset();
139 | foreach (char c in input)
140 | {
141 | StepForward(c);
142 | }
143 | }
144 |
145 | }
146 |
147 |
148 |
149 | /// character automaton state
150 | abstract class InputBehaviourState
151 | {
152 |
153 | protected readonly InputBehaviourStateMachine stateMachine;
154 | public bool ShouldCollapse { get; protected set; } = false;
155 |
156 | ///
157 | /// mark if current state is after an assign operation
158 | ///
159 | public bool AfterAssign { get; protected set; } = false;
160 |
161 | public InputBehaviourState(InputBehaviourStateMachine stateMachine, bool afterAssign = false)
162 | {
163 |
164 | this.stateMachine = stateMachine;
165 | this.AfterAssign = afterAssign;
166 | }
167 |
168 | /// input new character and check if
169 | /// new input character
170 | /// should change current state to nextState
171 | public abstract InputBehaviourState StepForward(char c);
172 |
173 | /// step backward, return to last state
174 | /// should change current state to last state
175 | public abstract bool StepBackward();
176 |
177 | /// get suggestion query
178 | /// current suggestion query
179 | public virtual SuggestionQuery GetSuggestion() => SuggestionQuery.None;
180 |
181 | protected IBS_Wait Wait() => new(stateMachine);
182 | protected IBS_Unknown Unknown() => new(stateMachine);
183 | protected IBS_Variable Variable() => new(stateMachine);
184 | protected InputBehaviourState VariableMember(string typeName) => stateMachine.TryGetVariableMemberState(typeName);
185 | protected InputBehaviourState CommandMember(string commandName) => stateMachine.TryGetCallableMemberState(commandName);
186 | protected InputBehaviourState Member(Type type, string name)
187 | {
188 |
189 | MemberInfo member = type.GetDefaultMember(name);
190 | if (member == null)
191 | {
192 | return new IBS_Unknown(stateMachine);
193 | }
194 | return member.MemberType switch
195 | {
196 | MemberTypes.Field => new IBS_Member(stateMachine, ((FieldInfo)member).FieldType),
197 | MemberTypes.Property => new IBS_Member(stateMachine, ((PropertyInfo)member).PropertyType),
198 | _ => new IBS_Unknown(stateMachine),
199 | };
200 | }
201 | protected IBS_Command Callable(char firstChar, bool shouldCollapse = false, bool afterAssign = false)
202 | {
203 |
204 | return new IBS_Command(stateMachine, firstChar, shouldCollapse, afterAssign);
205 | }
206 | public override string ToString() => "";
207 | }
208 |
209 | abstract class InputBehaviourStateWithInput : InputBehaviourState
210 | {
211 |
212 | public string CurrentInput { get; protected set; } = string.Empty;
213 | public InputBehaviourStateWithInput(
214 | InputBehaviourStateMachine stateMachine,
215 | string input = "",
216 | bool afterAssign = false
217 | ) : base(stateMachine, afterAssign)
218 | {
219 | this.CurrentInput = input;
220 | }
221 | public override string ToString() => $"";
222 | }
223 |
224 |
225 |
226 |
227 | class IBS_Ready : InputBehaviourStateWithInput
228 | {
229 | public IBS_Ready(InputBehaviourStateMachine stateMachine) : base(stateMachine) { }
230 | public override InputBehaviourState StepForward(char c)
231 | {
232 | switch (c)
233 | {
234 | case Lexer.VAR:
235 | return Variable();
236 |
237 | case Lexer.DOUBLE_QUOTE:
238 | case Lexer.SINGLE_QUOTE:
239 | return new IBS_String(stateMachine, c);
240 |
241 | case Lexer.UNDERLINE:
242 | return Callable(c, shouldCollapse: true, AfterAssign);
243 |
244 | default:
245 | if (Lexer.IsWhiteSpace(c)) return Wait();
246 | if (c == Lexer.UNDERLINE || char.IsLetter(c)) return Callable(c);
247 | return Unknown();
248 | }
249 |
250 | }
251 | public override bool StepBackward() => false;
252 | public override string ToString() => "";
253 | }
254 |
255 | class IBS_Wait : InputBehaviourStateWithInput
256 | {
257 |
258 | public IBS_Wait(InputBehaviourStateMachine stateMachine, string input = "") : base(stateMachine, input)
259 | {
260 | }
261 | public override InputBehaviourState StepForward(char c)
262 | {
263 |
264 | switch (c)
265 | {
266 | case Lexer.VAR:
267 | return Variable();
268 |
269 | case Lexer.DOUBLE_QUOTE:
270 | case Lexer.SINGLE_QUOTE:
271 | return new IBS_String(stateMachine, c);
272 |
273 | default:
274 | if (Lexer.IsWhiteSpace(c)) return Wait();
275 | if (c == Lexer.ASSIGN) return Wait();
276 | if (char.IsLetter(c) || c == Lexer.UNDERLINE) return Callable(c, shouldCollapse: true, AfterAssign);
277 | return Unknown();
278 | }
279 | }
280 |
281 | public override bool StepBackward() => true;
282 | public override string ToString() => $"";
283 | }
284 |
285 |
286 | class IBS_Command : InputBehaviourStateWithInput
287 | {
288 |
289 | public IBS_Command(
290 | InputBehaviourStateMachine stateMachine,
291 | char firstChar,
292 | bool shouldCollapse = false,
293 | bool afterAssign = false) : base(stateMachine, firstChar.ToString(), afterAssign)
294 | {
295 |
296 | CurrentInput = firstChar.ToString();
297 | this.ShouldCollapse = shouldCollapse;
298 | }
299 | public override InputBehaviourState StepForward(char c)
300 | {
301 | if (Lexer.IsWhiteSpace(c)) return Wait();
302 | if (c == Lexer.DOT) return CommandMember(CurrentInput);
303 | if (c == Lexer.UNDERLINE || char.IsLetterOrDigit(c))
304 | {
305 | CurrentInput += c;
306 | return this;
307 | }
308 | return Unknown();
309 | }
310 | public override bool StepBackward()
311 | {
312 | if (CurrentInput.Length == 0) return true;
313 | CurrentInput = CurrentInput[..^1];
314 | return false;
315 | }
316 | public override SuggestionQuery GetSuggestion() => new(SuggestionType.Command, CurrentInput);
317 | public override string ToString() => $"";
318 | }
319 |
320 |
321 | ///
322 | /// bad state only use to mark the input is bad,
323 | /// and wait for user delete all and reinput
324 | ///
325 | class IBS_Unknown : InputBehaviourState
326 | {
327 |
328 | private int count;
329 | public IBS_Unknown(InputBehaviourStateMachine stateMachine) : base(stateMachine)
330 | {
331 | this.count = 0;
332 | }
333 | public override InputBehaviourState StepForward(char c)
334 | {
335 | count++;
336 | return this;
337 | }
338 | public override bool StepBackward()
339 | {
340 | return --count < 0;
341 | }
342 | public override string ToString() => "";
343 | }
344 |
345 | class IBS_Variable : InputBehaviourStateWithInput
346 | {
347 | public IBS_Variable(InputBehaviourStateMachine stateMachine, string input = "") : base(stateMachine, input) { }
348 | public override InputBehaviourState StepForward(char c)
349 | {
350 | if (CurrentInput.Length == 0)
351 | {
352 | if (char.IsLetter(c) || c == Lexer.UNDERLINE)
353 | {
354 | CurrentInput += c;
355 | return this;
356 | }
357 | return Unknown();
358 | }
359 |
360 | if (char.IsLetterOrDigit(c) || c == Lexer.UNDERLINE)
361 | {
362 |
363 | CurrentInput += c;
364 | return this;
365 | }
366 | if (c == Lexer.DOT)
367 | {
368 |
369 | return VariableMember(CurrentInput);
370 | }
371 | return Unknown();
372 | }
373 | public override bool StepBackward()
374 | {
375 | if (CurrentInput.Length == 0) return true;
376 | CurrentInput = CurrentInput[..^1];
377 | return false;
378 | }
379 | public override SuggestionQuery GetSuggestion() => new(SuggestionType.Variable, CurrentInput);
380 | public override string ToString() => $"";
381 | }
382 |
383 | class IBS_String : InputBehaviourStateWithInput
384 | {
385 |
386 | private readonly char quote;
387 | public IBS_String(InputBehaviourStateMachine stateMachine, char quote, string input = "") : base(stateMachine, input)
388 | {
389 | this.quote = quote;
390 | }
391 | public override InputBehaviourState StepForward(char c)
392 | {
393 | if (c == quote) return Wait();
394 | CurrentInput += c;
395 | return this;
396 | }
397 | public override bool StepBackward()
398 | {
399 | if (CurrentInput.Length == 0) return true;
400 | CurrentInput = CurrentInput[..^1];
401 | return false;
402 | }
403 | public override string ToString() => $"";
404 | }
405 |
406 | class IBS_Member : InputBehaviourStateWithInput
407 | {
408 | private readonly Type queryType;
409 | public IBS_Member(InputBehaviourStateMachine stateMachine, Type type) : base(stateMachine, string.Empty)
410 | {
411 | queryType = type;
412 | }
413 |
414 | public override bool StepBackward()
415 | {
416 | if (CurrentInput.Length == 0) return true;
417 | CurrentInput = CurrentInput[..^1];
418 | return false;
419 | }
420 |
421 | public override InputBehaviourState StepForward(char c)
422 | {
423 | if (CurrentInput.Length == 0)
424 | {
425 | if (char.IsLetter(c) || c == Lexer.UNDERLINE)
426 | {
427 | CurrentInput += c;
428 | return this;
429 | }
430 | return Unknown();
431 | }
432 | if (char.IsLetterOrDigit(c) || c == Lexer.UNDERLINE)
433 | {
434 |
435 | CurrentInput += c;
436 | return this;
437 | }
438 | else if (c == Lexer.DOT)
439 | {
440 |
441 | return Member(queryType, CurrentInput);
442 | }
443 | return Callable(c);
444 | }
445 | public override SuggestionQuery GetSuggestion()
446 | {
447 | return new(SuggestionType.Member, CurrentInput, queryType);
448 | }
449 | public override string ToString() => $"";
450 | }
451 |
452 |
453 |
454 |
455 | ///
456 | /// lexer use to parse input realtime
457 | ///
458 | public class CharAutomaton
459 | {
460 |
461 | private readonly InputBehaviourStateMachine stateMachine;
462 | private string lastInput = string.Empty;
463 | public string CurrentStatus => stateMachine.CurrentStatus;
464 |
465 | public CharAutomaton(
466 | Func getVariableType,
467 | Func getCallableType
468 | )
469 | {
470 | stateMachine = new InputBehaviourStateMachine(getVariableType, getCallableType);
471 | }
472 |
473 | ///
474 | /// input current input text and get suggestion query
475 | ///
476 | public SuggestionQuery Input(string newInput)
477 | {
478 |
479 | /* input string is empty */
480 | if (newInput == null || newInput.Length == 0)
481 | {
482 |
483 | stateMachine.Reset();
484 | lastInput = string.Empty;
485 | return SuggestionQuery.None;
486 | }
487 |
488 | /* input string is equal to last input, it maybe nothing change
489 | or an totally new input */
490 | if (newInput.Length == lastInput.Length)
491 | {
492 | if (newInput == lastInput)
493 | {
494 | return stateMachine.CurrentSuggestionQuery;
495 | }
496 | Rehandle(newInput);
497 | return stateMachine.CurrentSuggestionQuery;
498 | }
499 |
500 | /* input some new characters */
501 | if (newInput.Length > lastInput.Length)
502 | {
503 |
504 | /* add some new characters */
505 | if (newInput.StartsWith(lastInput))
506 | {
507 | foreach (char c in newInput[lastInput.Length..])
508 | {
509 | stateMachine.StepForward(c);
510 | }
511 | lastInput = newInput;
512 | return stateMachine.CurrentSuggestionQuery;
513 | }
514 |
515 | /* maybe an totally new input */
516 | Rehandle(newInput);
517 | return stateMachine.CurrentSuggestionQuery;
518 | }
519 |
520 | /* delete some characaters */
521 | if (newInput.Length < lastInput.Length)
522 | {
523 | if (lastInput.StartsWith(newInput))
524 | {
525 | foreach (char _ in lastInput[newInput.Length..])
526 | {
527 | stateMachine.StepBackward();
528 | }
529 | lastInput = newInput;
530 | return stateMachine.CurrentSuggestionQuery;
531 | }
532 | Rehandle(newInput);
533 | return stateMachine.CurrentSuggestionQuery;
534 | }
535 |
536 | /* code never run below */
537 | return stateMachine.CurrentSuggestionQuery;
538 | }
539 | void Rehandle(string input)
540 | {
541 |
542 | lastInput = input;
543 | stateMachine.Reset(input);
544 | }
545 | }
546 |
547 |
548 | ///
549 | /// used to save suggestion information
550 | ///
551 | public readonly struct Suggestion
552 | {
553 |
554 | public readonly string primary;
555 | public readonly string description;
556 |
557 | public Suggestion(string primary, string description)
558 | {
559 |
560 | this.primary = primary;
561 | this.description = description;
562 | }
563 | public Suggestion(string primary)
564 | {
565 |
566 | this.primary = primary;
567 | this.description = string.Empty;
568 | }
569 | public override string ToString()
570 | {
571 | if (this.description.Length > 0)
572 | {
573 | return $"{primary} :{description}";
574 | }
575 | return primary;
576 | }
577 | }
578 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/SuggestionQuery.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: de5a397c444b0ad4dbb630d98fda9ba2
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/SyntaxAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Collections.Generic;
4 |
5 | namespace RedSaw.CommandLineInterface
6 | {
7 |
8 | ///
9 | /// syntax tree code, tell VirualMachine how to execute given node
10 | ///
11 | public enum SyntaxTreeCode
12 | {
13 |
14 | FACTOR_STRING,
15 | FACTOR_FLOAT,
16 | FACTOR_TRUE,
17 | FACTOR_FALSE,
18 | FACTOR_NULL,
19 | FACTOR_INT,
20 | FACTOR_ID,
21 | FACTOR_INPUT,
22 | FACTOR_PARAMS,
23 |
24 | OP_SET_ELEMENT,
25 | OP_SET_FIELD,
26 | OP_LOADVAR,
27 | OP_ASSIGN,
28 | OP_INDEX,
29 | OP_CALL,
30 | OP_CVT,
31 | OP_DOT,
32 | }
33 |
34 | ///
35 | /// syntax tree, tree type, and represent the whole execution logic
36 | ///
37 | public class SyntaxTree
38 | {
39 |
40 | public readonly string data;
41 | public readonly SyntaxTreeCode opcode;
42 | public readonly SyntaxTree[] children;
43 |
44 | public SyntaxTree L => children[0];
45 | public SyntaxTree R => children[1];
46 | public bool IsLeaf => children.Length == 0;
47 |
48 | /// provide debug info about current node
49 | public string DebugInfo
50 | {
51 | get
52 | {
53 | return opcode switch
54 | {
55 | SyntaxTreeCode.OP_DOT => $"{L?.DebugInfo}.{R?.DebugInfo}",
56 | SyntaxTreeCode.OP_CALL => $"{L?.DebugInfo}{R?.DebugInfo}",
57 | SyntaxTreeCode.OP_INDEX => $"{L?.DebugInfo}[{R?.DebugInfo}]",
58 | SyntaxTreeCode.OP_ASSIGN => $"{L?.DebugInfo} = {R?.DebugInfo}",
59 | SyntaxTreeCode.OP_SET_FIELD => $"{L?.DebugInfo}.{R?.DebugInfo} = {children[2]?.DebugInfo}",
60 | SyntaxTreeCode.OP_CVT => $"{L?.DebugInfo}:{R?.DebugInfo}",
61 | SyntaxTreeCode.FACTOR_PARAMS => $"({string.Join(", ", children.Select(x => x.DebugInfo))})",
62 | SyntaxTreeCode.FACTOR_FLOAT => $"{data}f",
63 | SyntaxTreeCode.FACTOR_STRING => data,
64 | SyntaxTreeCode.FACTOR_TRUE => data,
65 | SyntaxTreeCode.FACTOR_FALSE => data,
66 | SyntaxTreeCode.FACTOR_NULL => data,
67 | SyntaxTreeCode.FACTOR_INT => data,
68 | SyntaxTreeCode.FACTOR_ID => "@" + data,
69 | SyntaxTreeCode.FACTOR_INPUT => "?" + data,
70 | _ => ToString(),
71 | };
72 | }
73 | }
74 |
75 | public SyntaxTree(SyntaxTreeCode opcode, string data)
76 | {
77 |
78 | this.opcode = opcode;
79 | this.data = data;
80 | this.children = Array.Empty();
81 | }
82 | public SyntaxTree(SyntaxTreeCode opcode, string data, SyntaxTree L)
83 | {
84 |
85 | this.opcode = opcode;
86 | this.data = data;
87 | this.children = new SyntaxTree[] { L };
88 | }
89 | public SyntaxTree(SyntaxTreeCode opcode, string data, SyntaxTree L, SyntaxTree R)
90 | {
91 |
92 | this.opcode = opcode;
93 | this.data = data;
94 | this.children = new SyntaxTree[] { L, R };
95 | }
96 | public SyntaxTree(SyntaxTreeCode opcode, string data, SyntaxTree[] children)
97 | {
98 |
99 | this.opcode = opcode;
100 | this.data = data;
101 | this.children = children;
102 | }
103 | public override string ToString()
104 | {
105 | if (data != null || data.Length > 0) return data;
106 | return opcode.ToString();
107 | }
108 | }
109 |
110 | ///
111 | /// analyze lexer result and generate syntax tree
112 | ///
113 | public class SyntaxAnalyzer
114 | {
115 |
116 | LexerResult lexerResult;
117 | int index;
118 |
119 | ///
120 | /// indicate that while system has occur a 'command' parse process
121 | /// and if true, then the next input would be treated as command parameters
122 | ///
123 | bool __command_latch;
124 |
125 | Token[] Tokens => lexerResult.tokens;
126 | string[] TokenValues => lexerResult.sourceBuffer;
127 |
128 | public bool HasMore => index < Tokens.Length;
129 | public Token CurrentToken => Tokens[index];
130 | public Token NextToken => Tokens[index++];
131 |
132 | ///
133 | /// analyze lexer result and generate syntax tree
134 | ///
135 | /// lexer result
136 | /// syntax tree
137 | /// when syntax error occured
138 | public SyntaxTree Analyze(LexerResult lexerResult)
139 | {
140 |
141 | this.lexerResult = lexerResult;
142 |
143 | index = 0;
144 | __command_latch = false;
145 | return NextExpr(true);
146 | }
147 |
148 | ///
149 | /// match current token type, if match, then move to next token
150 | ///
151 | bool Match(TokenType tokenType)
152 | {
153 |
154 | if (HasMore && CurrentToken.type == tokenType)
155 | {
156 | index++;
157 | return true;
158 | }
159 | return false;
160 | }
161 | ///
162 | /// only check if current token type is matched, don't move
163 | ///
164 | bool PeekMatch(TokenType tokenType)
165 | {
166 | return HasMore && CurrentToken.type == tokenType;
167 | }
168 |
169 | ///
170 | /// check if token is Input and is Id legal
171 | ///
172 | bool MatchId(out SyntaxTree factor)
173 | {
174 |
175 | var token = CurrentToken;
176 | if (token.type == TokenType.TK_INPUT && Lexer.IsId(TokenValues[token.sourceIdx]))
177 | {
178 | factor = new SyntaxTree(SyntaxTreeCode.FACTOR_ID, TokenValues[token.sourceIdx]);
179 | index++;
180 | return true;
181 | }
182 | factor = null;
183 | return false;
184 | }
185 | SyntaxTree NextParameterList()
186 | {
187 |
188 | if (Match(TokenType.TK_R_PAREN))
189 | return new SyntaxTree(SyntaxTreeCode.FACTOR_PARAMS, string.Empty);
190 |
191 | __command_latch = true;
192 | List parameters = new();
193 | while (HasMore)
194 | {
195 | parameters.Add(NextExpr());
196 | if (Match(TokenType.TK_R_PAREN)) break;
197 | if (Match(TokenType.TK_COMMA)) continue;
198 | }
199 | __command_latch = false;
200 | return new SyntaxTree(SyntaxTreeCode.FACTOR_PARAMS, string.Empty, parameters.ToArray());
201 | }
202 | SyntaxTree NextParameterList_Command()
203 | {
204 |
205 | __command_latch = true;
206 | List parameters = new();
207 | while (HasMore)
208 | {
209 | if (Match(TokenType.TK_EOF)) break;
210 | parameters.Add(NextExpr());
211 | }
212 | __command_latch = false;
213 | return new SyntaxTree(SyntaxTreeCode.FACTOR_PARAMS, string.Empty, parameters.ToArray());
214 | }
215 | SyntaxTree NextFactor()
216 | {
217 |
218 | Token tk = NextToken;
219 | switch (tk.type)
220 | {
221 |
222 | case TokenType.TK_INT:
223 | return new SyntaxTree(SyntaxTreeCode.FACTOR_INT, TokenValues[tk.sourceIdx]);
224 |
225 | case TokenType.TK_FLOAT:
226 | return new SyntaxTree(SyntaxTreeCode.FACTOR_FLOAT, TokenValues[tk.sourceIdx]);
227 |
228 | case TokenType.TK_STRING:
229 | return new SyntaxTree(SyntaxTreeCode.FACTOR_STRING, TokenValues[tk.sourceIdx]);
230 |
231 | case TokenType.TK_NULL:
232 | return new SyntaxTree(SyntaxTreeCode.FACTOR_NULL, Lexer.NULL);
233 |
234 | case TokenType.TK_TRUE:
235 | return new SyntaxTree(SyntaxTreeCode.FACTOR_TRUE, Lexer.TRUE);
236 |
237 | case TokenType.TK_FALSE:
238 | return new SyntaxTree(SyntaxTreeCode.FACTOR_FALSE, Lexer.FALSE);
239 |
240 | case TokenType.TK_VAR:
241 | return new SyntaxTree(SyntaxTreeCode.OP_LOADVAR, TokenValues[tk.sourceIdx]);
242 |
243 | case TokenType.TK_INPUT:
244 | /*
245 | all other input would be treated as command, if next token is '(',
246 | then treat as function call, otherwise treat as command
247 | */
248 | if (Match(TokenType.TK_L_PAREN))
249 | {
250 | var functionCmd = new SyntaxTree(SyntaxTreeCode.FACTOR_INPUT, TokenValues[tk.sourceIdx]);
251 | return new SyntaxTree(SyntaxTreeCode.OP_CALL, string.Empty, functionCmd, NextParameterList());
252 | }
253 | if (__command_latch)
254 | {
255 | return new SyntaxTree(SyntaxTreeCode.FACTOR_INPUT, TokenValues[tk.sourceIdx]);
256 | }
257 | var commandCall = new SyntaxTree(SyntaxTreeCode.FACTOR_INPUT, TokenValues[tk.sourceIdx]);
258 | return new SyntaxTree(SyntaxTreeCode.OP_CALL, string.Empty, commandCall, NextParameterList_Command());
259 |
260 | case TokenType.TK_L_PAREN:
261 | SyntaxTree expr = NextExpr();
262 | if (!Match(TokenType.TK_R_PAREN))
263 | {
264 | throw new CommandSyntaxException($"Missing ')' near \"..{lexerResult.NearInfo(tk)}\"");
265 | }
266 | return expr;
267 | }
268 | throw new CommandSyntaxException($"Unexpected token {tk.type} near \"..{lexerResult.NearInfo(tk)}\"");
269 | }
270 | SyntaxTree NextConverter()
271 | {
272 |
273 | var factor = NextFactor();
274 | if (Match(TokenType.TK_COLON))
275 | {
276 | var currentTk = NextToken;
277 | if (currentTk.type == TokenType.TK_STRING || currentTk.type == TokenType.TK_INPUT)
278 | {
279 | var opFactor = new SyntaxTree(SyntaxTreeCode.FACTOR_INPUT, TokenValues[currentTk.sourceIdx]);
280 | var opConverter = new SyntaxTree(SyntaxTreeCode.OP_CVT, string.Empty, factor, opFactor);
281 | return opConverter;
282 | }
283 | throw new CommandSyntaxException($"Unexpected token near \"..{lexerResult.NearInfo(currentTk)}\"");
284 | }
285 | return factor;
286 | }
287 | SyntaxTree NextAccess()
288 | {
289 |
290 | SyntaxTree tmp = NextConverter();
291 | while (HasMore)
292 | {
293 | int startPosition = CurrentToken.startIdx;
294 |
295 | // dot
296 | if (Match(TokenType.TK_DOT))
297 | {
298 | if (MatchId(out var id))
299 | {
300 |
301 | // function call
302 | if (Match(TokenType.TK_L_PAREN))
303 | {
304 | var callable = new SyntaxTree(SyntaxTreeCode.OP_DOT, string.Empty, tmp, id);
305 | tmp = new SyntaxTree(SyntaxTreeCode.OP_CALL, string.Empty, callable, NextParameterList());
306 | continue;
307 | }
308 |
309 | tmp = new SyntaxTree(SyntaxTreeCode.OP_DOT, string.Empty, tmp, id);
310 | continue;
311 | }
312 | throw new CommandSyntaxException($"Unexpected token near \"..{lexerResult.NearInfo(startPosition, CurrentToken.endIdx)}\"");
313 | }
314 |
315 | // index
316 | if (Match(TokenType.TK_L_BRACKET))
317 | {
318 | SyntaxTree expr = NextExpr();
319 | if (!Match(TokenType.TK_R_BRACKET))
320 | {
321 | throw new CommandSyntaxException($"Missing ']' near \"..{lexerResult.NearInfo(startPosition, CurrentToken.endIdx)}\"");
322 | }
323 | tmp = new SyntaxTree(SyntaxTreeCode.OP_INDEX, string.Empty, tmp, expr);
324 | continue;
325 | }
326 | break;
327 | }
328 | return tmp;
329 | }
330 | public SyntaxTree NextExpr(bool isRoot = false)
331 | {
332 |
333 | SyntaxTree access = NextAccess();
334 | if (!HasMore || PeekMatch(TokenType.TK_EOF))
335 | return access;
336 |
337 | if (Match(TokenType.TK_ASSIGN))
338 | {
339 |
340 | // instance.fieldName =
341 | if (access.opcode == SyntaxTreeCode.OP_DOT)
342 | {
343 | SyntaxTree expr = NextExpr();
344 | return new SyntaxTree(SyntaxTreeCode.OP_SET_FIELD, string.Empty, new SyntaxTree[]{
345 | access.children[0],
346 | access.children[1],
347 | expr
348 | });
349 | }
350 |
351 | // instance["test"] = 10 or instance[10] = 10
352 | if (access.opcode == SyntaxTreeCode.OP_INDEX)
353 | {
354 | SyntaxTree expr = NextExpr();
355 | return new SyntaxTree(SyntaxTreeCode.OP_SET_ELEMENT, string.Empty, new SyntaxTree[]{
356 | access.children[0],
357 | access.children[1],
358 | expr
359 | });
360 | }
361 |
362 | return new SyntaxTree(SyntaxTreeCode.OP_ASSIGN, string.Empty, access, NextExpr());
363 | }
364 | if (isRoot)
365 | {
366 | throw new CommandSyntaxException($"Unexpected token {CurrentToken.type} near \"..{lexerResult.NearInfo(CurrentToken)}\"");
367 | }
368 | return access;
369 | }
370 | }
371 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/SyntaxAnalyzer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a58a1265b6dd8334cb10af5049b1f823
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/Utils.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0d604af26230e0e47b4d07a7cb0e5296
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Core/VirtualMachine.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c6f04fdcd5288554a92e342eec1540e9
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Helper.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ff1aab6dcc1df3f45809446ee4b8684e
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Helper/DebugHelper.cs:
--------------------------------------------------------------------------------
1 | /*
2 | provide an attribute to mark debug info, when we use CommandHelper.Debug(), we can get all debug info
3 | include parent object's debug info
4 | */
5 |
6 | using System;
7 | using System.Reflection;
8 | using System.Collections.Generic;
9 |
10 | namespace RedSaw.CommandLineInterface{
11 |
12 | ///
13 | /// mark one field or property is debug info, when we use CommandHelper.Debug(), we can get all debug info
14 | /// include parent object's debug info
15 | ///
16 | [AttributeUsage(AttributeTargets.Field|AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
17 | public class DebugInfoAttribute: Attribute{
18 |
19 | public string Key{ get; protected set; }
20 |
21 | /// you must provide an color key, and any invalid colors would be changed to white
22 | public string Color{ get; set; }
23 |
24 | public DebugInfoAttribute(string key){
25 |
26 | this.Key = key;
27 | this.Color = "#ffffff";
28 | }
29 | public DebugInfoAttribute(){
30 |
31 | this.Key = string.Empty;
32 | this.Color = "#ffffff";
33 | }
34 | }
35 |
36 | ///
37 | /// mark one element is container of debug info
38 | ///
39 | [AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]
40 | public class DebugObjectAttribute: Attribute{
41 |
42 | public string Title{ get; protected set; }
43 |
44 | public DebugObjectAttribute(string title){
45 | this.Title = title;
46 | }
47 | public DebugObjectAttribute(){
48 | this.Title = null;
49 | }
50 | }
51 |
52 | public static class DebugHelper{
53 |
54 |
55 | /// get all parent types of given type
56 | static Type[] GetParentTypes(Type type){
57 |
58 | Stack typeList = new();
59 | Type parent = type.BaseType;
60 | while(parent != null){
61 | typeList.Push(parent);
62 | parent = parent.BaseType;
63 | }
64 | return typeList.ToArray();
65 | }
66 |
67 | ///
68 | /// get all debug informations from given instance
69 | ///
70 | /// the instance to get debug info
71 | /// the depth of debug info
72 | /// only get debug info from given namespace
73 | public static (string, string)[] GetDebugInfos(this object instance, int depth = 0, int depthLimit = 5, string constraintNamespace = null){
74 |
75 | if( depth >= depthLimit )return Array.Empty<(string, string)>();
76 |
77 | var usedKeys = new HashSet();
78 | Type currentType = instance.GetType();
79 | Type[] types = GetParentTypes(currentType);
80 |
81 | List<(string, string)> totalDebugInfos = new();
82 | foreach(Type type in types){
83 | if( constraintNamespace != null && !type.Namespace.StartsWith(constraintNamespace) )continue;
84 | totalDebugInfos.AddRange(GetDebugInfos(type, instance, usedKeys, depth));
85 | }
86 | totalDebugInfos.AddRange(GetDebugInfos(currentType, instance, usedKeys, depth));
87 | return totalDebugInfos.ToArray();
88 | }
89 | ///
90 | /// get all debug informations from given instance
91 | ///
92 | static List<(string, string)> GetDebugInfos(Type type, object instance, HashSet usedKeys, int depth){
93 |
94 | if(type == null)return new List<(string, string)>();
95 | List<(string, string)> debugInfos = new();
96 | string typeName = type.Name;
97 | string space = new(' ', depth * 4);
98 | string typeSpace = new('-', depth * 4);
99 | debugInfos.Add(($">{typeSpace}[{typeName}]", "#ffffff"));
100 |
101 | /* read field infos */
102 | foreach(FieldInfo fieldInfo in type.GetFields(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance)){
103 | var attr = fieldInfo.GetCustomAttribute();
104 | if(attr != null){
105 | string title = attr.Key == string.Empty ? fieldInfo.Name : attr.Key;
106 | if(usedKeys.Contains(title))continue;
107 | usedKeys.Add(title);
108 | var subTypeAttr = fieldInfo.FieldType.GetCustomAttribute();
109 | if(subTypeAttr != null){
110 | debugInfos.AddRange(GetDebugInfos(fieldInfo.GetValue(instance), depth + 1));
111 | continue;
112 | }
113 | debugInfos.Add(($">{space}{typeName}.{title}: {fieldInfo.GetValue(instance)}", attr.Color));
114 | }
115 | }
116 |
117 | /* read property infos */
118 | foreach(PropertyInfo propertyInfo in type.GetProperties(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance)){
119 | var attr = propertyInfo.GetCustomAttribute();
120 | if(attr != null){
121 | string title = attr.Key == string.Empty ? propertyInfo.Name : attr.Key;
122 | if(usedKeys.Contains(title))continue;
123 | usedKeys.Add(title);
124 | var subTypeAttr = propertyInfo.PropertyType.GetCustomAttribute();
125 | if(subTypeAttr != null){
126 | debugInfos.AddRange(GetDebugInfos(propertyInfo.GetValue(instance), depth + 1));
127 | continue;
128 | }
129 | debugInfos.Add(($">{space}{typeName}.{title}: {propertyInfo.GetValue(instance)}", attr.Color));
130 | }
131 | }
132 | return debugInfos;
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/Helper/DebugHelper.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 546093bfbcd104f4884ca4aefba2ec61
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9b5993f5a2f280e4c98d2e0bc492e6eb
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsole.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEditor.EditorTools;
3 | using UnityEngine;
4 |
5 | namespace RedSaw.CommandLineInterface.UnityImpl
6 | {
7 |
8 | /// the final wrapper of CommandConsoleSystem build in Unity
9 | public class GameConsole : MonoBehaviour
10 | {
11 |
12 | [Header("Initialize Parameters")]
13 | [SerializeField]
14 | private GameConsoleRenderer consoleRenderer;
15 |
16 | [SerializeField]
17 | private GameConsoleHeader headerBar;
18 |
19 | [SerializeField, Tooltip("the capacity of input history, at least 1")]
20 | private int inputHistoryCapacity = 20;
21 |
22 | [SerializeField, Tooltip("the capacity of command query cache, at least 1")]
23 | private int commandQueryCacheCapacity = 20;
24 |
25 | [SerializeField, Tooltip("alternative command options count, at least 1")]
26 | private int alternativeCommandCount = 8;
27 |
28 | [SerializeField, Tooltip("should output with time information of [HH:mm:ss]")]
29 | private bool shouldOutputWithTime = true;
30 |
31 | [SerializeField, Tooltip("should record failed command input")]
32 | private bool shouldRecordFailedCommand = true;
33 |
34 | [SerializeField, Tooltip("should receive unity message")]
35 | private bool shouldReceiveUnityMessage = true;
36 |
37 | [SerializeField, Tooltip("[debug] output virtual machine exception call stack")]
38 | private bool shouldOutputVMExceptionStack = false;
39 |
40 | [SerializeField, Tooltip("initialize on awake")]
41 | private bool initializeOnAwake = true;
42 |
43 | ConsoleController console;
44 |
45 | void Awake()
46 | {
47 | if(initializeOnAwake)Init();
48 | }
49 |
50 | /// initialize console, call this function to initialize console
51 | public void Init(){
52 |
53 | if (consoleRenderer == null)
54 | {
55 | Debug.LogError("ConsoleRenderer is missing!!");
56 | gameObject.SetActive(false);
57 | return;
58 | }
59 | gameObject.SetActive(true);
60 |
61 | /* intialize console */
62 | console = new ConsoleController(
63 | consoleRenderer,
64 | new UserInput(),
65 |
66 | inputHistoryCapacity: inputHistoryCapacity,
67 | commandQueryCacheCapacity: commandQueryCacheCapacity,
68 | alternativeCommandCount: alternativeCommandCount,
69 | shouldRecordFailedCommand: shouldRecordFailedCommand,
70 | outputWithTime: shouldOutputWithTime,
71 | outputStackTraceOfCommandExecution: shouldOutputVMExceptionStack
72 | );
73 | if (shouldReceiveUnityMessage) Application.logMessageReceived += UnityConsoleLog;
74 |
75 |
76 | headerBar.Init( (RectTransform)transform );
77 | }
78 |
79 | void Update() => console.Update();
80 | void OnDestroy()
81 | {
82 | if (shouldReceiveUnityMessage)
83 | Application.logMessageReceived -= UnityConsoleLog;
84 | }
85 |
86 | void UnityConsoleLog(string msg, string stack, LogType type)
87 | {
88 |
89 | console.Output(msg, GetHexColor(type));
90 | }
91 | string GetHexColor(LogType type)
92 | {
93 | return type switch
94 | {
95 | LogType.Error or LogType.Exception or LogType.Assert => "#b13c45",
96 | LogType.Warning => "yellow",
97 | _ => "#fffde3",
98 | };
99 | }
100 |
101 | /// clear output of current console
102 | public void ClearOutput() => console.ClearOutputPanel();
103 | }
104 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsole.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2104a3d8c11d33849ba85e6a79797ea2
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleAlternativeOptionsPanel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using UnityEngine;
4 | using UnityEngine.UI;
5 |
6 | namespace RedSaw.CommandLineInterface.UnityImpl
7 | {
8 |
9 | /// default implementation of alternative options panel
10 | public class GameConsoleAlternativeOptionsPanel : MonoBehaviour
11 | {
12 |
13 | [SerializeField] private Text textPanel;
14 |
15 | List options;
16 |
17 | public int SelectionIndex
18 | {
19 | set
20 | {
21 | textPanel.text = string.Empty;
22 | if (options == null || options.Count == 0) return;
23 | string output = string.Empty;
24 | for (int i = 0; i < options.Count; i++)
25 | {
26 | if (i == value)
27 | {
28 | output += $"{options[i]}\n";
29 | continue;
30 | }
31 | output += options[i] + "\n";
32 | }
33 | textPanel.text = output;
34 | }
35 | }
36 |
37 | /// render current alternative options
38 | public void SetOptions(List values)
39 | {
40 | this.options = values;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleAlternativeOptionsPanel.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9465ef6881de94c4095bf5f16081b782
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleHeader.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEngine.UI;
3 | using UnityEngine.EventSystems;
4 | using System;
5 |
6 | namespace RedSaw.CommandLineInterface{
7 |
8 | [RequireComponent(typeof(Image))]
9 | public class GameConsoleHeader : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, IPointerMoveHandler
10 | {
11 | [SerializeField] private Color hoverColor = Color.white;
12 | private Image headerImage;
13 | private Color normalColor;
14 | private RectTransform movTarget;
15 | private Vector2 movPos;
16 | // private Action movCb;
17 |
18 | bool shouldMov;
19 | Vector2 startPos;
20 |
21 | public void Init(RectTransform target){
22 |
23 | this.movTarget = target;
24 | headerImage = GetComponent();
25 | if( headerImage == null ){
26 | throw new System.Exception("GameConsoleHeader must attach to a GameObject with Image component");
27 | }
28 | normalColor = headerImage.color;
29 | }
30 | public void OnPointerEnter(PointerEventData eventData)
31 | {
32 | headerImage.color = hoverColor;
33 | }
34 |
35 | public void OnPointerExit(PointerEventData eventData)
36 | {
37 | headerImage.color = normalColor;
38 | }
39 |
40 | public void OnPointerDown(PointerEventData eventData)
41 | {
42 | shouldMov = true;
43 | startPos = eventData.position;
44 | movPos = movTarget.position;
45 | }
46 | public void OnPointerMove(PointerEventData eventData)
47 | {
48 | if(shouldMov){
49 | Vector2 offset = eventData.position - startPos;
50 | movTarget.position = movPos + offset;
51 | }
52 | }
53 |
54 | public void OnPointerUp(PointerEventData eventData)
55 | {
56 | shouldMov = false;
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleHeader.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 89c87e651b422b94d9463fa0ced6d3de
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleInput.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace RedSaw.CommandLineInterface.UnityImpl
4 | {
5 |
6 | /// default implementation of IConsoleInput with legacy Input
7 | public class UserInput : IConsoleInput
8 | {
9 | /// push ctrl+c or other keys to focus on console
10 | public bool Focus => Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.C);
11 |
12 | /// push up to get history input or change selection of alternative options
13 | public bool MoveUp => Input.GetKeyDown(KeyCode.UpArrow);
14 |
15 | /// push down to get history input or change selection of alternative options
16 | public bool MoveDown => Input.GetKeyDown(KeyCode.DownArrow);
17 |
18 | /// push esc or other keys to quit focus on console
19 | public bool QuitFocus => Input.GetKeyDown(KeyCode.Escape);
20 |
21 | /// push F1 or other keys to show or hide console
22 | public bool ShowOrHide => Input.GetKeyDown(KeyCode.F1);
23 | }
24 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleInput.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 612a875795f387847925f3ff8631965d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleRenderer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 | using UnityEngine.UI;
6 | using UnityEngine.EventSystems;
7 |
8 | namespace RedSaw.CommandLineInterface.UnityImpl
9 | {
10 |
11 | /// default implementation of IConsoleRenderer with Unity legacy UI
12 | public class GameConsoleRenderer : MonoBehaviour, IConsoleRenderer
13 | {
14 | [SerializeField] private Text outputPanel;
15 | [SerializeField] private InputField inputField;
16 | [SerializeField] private GameConsoleAlternativeOptionsPanel optionsPanel;
17 |
18 | private int outputPanelCapacity = 400;
19 | private int lineCount = 0;
20 |
21 | public bool IsVisible
22 | {
23 | get => gameObject.activeSelf;
24 | set => gameObject.SetActive(value);
25 | }
26 |
27 | public bool IsInputFieldFocus => inputField.isFocused;
28 | public int OutputPanelCapacity => outputPanelCapacity;
29 |
30 | public string InputText
31 | {
32 |
33 | get => inputField.text;
34 | set => inputField.text = value;
35 | }
36 | public string InputTextToCursor
37 | {
38 |
39 | get => inputField.text[..inputField.caretPosition];
40 | set => inputField.text = value + inputField.text[inputField.caretPosition..];
41 | }
42 |
43 | public bool IsAlternativeOptionsActive
44 | {
45 | get => optionsPanel.gameObject.activeSelf;
46 | set => optionsPanel.gameObject.SetActive(value);
47 | }
48 | public List AlternativeOptions
49 | {
50 | set => optionsPanel.SetOptions(value);
51 | }
52 | public int AlternativeOptionsIndex
53 | {
54 | set => optionsPanel.SelectionIndex = value;
55 | }
56 |
57 | public void ActivateInput()
58 | {
59 | inputField.Select();
60 | inputField.ActivateInputField();
61 | StartCoroutine(DisableHighlight());
62 | }
63 | IEnumerator DisableHighlight()
64 | {
65 |
66 | //Get original selection color
67 | Color originalTextColor = inputField.selectionColor;
68 | //Remove alpha
69 | originalTextColor.a = 0f;
70 |
71 | //Apply new selection color without alpha
72 | inputField.selectionColor = originalTextColor;
73 |
74 | //Wait one Frame(MUST DO THIS!)
75 | yield return null;
76 |
77 | //Change the caret pos to the end of the text
78 | inputField.caretPosition = inputField.text.Length;
79 |
80 | //Return alpha
81 | originalTextColor.a = 1f;
82 |
83 | //Apply new selection color with alpha
84 | inputField.selectionColor = originalTextColor;
85 | }
86 |
87 | public void SetInputCursorPosition(int value)
88 | {
89 | inputField.caretPosition = Mathf.Clamp(value, 0, inputField.text.Length);
90 | }
91 |
92 | public void MoveScrollBarToEnd()
93 | {
94 | StartCoroutine(MoveToLast());
95 | }
96 | IEnumerator MoveToLast()
97 | {
98 | yield return null;
99 | GetComponentInChildren(true).verticalNormalizedPosition = 0;
100 | }
101 |
102 | public void BindOnSubmit(Action callback)
103 | {
104 | /*
105 | cause maybe the implementation of the game console in Non-Unity GameEngine
106 | so use Action instead of UnityEvent
107 | */
108 |
109 | inputField.onSubmit.AddListener((string input) =>
110 | {
111 | callback?.Invoke(input);
112 | });
113 | }
114 | public void BindOnTextChanged(Action callback)
115 | {
116 | /*
117 | cause maybe the implementation of the game console in Non-Unity GameEngine
118 | so use Action instead of UnityEvent
119 | */
120 | inputField.onValueChanged.AddListener((string input) =>
121 | {
122 | callback?.Invoke(input);
123 | });
124 | }
125 |
126 | public void Focus()
127 | {
128 | inputField.Select();
129 | EventSystem.current.SetSelectedGameObject(inputField.gameObject);
130 | }
131 | public void QuitFocus()
132 | {
133 | EventSystem.current.SetSelectedGameObject(null);
134 | }
135 |
136 | public void Clear()
137 | {
138 | lineCount = 0;
139 | outputPanel.text = string.Empty;
140 | }
141 | public void Output(string msg)
142 | {
143 | try
144 | {
145 | outputPanel.text += msg + "\n";
146 | if (++lineCount > outputPanelCapacity)
147 | {
148 | outputPanel.text = outputPanel.text[(outputPanel.text.IndexOf('\n') + 1)..];
149 | lineCount--;
150 | }
151 | /* update outputPanel's size */
152 | var generator = new TextGenerator();
153 | var settings = outputPanel.GetGenerationSettings(outputPanel.rectTransform.sizeDelta);
154 | outputPanel.rectTransform.sizeDelta =
155 | new Vector2(outputPanel.rectTransform.sizeDelta.x, generator.GetPreferredHeight(outputPanel.text, settings));
156 |
157 | GetComponentInChildren().verticalNormalizedPosition = 0;
158 | }
159 | catch (Exception)
160 | {
161 | Clear();
162 | }
163 | }
164 | public void Output(string[] msgs)
165 | {
166 | Output(string.Concat(msgs, '\n'));
167 | }
168 | public void Output(string msg, string color = "#ffffff")
169 | {
170 | Output($"{msg}");
171 | }
172 | public void Output(string[] msgs, string color = "#ffffff")
173 | {
174 |
175 | string msg = string.Empty;
176 | foreach (string line in msgs)
177 | {
178 | msg += $"{line}\n";
179 | }
180 | Output(msg);
181 | }
182 | }
183 | }
--------------------------------------------------------------------------------
/Assets/CommandLine/Scripts/UnityImpl/GameConsoleRenderer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a8aee9544275c4142a4db54acfd95168
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Example.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 78d8776bea74f1341a08d0b7ab20410d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Example/CommandExample.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using RedSaw.CommandLineInterface;
3 |
4 | public class MyClass{
5 |
6 | [DebugInfo("health", Color = "#ff0000")]
7 | public int health = 100;
8 |
9 | [DebugInfo("name")]
10 | public string name = "hello world";
11 |
12 | public void Do(){
13 | Debug.Log("do something");
14 | }
15 | }
16 |
17 | public static class CommandExample
18 | {
19 | [CommandProperty("myObj")]
20 | public static MyClass myObject = new MyClass();
21 |
22 |
23 | [Command("print")]
24 | static void Print(object value){
25 | if( value == null ){
26 | Debug.Log("null");
27 | return;
28 | }
29 | Debug.Log(value);
30 | }
31 |
32 | [Command("debug")]
33 | static void DebugTest(int a, bool v1, bool v2)
34 | {
35 | Debug.Log(a);
36 | Debug.Log(v1);
37 | Debug.Log(v2);
38 | }
39 |
40 | [Command("add")]
41 | static int Add(int a, int b = 1){
42 | return a + b;
43 | }
44 |
45 |
46 | [Command]
47 | static void printType(object value){
48 | if( value == null){
49 | Debug.Log(typeof(void));
50 | return;
51 | }
52 | Debug.Log(value.GetType());
53 | }
54 |
55 | [CommandValueParser(typeof(Vector2), Alias = "pos2")]
56 | public static bool TryParseVector2(string input, out object data){
57 |
58 | string[] result= input.Trim(new char[]{'(', ')'}).Split(',');
59 | if(result.Length == 2){
60 | if(float.TryParse(result[0], out float x) && float.TryParse(result[1], out float y)){
61 |
62 | data = new Vector2(x, y);
63 | return true;
64 | }
65 | }
66 | data = default;
67 | return false;
68 | }
69 |
70 | [CommandValueParser(typeof(Vector3), Alias = "v3")]
71 | public static bool TryParseVector3(string input, out object data){
72 |
73 | switch(input){
74 | case "up":
75 | data = Vector3.up;
76 | return true;
77 | case "down":
78 | data = Vector3.down;
79 | return true;
80 | case "left":
81 | data = Vector3.left;
82 | return true;
83 | case "right":
84 | data = Vector3.right;
85 | return true;
86 | case "forward":
87 | data = Vector3.forward;
88 | return true;
89 | case "back":
90 | data = Vector3.back;
91 | return true;
92 | }
93 |
94 | string[] result= input.Trim(new char[]{'(', ')'}).Split(',');
95 | if(result.Length == 3){
96 | if( float.TryParse(result[0], out float x) &&
97 | float.TryParse(result[1], out float y) &&
98 | float.TryParse(result[2], out float z)){
99 |
100 | data = new Vector3(x, y, z);
101 | return true;
102 | }
103 | }
104 | data = default;
105 | return false;
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/Assets/Example/CommandExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cc1be2ecba79b5e4486b9c6b75956ea9
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Example/ConsoleExampleScene.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9fc0d4010bbf28b4594072e72b8655ab
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Example/Example.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using RedSaw.CommandLineInterface;
4 | using UnityEngine;
5 |
6 | public class Example : MonoBehaviour
7 | {
8 | [CommandProperty("player")]
9 | private static Example Instance { get; set; }
10 |
11 | public Vector3 pos{
12 | get => transform.position;
13 | set => transform.position = value;
14 | }
15 |
16 | public void Start(){
17 | Example.Instance = this;
18 | }
19 |
20 | public void Jump(float value = 10){
21 | GetComponent().AddForce(Vector3.up * value, ForceMode.Impulse);
22 | transform.rotation = Random.rotation;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Assets/Example/Example.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 54642f428f22b2948a74fdc46e392590
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) 2024 PrinceBiscuit
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 |
--------------------------------------------------------------------------------
/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.unity.collab-proxy": "1.17.7",
4 | "com.unity.feature.development": "1.0.1",
5 | "com.unity.ide.rider": "3.0.16",
6 | "com.unity.ide.visualstudio": "2.0.16",
7 | "com.unity.ide.vscode": "1.2.5",
8 | "com.unity.test-framework": "1.1.31",
9 | "com.unity.textmeshpro": "3.0.6",
10 | "com.unity.timeline": "1.6.4",
11 | "com.unity.ugui": "1.0.0",
12 | "com.unity.visualscripting": "1.7.8",
13 | "com.unity.modules.ai": "1.0.0",
14 | "com.unity.modules.androidjni": "1.0.0",
15 | "com.unity.modules.animation": "1.0.0",
16 | "com.unity.modules.assetbundle": "1.0.0",
17 | "com.unity.modules.audio": "1.0.0",
18 | "com.unity.modules.cloth": "1.0.0",
19 | "com.unity.modules.director": "1.0.0",
20 | "com.unity.modules.imageconversion": "1.0.0",
21 | "com.unity.modules.imgui": "1.0.0",
22 | "com.unity.modules.jsonserialize": "1.0.0",
23 | "com.unity.modules.particlesystem": "1.0.0",
24 | "com.unity.modules.physics": "1.0.0",
25 | "com.unity.modules.physics2d": "1.0.0",
26 | "com.unity.modules.screencapture": "1.0.0",
27 | "com.unity.modules.terrain": "1.0.0",
28 | "com.unity.modules.terrainphysics": "1.0.0",
29 | "com.unity.modules.tilemap": "1.0.0",
30 | "com.unity.modules.ui": "1.0.0",
31 | "com.unity.modules.uielements": "1.0.0",
32 | "com.unity.modules.umbra": "1.0.0",
33 | "com.unity.modules.unityanalytics": "1.0.0",
34 | "com.unity.modules.unitywebrequest": "1.0.0",
35 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
36 | "com.unity.modules.unitywebrequestaudio": "1.0.0",
37 | "com.unity.modules.unitywebrequesttexture": "1.0.0",
38 | "com.unity.modules.unitywebrequestwww": "1.0.0",
39 | "com.unity.modules.vehicles": "1.0.0",
40 | "com.unity.modules.video": "1.0.0",
41 | "com.unity.modules.vr": "1.0.0",
42 | "com.unity.modules.wind": "1.0.0",
43 | "com.unity.modules.xr": "1.0.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Packages/packages-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.unity.collab-proxy": {
4 | "version": "1.17.7",
5 | "depth": 0,
6 | "source": "registry",
7 | "dependencies": {
8 | "com.unity.services.core": "1.0.1"
9 | },
10 | "url": "https://packages.unity.cn"
11 | },
12 | "com.unity.editorcoroutines": {
13 | "version": "1.0.0",
14 | "depth": 1,
15 | "source": "registry",
16 | "dependencies": {},
17 | "url": "https://packages.unity.cn"
18 | },
19 | "com.unity.ext.nunit": {
20 | "version": "1.0.6",
21 | "depth": 1,
22 | "source": "registry",
23 | "dependencies": {},
24 | "url": "https://packages.unity.cn"
25 | },
26 | "com.unity.feature.development": {
27 | "version": "1.0.1",
28 | "depth": 0,
29 | "source": "builtin",
30 | "dependencies": {
31 | "com.unity.ide.visualstudio": "2.0.16",
32 | "com.unity.ide.rider": "3.0.16",
33 | "com.unity.ide.vscode": "1.2.5",
34 | "com.unity.editorcoroutines": "1.0.0",
35 | "com.unity.performance.profile-analyzer": "1.1.1",
36 | "com.unity.test-framework": "1.1.31",
37 | "com.unity.testtools.codecoverage": "1.2.2"
38 | }
39 | },
40 | "com.unity.ide.rider": {
41 | "version": "3.0.16",
42 | "depth": 0,
43 | "source": "registry",
44 | "dependencies": {
45 | "com.unity.ext.nunit": "1.0.6"
46 | },
47 | "url": "https://packages.unity.cn"
48 | },
49 | "com.unity.ide.visualstudio": {
50 | "version": "2.0.16",
51 | "depth": 0,
52 | "source": "registry",
53 | "dependencies": {
54 | "com.unity.test-framework": "1.1.9"
55 | },
56 | "url": "https://packages.unity.cn"
57 | },
58 | "com.unity.ide.vscode": {
59 | "version": "1.2.5",
60 | "depth": 0,
61 | "source": "registry",
62 | "dependencies": {},
63 | "url": "https://packages.unity.cn"
64 | },
65 | "com.unity.nuget.newtonsoft-json": {
66 | "version": "3.0.2",
67 | "depth": 2,
68 | "source": "registry",
69 | "dependencies": {},
70 | "url": "https://packages.unity.cn"
71 | },
72 | "com.unity.performance.profile-analyzer": {
73 | "version": "1.1.1",
74 | "depth": 1,
75 | "source": "registry",
76 | "dependencies": {},
77 | "url": "https://packages.unity.cn"
78 | },
79 | "com.unity.services.core": {
80 | "version": "1.6.0",
81 | "depth": 1,
82 | "source": "registry",
83 | "dependencies": {
84 | "com.unity.modules.unitywebrequest": "1.0.0",
85 | "com.unity.nuget.newtonsoft-json": "3.0.2",
86 | "com.unity.modules.androidjni": "1.0.0"
87 | },
88 | "url": "https://packages.unity.cn"
89 | },
90 | "com.unity.settings-manager": {
91 | "version": "1.0.3",
92 | "depth": 2,
93 | "source": "registry",
94 | "dependencies": {},
95 | "url": "https://packages.unity.cn"
96 | },
97 | "com.unity.test-framework": {
98 | "version": "1.1.31",
99 | "depth": 0,
100 | "source": "registry",
101 | "dependencies": {
102 | "com.unity.ext.nunit": "1.0.6",
103 | "com.unity.modules.imgui": "1.0.0",
104 | "com.unity.modules.jsonserialize": "1.0.0"
105 | },
106 | "url": "https://packages.unity.cn"
107 | },
108 | "com.unity.testtools.codecoverage": {
109 | "version": "1.2.2",
110 | "depth": 1,
111 | "source": "registry",
112 | "dependencies": {
113 | "com.unity.test-framework": "1.0.16",
114 | "com.unity.settings-manager": "1.0.1"
115 | },
116 | "url": "https://packages.unity.cn"
117 | },
118 | "com.unity.textmeshpro": {
119 | "version": "3.0.6",
120 | "depth": 0,
121 | "source": "registry",
122 | "dependencies": {
123 | "com.unity.ugui": "1.0.0"
124 | },
125 | "url": "https://packages.unity.cn"
126 | },
127 | "com.unity.timeline": {
128 | "version": "1.6.4",
129 | "depth": 0,
130 | "source": "registry",
131 | "dependencies": {
132 | "com.unity.modules.director": "1.0.0",
133 | "com.unity.modules.animation": "1.0.0",
134 | "com.unity.modules.audio": "1.0.0",
135 | "com.unity.modules.particlesystem": "1.0.0"
136 | },
137 | "url": "https://packages.unity.cn"
138 | },
139 | "com.unity.ugui": {
140 | "version": "1.0.0",
141 | "depth": 0,
142 | "source": "builtin",
143 | "dependencies": {
144 | "com.unity.modules.ui": "1.0.0",
145 | "com.unity.modules.imgui": "1.0.0"
146 | }
147 | },
148 | "com.unity.visualscripting": {
149 | "version": "1.7.8",
150 | "depth": 0,
151 | "source": "registry",
152 | "dependencies": {
153 | "com.unity.ugui": "1.0.0",
154 | "com.unity.modules.jsonserialize": "1.0.0"
155 | },
156 | "url": "https://packages.unity.cn"
157 | },
158 | "com.unity.modules.ai": {
159 | "version": "1.0.0",
160 | "depth": 0,
161 | "source": "builtin",
162 | "dependencies": {}
163 | },
164 | "com.unity.modules.androidjni": {
165 | "version": "1.0.0",
166 | "depth": 0,
167 | "source": "builtin",
168 | "dependencies": {}
169 | },
170 | "com.unity.modules.animation": {
171 | "version": "1.0.0",
172 | "depth": 0,
173 | "source": "builtin",
174 | "dependencies": {}
175 | },
176 | "com.unity.modules.assetbundle": {
177 | "version": "1.0.0",
178 | "depth": 0,
179 | "source": "builtin",
180 | "dependencies": {}
181 | },
182 | "com.unity.modules.audio": {
183 | "version": "1.0.0",
184 | "depth": 0,
185 | "source": "builtin",
186 | "dependencies": {}
187 | },
188 | "com.unity.modules.cloth": {
189 | "version": "1.0.0",
190 | "depth": 0,
191 | "source": "builtin",
192 | "dependencies": {
193 | "com.unity.modules.physics": "1.0.0"
194 | }
195 | },
196 | "com.unity.modules.director": {
197 | "version": "1.0.0",
198 | "depth": 0,
199 | "source": "builtin",
200 | "dependencies": {
201 | "com.unity.modules.audio": "1.0.0",
202 | "com.unity.modules.animation": "1.0.0"
203 | }
204 | },
205 | "com.unity.modules.imageconversion": {
206 | "version": "1.0.0",
207 | "depth": 0,
208 | "source": "builtin",
209 | "dependencies": {}
210 | },
211 | "com.unity.modules.imgui": {
212 | "version": "1.0.0",
213 | "depth": 0,
214 | "source": "builtin",
215 | "dependencies": {}
216 | },
217 | "com.unity.modules.jsonserialize": {
218 | "version": "1.0.0",
219 | "depth": 0,
220 | "source": "builtin",
221 | "dependencies": {}
222 | },
223 | "com.unity.modules.particlesystem": {
224 | "version": "1.0.0",
225 | "depth": 0,
226 | "source": "builtin",
227 | "dependencies": {}
228 | },
229 | "com.unity.modules.physics": {
230 | "version": "1.0.0",
231 | "depth": 0,
232 | "source": "builtin",
233 | "dependencies": {}
234 | },
235 | "com.unity.modules.physics2d": {
236 | "version": "1.0.0",
237 | "depth": 0,
238 | "source": "builtin",
239 | "dependencies": {}
240 | },
241 | "com.unity.modules.screencapture": {
242 | "version": "1.0.0",
243 | "depth": 0,
244 | "source": "builtin",
245 | "dependencies": {
246 | "com.unity.modules.imageconversion": "1.0.0"
247 | }
248 | },
249 | "com.unity.modules.subsystems": {
250 | "version": "1.0.0",
251 | "depth": 1,
252 | "source": "builtin",
253 | "dependencies": {
254 | "com.unity.modules.jsonserialize": "1.0.0"
255 | }
256 | },
257 | "com.unity.modules.terrain": {
258 | "version": "1.0.0",
259 | "depth": 0,
260 | "source": "builtin",
261 | "dependencies": {}
262 | },
263 | "com.unity.modules.terrainphysics": {
264 | "version": "1.0.0",
265 | "depth": 0,
266 | "source": "builtin",
267 | "dependencies": {
268 | "com.unity.modules.physics": "1.0.0",
269 | "com.unity.modules.terrain": "1.0.0"
270 | }
271 | },
272 | "com.unity.modules.tilemap": {
273 | "version": "1.0.0",
274 | "depth": 0,
275 | "source": "builtin",
276 | "dependencies": {
277 | "com.unity.modules.physics2d": "1.0.0"
278 | }
279 | },
280 | "com.unity.modules.ui": {
281 | "version": "1.0.0",
282 | "depth": 0,
283 | "source": "builtin",
284 | "dependencies": {}
285 | },
286 | "com.unity.modules.uielements": {
287 | "version": "1.0.0",
288 | "depth": 0,
289 | "source": "builtin",
290 | "dependencies": {
291 | "com.unity.modules.ui": "1.0.0",
292 | "com.unity.modules.imgui": "1.0.0",
293 | "com.unity.modules.jsonserialize": "1.0.0",
294 | "com.unity.modules.uielementsnative": "1.0.0"
295 | }
296 | },
297 | "com.unity.modules.uielementsnative": {
298 | "version": "1.0.0",
299 | "depth": 1,
300 | "source": "builtin",
301 | "dependencies": {
302 | "com.unity.modules.ui": "1.0.0",
303 | "com.unity.modules.imgui": "1.0.0",
304 | "com.unity.modules.jsonserialize": "1.0.0"
305 | }
306 | },
307 | "com.unity.modules.umbra": {
308 | "version": "1.0.0",
309 | "depth": 0,
310 | "source": "builtin",
311 | "dependencies": {}
312 | },
313 | "com.unity.modules.unityanalytics": {
314 | "version": "1.0.0",
315 | "depth": 0,
316 | "source": "builtin",
317 | "dependencies": {
318 | "com.unity.modules.unitywebrequest": "1.0.0",
319 | "com.unity.modules.jsonserialize": "1.0.0"
320 | }
321 | },
322 | "com.unity.modules.unitywebrequest": {
323 | "version": "1.0.0",
324 | "depth": 0,
325 | "source": "builtin",
326 | "dependencies": {}
327 | },
328 | "com.unity.modules.unitywebrequestassetbundle": {
329 | "version": "1.0.0",
330 | "depth": 0,
331 | "source": "builtin",
332 | "dependencies": {
333 | "com.unity.modules.assetbundle": "1.0.0",
334 | "com.unity.modules.unitywebrequest": "1.0.0"
335 | }
336 | },
337 | "com.unity.modules.unitywebrequestaudio": {
338 | "version": "1.0.0",
339 | "depth": 0,
340 | "source": "builtin",
341 | "dependencies": {
342 | "com.unity.modules.unitywebrequest": "1.0.0",
343 | "com.unity.modules.audio": "1.0.0"
344 | }
345 | },
346 | "com.unity.modules.unitywebrequesttexture": {
347 | "version": "1.0.0",
348 | "depth": 0,
349 | "source": "builtin",
350 | "dependencies": {
351 | "com.unity.modules.unitywebrequest": "1.0.0",
352 | "com.unity.modules.imageconversion": "1.0.0"
353 | }
354 | },
355 | "com.unity.modules.unitywebrequestwww": {
356 | "version": "1.0.0",
357 | "depth": 0,
358 | "source": "builtin",
359 | "dependencies": {
360 | "com.unity.modules.unitywebrequest": "1.0.0",
361 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
362 | "com.unity.modules.unitywebrequestaudio": "1.0.0",
363 | "com.unity.modules.audio": "1.0.0",
364 | "com.unity.modules.assetbundle": "1.0.0",
365 | "com.unity.modules.imageconversion": "1.0.0"
366 | }
367 | },
368 | "com.unity.modules.vehicles": {
369 | "version": "1.0.0",
370 | "depth": 0,
371 | "source": "builtin",
372 | "dependencies": {
373 | "com.unity.modules.physics": "1.0.0"
374 | }
375 | },
376 | "com.unity.modules.video": {
377 | "version": "1.0.0",
378 | "depth": 0,
379 | "source": "builtin",
380 | "dependencies": {
381 | "com.unity.modules.audio": "1.0.0",
382 | "com.unity.modules.ui": "1.0.0",
383 | "com.unity.modules.unitywebrequest": "1.0.0"
384 | }
385 | },
386 | "com.unity.modules.vr": {
387 | "version": "1.0.0",
388 | "depth": 0,
389 | "source": "builtin",
390 | "dependencies": {
391 | "com.unity.modules.jsonserialize": "1.0.0",
392 | "com.unity.modules.physics": "1.0.0",
393 | "com.unity.modules.xr": "1.0.0"
394 | }
395 | },
396 | "com.unity.modules.wind": {
397 | "version": "1.0.0",
398 | "depth": 0,
399 | "source": "builtin",
400 | "dependencies": {}
401 | },
402 | "com.unity.modules.xr": {
403 | "version": "1.0.0",
404 | "depth": 0,
405 | "source": "builtin",
406 | "dependencies": {
407 | "com.unity.modules.physics": "1.0.0",
408 | "com.unity.modules.jsonserialize": "1.0.0",
409 | "com.unity.modules.subsystems": "1.0.0"
410 | }
411 | }
412 | }
413 | }
414 |
--------------------------------------------------------------------------------
/ProjectSettings/AudioManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!11 &1
4 | AudioManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Volume: 1
8 | Rolloff Scale: 1
9 | Doppler Factor: 1
10 | Default Speaker Mode: 2
11 | m_SampleRate: 0
12 | m_DSPBufferSize: 1024
13 | m_VirtualVoiceCount: 512
14 | m_RealVoiceCount: 32
15 | m_SpatializerPlugin:
16 | m_AmbisonicDecoderPlugin:
17 | m_DisableAudio: 0
18 | m_VirtualizeEffects: 1
19 | m_RequestedDSPBufferSize: 1024
20 |
--------------------------------------------------------------------------------
/ProjectSettings/AutoStreamingSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1200 &1
4 | AutoStreamingSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | mSearchMode: 15
8 | mCustomSearchFile:
9 | mTextureSearchString:
10 | mMeshSearchString:
11 | mTextures: []
12 | mAudios: []
13 | mMeshes: []
14 | mScenes: []
15 | mConfigCCD:
16 | useCCD: 0
17 | cosKey:
18 | projectGuid:
19 | bucketUuid:
20 | bucketName:
21 | badgeName:
22 |
--------------------------------------------------------------------------------
/ProjectSettings/ClusterInputManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!236 &1
4 | ClusterInputManager:
5 | m_ObjectHideFlags: 0
6 | m_Inputs: []
7 |
--------------------------------------------------------------------------------
/ProjectSettings/DynamicsManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!55 &1
4 | PhysicsManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 11
7 | m_Gravity: {x: 0, y: -9.81, z: 0}
8 | m_DefaultMaterial: {fileID: 0}
9 | m_BounceThreshold: 2
10 | m_SleepThreshold: 0.005
11 | m_DefaultContactOffset: 0.01
12 | m_DefaultSolverIterations: 6
13 | m_DefaultSolverVelocityIterations: 1
14 | m_QueriesHitBackfaces: 0
15 | m_QueriesHitTriggers: 1
16 | m_EnableAdaptiveForce: 0
17 | m_ClothInterCollisionDistance: 0
18 | m_ClothInterCollisionStiffness: 0
19 | m_ContactsGeneration: 1
20 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
21 | m_AutoSimulation: 1
22 | m_AutoSyncTransforms: 0
23 | m_ReuseCollisionCallbacks: 1
24 | m_ClothInterCollisionSettingsToggle: 0
25 | m_ContactPairsMode: 0
26 | m_BroadphaseType: 0
27 | m_WorldBounds:
28 | m_Center: {x: 0, y: 0, z: 0}
29 | m_Extent: {x: 250, y: 250, z: 250}
30 | m_WorldSubdivisions: 8
31 | m_FrictionType: 0
32 | m_EnableEnhancedDeterminism: 0
33 | m_EnableUnifiedHeightmaps: 1
34 | m_DefaultMaxAngluarSpeed: 7
35 |
--------------------------------------------------------------------------------
/ProjectSettings/EditorBuildSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1045 &1
4 | EditorBuildSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Scenes: []
8 | m_configObjects: {}
9 |
--------------------------------------------------------------------------------
/ProjectSettings/EditorSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!159 &1
4 | EditorSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 11
7 | m_ExternalVersionControlSupport: Visible Meta Files
8 | m_SerializationMode: 2
9 | m_LineEndingsForNewScripts: 0
10 | m_DefaultBehaviorMode: 0
11 | m_PrefabRegularEnvironment: {fileID: 0}
12 | m_PrefabUIEnvironment: {fileID: 0}
13 | m_SpritePackerMode: 0
14 | m_SpritePackerPaddingPower: 1
15 | m_EtcTextureCompressorBehavior: 1
16 | m_EtcTextureFastCompressor: 1
17 | m_EtcTextureNormalCompressor: 2
18 | m_EtcTextureBestCompressor: 4
19 | m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref
20 | m_ProjectGenerationRootNamespace:
21 | m_CollabEditorSettings:
22 | inProgressEnabled: 1
23 | m_EnableTextureStreamingInEditMode: 1
24 | m_EnableTextureStreamingInPlayMode: 1
25 | m_AsyncShaderCompilation: 1
26 | m_EnterPlayModeOptionsEnabled: 0
27 | m_EnterPlayModeOptions: 3
28 | m_ShowLightmapResolutionOverlay: 1
29 | m_UseLegacyProbeSampleCount: 0
30 | m_SerializeInlineMappingsOnOneLine: 1
31 |
--------------------------------------------------------------------------------
/ProjectSettings/GraphicsSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!30 &1
4 | GraphicsSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 13
7 | m_Deferred:
8 | m_Mode: 1
9 | m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
10 | m_DeferredReflections:
11 | m_Mode: 1
12 | m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
13 | m_ScreenSpaceShadows:
14 | m_Mode: 1
15 | m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
16 | m_LegacyDeferred:
17 | m_Mode: 1
18 | m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
19 | m_DepthNormals:
20 | m_Mode: 1
21 | m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
22 | m_MotionVectors:
23 | m_Mode: 1
24 | m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
25 | m_LightHalo:
26 | m_Mode: 1
27 | m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
28 | m_LensFlare:
29 | m_Mode: 1
30 | m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
31 | m_AlwaysIncludedShaders:
32 | - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
33 | - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
34 | - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
35 | - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
36 | - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
37 | - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
38 | m_PreloadedShaders: []
39 | m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
40 | type: 0}
41 | m_CustomRenderPipeline: {fileID: 0}
42 | m_TransparencySortMode: 0
43 | m_TransparencySortAxis: {x: 0, y: 0, z: 1}
44 | m_DefaultRenderingPath: 1
45 | m_DefaultMobileRenderingPath: 1
46 | m_TierSettings: []
47 | m_LightmapStripping: 0
48 | m_FogStripping: 0
49 | m_InstancingStripping: 0
50 | m_LightmapKeepPlain: 1
51 | m_LightmapKeepDirCombined: 1
52 | m_LightmapKeepDynamicPlain: 1
53 | m_LightmapKeepDynamicDirCombined: 1
54 | m_LightmapKeepShadowMask: 1
55 | m_LightmapKeepSubtractive: 1
56 | m_FogKeepLinear: 1
57 | m_FogKeepExp: 1
58 | m_FogKeepExp2: 1
59 | m_AlbedoSwatchInfos: []
60 | m_LightsUseLinearIntensity: 0
61 | m_LightsUseColorTemperature: 0
62 | m_LogWhenShaderIsCompiled: 0
63 | m_AllowEnlightenSupportForUpgradedProject: 0
64 |
--------------------------------------------------------------------------------
/ProjectSettings/InputManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!13 &1
4 | InputManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Axes:
8 | - serializedVersion: 3
9 | m_Name: Horizontal
10 | descriptiveName:
11 | descriptiveNegativeName:
12 | negativeButton: left
13 | positiveButton: right
14 | altNegativeButton: a
15 | altPositiveButton: d
16 | gravity: 3
17 | dead: 0.001
18 | sensitivity: 3
19 | snap: 1
20 | invert: 0
21 | type: 0
22 | axis: 0
23 | joyNum: 0
24 | - serializedVersion: 3
25 | m_Name: Vertical
26 | descriptiveName:
27 | descriptiveNegativeName:
28 | negativeButton: down
29 | positiveButton: up
30 | altNegativeButton: s
31 | altPositiveButton: w
32 | gravity: 3
33 | dead: 0.001
34 | sensitivity: 3
35 | snap: 1
36 | invert: 0
37 | type: 0
38 | axis: 0
39 | joyNum: 0
40 | - serializedVersion: 3
41 | m_Name: Fire1
42 | descriptiveName:
43 | descriptiveNegativeName:
44 | negativeButton:
45 | positiveButton: left ctrl
46 | altNegativeButton:
47 | altPositiveButton: mouse 0
48 | gravity: 1000
49 | dead: 0.001
50 | sensitivity: 1000
51 | snap: 0
52 | invert: 0
53 | type: 0
54 | axis: 0
55 | joyNum: 0
56 | - serializedVersion: 3
57 | m_Name: Fire2
58 | descriptiveName:
59 | descriptiveNegativeName:
60 | negativeButton:
61 | positiveButton: left alt
62 | altNegativeButton:
63 | altPositiveButton: mouse 1
64 | gravity: 1000
65 | dead: 0.001
66 | sensitivity: 1000
67 | snap: 0
68 | invert: 0
69 | type: 0
70 | axis: 0
71 | joyNum: 0
72 | - serializedVersion: 3
73 | m_Name: Fire3
74 | descriptiveName:
75 | descriptiveNegativeName:
76 | negativeButton:
77 | positiveButton: left shift
78 | altNegativeButton:
79 | altPositiveButton: mouse 2
80 | gravity: 1000
81 | dead: 0.001
82 | sensitivity: 1000
83 | snap: 0
84 | invert: 0
85 | type: 0
86 | axis: 0
87 | joyNum: 0
88 | - serializedVersion: 3
89 | m_Name: Jump
90 | descriptiveName:
91 | descriptiveNegativeName:
92 | negativeButton:
93 | positiveButton: space
94 | altNegativeButton:
95 | altPositiveButton:
96 | gravity: 1000
97 | dead: 0.001
98 | sensitivity: 1000
99 | snap: 0
100 | invert: 0
101 | type: 0
102 | axis: 0
103 | joyNum: 0
104 | - serializedVersion: 3
105 | m_Name: Mouse X
106 | descriptiveName:
107 | descriptiveNegativeName:
108 | negativeButton:
109 | positiveButton:
110 | altNegativeButton:
111 | altPositiveButton:
112 | gravity: 0
113 | dead: 0
114 | sensitivity: 0.1
115 | snap: 0
116 | invert: 0
117 | type: 1
118 | axis: 0
119 | joyNum: 0
120 | - serializedVersion: 3
121 | m_Name: Mouse Y
122 | descriptiveName:
123 | descriptiveNegativeName:
124 | negativeButton:
125 | positiveButton:
126 | altNegativeButton:
127 | altPositiveButton:
128 | gravity: 0
129 | dead: 0
130 | sensitivity: 0.1
131 | snap: 0
132 | invert: 0
133 | type: 1
134 | axis: 1
135 | joyNum: 0
136 | - serializedVersion: 3
137 | m_Name: Mouse ScrollWheel
138 | descriptiveName:
139 | descriptiveNegativeName:
140 | negativeButton:
141 | positiveButton:
142 | altNegativeButton:
143 | altPositiveButton:
144 | gravity: 0
145 | dead: 0
146 | sensitivity: 0.1
147 | snap: 0
148 | invert: 0
149 | type: 1
150 | axis: 2
151 | joyNum: 0
152 | - serializedVersion: 3
153 | m_Name: Horizontal
154 | descriptiveName:
155 | descriptiveNegativeName:
156 | negativeButton:
157 | positiveButton:
158 | altNegativeButton:
159 | altPositiveButton:
160 | gravity: 0
161 | dead: 0.19
162 | sensitivity: 1
163 | snap: 0
164 | invert: 0
165 | type: 2
166 | axis: 0
167 | joyNum: 0
168 | - serializedVersion: 3
169 | m_Name: Vertical
170 | descriptiveName:
171 | descriptiveNegativeName:
172 | negativeButton:
173 | positiveButton:
174 | altNegativeButton:
175 | altPositiveButton:
176 | gravity: 0
177 | dead: 0.19
178 | sensitivity: 1
179 | snap: 0
180 | invert: 1
181 | type: 2
182 | axis: 1
183 | joyNum: 0
184 | - serializedVersion: 3
185 | m_Name: Fire1
186 | descriptiveName:
187 | descriptiveNegativeName:
188 | negativeButton:
189 | positiveButton: joystick button 0
190 | altNegativeButton:
191 | altPositiveButton:
192 | gravity: 1000
193 | dead: 0.001
194 | sensitivity: 1000
195 | snap: 0
196 | invert: 0
197 | type: 0
198 | axis: 0
199 | joyNum: 0
200 | - serializedVersion: 3
201 | m_Name: Fire2
202 | descriptiveName:
203 | descriptiveNegativeName:
204 | negativeButton:
205 | positiveButton: joystick button 1
206 | altNegativeButton:
207 | altPositiveButton:
208 | gravity: 1000
209 | dead: 0.001
210 | sensitivity: 1000
211 | snap: 0
212 | invert: 0
213 | type: 0
214 | axis: 0
215 | joyNum: 0
216 | - serializedVersion: 3
217 | m_Name: Fire3
218 | descriptiveName:
219 | descriptiveNegativeName:
220 | negativeButton:
221 | positiveButton: joystick button 2
222 | altNegativeButton:
223 | altPositiveButton:
224 | gravity: 1000
225 | dead: 0.001
226 | sensitivity: 1000
227 | snap: 0
228 | invert: 0
229 | type: 0
230 | axis: 0
231 | joyNum: 0
232 | - serializedVersion: 3
233 | m_Name: Jump
234 | descriptiveName:
235 | descriptiveNegativeName:
236 | negativeButton:
237 | positiveButton: joystick button 3
238 | altNegativeButton:
239 | altPositiveButton:
240 | gravity: 1000
241 | dead: 0.001
242 | sensitivity: 1000
243 | snap: 0
244 | invert: 0
245 | type: 0
246 | axis: 0
247 | joyNum: 0
248 | - serializedVersion: 3
249 | m_Name: Submit
250 | descriptiveName:
251 | descriptiveNegativeName:
252 | negativeButton:
253 | positiveButton: return
254 | altNegativeButton:
255 | altPositiveButton: joystick button 0
256 | gravity: 1000
257 | dead: 0.001
258 | sensitivity: 1000
259 | snap: 0
260 | invert: 0
261 | type: 0
262 | axis: 0
263 | joyNum: 0
264 | - serializedVersion: 3
265 | m_Name: Submit
266 | descriptiveName:
267 | descriptiveNegativeName:
268 | negativeButton:
269 | positiveButton: enter
270 | altNegativeButton:
271 | altPositiveButton: space
272 | gravity: 1000
273 | dead: 0.001
274 | sensitivity: 1000
275 | snap: 0
276 | invert: 0
277 | type: 0
278 | axis: 0
279 | joyNum: 0
280 | - serializedVersion: 3
281 | m_Name: Cancel
282 | descriptiveName:
283 | descriptiveNegativeName:
284 | negativeButton:
285 | positiveButton: escape
286 | altNegativeButton:
287 | altPositiveButton: joystick button 1
288 | gravity: 1000
289 | dead: 0.001
290 | sensitivity: 1000
291 | snap: 0
292 | invert: 0
293 | type: 0
294 | axis: 0
295 | joyNum: 0
296 | - serializedVersion: 3
297 | m_Name: Enable Debug Button 1
298 | descriptiveName:
299 | descriptiveNegativeName:
300 | negativeButton:
301 | positiveButton: left ctrl
302 | altNegativeButton:
303 | altPositiveButton: joystick button 8
304 | gravity: 0
305 | dead: 0
306 | sensitivity: 0
307 | snap: 0
308 | invert: 0
309 | type: 0
310 | axis: 0
311 | joyNum: 0
312 | - serializedVersion: 3
313 | m_Name: Enable Debug Button 2
314 | descriptiveName:
315 | descriptiveNegativeName:
316 | negativeButton:
317 | positiveButton: backspace
318 | altNegativeButton:
319 | altPositiveButton: joystick button 9
320 | gravity: 0
321 | dead: 0
322 | sensitivity: 0
323 | snap: 0
324 | invert: 0
325 | type: 0
326 | axis: 0
327 | joyNum: 0
328 | - serializedVersion: 3
329 | m_Name: Debug Reset
330 | descriptiveName:
331 | descriptiveNegativeName:
332 | negativeButton:
333 | positiveButton: left alt
334 | altNegativeButton:
335 | altPositiveButton: joystick button 1
336 | gravity: 0
337 | dead: 0
338 | sensitivity: 0
339 | snap: 0
340 | invert: 0
341 | type: 0
342 | axis: 0
343 | joyNum: 0
344 | - serializedVersion: 3
345 | m_Name: Debug Next
346 | descriptiveName:
347 | descriptiveNegativeName:
348 | negativeButton:
349 | positiveButton: page down
350 | altNegativeButton:
351 | altPositiveButton: joystick button 5
352 | gravity: 0
353 | dead: 0
354 | sensitivity: 0
355 | snap: 0
356 | invert: 0
357 | type: 0
358 | axis: 0
359 | joyNum: 0
360 | - serializedVersion: 3
361 | m_Name: Debug Previous
362 | descriptiveName:
363 | descriptiveNegativeName:
364 | negativeButton:
365 | positiveButton: page up
366 | altNegativeButton:
367 | altPositiveButton: joystick button 4
368 | gravity: 0
369 | dead: 0
370 | sensitivity: 0
371 | snap: 0
372 | invert: 0
373 | type: 0
374 | axis: 0
375 | joyNum: 0
376 | - serializedVersion: 3
377 | m_Name: Debug Validate
378 | descriptiveName:
379 | descriptiveNegativeName:
380 | negativeButton:
381 | positiveButton: return
382 | altNegativeButton:
383 | altPositiveButton: joystick button 0
384 | gravity: 0
385 | dead: 0
386 | sensitivity: 0
387 | snap: 0
388 | invert: 0
389 | type: 0
390 | axis: 0
391 | joyNum: 0
392 | - serializedVersion: 3
393 | m_Name: Debug Persistent
394 | descriptiveName:
395 | descriptiveNegativeName:
396 | negativeButton:
397 | positiveButton: right shift
398 | altNegativeButton:
399 | altPositiveButton: joystick button 2
400 | gravity: 0
401 | dead: 0
402 | sensitivity: 0
403 | snap: 0
404 | invert: 0
405 | type: 0
406 | axis: 0
407 | joyNum: 0
408 | - serializedVersion: 3
409 | m_Name: Debug Multiplier
410 | descriptiveName:
411 | descriptiveNegativeName:
412 | negativeButton:
413 | positiveButton: left shift
414 | altNegativeButton:
415 | altPositiveButton: joystick button 3
416 | gravity: 0
417 | dead: 0
418 | sensitivity: 0
419 | snap: 0
420 | invert: 0
421 | type: 0
422 | axis: 0
423 | joyNum: 0
424 | - serializedVersion: 3
425 | m_Name: Debug Horizontal
426 | descriptiveName:
427 | descriptiveNegativeName:
428 | negativeButton: left
429 | positiveButton: right
430 | altNegativeButton:
431 | altPositiveButton:
432 | gravity: 1000
433 | dead: 0.001
434 | sensitivity: 1000
435 | snap: 0
436 | invert: 0
437 | type: 0
438 | axis: 0
439 | joyNum: 0
440 | - serializedVersion: 3
441 | m_Name: Debug Vertical
442 | descriptiveName:
443 | descriptiveNegativeName:
444 | negativeButton: down
445 | positiveButton: up
446 | altNegativeButton:
447 | altPositiveButton:
448 | gravity: 1000
449 | dead: 0.001
450 | sensitivity: 1000
451 | snap: 0
452 | invert: 0
453 | type: 0
454 | axis: 0
455 | joyNum: 0
456 | - serializedVersion: 3
457 | m_Name: Debug Vertical
458 | descriptiveName:
459 | descriptiveNegativeName:
460 | negativeButton: down
461 | positiveButton: up
462 | altNegativeButton:
463 | altPositiveButton:
464 | gravity: 1000
465 | dead: 0.001
466 | sensitivity: 1000
467 | snap: 0
468 | invert: 0
469 | type: 2
470 | axis: 6
471 | joyNum: 0
472 | - serializedVersion: 3
473 | m_Name: Debug Horizontal
474 | descriptiveName:
475 | descriptiveNegativeName:
476 | negativeButton: left
477 | positiveButton: right
478 | altNegativeButton:
479 | altPositiveButton:
480 | gravity: 1000
481 | dead: 0.001
482 | sensitivity: 1000
483 | snap: 0
484 | invert: 0
485 | type: 2
486 | axis: 5
487 | joyNum: 0
488 | m_UsePhysicalKeys: 0
489 |
--------------------------------------------------------------------------------
/ProjectSettings/MemorySettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!387306366 &1
4 | MemorySettings:
5 | m_ObjectHideFlags: 0
6 | m_EditorMemorySettings:
7 | m_MainAllocatorBlockSize: -1
8 | m_ThreadAllocatorBlockSize: -1
9 | m_MainGfxBlockSize: -1
10 | m_ThreadGfxBlockSize: -1
11 | m_CacheBlockSize: -1
12 | m_TypetreeBlockSize: -1
13 | m_ProfilerBlockSize: -1
14 | m_ProfilerEditorBlockSize: -1
15 | m_BucketAllocatorGranularity: -1
16 | m_BucketAllocatorBucketsCount: -1
17 | m_BucketAllocatorBlockSize: -1
18 | m_BucketAllocatorBlockCount: -1
19 | m_ProfilerBucketAllocatorGranularity: -1
20 | m_ProfilerBucketAllocatorBucketsCount: -1
21 | m_ProfilerBucketAllocatorBlockSize: -1
22 | m_ProfilerBucketAllocatorBlockCount: -1
23 | m_TempAllocatorSizeMain: -1
24 | m_JobTempAllocatorBlockSize: -1
25 | m_BackgroundJobTempAllocatorBlockSize: -1
26 | m_JobTempAllocatorReducedBlockSize: -1
27 | m_TempAllocatorSizeGIBakingWorker: -1
28 | m_TempAllocatorSizeNavMeshWorker: -1
29 | m_TempAllocatorSizeAudioWorker: -1
30 | m_TempAllocatorSizeCloudWorker: -1
31 | m_TempAllocatorSizeGfx: -1
32 | m_TempAllocatorSizeJobWorker: -1
33 | m_TempAllocatorSizeBackgroundWorker: -1
34 | m_TempAllocatorSizePreloadManager: -1
35 | m_PlatformMemorySettings: {}
36 |
--------------------------------------------------------------------------------
/ProjectSettings/NavMeshAreas.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!126 &1
4 | NavMeshProjectSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | areas:
8 | - name: Walkable
9 | cost: 1
10 | - name: Not Walkable
11 | cost: 1
12 | - name: Jump
13 | cost: 2
14 | - name:
15 | cost: 1
16 | - name:
17 | cost: 1
18 | - name:
19 | cost: 1
20 | - name:
21 | cost: 1
22 | - name:
23 | cost: 1
24 | - name:
25 | cost: 1
26 | - name:
27 | cost: 1
28 | - name:
29 | cost: 1
30 | - name:
31 | cost: 1
32 | - name:
33 | cost: 1
34 | - name:
35 | cost: 1
36 | - name:
37 | cost: 1
38 | - name:
39 | cost: 1
40 | - name:
41 | cost: 1
42 | - name:
43 | cost: 1
44 | - name:
45 | cost: 1
46 | - name:
47 | cost: 1
48 | - name:
49 | cost: 1
50 | - name:
51 | cost: 1
52 | - name:
53 | cost: 1
54 | - name:
55 | cost: 1
56 | - name:
57 | cost: 1
58 | - name:
59 | cost: 1
60 | - name:
61 | cost: 1
62 | - name:
63 | cost: 1
64 | - name:
65 | cost: 1
66 | - name:
67 | cost: 1
68 | - name:
69 | cost: 1
70 | - name:
71 | cost: 1
72 | m_LastAgentTypeID: -887442657
73 | m_Settings:
74 | - serializedVersion: 2
75 | agentTypeID: 0
76 | agentRadius: 0.5
77 | agentHeight: 2
78 | agentSlope: 45
79 | agentClimb: 0.75
80 | ledgeDropHeight: 0
81 | maxJumpAcrossDistance: 0
82 | minRegionArea: 2
83 | manualCellSize: 0
84 | cellSize: 0.16666667
85 | manualTileSize: 0
86 | tileSize: 256
87 | accuratePlacement: 0
88 | debug:
89 | m_Flags: 0
90 | m_SettingNames:
91 | - Humanoid
92 |
--------------------------------------------------------------------------------
/ProjectSettings/PackageManagerSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &1
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 61
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
13 | m_Name:
14 | m_EditorClassIdentifier:
15 | m_EnablePreReleasePackages: 0
16 | m_EnablePackageDependencies: 0
17 | m_AdvancedSettingsExpanded: 1
18 | m_ScopedRegistriesSettingsExpanded: 1
19 | m_SeeAllPackageVersions: 0
20 | oneTimeWarningShown: 0
21 | m_Registries:
22 | - m_Id: main
23 | m_Name:
24 | m_Url: https://packages.unity.cn
25 | m_Scopes: []
26 | m_IsDefault: 1
27 | m_Capabilities: 7
28 | m_ConfigSource: 0
29 | m_UserSelectedRegistryName:
30 | m_UserAddingNewScopedRegistry: 0
31 | m_RegistryInfoDraft:
32 | m_Modified: 0
33 | m_ErrorMessage:
34 | m_UserModificationsInstanceId: -830
35 | m_OriginalInstanceId: -832
36 | m_LoadAssets: 0
37 |
--------------------------------------------------------------------------------
/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "m_Name": "Settings",
3 | "m_Path": "ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json",
4 | "m_Dictionary": {
5 | "m_DictionaryValues": []
6 | }
7 | }
--------------------------------------------------------------------------------
/ProjectSettings/Physics2DSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!19 &1
4 | Physics2DSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 4
7 | m_Gravity: {x: 0, y: -9.81}
8 | m_DefaultMaterial: {fileID: 0}
9 | m_VelocityIterations: 8
10 | m_PositionIterations: 3
11 | m_VelocityThreshold: 1
12 | m_MaxLinearCorrection: 0.2
13 | m_MaxAngularCorrection: 8
14 | m_MaxTranslationSpeed: 100
15 | m_MaxRotationSpeed: 360
16 | m_BaumgarteScale: 0.2
17 | m_BaumgarteTimeOfImpactScale: 0.75
18 | m_TimeToSleep: 0.5
19 | m_LinearSleepTolerance: 0.01
20 | m_AngularSleepTolerance: 2
21 | m_DefaultContactOffset: 0.01
22 | m_JobOptions:
23 | serializedVersion: 2
24 | useMultithreading: 0
25 | useConsistencySorting: 0
26 | m_InterpolationPosesPerJob: 100
27 | m_NewContactsPerJob: 30
28 | m_CollideContactsPerJob: 100
29 | m_ClearFlagsPerJob: 200
30 | m_ClearBodyForcesPerJob: 200
31 | m_SyncDiscreteFixturesPerJob: 50
32 | m_SyncContinuousFixturesPerJob: 50
33 | m_FindNearestContactsPerJob: 100
34 | m_UpdateTriggerContactsPerJob: 100
35 | m_IslandSolverCostThreshold: 100
36 | m_IslandSolverBodyCostScale: 1
37 | m_IslandSolverContactCostScale: 10
38 | m_IslandSolverJointCostScale: 10
39 | m_IslandSolverBodiesPerJob: 50
40 | m_IslandSolverContactsPerJob: 50
41 | m_AutoSimulation: 1
42 | m_QueriesHitTriggers: 1
43 | m_QueriesStartInColliders: 1
44 | m_CallbacksOnDisable: 1
45 | m_ReuseCollisionCallbacks: 1
46 | m_AutoSyncTransforms: 0
47 | m_AlwaysShowColliders: 0
48 | m_ShowColliderSleep: 1
49 | m_ShowColliderContacts: 0
50 | m_ShowColliderAABB: 0
51 | m_ContactArrowScale: 0.2
52 | m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
53 | m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
54 | m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
55 | m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
56 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
57 |
--------------------------------------------------------------------------------
/ProjectSettings/PresetManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1386491679 &1
4 | PresetManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_DefaultPresets: {}
8 |
--------------------------------------------------------------------------------
/ProjectSettings/ProjectSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!129 &1
4 | PlayerSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 23
7 | productGUID: 17985cfac24b4a7429bf22378ab75bba
8 | AndroidProfiler: 0
9 | AndroidFilterTouchesWhenObscured: 0
10 | AndroidEnableSustainedPerformanceMode: 0
11 | defaultScreenOrientation: 4
12 | targetDevice: 2
13 | useOnDemandResources: 0
14 | accelerometerFrequency: 60
15 | companyName: DefaultCompany
16 | productName: CommandConsole
17 | defaultCursor: {fileID: 0}
18 | cursorHotspot: {x: 0, y: 0}
19 | m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
20 | m_ShowUnitySplashScreen: 1
21 | m_ShowUnitySplashLogo: 1
22 | m_SplashScreenOverlayOpacity: 1
23 | m_SplashScreenAnimation: 1
24 | m_SplashScreenLogoStyle: 1
25 | m_SplashScreenDrawMode: 0
26 | m_SplashScreenBackgroundAnimationZoom: 1
27 | m_SplashScreenLogoAnimationZoom: 1
28 | m_SplashScreenBackgroundLandscapeAspect: 1
29 | m_SplashScreenBackgroundPortraitAspect: 1
30 | m_SplashScreenBackgroundLandscapeUvs:
31 | serializedVersion: 2
32 | x: 0
33 | y: 0
34 | width: 1
35 | height: 1
36 | m_SplashScreenBackgroundPortraitUvs:
37 | serializedVersion: 2
38 | x: 0
39 | y: 0
40 | width: 1
41 | height: 1
42 | m_SplashScreenLogos: []
43 | m_VirtualRealitySplashScreen: {fileID: 0}
44 | m_ShowUnitySplashAds: 0
45 | m_AdsAndroidGameId:
46 | m_AdsIosGameId:
47 | m_ShowSplashAdsSlogan: 0
48 | m_SloganImage: {fileID: 0}
49 | m_SloganHeight: 150
50 | m_HolographicTrackingLossScreen: {fileID: 0}
51 | defaultScreenWidth: 1920
52 | defaultScreenHeight: 1080
53 | defaultScreenWidthWeb: 960
54 | defaultScreenHeightWeb: 600
55 | m_StereoRenderingPath: 0
56 | m_ActiveColorSpace: 1
57 | m_MTRendering: 1
58 | mipStripping: 0
59 | numberOfMipsStripped: 0
60 | m_StackTraceTypes: 010000000100000001000000010000000100000001000000
61 | iosShowActivityIndicatorOnLoading: -1
62 | androidShowActivityIndicatorOnLoading: -1
63 | iosUseCustomAppBackgroundBehavior: 0
64 | iosAllowHTTPDownload: 1
65 | allowedAutorotateToPortrait: 1
66 | allowedAutorotateToPortraitUpsideDown: 1
67 | allowedAutorotateToLandscapeRight: 1
68 | allowedAutorotateToLandscapeLeft: 1
69 | useOSAutorotation: 1
70 | use32BitDisplayBuffer: 1
71 | preserveFramebufferAlpha: 0
72 | disableDepthAndStencilBuffers: 0
73 | androidStartInFullscreen: 1
74 | androidRenderOutsideSafeArea: 1
75 | androidUseSwappy: 1
76 | androidBlitType: 0
77 | androidResizableWindow: 0
78 | androidDefaultWindowWidth: 1920
79 | androidDefaultWindowHeight: 1080
80 | androidMinimumWindowWidth: 400
81 | androidMinimumWindowHeight: 300
82 | androidFullscreenMode: 1
83 | defaultIsNativeResolution: 1
84 | macRetinaSupport: 1
85 | runInBackground: 1
86 | captureSingleScreen: 0
87 | muteOtherAudioSources: 0
88 | Prepare IOS For Recording: 0
89 | Force IOS Speakers When Recording: 0
90 | deferSystemGesturesMode: 0
91 | hideHomeButton: 0
92 | submitAnalytics: 1
93 | usePlayerLog: 1
94 | autoStreaming: 0
95 | useAnimationStreaming: 0
96 | useFontStreaming: 0
97 | autoStreamingId:
98 | instantGameAppId:
99 | bakeCollisionMeshes: 0
100 | forceSingleInstance: 0
101 | useFlipModelSwapchain: 1
102 | resizableWindow: 0
103 | useMacAppStoreValidation: 0
104 | macAppStoreCategory: public.app-category.games
105 | gpuSkinning: 1
106 | xboxPIXTextureCapture: 0
107 | xboxEnableAvatar: 0
108 | xboxEnableKinect: 0
109 | xboxEnableKinectAutoTracking: 0
110 | xboxEnableFitness: 0
111 | visibleInBackground: 1
112 | allowFullscreenSwitch: 1
113 | fullscreenMode: 1
114 | xboxSpeechDB: 0
115 | xboxEnableHeadOrientation: 0
116 | xboxEnableGuest: 0
117 | xboxEnablePIXSampling: 0
118 | metalFramebufferOnly: 0
119 | xboxOneResolution: 0
120 | xboxOneSResolution: 0
121 | xboxOneXResolution: 3
122 | xboxOneMonoLoggingLevel: 0
123 | xboxOneLoggingLevel: 1
124 | xboxOneDisableEsram: 0
125 | xboxOneEnableTypeOptimization: 0
126 | xboxOnePresentImmediateThreshold: 0
127 | switchQueueCommandMemory: 0
128 | switchQueueControlMemory: 16384
129 | switchQueueComputeMemory: 262144
130 | switchNVNShaderPoolsGranularity: 33554432
131 | switchNVNDefaultPoolsGranularity: 16777216
132 | switchNVNOtherPoolsGranularity: 16777216
133 | switchNVNMaxPublicTextureIDCount: 0
134 | switchNVNMaxPublicSamplerIDCount: 0
135 | stadiaPresentMode: 0
136 | stadiaTargetFramerate: 0
137 | vulkanNumSwapchainBuffers: 3
138 | vulkanEnableSetSRGBWrite: 0
139 | vulkanEnablePreTransform: 1
140 | vulkanEnableLateAcquireNextImage: 0
141 | vulkanEnableCommandBufferRecycling: 1
142 | m_SupportedAspectRatios:
143 | 4:3: 1
144 | 5:4: 1
145 | 16:10: 1
146 | 16:9: 1
147 | Others: 1
148 | bundleVersion: 0.1
149 | preloadedAssets: []
150 | metroInputSource: 0
151 | wsaTransparentSwapchain: 0
152 | m_HolographicPauseOnTrackingLoss: 1
153 | xboxOneDisableKinectGpuReservation: 1
154 | xboxOneEnable7thCore: 1
155 | vrSettings:
156 | enable360StereoCapture: 0
157 | isWsaHolographicRemotingEnabled: 0
158 | enableFrameTimingStats: 0
159 | enableOpenGLProfilerGPURecorders: 1
160 | useHDRDisplay: 0
161 | D3DHDRBitDepth: 0
162 | m_ColorGamuts: 00000000
163 | targetPixelDensity: 30
164 | resolutionScalingMode: 0
165 | resetResolutionOnWindowResize: 0
166 | androidSupportedAspectRatio: 1
167 | androidMaxAspectRatio: 2.1
168 | applicationIdentifier: {}
169 | buildNumber:
170 | Standalone: 0
171 | iPhone: 0
172 | tvOS: 0
173 | overrideDefaultApplicationIdentifier: 0
174 | AndroidBundleVersionCode: 1
175 | AndroidMinSdkVersion: 22
176 | AndroidTargetSdkVersion: 0
177 | AndroidPreferredInstallLocation: 1
178 | aotOptions:
179 | stripEngineCode: 1
180 | iPhoneStrippingLevel: 0
181 | iPhoneScriptCallOptimization: 0
182 | ForceInternetPermission: 0
183 | ForceSDCardPermission: 0
184 | CreateWallpaper: 0
185 | APKExpansionFiles: 0
186 | keepLoadedShadersAlive: 0
187 | StripUnusedMeshComponents: 1
188 | VertexChannelCompressionMask: 4054
189 | iPhoneSdkVersion: 988
190 | iOSTargetOSVersionString: 11.0
191 | tvOSSdkVersion: 0
192 | tvOSRequireExtendedGameController: 0
193 | tvOSTargetOSVersionString: 11.0
194 | uIPrerenderedIcon: 0
195 | uIRequiresPersistentWiFi: 0
196 | uIRequiresFullScreen: 1
197 | uIStatusBarHidden: 1
198 | uIExitOnSuspend: 0
199 | uIStatusBarStyle: 0
200 | appleTVSplashScreen: {fileID: 0}
201 | appleTVSplashScreen2x: {fileID: 0}
202 | tvOSSmallIconLayers: []
203 | tvOSSmallIconLayers2x: []
204 | tvOSLargeIconLayers: []
205 | tvOSLargeIconLayers2x: []
206 | tvOSTopShelfImageLayers: []
207 | tvOSTopShelfImageLayers2x: []
208 | tvOSTopShelfImageWideLayers: []
209 | tvOSTopShelfImageWideLayers2x: []
210 | iOSLaunchScreenType: 0
211 | iOSLaunchScreenPortrait: {fileID: 0}
212 | iOSLaunchScreenLandscape: {fileID: 0}
213 | iOSLaunchScreenBackgroundColor:
214 | serializedVersion: 2
215 | rgba: 0
216 | iOSLaunchScreenFillPct: 100
217 | iOSLaunchScreenSize: 100
218 | iOSLaunchScreenCustomXibPath:
219 | iOSLaunchScreeniPadType: 0
220 | iOSLaunchScreeniPadImage: {fileID: 0}
221 | iOSLaunchScreeniPadBackgroundColor:
222 | serializedVersion: 2
223 | rgba: 0
224 | iOSLaunchScreeniPadFillPct: 100
225 | iOSLaunchScreeniPadSize: 100
226 | iOSLaunchScreeniPadCustomXibPath:
227 | iOSLaunchScreenCustomStoryboardPath:
228 | iOSLaunchScreeniPadCustomStoryboardPath:
229 | iOSDeviceRequirements: []
230 | iOSURLSchemes: []
231 | macOSURLSchemes: []
232 | iOSBackgroundModes: 0
233 | iOSMetalForceHardShadows: 0
234 | metalEditorSupport: 1
235 | metalAPIValidation: 1
236 | iOSRenderExtraFrameOnPause: 0
237 | iosCopyPluginsCodeInsteadOfSymlink: 0
238 | appleDeveloperTeamID:
239 | iOSManualSigningProvisioningProfileID:
240 | tvOSManualSigningProvisioningProfileID:
241 | iOSManualSigningProvisioningProfileType: 0
242 | tvOSManualSigningProvisioningProfileType: 0
243 | appleEnableAutomaticSigning: 0
244 | iOSRequireARKit: 0
245 | iOSAutomaticallyDetectAndAddCapabilities: 1
246 | appleEnableProMotion: 0
247 | shaderPrecisionModel: 0
248 | clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea
249 | templatePackageId: com.unity.template.3d@8.1.3
250 | templateDefaultScene: Assets/Scenes/SampleScene.unity
251 | useCustomMainManifest: 0
252 | useCustomLauncherManifest: 0
253 | useCustomMainGradleTemplate: 0
254 | useCustomLauncherGradleManifest: 0
255 | useCustomBaseGradleTemplate: 0
256 | useCustomGradlePropertiesTemplate: 0
257 | useCustomProguardFile: 0
258 | AndroidTargetArchitectures: 1
259 | AndroidTargetDevices: 0
260 | AndroidSplashScreenScale: 0
261 | androidSplashScreen: {fileID: 0}
262 | AndroidKeystoreName:
263 | AndroidKeyaliasName:
264 | AndroidBuildApkPerCpuArchitecture: 0
265 | AndroidTVCompatibility: 0
266 | AndroidIsGame: 1
267 | AndroidEnableTango: 0
268 | androidEnableBanner: 1
269 | androidUseLowAccuracyLocation: 0
270 | androidUseCustomKeystore: 0
271 | m_AndroidBanners:
272 | - width: 320
273 | height: 180
274 | banner: {fileID: 0}
275 | androidGamepadSupportLevel: 0
276 | chromeosInputEmulation: 1
277 | AndroidMinifyWithR8: 0
278 | AndroidMinifyRelease: 0
279 | AndroidMinifyDebug: 0
280 | AndroidValidateAppBundleSize: 1
281 | AndroidAppBundleSizeToValidate: 150
282 | m_BuildTargetIcons: []
283 | m_BuildTargetPlatformIcons: []
284 | m_BuildTargetBatching:
285 | - m_BuildTarget: Standalone
286 | m_StaticBatching: 1
287 | m_DynamicBatching: 0
288 | - m_BuildTarget: tvOS
289 | m_StaticBatching: 1
290 | m_DynamicBatching: 0
291 | - m_BuildTarget: Android
292 | m_StaticBatching: 1
293 | m_DynamicBatching: 0
294 | - m_BuildTarget: iPhone
295 | m_StaticBatching: 1
296 | m_DynamicBatching: 0
297 | - m_BuildTarget: WebGL
298 | m_StaticBatching: 0
299 | m_DynamicBatching: 0
300 | m_BuildTargetShaderSettings: []
301 | m_BuildTargetGraphicsJobs:
302 | - m_BuildTarget: MacStandaloneSupport
303 | m_GraphicsJobs: 0
304 | - m_BuildTarget: Switch
305 | m_GraphicsJobs: 1
306 | - m_BuildTarget: MetroSupport
307 | m_GraphicsJobs: 1
308 | - m_BuildTarget: AppleTVSupport
309 | m_GraphicsJobs: 0
310 | - m_BuildTarget: BJMSupport
311 | m_GraphicsJobs: 1
312 | - m_BuildTarget: LinuxStandaloneSupport
313 | m_GraphicsJobs: 1
314 | - m_BuildTarget: PS4Player
315 | m_GraphicsJobs: 1
316 | - m_BuildTarget: iOSSupport
317 | m_GraphicsJobs: 0
318 | - m_BuildTarget: WindowsStandaloneSupport
319 | m_GraphicsJobs: 1
320 | - m_BuildTarget: XboxOnePlayer
321 | m_GraphicsJobs: 1
322 | - m_BuildTarget: LuminSupport
323 | m_GraphicsJobs: 0
324 | - m_BuildTarget: AndroidPlayer
325 | m_GraphicsJobs: 0
326 | - m_BuildTarget: WebGLSupport
327 | m_GraphicsJobs: 0
328 | m_BuildTargetGraphicsJobMode:
329 | - m_BuildTarget: PS4Player
330 | m_GraphicsJobMode: 0
331 | - m_BuildTarget: XboxOnePlayer
332 | m_GraphicsJobMode: 0
333 | m_BuildTargetGraphicsAPIs:
334 | - m_BuildTarget: AndroidPlayer
335 | m_APIs: 150000000b000000
336 | m_Automatic: 1
337 | - m_BuildTarget: iOSSupport
338 | m_APIs: 10000000
339 | m_Automatic: 1
340 | - m_BuildTarget: AppleTVSupport
341 | m_APIs: 10000000
342 | m_Automatic: 1
343 | - m_BuildTarget: WebGLSupport
344 | m_APIs: 0b000000
345 | m_Automatic: 1
346 | m_BuildTargetVRSettings:
347 | - m_BuildTarget: Standalone
348 | m_Enabled: 0
349 | m_Devices:
350 | - Oculus
351 | - OpenVR
352 | m_DefaultShaderChunkSizeInMB: 16
353 | m_DefaultShaderChunkCount: 0
354 | openGLRequireES31: 0
355 | openGLRequireES31AEP: 0
356 | openGLRequireES32: 0
357 | m_TemplateCustomTags: {}
358 | mobileMTRendering:
359 | Android: 1
360 | iPhone: 1
361 | tvOS: 1
362 | m_BuildTargetGroupLightmapEncodingQuality:
363 | - m_BuildTarget: Android
364 | m_EncodingQuality: 1
365 | - m_BuildTarget: iPhone
366 | m_EncodingQuality: 1
367 | - m_BuildTarget: tvOS
368 | m_EncodingQuality: 1
369 | m_BuildTargetGroupLightmapSettings: []
370 | m_BuildTargetNormalMapEncoding:
371 | - m_BuildTarget: Android
372 | m_Encoding: 1
373 | - m_BuildTarget: iPhone
374 | m_Encoding: 1
375 | - m_BuildTarget: tvOS
376 | m_Encoding: 1
377 | m_BuildTargetDefaultTextureCompressionFormat:
378 | - m_BuildTarget: Android
379 | m_Format: 3
380 | playModeTestRunnerEnabled: 0
381 | runPlayModeTestAsEditModeTest: 0
382 | actionOnDotNetUnhandledException: 1
383 | enableInternalProfiler: 0
384 | logObjCUncaughtExceptions: 1
385 | enableCrashReportAPI: 0
386 | cameraUsageDescription:
387 | locationUsageDescription:
388 | microphoneUsageDescription:
389 | bluetoothUsageDescription:
390 | switchNMETAOverride:
391 | switchNetLibKey:
392 | switchSocketMemoryPoolSize: 6144
393 | switchSocketAllocatorPoolSize: 128
394 | switchSocketConcurrencyLimit: 14
395 | switchScreenResolutionBehavior: 2
396 | switchUseCPUProfiler: 0
397 | switchUseGOLDLinker: 0
398 | switchLTOSetting: 0
399 | switchApplicationID: 0x01004b9000490000
400 | switchNSODependencies:
401 | switchTitleNames_0:
402 | switchTitleNames_1:
403 | switchTitleNames_2:
404 | switchTitleNames_3:
405 | switchTitleNames_4:
406 | switchTitleNames_5:
407 | switchTitleNames_6:
408 | switchTitleNames_7:
409 | switchTitleNames_8:
410 | switchTitleNames_9:
411 | switchTitleNames_10:
412 | switchTitleNames_11:
413 | switchTitleNames_12:
414 | switchTitleNames_13:
415 | switchTitleNames_14:
416 | switchTitleNames_15:
417 | switchPublisherNames_0:
418 | switchPublisherNames_1:
419 | switchPublisherNames_2:
420 | switchPublisherNames_3:
421 | switchPublisherNames_4:
422 | switchPublisherNames_5:
423 | switchPublisherNames_6:
424 | switchPublisherNames_7:
425 | switchPublisherNames_8:
426 | switchPublisherNames_9:
427 | switchPublisherNames_10:
428 | switchPublisherNames_11:
429 | switchPublisherNames_12:
430 | switchPublisherNames_13:
431 | switchPublisherNames_14:
432 | switchPublisherNames_15:
433 | switchIcons_0: {fileID: 0}
434 | switchIcons_1: {fileID: 0}
435 | switchIcons_2: {fileID: 0}
436 | switchIcons_3: {fileID: 0}
437 | switchIcons_4: {fileID: 0}
438 | switchIcons_5: {fileID: 0}
439 | switchIcons_6: {fileID: 0}
440 | switchIcons_7: {fileID: 0}
441 | switchIcons_8: {fileID: 0}
442 | switchIcons_9: {fileID: 0}
443 | switchIcons_10: {fileID: 0}
444 | switchIcons_11: {fileID: 0}
445 | switchIcons_12: {fileID: 0}
446 | switchIcons_13: {fileID: 0}
447 | switchIcons_14: {fileID: 0}
448 | switchIcons_15: {fileID: 0}
449 | switchSmallIcons_0: {fileID: 0}
450 | switchSmallIcons_1: {fileID: 0}
451 | switchSmallIcons_2: {fileID: 0}
452 | switchSmallIcons_3: {fileID: 0}
453 | switchSmallIcons_4: {fileID: 0}
454 | switchSmallIcons_5: {fileID: 0}
455 | switchSmallIcons_6: {fileID: 0}
456 | switchSmallIcons_7: {fileID: 0}
457 | switchSmallIcons_8: {fileID: 0}
458 | switchSmallIcons_9: {fileID: 0}
459 | switchSmallIcons_10: {fileID: 0}
460 | switchSmallIcons_11: {fileID: 0}
461 | switchSmallIcons_12: {fileID: 0}
462 | switchSmallIcons_13: {fileID: 0}
463 | switchSmallIcons_14: {fileID: 0}
464 | switchSmallIcons_15: {fileID: 0}
465 | switchManualHTML:
466 | switchAccessibleURLs:
467 | switchLegalInformation:
468 | switchMainThreadStackSize: 1048576
469 | switchPresenceGroupId:
470 | switchLogoHandling: 0
471 | switchReleaseVersion: 0
472 | switchDisplayVersion: 1.0.0
473 | switchStartupUserAccount: 0
474 | switchTouchScreenUsage: 0
475 | switchSupportedLanguagesMask: 0
476 | switchLogoType: 0
477 | switchApplicationErrorCodeCategory:
478 | switchUserAccountSaveDataSize: 0
479 | switchUserAccountSaveDataJournalSize: 0
480 | switchApplicationAttribute: 0
481 | switchCardSpecSize: -1
482 | switchCardSpecClock: -1
483 | switchRatingsMask: 0
484 | switchRatingsInt_0: 0
485 | switchRatingsInt_1: 0
486 | switchRatingsInt_2: 0
487 | switchRatingsInt_3: 0
488 | switchRatingsInt_4: 0
489 | switchRatingsInt_5: 0
490 | switchRatingsInt_6: 0
491 | switchRatingsInt_7: 0
492 | switchRatingsInt_8: 0
493 | switchRatingsInt_9: 0
494 | switchRatingsInt_10: 0
495 | switchRatingsInt_11: 0
496 | switchRatingsInt_12: 0
497 | switchLocalCommunicationIds_0:
498 | switchLocalCommunicationIds_1:
499 | switchLocalCommunicationIds_2:
500 | switchLocalCommunicationIds_3:
501 | switchLocalCommunicationIds_4:
502 | switchLocalCommunicationIds_5:
503 | switchLocalCommunicationIds_6:
504 | switchLocalCommunicationIds_7:
505 | switchParentalControl: 0
506 | switchAllowsScreenshot: 1
507 | switchAllowsVideoCapturing: 1
508 | switchAllowsRuntimeAddOnContentInstall: 0
509 | switchDataLossConfirmation: 0
510 | switchUserAccountLockEnabled: 0
511 | switchSystemResourceMemory: 16777216
512 | switchSupportedNpadStyles: 22
513 | switchNativeFsCacheSize: 32
514 | switchIsHoldTypeHorizontal: 0
515 | switchSupportedNpadCount: 8
516 | switchSocketConfigEnabled: 0
517 | switchTcpInitialSendBufferSize: 32
518 | switchTcpInitialReceiveBufferSize: 64
519 | switchTcpAutoSendBufferSizeMax: 256
520 | switchTcpAutoReceiveBufferSizeMax: 256
521 | switchUdpSendBufferSize: 9
522 | switchUdpReceiveBufferSize: 42
523 | switchSocketBufferEfficiency: 4
524 | switchSocketInitializeEnabled: 1
525 | switchNetworkInterfaceManagerInitializeEnabled: 1
526 | switchPlayerConnectionEnabled: 1
527 | switchUseNewStyleFilepaths: 0
528 | switchUseLegacyFmodPriorities: 1
529 | switchUseMicroSleepForYield: 1
530 | switchEnableRamDiskSupport: 0
531 | switchMicroSleepForYieldTime: 25
532 | switchRamDiskSpaceSize: 12
533 | ps4NPAgeRating: 12
534 | ps4NPTitleSecret:
535 | ps4NPTrophyPackPath:
536 | ps4ParentalLevel: 11
537 | ps4ContentID: ED1633-NPXX51362_00-0000000000000000
538 | ps4Category: 0
539 | ps4MasterVersion: 01.00
540 | ps4AppVersion: 01.00
541 | ps4AppType: 0
542 | ps4ParamSfxPath:
543 | ps4VideoOutPixelFormat: 0
544 | ps4VideoOutInitialWidth: 1920
545 | ps4VideoOutBaseModeInitialWidth: 1920
546 | ps4VideoOutReprojectionRate: 60
547 | ps4PronunciationXMLPath:
548 | ps4PronunciationSIGPath:
549 | ps4BackgroundImagePath:
550 | ps4StartupImagePath:
551 | ps4StartupImagesFolder:
552 | ps4IconImagesFolder:
553 | ps4SaveDataImagePath:
554 | ps4SdkOverride:
555 | ps4BGMPath:
556 | ps4ShareFilePath:
557 | ps4ShareOverlayImagePath:
558 | ps4PrivacyGuardImagePath:
559 | ps4ExtraSceSysFile:
560 | ps4NPtitleDatPath:
561 | ps4RemotePlayKeyAssignment: -1
562 | ps4RemotePlayKeyMappingDir:
563 | ps4PlayTogetherPlayerCount: 0
564 | ps4EnterButtonAssignment: 1
565 | ps4ApplicationParam1: 0
566 | ps4ApplicationParam2: 0
567 | ps4ApplicationParam3: 0
568 | ps4ApplicationParam4: 0
569 | ps4DownloadDataSize: 0
570 | ps4GarlicHeapSize: 2048
571 | ps4ProGarlicHeapSize: 2560
572 | playerPrefsMaxSize: 32768
573 | ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ
574 | ps4pnSessions: 1
575 | ps4pnPresence: 1
576 | ps4pnFriends: 1
577 | ps4pnGameCustomData: 1
578 | playerPrefsSupport: 0
579 | enableApplicationExit: 0
580 | resetTempFolder: 1
581 | restrictedAudioUsageRights: 0
582 | ps4UseResolutionFallback: 0
583 | ps4ReprojectionSupport: 0
584 | ps4UseAudio3dBackend: 0
585 | ps4UseLowGarlicFragmentationMode: 1
586 | ps4SocialScreenEnabled: 0
587 | ps4ScriptOptimizationLevel: 0
588 | ps4Audio3dVirtualSpeakerCount: 14
589 | ps4attribCpuUsage: 0
590 | ps4PatchPkgPath:
591 | ps4PatchLatestPkgPath:
592 | ps4PatchChangeinfoPath:
593 | ps4PatchDayOne: 0
594 | ps4attribUserManagement: 0
595 | ps4attribMoveSupport: 0
596 | ps4attrib3DSupport: 0
597 | ps4attribShareSupport: 0
598 | ps4attribExclusiveVR: 0
599 | ps4disableAutoHideSplash: 0
600 | ps4videoRecordingFeaturesUsed: 0
601 | ps4contentSearchFeaturesUsed: 0
602 | ps4CompatibilityPS5: 0
603 | ps4AllowPS5Detection: 0
604 | ps4GPU800MHz: 1
605 | ps4attribEyeToEyeDistanceSettingVR: 0
606 | ps4IncludedModules: []
607 | ps4attribVROutputEnabled: 0
608 | monoEnv:
609 | splashScreenBackgroundSourceLandscape: {fileID: 0}
610 | splashScreenBackgroundSourcePortrait: {fileID: 0}
611 | blurSplashScreenBackground: 1
612 | spritePackerPolicy:
613 | webGLMemorySize: 16
614 | webGLExceptionSupport: 1
615 | webGLNameFilesAsHashes: 0
616 | webGLDataCaching: 1
617 | webGLDebugSymbols: 0
618 | webGLEmscriptenArgs:
619 | webGLModulesDirectory:
620 | webGLTemplate: APPLICATION:Default
621 | webGLAnalyzeBuildSize: 0
622 | webGLUseEmbeddedResources: 0
623 | webGLCompressionFormat: 1
624 | webGLWasmArithmeticExceptions: 0
625 | webGLLinkerTarget: 1
626 | webGLThreadsSupport: 0
627 | webGLDecompressionFallback: 0
628 | webGLPowerPreference: 2
629 | scriptingDefineSymbols: {}
630 | additionalCompilerArguments: {}
631 | platformArchitecture: {}
632 | scriptingBackend: {}
633 | il2cppCompilerConfiguration: {}
634 | managedStrippingLevel: {}
635 | incrementalIl2cppBuild: {}
636 | suppressCommonWarnings: 1
637 | allowUnsafeCode: 0
638 | useDeterministicCompilation: 1
639 | enableRoslynAnalyzers: 1
640 | selectedPlatform: 0
641 | additionalIl2CppArgs:
642 | scriptingRuntimeVersion: 1
643 | gcIncremental: 1
644 | assemblyVersionValidation: 1
645 | gcWBarrierValidation: 0
646 | apiCompatibilityLevelPerPlatform: {}
647 | m_RenderingPath: 1
648 | m_MobileRenderingPath: 1
649 | metroPackageName: Template_3D
650 | metroPackageVersion:
651 | metroCertificatePath:
652 | metroCertificatePassword:
653 | metroCertificateSubject:
654 | metroCertificateIssuer:
655 | metroCertificateNotAfter: 0000000000000000
656 | metroApplicationDescription: Template_3D
657 | wsaImages: {}
658 | metroTileShortName:
659 | metroTileShowName: 0
660 | metroMediumTileShowName: 0
661 | metroLargeTileShowName: 0
662 | metroWideTileShowName: 0
663 | metroSupportStreamingInstall: 0
664 | metroLastRequiredScene: 0
665 | metroDefaultTileSize: 1
666 | metroTileForegroundText: 2
667 | metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
668 | metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1}
669 | metroSplashScreenUseBackgroundColor: 0
670 | platformCapabilities: {}
671 | metroTargetDeviceFamilies: {}
672 | metroFTAName:
673 | metroFTAFileTypes: []
674 | metroProtocolName:
675 | vcxProjDefaultLanguage:
676 | XboxOneProductId:
677 | XboxOneUpdateKey:
678 | XboxOneSandboxId:
679 | XboxOneContentId:
680 | XboxOneTitleId:
681 | XboxOneSCId:
682 | XboxOneGameOsOverridePath:
683 | XboxOnePackagingOverridePath:
684 | XboxOneAppManifestOverridePath:
685 | XboxOneVersion: 1.0.0.0
686 | XboxOnePackageEncryption: 0
687 | XboxOnePackageUpdateGranularity: 2
688 | XboxOneDescription:
689 | XboxOneLanguage:
690 | - enus
691 | XboxOneCapability: []
692 | XboxOneGameRating: {}
693 | XboxOneIsContentPackage: 0
694 | XboxOneEnhancedXboxCompatibilityMode: 0
695 | XboxOneEnableGPUVariability: 1
696 | XboxOneSockets: {}
697 | XboxOneSplashScreen: {fileID: 0}
698 | XboxOneAllowedProductIds: []
699 | XboxOnePersistentLocalStorageSize: 0
700 | XboxOneXTitleMemory: 8
701 | XboxOneOverrideIdentityName:
702 | XboxOneOverrideIdentityPublisher:
703 | vrEditorSettings: {}
704 | cloudServicesEnabled:
705 | UNet: 1
706 | luminIcon:
707 | m_Name:
708 | m_ModelFolderPath:
709 | m_PortalFolderPath:
710 | luminCert:
711 | m_CertPath:
712 | m_SignPackage: 1
713 | luminIsChannelApp: 0
714 | luminVersion:
715 | m_VersionCode: 1
716 | m_VersionName:
717 | apiCompatibilityLevel: 6
718 | activeInputHandler: 0
719 | windowsGamepadBackendHint: 0
720 | cloudProjectId:
721 | framebufferDepthMemorylessMode: 0
722 | qualitySettingsNames: []
723 | projectName:
724 | organizationId:
725 | cloudEnabled: 0
726 | legacyClampBlendShapeWeights: 0
727 | playerDataPath:
728 | forceSRGBBlit: 1
729 | virtualTexturingSupportEnabled: 0
730 |
--------------------------------------------------------------------------------
/ProjectSettings/ProjectVersion.txt:
--------------------------------------------------------------------------------
1 | m_EditorVersion: 2021.3.16f1c1
2 | m_EditorVersionWithRevision: 2021.3.16f1c1 (56dbfdd6697f)
3 |
--------------------------------------------------------------------------------
/ProjectSettings/QualitySettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!47 &1
4 | QualitySettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 5
7 | m_CurrentQuality: 5
8 | m_QualitySettings:
9 | - serializedVersion: 2
10 | name: Very Low
11 | pixelLightCount: 0
12 | shadows: 0
13 | shadowResolution: 0
14 | shadowProjection: 1
15 | shadowCascades: 1
16 | shadowDistance: 15
17 | shadowNearPlaneOffset: 3
18 | shadowCascade2Split: 0.33333334
19 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
20 | shadowmaskMode: 0
21 | blendWeights: 1
22 | textureQuality: 1
23 | anisotropicTextures: 0
24 | antiAliasing: 0
25 | softParticles: 0
26 | softVegetation: 0
27 | realtimeReflectionProbes: 0
28 | billboardsFaceCameraPosition: 0
29 | vSyncCount: 0
30 | lodBias: 0.3
31 | maximumLODLevel: 0
32 | streamingMipmapsActive: 0
33 | streamingMipmapsAddAllCameras: 1
34 | streamingMipmapsMemoryBudget: 512
35 | streamingMipmapsRenderersPerFrame: 512
36 | streamingMipmapsMaxLevelReduction: 2
37 | streamingMipmapsMaxFileIORequests: 1024
38 | particleRaycastBudget: 4
39 | asyncUploadTimeSlice: 2
40 | asyncUploadBufferSize: 16
41 | asyncUploadPersistentBuffer: 1
42 | resolutionScalingFixedDPIFactor: 1
43 | excludedTargetPlatforms: []
44 | - serializedVersion: 2
45 | name: Low
46 | pixelLightCount: 0
47 | shadows: 0
48 | shadowResolution: 0
49 | shadowProjection: 1
50 | shadowCascades: 1
51 | shadowDistance: 20
52 | shadowNearPlaneOffset: 3
53 | shadowCascade2Split: 0.33333334
54 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
55 | shadowmaskMode: 0
56 | blendWeights: 2
57 | textureQuality: 0
58 | anisotropicTextures: 0
59 | antiAliasing: 0
60 | softParticles: 0
61 | softVegetation: 0
62 | realtimeReflectionProbes: 0
63 | billboardsFaceCameraPosition: 0
64 | vSyncCount: 0
65 | lodBias: 0.4
66 | maximumLODLevel: 0
67 | streamingMipmapsActive: 0
68 | streamingMipmapsAddAllCameras: 1
69 | streamingMipmapsMemoryBudget: 512
70 | streamingMipmapsRenderersPerFrame: 512
71 | streamingMipmapsMaxLevelReduction: 2
72 | streamingMipmapsMaxFileIORequests: 1024
73 | particleRaycastBudget: 16
74 | asyncUploadTimeSlice: 2
75 | asyncUploadBufferSize: 16
76 | asyncUploadPersistentBuffer: 1
77 | resolutionScalingFixedDPIFactor: 1
78 | excludedTargetPlatforms: []
79 | - serializedVersion: 2
80 | name: Medium
81 | pixelLightCount: 1
82 | shadows: 1
83 | shadowResolution: 0
84 | shadowProjection: 1
85 | shadowCascades: 1
86 | shadowDistance: 20
87 | shadowNearPlaneOffset: 3
88 | shadowCascade2Split: 0.33333334
89 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
90 | shadowmaskMode: 0
91 | blendWeights: 2
92 | textureQuality: 0
93 | anisotropicTextures: 1
94 | antiAliasing: 0
95 | softParticles: 0
96 | softVegetation: 0
97 | realtimeReflectionProbes: 0
98 | billboardsFaceCameraPosition: 0
99 | vSyncCount: 1
100 | lodBias: 0.7
101 | maximumLODLevel: 0
102 | streamingMipmapsActive: 0
103 | streamingMipmapsAddAllCameras: 1
104 | streamingMipmapsMemoryBudget: 512
105 | streamingMipmapsRenderersPerFrame: 512
106 | streamingMipmapsMaxLevelReduction: 2
107 | streamingMipmapsMaxFileIORequests: 1024
108 | particleRaycastBudget: 64
109 | asyncUploadTimeSlice: 2
110 | asyncUploadBufferSize: 16
111 | asyncUploadPersistentBuffer: 1
112 | resolutionScalingFixedDPIFactor: 1
113 | excludedTargetPlatforms: []
114 | - serializedVersion: 2
115 | name: High
116 | pixelLightCount: 2
117 | shadows: 2
118 | shadowResolution: 1
119 | shadowProjection: 1
120 | shadowCascades: 2
121 | shadowDistance: 40
122 | shadowNearPlaneOffset: 3
123 | shadowCascade2Split: 0.33333334
124 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
125 | shadowmaskMode: 1
126 | blendWeights: 2
127 | textureQuality: 0
128 | anisotropicTextures: 1
129 | antiAliasing: 0
130 | softParticles: 0
131 | softVegetation: 1
132 | realtimeReflectionProbes: 1
133 | billboardsFaceCameraPosition: 1
134 | vSyncCount: 1
135 | lodBias: 1
136 | maximumLODLevel: 0
137 | streamingMipmapsActive: 0
138 | streamingMipmapsAddAllCameras: 1
139 | streamingMipmapsMemoryBudget: 512
140 | streamingMipmapsRenderersPerFrame: 512
141 | streamingMipmapsMaxLevelReduction: 2
142 | streamingMipmapsMaxFileIORequests: 1024
143 | particleRaycastBudget: 256
144 | asyncUploadTimeSlice: 2
145 | asyncUploadBufferSize: 16
146 | asyncUploadPersistentBuffer: 1
147 | resolutionScalingFixedDPIFactor: 1
148 | excludedTargetPlatforms: []
149 | - serializedVersion: 2
150 | name: Very High
151 | pixelLightCount: 3
152 | shadows: 2
153 | shadowResolution: 2
154 | shadowProjection: 1
155 | shadowCascades: 2
156 | shadowDistance: 70
157 | shadowNearPlaneOffset: 3
158 | shadowCascade2Split: 0.33333334
159 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
160 | shadowmaskMode: 1
161 | blendWeights: 4
162 | textureQuality: 0
163 | anisotropicTextures: 2
164 | antiAliasing: 2
165 | softParticles: 1
166 | softVegetation: 1
167 | realtimeReflectionProbes: 1
168 | billboardsFaceCameraPosition: 1
169 | vSyncCount: 1
170 | lodBias: 1.5
171 | maximumLODLevel: 0
172 | streamingMipmapsActive: 0
173 | streamingMipmapsAddAllCameras: 1
174 | streamingMipmapsMemoryBudget: 512
175 | streamingMipmapsRenderersPerFrame: 512
176 | streamingMipmapsMaxLevelReduction: 2
177 | streamingMipmapsMaxFileIORequests: 1024
178 | particleRaycastBudget: 1024
179 | asyncUploadTimeSlice: 2
180 | asyncUploadBufferSize: 16
181 | asyncUploadPersistentBuffer: 1
182 | resolutionScalingFixedDPIFactor: 1
183 | excludedTargetPlatforms: []
184 | - serializedVersion: 2
185 | name: Ultra
186 | pixelLightCount: 4
187 | shadows: 2
188 | shadowResolution: 2
189 | shadowProjection: 1
190 | shadowCascades: 4
191 | shadowDistance: 150
192 | shadowNearPlaneOffset: 3
193 | shadowCascade2Split: 0.33333334
194 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
195 | shadowmaskMode: 1
196 | blendWeights: 4
197 | textureQuality: 0
198 | anisotropicTextures: 2
199 | antiAliasing: 2
200 | softParticles: 1
201 | softVegetation: 1
202 | realtimeReflectionProbes: 1
203 | billboardsFaceCameraPosition: 1
204 | vSyncCount: 1
205 | lodBias: 2
206 | maximumLODLevel: 0
207 | streamingMipmapsActive: 0
208 | streamingMipmapsAddAllCameras: 1
209 | streamingMipmapsMemoryBudget: 512
210 | streamingMipmapsRenderersPerFrame: 512
211 | streamingMipmapsMaxLevelReduction: 2
212 | streamingMipmapsMaxFileIORequests: 1024
213 | particleRaycastBudget: 4096
214 | asyncUploadTimeSlice: 2
215 | asyncUploadBufferSize: 16
216 | asyncUploadPersistentBuffer: 1
217 | resolutionScalingFixedDPIFactor: 1
218 | excludedTargetPlatforms: []
219 | m_PerPlatformDefaultQuality:
220 | Android: 2
221 | Lumin: 5
222 | GameCoreScarlett: 5
223 | GameCoreXboxOne: 5
224 | Nintendo 3DS: 5
225 | Nintendo Switch: 5
226 | PS4: 5
227 | PS5: 5
228 | Stadia: 5
229 | Standalone: 5
230 | WebGL: 3
231 | Windows Store Apps: 5
232 | XboxOne: 5
233 | iPhone: 2
234 | tvOS: 2
235 |
--------------------------------------------------------------------------------
/ProjectSettings/SceneTemplateSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "templatePinStates": [],
3 | "dependencyTypeInfos": [
4 | {
5 | "userAdded": false,
6 | "type": "UnityEngine.AnimationClip",
7 | "ignore": false,
8 | "defaultInstantiationMode": 0,
9 | "supportsModification": true
10 | },
11 | {
12 | "userAdded": false,
13 | "type": "UnityEditor.Animations.AnimatorController",
14 | "ignore": false,
15 | "defaultInstantiationMode": 0,
16 | "supportsModification": true
17 | },
18 | {
19 | "userAdded": false,
20 | "type": "UnityEngine.AnimatorOverrideController",
21 | "ignore": false,
22 | "defaultInstantiationMode": 0,
23 | "supportsModification": true
24 | },
25 | {
26 | "userAdded": false,
27 | "type": "UnityEditor.Audio.AudioMixerController",
28 | "ignore": false,
29 | "defaultInstantiationMode": 0,
30 | "supportsModification": true
31 | },
32 | {
33 | "userAdded": false,
34 | "type": "UnityEngine.ComputeShader",
35 | "ignore": true,
36 | "defaultInstantiationMode": 1,
37 | "supportsModification": true
38 | },
39 | {
40 | "userAdded": false,
41 | "type": "UnityEngine.Cubemap",
42 | "ignore": false,
43 | "defaultInstantiationMode": 0,
44 | "supportsModification": true
45 | },
46 | {
47 | "userAdded": false,
48 | "type": "UnityEngine.GameObject",
49 | "ignore": false,
50 | "defaultInstantiationMode": 0,
51 | "supportsModification": true
52 | },
53 | {
54 | "userAdded": false,
55 | "type": "UnityEditor.LightingDataAsset",
56 | "ignore": false,
57 | "defaultInstantiationMode": 0,
58 | "supportsModification": false
59 | },
60 | {
61 | "userAdded": false,
62 | "type": "UnityEngine.LightingSettings",
63 | "ignore": false,
64 | "defaultInstantiationMode": 0,
65 | "supportsModification": true
66 | },
67 | {
68 | "userAdded": false,
69 | "type": "UnityEngine.Material",
70 | "ignore": false,
71 | "defaultInstantiationMode": 0,
72 | "supportsModification": true
73 | },
74 | {
75 | "userAdded": false,
76 | "type": "UnityEditor.MonoScript",
77 | "ignore": true,
78 | "defaultInstantiationMode": 1,
79 | "supportsModification": true
80 | },
81 | {
82 | "userAdded": false,
83 | "type": "UnityEngine.PhysicMaterial",
84 | "ignore": false,
85 | "defaultInstantiationMode": 0,
86 | "supportsModification": true
87 | },
88 | {
89 | "userAdded": false,
90 | "type": "UnityEngine.PhysicsMaterial2D",
91 | "ignore": false,
92 | "defaultInstantiationMode": 0,
93 | "supportsModification": true
94 | },
95 | {
96 | "userAdded": false,
97 | "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile",
98 | "ignore": false,
99 | "defaultInstantiationMode": 0,
100 | "supportsModification": true
101 | },
102 | {
103 | "userAdded": false,
104 | "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources",
105 | "ignore": false,
106 | "defaultInstantiationMode": 0,
107 | "supportsModification": true
108 | },
109 | {
110 | "userAdded": false,
111 | "type": "UnityEngine.Rendering.VolumeProfile",
112 | "ignore": false,
113 | "defaultInstantiationMode": 0,
114 | "supportsModification": true
115 | },
116 | {
117 | "userAdded": false,
118 | "type": "UnityEditor.SceneAsset",
119 | "ignore": false,
120 | "defaultInstantiationMode": 0,
121 | "supportsModification": false
122 | },
123 | {
124 | "userAdded": false,
125 | "type": "UnityEngine.Shader",
126 | "ignore": true,
127 | "defaultInstantiationMode": 1,
128 | "supportsModification": true
129 | },
130 | {
131 | "userAdded": false,
132 | "type": "UnityEngine.ShaderVariantCollection",
133 | "ignore": true,
134 | "defaultInstantiationMode": 1,
135 | "supportsModification": true
136 | },
137 | {
138 | "userAdded": false,
139 | "type": "UnityEngine.Texture",
140 | "ignore": false,
141 | "defaultInstantiationMode": 0,
142 | "supportsModification": true
143 | },
144 | {
145 | "userAdded": false,
146 | "type": "UnityEngine.Texture2D",
147 | "ignore": false,
148 | "defaultInstantiationMode": 0,
149 | "supportsModification": true
150 | },
151 | {
152 | "userAdded": false,
153 | "type": "UnityEngine.Timeline.TimelineAsset",
154 | "ignore": false,
155 | "defaultInstantiationMode": 0,
156 | "supportsModification": true
157 | }
158 | ],
159 | "defaultDependencyTypeInfo": {
160 | "userAdded": false,
161 | "type": "",
162 | "ignore": false,
163 | "defaultInstantiationMode": 1,
164 | "supportsModification": true
165 | },
166 | "newSceneOverride": 0
167 | }
--------------------------------------------------------------------------------
/ProjectSettings/ShaderGraphSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &1
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 61
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 11500000, guid: de02f9e1d18f588468e474319d09a723, type: 3}
13 | m_Name:
14 | m_EditorClassIdentifier:
15 | customInterpolatorErrorThreshold: 32
16 | customInterpolatorWarningThreshold: 16
17 |
--------------------------------------------------------------------------------
/ProjectSettings/TagManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!78 &1
4 | TagManager:
5 | serializedVersion: 2
6 | tags: []
7 | layers:
8 | - Default
9 | - TransparentFX
10 | - Ignore Raycast
11 | -
12 | - Water
13 | - UI
14 | -
15 | -
16 | -
17 | -
18 | -
19 | -
20 | -
21 | -
22 | -
23 | -
24 | -
25 | -
26 | -
27 | -
28 | -
29 | -
30 | -
31 | -
32 | -
33 | -
34 | -
35 | -
36 | -
37 | -
38 | -
39 | -
40 | m_SortingLayers:
41 | - name: Default
42 | uniqueID: 0
43 | locked: 0
44 |
--------------------------------------------------------------------------------
/ProjectSettings/TimeManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!5 &1
4 | TimeManager:
5 | m_ObjectHideFlags: 0
6 | Fixed Timestep: 0.02
7 | Maximum Allowed Timestep: 0.33333334
8 | m_TimeScale: 1
9 | Maximum Particle Timestep: 0.03
10 |
--------------------------------------------------------------------------------
/ProjectSettings/URPProjectSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &1
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 61
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 11500000, guid: 247994e1f5a72c2419c26a37e9334c01, type: 3}
13 | m_Name:
14 | m_EditorClassIdentifier:
15 | m_LastMaterialVersion: 5
16 |
--------------------------------------------------------------------------------
/ProjectSettings/UnityConnectSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!310 &1
4 | UnityConnectSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 1
7 | m_Enabled: 0
8 | m_TestMode: 0
9 | m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
10 | m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
11 | m_ConfigUrl: https://config.uca.cloud.unity3d.com
12 | m_DashboardUrl: https://dashboard.unity3d.com
13 | m_CNEventUrl: https://cdp.cloud.unity.cn/v1/events
14 | m_CNConfigUrl: https://cdp.cloud.unity.cn/config
15 | m_TestInitMode: 0
16 | CrashReportingSettings:
17 | m_EventUrl: https://perf-events.cloud.unity.cn
18 | m_Enabled: 0
19 | m_LogBufferSize: 10
20 | m_CaptureEditorExceptions: 1
21 | UnityPurchasingSettings:
22 | m_Enabled: 0
23 | m_TestMode: 0
24 | UnityAnalyticsSettings:
25 | m_Enabled: 1
26 | m_TestMode: 0
27 | m_InitializeOnStartup: 1
28 | m_PackageRequiringCoreStatsPresent: 0
29 | UnityAdsSettings:
30 | m_Enabled: 0
31 | m_InitializeOnStartup: 1
32 | m_TestMode: 0
33 | m_IosGameId:
34 | m_AndroidGameId:
35 | m_GameIds: {}
36 | m_GameId:
37 | PerformanceReportingSettings:
38 | m_Enabled: 0
39 |
--------------------------------------------------------------------------------
/ProjectSettings/VFXManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!937362698 &1
4 | VFXManager:
5 | m_ObjectHideFlags: 0
6 | m_IndirectShader: {fileID: 0}
7 | m_CopyBufferShader: {fileID: 0}
8 | m_SortShader: {fileID: 0}
9 | m_StripUpdateShader: {fileID: 0}
10 | m_RenderPipeSettingsPath:
11 | m_FixedTimeStep: 0.016666668
12 | m_MaxDeltaTime: 0.05
13 |
--------------------------------------------------------------------------------
/ProjectSettings/VersionControlSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!890905787 &1
4 | VersionControlSettings:
5 | m_ObjectHideFlags: 0
6 | m_Mode: Visible Meta Files
7 | m_CollabEditorSettings:
8 | inProgressEnabled: 1
9 |
--------------------------------------------------------------------------------
/ProjectSettings/XRSettings.asset:
--------------------------------------------------------------------------------
1 | {
2 | "m_SettingKeys": [
3 | "VR Device Disabled",
4 | "VR Device User Alert"
5 | ],
6 | "m_SettingValues": [
7 | "False",
8 | "False"
9 | ]
10 | }
--------------------------------------------------------------------------------
/ProjectSettings/boot.config:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/ProjectSettings/boot.config
--------------------------------------------------------------------------------
/README-ch.md:
--------------------------------------------------------------------------------
1 | # UnityCommandLineInterface
2 |
3 | [](https://openupm.com/packages/com.redsaw.commandline/)
4 |
5 | [English Document](./README.md)
6 |
7 | ## 简介
8 |
9 | 这个项目是一个游戏内置的控制台项目,主要用于执行一些简短的命令或者函数,或者用于设置某些数值等
10 |
11 |
12 |

13 |
14 |
15 | https://github.com/529324416/UnityCommandLineInterface/assets/30776995/a2290fff-ffa5-421f-8a4a-a5274e9c6d87
16 |
17 | ## 特性:
18 | - 使用简单,无需任何额外配置或者复杂学习流程
19 | - 模块间高度解耦合,其内部组件可以单独使用
20 | - 轻量级,无任何依赖
21 | - 支持输入文本提示
22 | - 支持自定义
23 | - 全版本支持
24 |
25 |
26 | ## 如何使用
27 |
28 | ## 1.注册命令
29 |
30 | 命令的注册和旧版的命令系统是一致的,不同的点在于,旧版的系统会审查目标静态函数的参数列表能够正常解析,而新版的系统不会,任何错误都会在执行的时候报出。不过注册行为同样简单,只需要在静态函数之上添加一个`Command`特性即可。
31 |
32 | ```csharp
33 | [Command("my_cmd")]
34 | public static void SomeMethod(){
35 | /* do something here .. */
36 | }
37 | ```
38 |
39 | 不过除了静态函数之外,新版系统还支持注册实例函数,不过注册实例函数只能手动注册,以及在拥有了**控制台变量**的情况下,注册实例函数的意义变得微乎其微。
40 |
41 | ## 2.执行命令
42 |
43 | 命令的执行和之前大有不同,首先,命令的执行有两种模式,第一种,你可以像执行一般的命令一样执行。
44 |
45 | ```
46 | your_command args1 args2
47 | ```
48 |
49 | 但是同样,你可以像执行一个函数一样执行命令
50 |
51 | ```
52 | your_command(args1, args2)
53 | ```
54 |
55 | 这么做的理由很简单,前者是继续维系执行简单命令的便捷,后者的意义在于,新版的命令系统支持获取命令的返回结果,于是在函数执行模式的时候,你可以对结果内部的信息继续访问。如下所示:
56 |
57 | ```
58 | get_enemy("slime").Jump()
59 | ```
60 |
61 | ## 3.控制台变量
62 |
63 | 接着就是新版系统的重点了,新版控制台支持你注册一个控制台变量,并通过`@`对其进行访问,这会使得命令系统整体变得非常自由。假设你有一个主角,如下所示:
64 |
65 | ```csharp
66 | public class Player : MonoBehaviour{
67 |
68 | public int health = 100;
69 |
70 | public void Jump(){
71 | // code for jump..
72 | }
73 | }
74 | ```
75 |
76 | 你可以设置一个静态变量,并为该变量添加一个`CommandProperty`来注册一个控制台变量。如下所示:
77 |
78 | ```csharp
79 | public class Player: MonoBehaviour{
80 |
81 | [CommandProperty("player")]
82 | public static Player Instance;
83 | }
84 | ```
85 |
86 | (当然,游戏启动的时候你得往Instance写入一个实例)
87 |
88 | 之后你就可以在控制台对其所有的内部信息进行访问了,不需要任何额外的参数,完全只借助于C#的反射系统以获取的强大的动态能力,下面这些命令都是支持且有效的。
89 |
90 | ```
91 | @player.health = 100
92 | @player.Jump()
93 | ```
94 |
95 | 同理,任何需要一个变量的地方都可以通过`@`来引用你需要的变量,比如作为其他命令的参数
96 |
97 | ```
98 | @enemy.Atk(@player)
99 | ```
100 |
101 | 控制台变量可以注册给一个字段或者一个属性,这意味着同一个控制台变量可以代表着不同的对象,比如离玩家最近的Npc。
102 |
103 | ```csharp
104 | [CommandProperty("nearest")]
105 | public static Npc NearestNpc{
106 | get{
107 | // code for getting nearest npc..
108 | }
109 | }
110 | ```
111 |
112 | 有了这个特性的支持,你可以将一组命令作为某个对象的子函数,不需要编写额外的信息,只需要将这个变量注册为控制台变量即可。
113 |
114 | 总的来说它大大提高了控制台的自由度
115 |
116 | ## 4.变量访问支持
117 |
118 | 新版的控制台支持访问成员和覆写成员,你可以通过一般点号或者中括号来对目标对象的成员或者子元素进行访问,前面已经展示了一些。类似于访问成员,或者覆写子元素都是支持的。
119 |
120 | ```
121 | @player.DoSomething()
122 | @player.buffs["buff_id"].AddTime(100)
123 | @enemies["slime"] = @new_slime
124 | ```
125 |
126 | 当然,这种访问是可以连续的,就像正常的语法那样
127 |
128 | ```
129 | @something.member.sub_member["elements"].member.sub_member.field.function().member
130 | ```
131 |
132 | 尽管这么做有些憨,这里面关于中括号的判断是根据目标对象来的,一般来说,系统会先判断目标对象是否为一个序列型的对象比如数组或者列表等,如果是的话,那么它会要求中括号内部的表达式为一个int字段,如果中括号内部的表达式无法得到一个int字段,这个访问就会失败并正常报错,同理作为字典也是一样的,只不过字典支持更多不同的键类型,所以字典的判断范围会更宽一些。
133 |
134 | ```
135 | @some_dict[@non_string_type] = get_something()
136 | ```
137 |
138 | 比如你的字典是一个非字符串类型,那么你可以把其他控制台变量作为它的键值来访问或者覆写。当然,前提是它支持覆写。任何不支持的行为都会报错,只不过控制台会尽可能正确执行你输入的命令。所以最好的办法就是不要设置一些奇怪的命令。
139 |
140 | ## 5.参数解析器
141 |
142 | 旧版控制台有过一个参数解析器,这个解析器在新版里变为了控制台系统理解一般参数的最后手段。简单来说,普通的控制台变量其实只有以下几种
143 |
144 | ```
145 | string
146 | float
147 | int
148 | bool
149 | null
150 | ```
151 |
152 | 这些也可以被称为是元类型,属于可以直接被解析出来的数据类型,一般来说是够用了。不过如果你希望控制台可以解析不同的数据类型,那么你可以通过注册参数解析器来实现。
153 |
154 | 举个简单的案例,对于主角的位置而言,它可能是一个`Vector3`类型,你也许希望可以像这样去设置主角的位置
155 |
156 | ```
157 | @player.pos = "1. 1. 1."
158 | ```
159 |
160 | 但是显然,字符串是无法转换为位置的,如果你真的想这么做,或者有必要这么做的话,你可以注册一个参数解析器,如下所示:
161 |
162 | ```csharp
163 | [CommandValueParser(typeof(Vector3))]
164 | static bool ParseFunction(string input, out object data){
165 | // code used to parse your input str
166 | }
167 | ```
168 |
169 | 这个函数只要符合签名即可,它的内部逻辑你可以自由编写,比如:
170 |
171 | ```csharp
172 | static bool ParseFunction(string input, out object data){
173 | if(input == "复活点"){
174 | data = Vector3.zero;
175 | return true;
176 | }
177 | // other prase here ..
178 |
179 | data = default;
180 | return false;
181 | }
182 | ```
183 |
184 | 那么你就可以这样使用了
185 |
186 | ```
187 | @player.pos = "复活点"
188 | ```
189 |
190 | 当然,这里的双引号其实是可以取消的,但是我不建议这么做,因为系统在理解这个输入的时候会首先把`复活点`当成是一个命令,认为你希望执行一个命令获取它的返回值,如果复活点不是命令的话,系统才会尝试去搜索有没有解析器可以将其解析为目标类型的数据。
191 |
192 | ```
193 | @player.pos = 复活点
194 | ```
195 |
196 | 所以尽量不要使用这种模棱两可的控制语句。
197 |
198 | 参数解析器一般用于一些比较特殊的情况或者非常复杂的数据类型,不便于手动设置的数据类型。像这里的`Vector3`类型的正确做法应该是什么呢?其实很简单,你可以注册一个这样的命令。
199 |
200 | ```csharp
201 | [Command("v3")]
202 | public static Vector3 BuildVector3(float x, float y, float z){
203 | return new Vector3(x, y, z);
204 | }
205 | ```
206 |
207 | 然后像下面这样调用即可
208 |
209 | ```
210 | @player.pos = v3(0, 0, 0)
211 | ```
212 |
213 | ## 6.注册Debug信息
214 |
215 | 如果希望直接获取该对象的一些信息,比如我输入了下面的命令到控制台
216 |
217 | ```
218 | @player
219 | ```
220 |
221 | 控制台可以直接返回给我一组关于玩家的数据信息,像下面这样
222 |
223 | 
224 |
225 | 这个功能属于附加功能,但是我也将其作为了控制台的一个内部特性来实现了。简单来说对于任何你希望了解的参数信息,你可以注册一个`DebugInfo`到它的字段或者属性用以获取它的信息
226 |
227 | ```csharp
228 | public class Player : MonoBehaviour{
229 |
230 | [DebugInfo]
231 | public int health = 100;
232 | }
233 | ```
234 |
235 | 这样一来,输入`@player`到控制台就可以直接获取player的所有DebugInfo了,输出如下所示
236 |
237 | ```
238 | ---------- Player start ----------
239 | >Player.health: 100
240 | ---------- Player end ----------
241 | ```
242 |
243 | 你可以设置它的标题和色彩,如下所示:
244 |
245 | ```csharp
246 | [DebugInfo("custom_title", Color = "#ff0000")]
247 | ```
248 |
249 | ### 6.1 父类追溯
250 |
251 | 它可以追溯所有父类的信息并从最初的父类开始往下依次传递,如果你有一个这样的继承链
252 |
253 | ```csharp
254 | public class A{
255 | [DebugInfo]
256 | public int someInt = 999;
257 | }
258 |
259 | public class B{
260 | [DebugInfo]
261 | public float someFloat = 12.3;
262 | }
263 |
264 | public class C{
265 | [DebugInfo]
266 | public string someStr = "Hello World!";
267 | }
268 | ```
269 |
270 | 如果你实例化了C并进行输出的话,你将获得
271 |
272 | ```
273 | ---------- C start ----------
274 | >A.someInt: 999
275 | >B.someFloat: 12.3
276 | >C.someStr: Hello World!
277 | ---------- C end ----------
278 | ```
279 |
280 | ### 6.3 子类DebugInfo
281 |
282 | 如果被注册为DebugInfo的对象是一个内部也拥有数个DebugInfo的对象,那么你可以为其类型注册一个`DebugObject`特性,如下所示
283 |
284 | ```csharp
285 | public class A{
286 | [DebugInfo("my_object")]
287 | public B b = new B();
288 | }
289 |
290 | [DebugObject]
291 | public class B : MonoBehaviour{
292 | [DebugInfo("name")]
293 | public string Name => this.gameObject.name;
294 |
295 | [DebugInfo]
296 | public string age = 25;
297 | }
298 | ```
299 |
300 | 如果你输出了A,将会获得下列信息
301 |
302 | ```
303 | ---------- C start ----------
304 | >----[B]
305 | > B.name: "instanceB"
306 | > B.age: 25
307 | ---------- C end ----------
308 | ```
309 |
310 | 不过这样一来,可能会出现环状数据展开,比如B引用了C但C又引用了B,所以你可以设置一个展开深度,超过这个深度时,系统会丢弃其子类信息,默认深度为4。
311 |
312 | ## 7.输入提示
313 |
314 | 旧版的控制台系统的提示很好做,只需要搜索所有的命令然后按照一定的相似度函数来进行提示即可。新版的系统多出来了很多信息,总的来说它支持三个元素的提示,即命令,控制台变量和成员对象。
315 |
316 | 不过我第一次做提示引擎,压根不知道怎么做最好,我采用了链式字符自动机的做法,你每输入一个字符都会根据规则改变现有字符状态机的状态从而让控制台知悉你现在正在输入什么,进而弹出提示。
317 |
318 | 这个部分确实难做,要在提示和语法的兼容度之间寻找一个平衡蛮难的。但还是做出来了,不过这部分没有用户和代码相关的部分。
319 |
320 | 
321 |
322 | 字符串相似度采用了编辑距离算法,也就是比较有名的莱文斯坦因字符串距离算法。对你的输入和目标字符串之间计算编辑距离并按照相似度排序。查询时会产生一定空间的缓存,用以复用最近输入的查询结果,从而降低计算损耗。
323 |
324 | ## 8.控制台日志
325 |
326 | 新版控制台最大的一个特点在于解除了日志输出与渲染的耦合,这意味着你控制台本体只会存储日志信息,不会进行任何渲染行为,你可以向控制台索要某个特定类型的日志信息,比如只显示错误信息或者只显示一般信息。
327 |
328 | Unity有一个自带的日志类型,`LogType`,不过你可以定义自己的日志枚举类型,或者直接使用Unity的日志类型。
329 |
330 | ```csharp
331 | /* intialize console */
332 | console = new ConsoleController(
333 | consoleRenderer,
334 | new UserInput(),
335 |
336 | inputHistoryCapacity: inputHistoryCapacity,
337 | commandQueryCacheCapacity: commandQueryCacheCapacity,
338 | alternativeCommandCount: alternativeCommandCount,
339 | shouldRecordFailedCommand: shouldRecordFailedCommand,
340 | outputWithTime: shouldOutputWithTime,
341 | outputStackTraceOfCommandExecution: shouldOutputVMExceptionStack
342 | );
343 | ```
344 |
345 | 以及你可以自由的决定这些日志该如何存储或者保存到什么位置。
346 |
347 |
348 |
349 | ## 其他
350 |
351 | ### 适用的Unity版本
352 |
353 | Unity 2018.03+
354 |
355 | 这个项目与Unity是高度解耦合的,所以它几乎可以用于所有版本的Unity,你可以为它定义自己的UI来使用,这个项目提供了默认的Unity实现,你可以学习或者自己改造。
356 |
357 | ### 特性支持:基础运算
358 |
359 | 该系统是为了能够进行方便的debug工作而设计的,所以它的定位并不是一个完整的编程语言,所以它现在还不支持基础的运算,比如数学运算,或者逻辑运算等。如果对该类特性有需求的话,可以在github提出,如果的确有必要的话,我会实现的。
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UnityCommandLineInterface
2 |
3 | [](https://openupm.com/packages/com.redsaw.commandline/)
4 |
5 | [中文文档](./README-ch.md)
6 |
7 | ## Summary
8 | this project is an inner game command console, usually use to execute some short command or set/get property
9 |
10 |
11 |

12 |
13 |
14 |
15 | https://github.com/529324416/UnityCommandLineInterface/assets/30776995/a2290fff-ffa5-421f-8a4a-a5274e9c6d87
16 |
17 |
18 | ## Features:
19 | - **Easy to use**, no need to do much learning
20 | - **Lightweight**, no dependencies
21 | - **Input text suggestion**
22 | - **Support all versions of Unity**
23 | - **Highly Decoupled**,
24 | - **Easy to port to other platforms**
25 |
26 | ## Usage
27 |
28 | ## 1.Register Commands
29 |
30 | Register a command is easy, you just need to add an `Command` attribute to your static method.
31 |
32 | ```csharp
33 | [Command("my_cmd")]
34 | public static void SomeMethod(){
35 | /* do something here .. */
36 | }
37 | ```
38 |
39 | but system also support to add instance method, but now system have a new feature **CommandProperty**, so it's meaningless to register instance method
40 |
41 | ## 2.Execute Commands
42 |
43 | command can be executed in two ways, the first way like this:
44 |
45 | ```
46 | your_command args1 args2
47 | ```
48 |
49 | but you can execute it like a method as well
50 |
51 | ```
52 | your_command(args1, args2)
53 | ```
54 |
55 | the first way keep it easy to execute a command, and the second way provide the ability to visit the member of return value looks like below.
56 |
57 | ```
58 | get_enemy("slime").Jump()
59 | ```
60 |
61 | ## 3.Console Variable
62 |
63 | the new console supports you to register a console variable and access it through `@`, this would make the overall command system very free, assuming that you have an object like this :
64 |
65 | ```csharp
66 | public class Player : MonoBehaviour{
67 |
68 | public int health = 100;
69 |
70 | public void Jump(){
71 | // code for jump..
72 | }
73 | }
74 | ```
75 | you can setup a static variable, and add an `CommandProperty` attribute for it to register a console variable
76 |
77 | ```csharp
78 | public class Player: MonoBehaviour{
79 |
80 | [CommandProperty("player")]
81 | public static Player Instance;
82 | }
83 | ```
84 | (you must write a Player instance into the static variable while the game start)
85 |
86 | Then you can access the inner member of the `Player`, its all rely on C#'s Reflection System, you can use the commands like below:
87 |
88 | ```
89 | @player.health = 100
90 | @player.Jump()
91 | ```
92 |
93 | Similarly, anywhere you need a variable, you can use `@` to reference the variable you need, for example, as a parameter to other commands
94 |
95 | ```
96 | @enemy.Atk(@player)
97 | ```
98 |
99 | the `CommandProperty` can be added to a Field or a Property, this means the console variable can return different value, like "the Npc nearest to player"
100 |
101 |
102 | ```csharp
103 | [CommandProperty("nearest")]
104 | public static Npc NearestNpc{
105 | get{
106 | // code for getting nearest npc..
107 | }
108 | }
109 | ```
110 |
111 | ## 4.Access console variable
112 |
113 | you can access or overwrite member of console variable through `.` or `[]`
114 |
115 | ```
116 | @player.DoSomething()
117 | @player.buffs["buff_id"].AddTime(100)
118 | @enemies["slime"] = @new_slime
119 | ```
120 |
121 | of course, the access is continues like normal programming language
122 |
123 | ```
124 | @something.member.sub_member["elements"].member.sub_member.field.function().member
125 | ```
126 |
127 | the `[]` has some special, it would check if the target member is a sequence object like an Array or List, if it is, then it requires the expression inner `[]` to be an Interger value.
128 |
129 | The Dictionary supports more key type, so the the judgment range of the dictionary would be wider.
130 |
131 | ```
132 | @some_dict[@non_string_type] = get_something()
133 | ```
134 |
135 | for example, your dictionary key type is a non-string type, you can use other console variables as the key to read or write.
136 |
137 | ## 5.Value Parse
138 |
139 | the normal value types supported by the CommandSystem has only 5.
140 |
141 | ```
142 | string
143 | float
144 | int
145 | bool
146 | null
147 | ```
148 |
149 | you can call these types as meta-type, it can be parsed by command system directly, generally speaking, it is sufficient to use. However, if you want the console to be able to parse different data types, you can do so by reigstering a value parser.
150 |
151 | for example, the position of the player maybe a `Vector3` type, you may wish to set the position of player like this:
152 |
153 | ```
154 | @player.pos = "1. 1. 1."
155 | ```
156 |
157 | Obviously, strings cannot be converted to `Vector3`, if you really want to do this, or if it's necessary to do so, you can register a
158 | ValueParser as follows:
159 |
160 | ```csharp
161 |
162 | [CommandValueParser(typeof(Vector3))]
163 | static bool ParseFunction(string input, out object data){
164 | // code used to parse your input str
165 | }
166 | ```
167 |
168 | the parse logic is totally free by you. for example, you can parse 'revival_point' to `Vector3`
169 |
170 | ```csharp
171 | static bool ParseFunction(string input, out object data){
172 | if(input == "revival_point"){
173 | data = Vector3.zero;
174 | return true;
175 | }
176 | // other prase here ..
177 |
178 | data = default;
179 | return false;
180 | }
181 | ```
182 |
183 | then you can use the command as follows:
184 |
185 | ```
186 | @player.pos = "revival_point"
187 | ```
188 |
189 | the double quotation here is not necessary, but it's not suggestted to do like this. the system would first consider 'revival_point' to a command the try to execute it to get return value. the system would try parse it to `Vector3` only if it cannot find a command named 'revival_point'
190 |
191 | ```
192 | @player.pos = revival_point
193 | ```
194 |
195 | so you'd better not to use such ambiguous commands;
196 |
197 | the ValueParser usually used in some special suitations or complex data type, if you want to parse `Vector3`, the right way is registering a command like this:
198 |
199 | ```csharp
200 | [Command("v3")]
201 | public static Vector3 BuildVector3(float x, float y, float z){
202 | return new Vector3(x, y, z);
203 | }
204 | ```
205 |
206 | and use it as follows
207 |
208 | ```
209 | @player.pos = v3(0, 0, 0)
210 | ```
211 |
212 | ## 6.Register Debug Infos
213 |
214 | it you want to show the information of target object, for example, you just input '@player' to the console.
215 |
216 | ```
217 | @player
218 | ```
219 |
220 | the console could output the player informations likes below:
221 |
222 | 
223 |
224 | to fetch the information structure, you can add an `DebugInfo` attribute to target field or property
225 |
226 | ```csharp
227 | public class Player : MonoBehaviour{
228 |
229 | [DebugInfo]
230 | public int health = 100;
231 | }
232 | ```
233 |
234 | In this way, by entering `@player` to the console, you can directly obtain all DebugInfo of the player, and the output is as follows:
235 |
236 | ```
237 | ---------- Player start ----------
238 | >Player.health: 100
239 | ---------- Player end ----------
240 | ```
241 |
242 | you can set title and color for it.
243 |
244 | ```csharp
245 | [DebugInfo("custom_title", Color = "#ff0000")]
246 | ```
247 |
248 | ### 6.1 Tracing Parent Class
249 |
250 | system would trace all its parent class, and output the DebugInfo of the original parent class to the target class one by one
251 |
252 | ```csharp
253 | public class A{
254 | [DebugInfo]
255 | public int someInt = 999;
256 | }
257 |
258 | public class B{
259 | [DebugInfo]
260 | public float someFloat = 12.3;
261 | }
262 |
263 | public class C{
264 | [DebugInfo]
265 | public string someStr = "Hello World!";
266 | }
267 | ```
268 |
269 | if you try to show the DebugInfo of C, you will get:
270 |
271 | ```
272 | ---------- C start ----------
273 | >A.someInt: 999
274 | >B.someFloat: 12.3
275 | >C.someStr: Hello World!
276 | ---------- C end ----------
277 | ```
278 |
279 | ### 6.3 DebugInfo of complex member
280 |
281 | if the member which has beed reigstered as a DebugInfo and its also have inner DebugInfos, you can add an `DebugObject` attribute to the target class:
282 |
283 | ```csharp
284 | public class A{
285 | [DebugInfo("my_object")]
286 | public B b = new B();
287 | }
288 |
289 | [DebugObject]
290 | public class B : MonoBehaviour{
291 | [DebugInfo("name")]
292 | public string Name => this.gameObject.name;
293 |
294 | [DebugInfo]
295 | public string age = 25;
296 | }
297 | ```
298 |
299 | if you try to ouput A, you will get this:
300 |
301 | ```
302 | ---------- C start ----------
303 | >----[B]
304 | > B.name: "instanceB"
305 | > B.age: 25
306 | ---------- C end ----------
307 | ```
308 |
309 | However, in this way, there may be circular data reference, such as B referencing C and C referencing B again, so you can set and depth to limit the circular data reference, if depth exceeds the limitation, system will discard the more informations, the default depth limitation is 4
310 |
311 | ## 7.Input Suggestion
312 |
313 | nothing to describe
314 |
315 | .png)
316 |
317 | ## 8.Console Logs
318 |
319 | the new system has decoupled the output and render behaviours, this means the console only save the log but don't care how to render it. but you need to provide an logType to mark the logs. you can set it while you initialize the game console.
320 |
321 | ```csharp
322 | /* intialize console */
323 | console = new ConsoleController(
324 | consoleRenderer,
325 | new UserInput(),
326 |
327 | inputHistoryCapacity: inputHistoryCapacity,
328 | commandQueryCacheCapacity: commandQueryCacheCapacity,
329 | alternativeCommandCount: alternativeCommandCount,
330 | shouldRecordFailedCommand: shouldRecordFailedCommand,
331 | outputWithTime: shouldOutputWithTime,
332 | outputStackTraceOfCommandExecution: shouldOutputVMExceptionStack
333 | );
334 | ```
335 | and you can decide where to save the logs.
336 |
337 | ## Other
338 |
339 | ### UnityEngine Versions
340 |
341 | Unity 2018.03+
342 |
343 | it support all version.
344 |
345 | ### Feature Support: Basic Calculation
346 |
347 | The system is designed for convenient debugging work, so its positioning it not a complete programing language, so it currently does not support basic operations such as mathematical or logical operations, if there is a need for this feature, you can proposed on Github, if it is indeed necessary, I will implement it.
348 |
--------------------------------------------------------------------------------
/Res/Untitled (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Res/Untitled (1).png
--------------------------------------------------------------------------------
/Res/Untitled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Res/Untitled.png
--------------------------------------------------------------------------------
/Res/screen-shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Res/screen-shot.png
--------------------------------------------------------------------------------
/Res/usage-part-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Res/usage-part-1.png
--------------------------------------------------------------------------------
/Res/usage.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Res/usage.mp4
--------------------------------------------------------------------------------
/Res/屏幕截图 2024-01-12 173800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/529324416/UnityCommandLineInterface/574198026618102dc3906a532bde063713ac85b7/Res/屏幕截图 2024-01-12 173800.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"com.redsaw.commandline",
3 | "displayName": "Inner Game Console",
4 | "version": "0.1.1",
5 | "unity": "2018.3",
6 | "description": "an inner game command line tool",
7 | "keywords": ["commandline", "cli", "debugging", "log"],
8 | "category": "Debugging",
9 | "dependencies": {},
10 | "samples": [
11 | {
12 | "displayName": "Inner Game Console",
13 | "description": "an inner game command line tool",
14 | "path": "Assets/Scenes/SampleScene"
15 | }
16 | ],
17 | "author": {
18 | "name": "Prince Biscuit",
19 | "url": "https://github.com/529324416"
20 | }
21 | }
--------------------------------------------------------------------------------