Factory Modal 개발중. UTKLoading 개발 완료

This commit is contained in:
logonkhi
2026-02-25 20:27:11 +09:00
parent 8ca8bd0df9
commit 2914667223
98 changed files with 5645 additions and 282 deletions

View File

@@ -36,7 +36,8 @@
"Bash(/bin/rm:*)",
"WebFetch(domain:docs.unity3d.com)",
"Bash(ls:*)",
"WebFetch(domain:discussions.unity.com)"
"WebFetch(domain:discussions.unity.com)",
"mcp__ai-game-developer__screenshot-game-view"
],
"deny": [],
"ask": []

View File

@@ -1,8 +1,8 @@
{
"mcpServers": {
"UnityMCP": {
"ai-game-developer": {
"type": "http",
"url": "http://localhost:8080/mcp"
"url": "http://localhost:55726"
}
}
}

View File

@@ -0,0 +1,423 @@
{
"logLevel": 3,
"keepServerRunning": true,
"transportMethod": 2,
"tools": [
{
"name": "assets-copy",
"enabled": true
},
{
"name": "assets-create-folder",
"enabled": true
},
{
"name": "assets-delete",
"enabled": true
},
{
"name": "assets-find",
"enabled": true
},
{
"name": "assets-find-built-in",
"enabled": true
},
{
"name": "assets-get-data",
"enabled": true
},
{
"name": "assets-material-create",
"enabled": true
},
{
"name": "assets-modify",
"enabled": true
},
{
"name": "assets-move",
"enabled": true
},
{
"name": "assets-refresh",
"enabled": true
},
{
"name": "assets-prefab-close",
"enabled": true
},
{
"name": "assets-prefab-create",
"enabled": true
},
{
"name": "assets-prefab-instantiate",
"enabled": true
},
{
"name": "assets-prefab-open",
"enabled": true
},
{
"name": "assets-prefab-save",
"enabled": true
},
{
"name": "assets-shader-list-all",
"enabled": true
},
{
"name": "console-get-logs",
"enabled": true
},
{
"name": "editor-application-get-state",
"enabled": true
},
{
"name": "editor-application-set-state",
"enabled": true
},
{
"name": "editor-selection-get",
"enabled": true
},
{
"name": "editor-selection-set",
"enabled": true
},
{
"name": "gameobject-component-add",
"enabled": true
},
{
"name": "gameobject-component-destroy",
"enabled": true
},
{
"name": "gameobject-component-get",
"enabled": true
},
{
"name": "gameobject-component-list-all",
"enabled": true
},
{
"name": "gameobject-component-modify",
"enabled": true
},
{
"name": "gameobject-create",
"enabled": true
},
{
"name": "gameobject-destroy",
"enabled": true
},
{
"name": "gameobject-duplicate",
"enabled": true
},
{
"name": "gameobject-find",
"enabled": true
},
{
"name": "gameobject-modify",
"enabled": true
},
{
"name": "gameobject-set-parent",
"enabled": true
},
{
"name": "object-get-data",
"enabled": true
},
{
"name": "object-modify",
"enabled": true
},
{
"name": "package-add",
"enabled": true
},
{
"name": "package-list",
"enabled": true
},
{
"name": "package-remove",
"enabled": true
},
{
"name": "package-search",
"enabled": true
},
{
"name": "reflection-method-call",
"enabled": true
},
{
"name": "reflection-method-find",
"enabled": true
},
{
"name": "scene-create",
"enabled": true
},
{
"name": "scene-get-data",
"enabled": true
},
{
"name": "scene-list-opened",
"enabled": true
},
{
"name": "scene-open",
"enabled": true
},
{
"name": "scene-save",
"enabled": true
},
{
"name": "scene-set-active",
"enabled": true
},
{
"name": "scene-unload",
"enabled": true
},
{
"name": "screenshot-camera",
"enabled": true
},
{
"name": "screenshot-game-view",
"enabled": true
},
{
"name": "screenshot-scene-view",
"enabled": true
},
{
"name": "script-delete",
"enabled": true
},
{
"name": "script-execute",
"enabled": true
},
{
"name": "script-read",
"enabled": true
},
{
"name": "script-update-or-create",
"enabled": true
},
{
"name": "tests-run",
"enabled": true
}
],
"prompts": [
{
"name": "setup-animator-controller",
"enabled": true
},
{
"name": "create-simple-tweening",
"enabled": true
},
{
"name": "setup-timeline-sequence",
"enabled": true
},
{
"name": "add-animation-events",
"enabled": true
},
{
"name": "create-procedural-animation",
"enabled": true
},
{
"name": "setup-sprite-animation",
"enabled": true
},
{
"name": "add-ik-system",
"enabled": true
},
{
"name": "create-animation-blending",
"enabled": true
},
{
"name": "organize-project-structure",
"enabled": true
},
{
"name": "import-setup-sprites",
"enabled": true
},
{
"name": "setup-audio-manager",
"enabled": true
},
{
"name": "configure-build-settings",
"enabled": true
},
{
"name": "create-material-library",
"enabled": true
},
{
"name": "setup-asset-bundles",
"enabled": true
},
{
"name": "optimize-texture-settings",
"enabled": true
},
{
"name": "setup-addressables",
"enabled": true
},
{
"name": "add-debug-visualization",
"enabled": true
},
{
"name": "setup-performance-profiling",
"enabled": true
},
{
"name": "create-test-scene",
"enabled": true
},
{
"name": "add-logging-system",
"enabled": true
},
{
"name": "create-unit-tests",
"enabled": true
},
{
"name": "setup-debug-ui",
"enabled": true
},
{
"name": "add-assertion-checks",
"enabled": true
},
{
"name": "create-automated-tests",
"enabled": true
},
{
"name": "add-standard-components",
"enabled": true
},
{
"name": "setup-player-controller",
"enabled": true
},
{
"name": "create-ui-canvas",
"enabled": true
},
{
"name": "add-physics-interactions",
"enabled": true
},
{
"name": "create-interactive-object",
"enabled": true
},
{
"name": "setup-audio-source",
"enabled": true
},
{
"name": "create-particle-effects",
"enabled": true
},
{
"name": "setup-animator-component",
"enabled": true
},
{
"name": "setup-basic-scene",
"enabled": true
},
{
"name": "organize-scene-hierarchy",
"enabled": true
},
{
"name": "add-lighting-setup",
"enabled": true
},
{
"name": "create-prefab-from-selection",
"enabled": true
},
{
"name": "setup-scene-camera",
"enabled": true
},
{
"name": "create-environment-template",
"enabled": true
},
{
"name": "generate-monobehaviour-template",
"enabled": true
},
{
"name": "add-event-system",
"enabled": true
},
{
"name": "create-singleton-manager",
"enabled": true
},
{
"name": "setup-coroutine-framework",
"enabled": true
},
{
"name": "create-scriptableobject-data",
"enabled": true
},
{
"name": "implement-object-pooling",
"enabled": true
},
{
"name": "add-state-machine",
"enabled": true
},
{
"name": "setup-dependency-injection",
"enabled": true
}
],
"resources": [
{
"name": "GameObject from Current Scene by Path",
"enabled": true
}
],
"host": "http://localhost:55726",
"timeoutMs": 10000,
"keepConnected": true,
"token": null
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5ecf6949f73508f49ba3a0633329a219
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -139,7 +139,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -12, y: -1}
m_AnchoredPosition: {x: -20, y: -1}
m_SizeDelta: {x: 22, y: 22}
m_Pivot: {x: 1, y: 1}
--- !u!222 &5485997075992529675
@@ -246,7 +246,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 1375483911066562825, guid: 963f47de712c3844183989c7fc7fbd8a, type: 3}
propertyPath: m_SizeDelta.x
value: 238
value: -65
objectReference: {fileID: 0}
- target: {fileID: 5097404189591199700, guid: 963f47de712c3844183989c7fc7fbd8a, type: 3}
propertyPath: m_AnchorMax.x

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 62b1c6b38fe308748b808fb2b576e6e2
guid: 35623d99e7e18de4c9218b9878efaac6
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9210d67d228d2814eb32cc841227db5b
guid: 9ffd6459fed612148ac685b6329657a1
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="root" style="padding-left: 20px;">
<utk:UTKLabel text="알람 정보"/>
</ui:VisualElement>
</ui:UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 92f820b7de9d43c428503b7c574c9e69
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="root" style="padding-left: 20px;">
<utk:UTKReordableTabList name="tab-list" />
</ui:VisualElement>
</ui:UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: db6157353e844a84fabf731aed504f56
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="root" style="padding-left: 20px;">
<utk:UTKLabel text="일반 정보"/>
</ui:VisualElement>
</ui:UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7b805d40178aceb48b21275834afbb24
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="root" style="padding-left: 20px;">
<utk:UTKLabel text="입력 정보"/>
</ui:VisualElement>
</ui:UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5224fd32ba226a340aaac4e6dc070861
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,68 @@
/*
* ===================================
* UTKReordableTabListUss.uss
* 탭 기반 재정렬 가능 리스트 스타일
* ===================================
*/
/* ===================================
Base Container
=================================== */
.reordable-tab-list {
flex-grow: 1;
padding: 8px;
background-color: var(--color-bg-secondary);
border-radius: var(--radius-s);
border-width: var(--border-width);
border-color: var(--color-border);
}
/* ===================================
TabView 내부 스타일 오버라이드
외부 utk-tabview--align-left 등 align 클래스가
descendant selector로 내부 TabView에 영향을 주지 않도록 리셋
=================================== */
.reordable-tab-list > .utk-tabview {
flex-grow: 1;
flex-direction: column;
}
.reordable-tab-list > .utk-tabview > .unity-tab-view__header-container {
flex-direction: row;
border-right-width: 0;
border-top-width: 0;
border-left-width: 0;
border-bottom-width: var(--border-width);
border-bottom-color: var(--color-border);
}
.reordable-tab-list > .utk-tabview > .unity-tab-view__content-container {
padding: 0;
}
.reordable-tab-list > .utk-tabview .unity-tab__header {
margin-right: var(--space-s);
margin-bottom: 0;
position: relative;
}
.reordable-tab-list > .utk-tabview .unity-tab__header-underline {
left: 0;
right: 0;
bottom: 0;
top: auto;
width: auto;
height: 2px;
}
/* ===================================
탭 콘텐츠 내부 ReordableList 스타일 오버라이드
=================================== */
.reordable-tab-list .reordable-list {
border-width: 0;
border-radius: 0;
background-color: transparent;
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 360ccda715f15bc479dec409d0ed87cf
guid: 5e72692fcb817e14e8fbcaaf9b4f6b17
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}

View File

@@ -1,47 +0,0 @@
/*
* ===================================
* UTKSettingDisplayInfoTabViewUss.uss
* 설정 표시 정보 탭 뷰 컨테이너 스타일
* ===================================
*/
/* ===================================
Base Container
=================================== */
.setting-display-tab-view {
flex-grow: 1;
background-color: var(--color-bg-secondary);
border-radius: var(--radius-s);
border-width: var(--border-width);
border-color: var(--color-border);
}
/* ===================================
ListView 내부 여백 조정
=================================== */
.setting-display-tab-view .utk-listview {
flex-grow: 1;
border-width: 0;
border-radius: 0;
background-color: transparent;
}
/* ===================================
드래그 중 아이템 스타일 (Unity 내장 클래스)
=================================== */
.setting-display-tab-view .unity-list-view__reorderable-item__container {
flex-direction: row;
align-items: center;
}
/* ===================================
드래그 핸들 바 (Unity 내장 reorder handle)
커스텀 드래그 핸들 사용하므로 기본 숨김
=================================== */
.setting-display-tab-view .unity-list-view__reorderable-handle-bar {
display: none;
}

View File

@@ -0,0 +1,16 @@
/*
* ===================================
* UTKLoadingUss.uss
* 회전 로딩 스피너 스타일
* ===================================
*
* 색상은 C#에서 CustomStyleResolvedEvent로 --color-primary 값을 읽어 처리합니다.
* 두께/크기는 C# 속성(Thickness, Size) 또는 UXML 태그(thickness, size)로 설정하세요.
*/
.utk-loading {
align-items: center;
justify-content: center;
flex-shrink: 0;
overflow: hidden;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21b7e91639030834c94d38263ad773ae
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKListSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="드래그로 항목 순서를 변경할 수 있는 리스트 컴포넌트 (체크박스 + 입력 필드)" />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="ReordableList" />
<utk:UTKReordableList name="reordable-list-sample" style="height: 250px; width: 350px;" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock name="code-csharp" title="C#" />
<utk:UTKCodeBlock name="code-uxml" title="UXML" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: cb106aca3db68c845acaa7b480de76e7
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKListSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="탭별로 독립적인 재정렬 가능 리스트를 제공하는 컴포넌트 (UTKTabView + UTKReordableList)" />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="ReordableTabList" />
<utk:UTKReordableTabList name="reordable-tab-list-sample" style="height: 300px;" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock name="code-csharp" title="C#" />
<utk:UTKCodeBlock name="code-uxml" title="UXML" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c0ae1e70887e1a4429de0c54e6d69c18
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="회전하면서 호가 길어졌다 짧아지는 원형 로딩 스피너" />
<!-- Static API: 전체 화면 로딩 + Blocker -->
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="Static API (전체 화면 로딩 + Blocker)" />
<VisualElement class="utk-sample-row">
<utk:UTKButton name="btn-show" text="Show (기본 속도)" variant="Primary" />
<utk:UTKButton name="btn-show-fast" text="Show (빠르게 x2)" variant="Normal" />
<utk:UTKButton name="btn-show-slow" text="Show (느리게 x0.5)" variant="Normal" />
<utk:UTKButton name="btn-hide" text="Hide" variant="Danger" />
</VisualElement>
</VisualElement>
<!-- UXML 인라인 배치 -->
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="UXML 인라인 배치 (Blocker 없음, auto-play)" />
<VisualElement class="utk-sample-row" style="align-items: center; justify-content: flex-start; gap: 24px;">
<!-- 기본 크기 -->
<VisualElement style="align-items: center; gap: 4px;">
<utk:UTKLoading speed="1.0" auto-play="true" />
<Label text="기본 (48px)" style="font-size: 10px; color: rgba(255,255,255,0.5);" />
</VisualElement>
<!-- 작은 크기 -->
<VisualElement style="align-items: center; gap: 4px;">
<utk:UTKLoading name="loading-small" speed="1.0" auto-play="true" />
<Label text="Small (32px)" style="font-size: 10px; color: rgba(255,255,255,0.5);" />
</VisualElement>
<!-- 큰 크기 -->
<VisualElement style="align-items: center; gap: 4px;">
<utk:UTKLoading name="loading-large" speed="1.0" auto-play="true" />
<Label text="Large (72px)" style="font-size: 10px; color: rgba(255,255,255,0.5);" />
</VisualElement>
<!-- 빠른 속도 -->
<VisualElement style="align-items: center; gap: 4px;">
<utk:UTKLoading speed="2.5" auto-play="true" />
<Label text="Fast (x2.5)" style="font-size: 10px; color: rgba(255,255,255,0.5);" />
</VisualElement>
</VisualElement>
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock name="code-csharp" title="C#" />
<utk:UTKCodeBlock name="code-uxml" title="UXML" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 99a140d3bee776941b1e6c197b653811
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -18,6 +18,7 @@ public class UTKReordableListSample : MonoBehaviour
private UTKToggle _themeToggle;
private UTKReordableList _reordableList;
private UTKReordableTabList _reordableTabList;
void Start()
{
@@ -48,6 +49,14 @@ public class UTKReordableListSample : MonoBehaviour
return;
}
// ReordableTabList
_reordableTabList = root.Q<UTKReordableTabList>("tab-list");
if (_reordableTabList == null)
{
Debug.LogError("UXML에서 UTKReordableTabList를 찾을 수 없습니다.");
return;
}
// 테마 초기화
UTKThemeManager.Instance.RegisterRoot(root);
UTKThemeManager.Instance.SetTheme(initialTheme);
@@ -63,6 +72,13 @@ public class UTKReordableListSample : MonoBehaviour
// 샘플 데이터 설정 (Dictionary 방식)
SetSampleData();
SetTabListSampleData();
// 이벤트 핸들러 등록 (TabList)
_reordableTabList.OnOrderChanged += (tabName, tabIndex) =>
Debug.Log($"[TabList] [{tabName}] (탭 {tabIndex}) 순서 변경됨");
_reordableTabList.OnDataChanged += (tabName, tabIndex) =>
Debug.Log($"[TabList] [{tabName}] (탭 {tabIndex}) 데이터 변경됨");
// 하단 버튼 영역 생성
CreateButtons(root);
@@ -84,6 +100,35 @@ public class UTKReordableListSample : MonoBehaviour
_reordableList.SetData(listDict);
}
/// <summary>
/// UTKReordableTabList에 탭별 샘플 데이터를 설정합니다.
/// </summary>
private void SetTabListSampleData()
{
var data = new Dictionary<string, List<Dictionary<string, string>>>
{
["센서"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "온도 센서" },
new() { ["order"] = "1", ["active"] = "True", ["text"] = "습도 센서" },
new() { ["order"] = "2", ["active"] = "False", ["text"] = "압력 센서" },
},
["장비"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "컨베이어" },
new() { ["order"] = "1", ["active"] = "False", ["text"] = "로봇암" },
new() { ["order"] = "2", ["active"] = "True", ["text"] = "CNC" },
new() { ["order"] = "3", ["active"] = "True", ["text"] = "AGV" },
},
["알림"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "경고" },
new() { ["order"] = "1", ["active"] = "True", ["text"] = "에러" },
}
};
_reordableTabList.SetData(data);
}
/// <summary>
/// 테스트 버튼들을 생성합니다.
/// </summary>
@@ -112,6 +157,18 @@ public class UTKReordableListSample : MonoBehaviour
addBtn.OnClicked += OnAddItemClicked;
buttonContainer.Add(addBtn);
// TabList ToDictionary 버튼
var tabDictBtn = new UTKButton("TabList ToDictionary", variant: UTKButton.ButtonVariant.Primary);
tabDictBtn.OnClicked += OnTabListToDictionaryClicked;
tabDictBtn.style.marginLeft = 16;
tabDictBtn.style.marginRight = 4;
buttonContainer.Add(tabDictBtn);
// TabList 리셋 버튼
var tabResetBtn = new UTKButton("TabList 리셋", variant: UTKButton.ButtonVariant.Normal);
tabResetBtn.OnClicked += () => SetTabListSampleData();
buttonContainer.Add(tabResetBtn);
root.Add(buttonContainer);
}
@@ -128,6 +185,23 @@ public class UTKReordableListSample : MonoBehaviour
}
}
/// <summary>
/// TabList의 ToDictionary를 호출하여 탭별 데이터를 콘솔에 출력합니다.
/// </summary>
private void OnTabListToDictionaryClicked()
{
var result = _reordableTabList.ToDictionary();
Debug.Log($"[TabList] ToDictionary 결과 ({result.Count}개 탭):");
foreach (var kvp in result)
{
Debug.Log($" 탭 [{kvp.Key}] ({kvp.Value.Count}건):");
foreach (var dict in kvp.Value)
{
Debug.Log($" order={dict["order"]}, active={dict["active"]}, text={dict["text"]}");
}
}
}
/// <summary>
/// 새 아이템을 추가합니다.
/// </summary>
@@ -148,5 +222,6 @@ public class UTKReordableListSample : MonoBehaviour
private void OnDestroy()
{
_reordableList?.Dispose();
_reordableTabList?.Dispose();
}
}

View File

@@ -1,6 +1,17 @@
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<VisualElement style="width: 100%; height: 100%;">
<utk:UTKReordableList name="window" style="width: 300px; height: 200px;" />
<VisualElement style="width: 100%; height: 100%; flex-direction: row; padding: 16px;">
<!-- 기존 UTKReordableList 샘플 -->
<VisualElement style="width: 300px; margin-right: 16px;">
<Label text="UTKReordableList" style="font-size: 14px; -unity-font-style: bold; margin-bottom: 8px;" />
<utk:UTKReordableList name="window" style="height: 200px;" />
</VisualElement>
<!-- UTKReordableTabList 샘플 -->
<VisualElement style="width: 400px;">
<Label text="UTKReordableTabList" style="font-size: 14px; -unity-font-style: bold; margin-bottom: 8px;" />
<utk:UTKReordableTabList name="tab-list" style="height: 300px;" />
</VisualElement>
<utk:UTKToggle name="toggle" label="테마 변경" style="position: absolute; top: 10px; right: 10px;" />
</VisualElement>
</UXML>

View File

@@ -6,6 +6,7 @@ using UVC.UIToolkit;
using UVC.UI.Commands;
using UVC.Log;
using UVC.Studio.UIToolkit.Modal;
using Factory.UIToolkit.Modal;
namespace UVC.Sample.UIToolkit
{
@@ -26,6 +27,11 @@ namespace UVC.Sample.UIToolkit
private UTKButton? _openButton0;
private UTKButton? _openButton1;
private UTKButton? _openButton2;
private UTKButton? _openButton10;
private UTKButton? _openButton11;
private UTKButton? _openButton12;
private UTKButton? _openButton13;
private void Start()
{
@@ -85,6 +91,49 @@ namespace UVC.Sample.UIToolkit
};
}
_openButton10 = _root.Q<UTKButton>("openButton10");
if (_openButton10 != null) {
_openButton10.OnClicked += async () =>
{
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
var content = new UTKFactorySettingModalContent(0); // 초기 탭 인덱스 설정
modal.Add(content);
await modal.ShowAsync();
};
}
_openButton11 = _root.Q<UTKButton>("openButton11");
if (_openButton11 != null) {
_openButton11.OnClicked += async () =>
{
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
var content = new UTKFactorySettingModalContent(1); // 초기 탭 인덱스 설정
modal.Add(content);
await modal.ShowAsync();
};
}
_openButton12 = _root.Q<UTKButton>("openButton12");
if (_openButton12 != null) {
_openButton12.OnClicked += async () =>
{
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
var content = new UTKFactorySettingModalContent(2); // 초기 탭 인덱스 설정
modal.Add(content);
await modal.ShowAsync();
};
}
_openButton13 = _root.Q<UTKButton>("openButton13");
if (_openButton13 != null) {
_openButton13.OnClicked += async () =>
{
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
var content = new UTKFactorySettingModalContent(3); // 초기 탭 인덱스 설정
modal.Add(content);
await modal.ShowAsync();
};
}
}
private void OnDestroy()

View File

@@ -4,5 +4,10 @@
<utk:UTKButton name="openButton0" text="Open0" style="position: absolute; top: 8px; left: 10px; z-index: 10;" />
<utk:UTKButton name="openButton1" text="Open1" style="position: absolute; top: 38px; left: 10px; z-index: 10;" />
<utk:UTKButton name="openButton2" text="Open2" style="position: absolute; top: 68px; left: 10px; z-index: 10;" />
<utk:UTKButton name="openButton10" text="Open10" style="position: absolute; top: 108px; left: 10px; z-index: 10;" />
<utk:UTKButton name="openButton11" text="Open11" style="position: absolute; top: 138px; left: 10px; z-index: 10;" />
<utk:UTKButton name="openButton12" text="Open12" style="position: absolute; top: 168px; left: 10px; z-index: 10;" />
<utk:UTKButton name="openButton13" text="Open13" style="position: absolute; top: 198px; left: 10px; z-index: 10;" />
</VisualElement>
</UXML>

View File

@@ -519,6 +519,166 @@ list.SetData(current);",
<!-- 고정 크기 -->
<utk:UTKShortcutList style=""height: 300px;"" />
<!-- 데이터와 이벤트는 C#에서 설정 -->");
}
private void InitializeReordableListSample(VisualElement root)
{
var reordableList = root.Q<UTKReordableList>("reordable-list-sample");
if (reordableList != null)
{
var listDict = new List<Dictionary<string, string>>
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "온도" },
new() { ["order"] = "1", ["active"] = "False", ["text"] = "습도" },
new() { ["order"] = "2", ["active"] = "True", ["text"] = "압력" },
new() { ["order"] = "3", ["active"] = "True", ["text"] = "풍속" },
new() { ["order"] = "4", ["active"] = "False", ["text"] = "조도" },
};
reordableList.SetData(listDict);
reordableList.OnOrderChanged += () =>
Debug.Log("[StyleGuide] ReordableList 순서 변경됨");
reordableList.OnDataChanged += () =>
Debug.Log("[StyleGuide] ReordableList 데이터 변경됨");
}
SetCodeSamples(root,
csharpCode:
@"// 리스트 생성
var list = new UTKReordableList();
// ReordableListItemData로 직접 설정
var items = new List<ReordableListItemData>
{
new() { Order = 0, IsActive = true, DisplayText = ""온도"" },
new() { Order = 1, IsActive = false, DisplayText = ""습도"" },
new() { Order = 2, IsActive = true, DisplayText = ""압력"" },
};
list.SetData(items);
// Dictionary<string, string> 형식으로도 설정 가능
var listDict = new List<Dictionary<string, string>>
{
new() { [""order""] = ""0"", [""active""] = ""True"", [""text""] = ""온도"" },
new() { [""order""] = ""1"", [""active""] = ""False"", [""text""] = ""습도"" },
};
list.SetData(listDict);
// 데이터 가져오기
var data = list.GetData();
// Dictionary 형식으로 변환
var dictResult = list.ToDictionary();
// 이벤트 구독
list.OnOrderChanged += () => Debug.Log(""순서 변경됨"");
list.OnDataChanged += () => Debug.Log(""데이터 변경됨"");
// 정리
list.Dispose();",
uxmlCode:
@"<!-- 기본 사용 -->
<utk:UTKReordableList name=""reordable-list"" />
<!-- 고정 크기 -->
<utk:UTKReordableList style=""height: 250px; width: 350px;"" />
<!-- 데이터와 이벤트는 C#에서 설정 -->");
}
private void InitializeReordableTabListSample(VisualElement root)
{
var tabList = root.Q<UTKReordableTabList>("reordable-tab-list-sample");
if (tabList != null)
{
var data = new Dictionary<string, List<Dictionary<string, string>>>
{
["센서"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "온도 센서" },
new() { ["order"] = "1", ["active"] = "True", ["text"] = "습도 센서" },
new() { ["order"] = "2", ["active"] = "False", ["text"] = "압력 센서" },
},
["장비"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "컨베이어" },
new() { ["order"] = "1", ["active"] = "False", ["text"] = "로봇암" },
new() { ["order"] = "2", ["active"] = "True", ["text"] = "CNC" },
new() { ["order"] = "3", ["active"] = "True", ["text"] = "AGV" },
},
["알림"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "경고" },
new() { ["order"] = "1", ["active"] = "True", ["text"] = "에러" },
}
};
tabList.SetData(data);
tabList.OnOrderChanged += (tabName, tabIndex) =>
Debug.Log($"[StyleGuide] [{tabName}] (탭 {tabIndex}) 순서 변경됨");
tabList.OnDataChanged += (tabName, tabIndex) =>
Debug.Log($"[StyleGuide] [{tabName}] (탭 {tabIndex}) 데이터 변경됨");
}
SetCodeSamples(root,
csharpCode:
@"// 탭별 데이터 설정
var tabList = new UTKReordableTabList();
var data = new Dictionary<string, List<ReordableListItemData>>
{
[""센서""] = new()
{
new() { Order = 0, IsActive = true, DisplayText = ""온도 센서"" },
new() { Order = 1, IsActive = true, DisplayText = ""습도 센서"" },
new() { Order = 2, IsActive = false, DisplayText = ""압력 센서"" },
},
[""장비""] = new()
{
new() { Order = 0, IsActive = true, DisplayText = ""컨베이어"" },
new() { Order = 1, IsActive = false, DisplayText = ""로봇암"" },
}
};
tabList.SetData(data);
// Dictionary<string, string> 형식도 지원
var dictData = new Dictionary<string, List<Dictionary<string, string>>>
{
[""탭1""] = new()
{
new() { [""order""] = ""0"", [""active""] = ""True"", [""text""] = ""항목1"" },
}
};
tabList.SetData(dictData);
// 전체 데이터 가져오기
var allData = tabList.GetData();
// 특정 탭 데이터 가져오기 (이름 또는 인덱스)
var sensorData = tabList.GetData(""센서"");
var firstTabData = tabList.GetData(0);
// Dictionary 형식으로 변환
var dictResult = tabList.ToDictionary();
// 이벤트 구독 (탭 이름, 탭 인덱스 전달)
tabList.OnOrderChanged += (tabName, tabIndex) =>
Debug.Log($""[{tabName}] (탭 {tabIndex}) 순서 변경됨"");
tabList.OnDataChanged += (tabName, tabIndex) =>
Debug.Log($""[{tabName}] (탭 {tabIndex}) 데이터 변경됨"");
// SetData 재호출 시 기존 구성 제거 후 재생성
tabList.SetData(newData);
// 정리
tabList.Dispose();",
uxmlCode:
@"<!-- 기본 사용 -->
<utk:UTKReordableTabList name=""tab-list"" />
<!-- 고정 크기 -->
<utk:UTKReordableTabList style=""height: 300px;"" />
<!-- 데이터와 이벤트는 C#에서 설정 -->");
}

View File

@@ -701,6 +701,103 @@ if (result != null)
#endregion
#region Loading Initializers
private void InitializeLoadingSample(VisualElement root)
{
if (_root == null) return;
UTKLoading.SetRoot(_root);
// Static API 버튼
var btnShow = root.Q<UTKButton>("btn-show");
btnShow?.RegisterCallback<ClickEvent>(_ => UTKLoading.Show());
var btnShowFast = root.Q<UTKButton>("btn-show-fast");
btnShowFast?.RegisterCallback<ClickEvent>(_ => UTKLoading.Show(speed: 2f));
var btnShowSlow = root.Q<UTKButton>("btn-show-slow");
btnShowSlow?.RegisterCallback<ClickEvent>(_ => UTKLoading.Show(speed: 0.5f));
var btnHide = root.Q<UTKButton>("btn-hide");
btnHide?.RegisterCallback<ClickEvent>(_ => UTKLoading.Hide());
// UXML 인라인 스피너 크기 커스터마이징 (Size 속성으로 지정)
var loadingSmall = root.Q<UTKLoading>("loading-small");
if (loadingSmall != null)
{
loadingSmall.Size = 32f;
loadingSmall.Thickness = 3f;
}
var loadingLarge = root.Q<UTKLoading>("loading-large");
if (loadingLarge != null)
{
loadingLarge.Size = 72f;
loadingLarge.Thickness = 6f;
}
SetCodeSamples(root,
csharpCode: @"// ========================================
// Static API (전체 화면 로딩 + Blocker)
// ========================================
// Root 설정 (한 번만)
UTKLoading.SetRoot(rootVisualElement);
// 기본 속도로 표시 (색상은 테마 --color-primary 자동 적용)
UTKLoading.Show();
// 속도 배율 지정 (1.0 = 기본 속도)
UTKLoading.Show(speed: 2.0f); // 빠르게
UTKLoading.Show(speed: 0.5f); // 느리게
// 이미 표시 중이면 중복 생성 없이 기존 인스턴스 반환
var loading = UTKLoading.Show();
// 숨기기 (애니메이션 정지 + 제거)
UTKLoading.Hide();
// 현재 표시 여부 확인
bool isShowing = UTKLoading.IsShowing;
// ========================================
// 실제 사용 예시
// ========================================
public async UniTask LoadDataAsync(CancellationToken ct)
{
UTKLoading.Show();
try
{
await FetchDataFromServerAsync(ct);
}
finally
{
UTKLoading.Hide(); // 성공/실패 모두 숨김
}
}",
uxmlCode: @"<!-- UXML 인라인 배치 (Blocker 없음, 레이아웃 내 배치) -->
<!-- 기본 (48px, speed=1.0, 테마 색상 자동 적용) -->
<utk:UTKLoading />
<!-- 빠른 회전 -->
<utk:UTKLoading speed=""2.5"" />
<!-- 크기/두께 커스터마이징 -->
<utk:UTKLoading size=""32"" thickness=""3"" />
<utk:UTKLoading size=""72"" thickness=""6"" />
<!-- 색상 직접 지정 (테마 색상 무시) -->
<utk:UTKLoading arc-color=""#73C991"" size=""64"" thickness=""6"" />
<!-- 수동 제어 (auto-play 끔) -->
<utk:UTKLoading name=""my-loading"" auto-play=""false"" />");
}
#endregion
#region Notification Initializers
private void InitializeNotificationSample(VisualElement root)

View File

@@ -89,6 +89,8 @@ public partial class UTKStyleGuideSample : MonoBehaviour
["UTKFoldout"] = "UIToolkit/Sample/List/UTKFoldoutSample",
["UTKScrollView"] = "UIToolkit/Sample/List/UTKScrollViewSample",
["UTKShortcutList"] = "UIToolkit/Sample/List/UTKShortcutListSample",
["UTKReordableList"] = "UIToolkit/Sample/List/UTKReordableListSample",
["UTKReordableTabList"] = "UIToolkit/Sample/List/UTKReordableTabListSample",
// Card
["UTKCard"] = "UIToolkit/Sample/Card/UTKCardSample",
["UTKPanel"] = "UIToolkit/Sample/Card/UTKPanelSample",
@@ -100,6 +102,7 @@ public partial class UTKStyleGuideSample : MonoBehaviour
["UTKTooltip"] = "UIToolkit/Sample/Modal/UTKTooltipSample",
["UTKNotification"] = "UIToolkit/Sample/Modal/UTKNotificationSample",
["UTKModal"] = "UIToolkit/Sample/Modal/UTKModalSample",
["UTKLoading"] = "UIToolkit/Sample/Modal/UTKLoadingSample",
// Picker
["UTKColorPicker"] = "UIToolkit/Sample/Picker/UTKColorPickerSample",
["UTKDatePicker"] = "UIToolkit/Sample/Picker/UTKDatePickerSample",
@@ -124,10 +127,10 @@ public partial class UTKStyleGuideSample : MonoBehaviour
["Slider"] = new[] { "UTKSlider", "UTKSliderInt", "UTKMinMaxSlider", "UTKProgressBar" },
["Dropdown"] = new[] { "UTKDropdown", "UTKEnumDropDown", "UTKMultiSelectDropdown" },
["Label"] = new[] { "UTKLabel", "UTKHelpBox" },
["List"] = new[] { "UTKListView", "UTKTreeView", "UTKMultiColumnListView", "UTKMultiColumnTreeView", "UTKFoldout", "UTKScrollView", "UTKShortcutList" },
["List"] = new[] { "UTKListView", "UTKTreeView", "UTKMultiColumnListView", "UTKMultiColumnTreeView", "UTKFoldout", "UTKScrollView", "UTKShortcutList", "UTKReordableList", "UTKReordableTabList" },
["Card"] = new[] { "UTKCard", "UTKPanel" },
["Tab"] = new[] { "UTKTabView" },
["Modal"] = new[] { "UTKAlert", "UTKToast", "UTKTooltip", "UTKNotification", "UTKModal" },
["Modal"] = new[] { "UTKAlert", "UTKToast", "UTKTooltip", "UTKNotification", "UTKModal", "UTKLoading" },
["Picker"] = new[] { "UTKColorPicker", "UTKDatePicker" },
["Menu"] = new[] { "UTKTopMenu" },
["ToolBar"] = new[] { "UTKToolBar" },
@@ -531,6 +534,12 @@ public partial class UTKStyleGuideSample : MonoBehaviour
case "UTKShortcutList":
InitializeShortcutListSample(root);
break;
case "UTKReordableList":
InitializeReordableListSample(root);
break;
case "UTKReordableTabList":
InitializeReordableTabListSample(root);
break;
// Card
case "UTKCard":
InitializeCardSample(root);
@@ -558,6 +567,9 @@ public partial class UTKStyleGuideSample : MonoBehaviour
case "UTKModal":
InitializeModalSample(root);
break;
case "UTKLoading":
InitializeLoadingSample(root);
break;
// Picker
case "UTKColorPicker":
InitializeColorPickerSample(root);

View File

@@ -0,0 +1,82 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
namespace Factory.UIToolkit.Modal
{
/// <summary>
/// 설정 표시 정보 탭 뷰.
/// UTKReordableList를 사용하여 표시 항목의 순서, 사용 유무, 내용을 관리합니다.
/// </summary>
[UxmlElement]
public partial class UTKFactorySettingModalContent : VisualElement, IDisposable, IUTKModalContent<object>
{
private UTKTabView tabView;
public UTKFactorySettingModalContent()
{
style.flexGrow = 1;
tabView = new UTKTabView();
tabView.style.flexGrow = 1;
tabView.TabWidth = 140; // 탭 너비 설정
// 탭 정렬 방향 설정
tabView.Align = TabAlign.Left; // 탭을 왼쪽에 세로로 배치
// 탭 추가
var tab1 = tabView.AddUTKTab("일반정보");
tab1.Add(new UTKFactorySettingModalContentGeneral());
var tab2 = tabView.AddUTKTab("표시정보");
tab2.Add(new UTKFactorySettingModalContentDisplay());
var tab3 = tabView.AddUTKTab("알람설정");
tab3.Add(new UTKFactorySettingModalContentAlarm());
var tab4 = tabView.AddUTKTab("입력설정");
tab4.Add(new UTKFactorySettingModalContentInput());
tabView.OnTabChanged += OnTabChanged;
tabView.tabClosed += OnTabClosed;
Add(tabView);
}
public UTKFactorySettingModalContent(int tabIndex = 0, object? data = null) : this()
{
// 생성자에서 탭 인덱스를 받아서 초기 탭 설정
tabView.SelectedIndex = tabIndex;
}
private void OnTabChanged(int index, UnityEngine.UIElements.Tab? tab)
{
Debug.Log($"Selected Tab Index: {index}");
}
private void OnTabClosed(UnityEngine.UIElements.Tab tab, int index)
{
Debug.Log($"Closed Tab Index: {index}");
}
public object? GetResult()
{
IUTKTabContent? content = UTKTabView.FindTabContent(tabView.activeTab);
if (content != null) {
return content.Hide();
}
return null;
}
public void Dispose()
{
// 필요한 경우 리소스 정리
tabView.OnTabChanged -= OnTabChanged;
tabView.tabClosed -= OnTabClosed;
}
}
}

View File

@@ -0,0 +1,61 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
namespace Factory.UIToolkit.Modal
{
/// <summary>
/// 설정 모달 - Alarm 탭 콘텐츠
/// </summary>
[UxmlElement]
public partial class UTKFactorySettingModalContentAlarm : VisualElement, IDisposable, IUTKTabContent
{
#region Constants
private const string UXML_PATH = "Factory/UIToolkit/Modal/UTKFactorySettingModalContentAlarmUXML";
#endregion
#region Fields
private bool _disposed;
#endregion
#region Properties
#endregion
#region Constructor
public UTKFactorySettingModalContentAlarm()
{
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (asset != null)
{
var root = asset.Instantiate();
root.style.flexGrow = 1;
Add(root);
}
}
#endregion
#region Public Methods
public void Show(object? data)
{
}
public async UniTask Hide()
{
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69e3bac5218612948a3c387aabbfac97

View File

@@ -0,0 +1,114 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
namespace Factory.UIToolkit.Modal
{
/// <summary>
/// 설정 모달 - Display 탭 콘텐츠
/// </summary>
[UxmlElement]
public partial class UTKFactorySettingModalContentDisplay : VisualElement, IDisposable, IUTKTabContent
{
#region Constants
private const string UXML_PATH = "Factory/UIToolkit/Modal/UTKFactorySettingModalContentDisplayUXML";
#endregion
#region Fields
private bool _disposed;
#endregion
#region Properties
private UTKReordableTabList? _reordableTabList;
#endregion
#region Constructor
public UTKFactorySettingModalContentDisplay()
{
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (asset != null)
{
var root = asset.Instantiate();
root.style.flexGrow = 1;
_reordableTabList = root.Q<UTKReordableTabList>("tab-list");
if(_reordableTabList != null)
{
_reordableTabList.OnDataChanged += OnDataChanged;
_reordableTabList.OnOrderChanged += OnOrderChanged;
SetTabListSampleData();
}
Add(root);
}
}
private void OnDataChanged(string tabName, int tabIndex)
{
Debug.Log($"Tab '{tabName}' at index {tabIndex} has changed.");
}
private void OnOrderChanged(string tabName, int tabIndex)
{
Debug.Log($"Tab '{tabName}' moved index {tabIndex}.");
}
private void SetTabListSampleData()
{
var data = new Dictionary<string, List<Dictionary<string, string>>>
{
["센서"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "온도 센서" },
new() { ["order"] = "1", ["active"] = "True", ["text"] = "습도 센서" },
new() { ["order"] = "2", ["active"] = "False", ["text"] = "압력 센서" },
},
["장비"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "컨베이어" },
new() { ["order"] = "1", ["active"] = "False", ["text"] = "로봇암" },
new() { ["order"] = "2", ["active"] = "True", ["text"] = "CNC" },
new() { ["order"] = "3", ["active"] = "True", ["text"] = "AGV" },
},
["알림"] = new()
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "경고" },
new() { ["order"] = "1", ["active"] = "True", ["text"] = "에러" },
}
};
_reordableTabList?.SetData(data);
}
#endregion
#region Public Methods
public void Show(object? data)
{
}
public async UniTask Hide()
{
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
if (_reordableTabList != null)
{
_reordableTabList.OnDataChanged -= OnDataChanged;
_reordableTabList.OnOrderChanged -= OnOrderChanged;
}
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0c06814030527b647a47600c3f5ca0ab

View File

@@ -0,0 +1,61 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
namespace Factory.UIToolkit.Modal
{
/// <summary>
/// 설정 모달 - General 탭 콘텐츠
/// </summary>
[UxmlElement]
public partial class UTKFactorySettingModalContentGeneral : VisualElement, IDisposable, IUTKTabContent
{
#region Constants
private const string UXML_PATH = "Factory/UIToolkit/Modal/UTKFactorySettingModalContentGeneralUXML";
#endregion
#region Fields
private bool _disposed;
#endregion
#region Properties
#endregion
#region Constructor
public UTKFactorySettingModalContentGeneral()
{
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (asset != null)
{
var root = asset.Instantiate();
root.style.flexGrow = 1;
Add(root);
}
}
#endregion
#region Public Methods
public void Show(object? data)
{
}
public async UniTask Hide()
{
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: be3d3c94c420c364e80b63ff4f383352

View File

@@ -0,0 +1,61 @@
#nullable enable
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
namespace Factory.UIToolkit.Modal
{
/// <summary>
/// 설정 모달 - Input 탭 콘텐츠
/// </summary>
[UxmlElement]
public partial class UTKFactorySettingModalContentInput : VisualElement, IDisposable, IUTKTabContent
{
#region Constants
private const string UXML_PATH = "Factory/UIToolkit/Modal/UTKFactorySettingModalContentInputUXML";
#endregion
#region Fields
private bool _disposed;
#endregion
#region Properties
#endregion
#region Constructor
public UTKFactorySettingModalContentInput()
{
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (asset != null)
{
var root = asset.Instantiate();
root.style.flexGrow = 1;
Add(root);
}
}
#endregion
#region Public Methods
public void Show(object? data)
{
}
public async UniTask Hide()
{
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 74b285c2c1dbdd24d9eb94f943ac251a

View File

@@ -0,0 +1,360 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 탭 기반 재정렬 가능 리스트.
/// UTKTabView와 UTKReordableList를 조합하여 탭별로 독립적인 재정렬 리스트를 제공합니다.
/// </summary>
/// <remarks>
/// <para><b>구조:</b></para>
/// <code>
/// UTKReordableTabList
/// └─ UTKTabView
/// ├─ UTKTab "탭1" → UTKReordableList (인스턴스 1)
/// ├─ UTKTab "탭2" → UTKReordableList (인스턴스 2)
/// └─ UTKTab "탭N" → UTKReordableList (인스턴스 N)
/// </code>
/// </remarks>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// var tabList = new UTKReordableTabList();
/// tabList.SetData(new Dictionary<string, List<ReordableListItemData>>
/// {
/// { "일반", generalItems },
/// { "고급", advancedItems }
/// });
///
/// // 전체 데이터 가져오기
/// var allData = tabList.GetData();
///
/// // 특정 탭 데이터 가져오기
/// var generalData = tabList.GetData("일반");
/// var firstTabData = tabList.GetData(0);
///
/// // 이벤트 구독
/// tabList.OnOrderChanged += (tabName, tabIndex) =>
/// {
/// Debug.Log($"[{tabName}] (탭 {tabIndex}) 순서 변경됨");
/// };
/// tabList.OnDataChanged += (tabName, tabIndex) =>
/// {
/// Debug.Log($"[{tabName}] (탭 {tabIndex}) 데이터 변경됨");
/// };
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKReordableTabList />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]
public partial class UTKReordableTabList : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/List/UTKReordableTabListUss";
#endregion
#region Fields
private bool _disposed;
private UTKTabView? _tabView;
/// <summary>탭 이름 → UTKReordableList 매핑</summary>
private readonly Dictionary<string, UTKReordableList> _tabLists = new();
/// <summary>탭 이름 순서 보존용</summary>
private readonly List<string> _tabNames = new();
/// <summary>리스트별 이벤트 콜백 참조 (해제용)</summary>
private readonly Dictionary<string, TabListCallbackInfo> _callbackInfos = new();
#endregion
#region Events
/// <summary>순서 변경 시 발생 (탭 이름, 탭 인덱스)</summary>
public event Action<string, int>? OnOrderChanged;
/// <summary>데이터(체크/텍스트) 변경 시 발생 (탭 이름, 탭 인덱스)</summary>
public event Action<string, int>? OnDataChanged;
#endregion
#region Constructor
public UTKReordableTabList() : base()
{
// 1. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. USS 로드
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. UI 생성
CreateUI();
// 4. 테마 변경 구독
SubscribeToThemeChanges();
}
#endregion
#region Setup
private void CreateUI()
{
AddToClassList("reordable-tab-list");
_tabView = new UTKTabView();
_tabView.AddToClassList("reordable-tab-list__tabview");
Add(_tabView);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
}
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Public API - SetData
/// <summary>
/// 탭별 데이터를 설정합니다. 기존 구성을 모두 제거하고 재생성합니다.
/// </summary>
/// <param name="data">탭 이름 → 아이템 데이터 목록 매핑.</param>
public void SetData(Dictionary<string, List<ReordableListItemData>> data)
{
// 기존 구성 제거
ClearAll();
if (data == null || _tabView == null) return;
foreach (var kvp in data)
{
var tabName = kvp.Key;
var items = kvp.Value ?? new List<ReordableListItemData>();
_tabNames.Add(tabName);
// UTKReordableList 생성
var reordableList = new UTKReordableList();
reordableList.AddToClassList("reordable-tab-list__list");
reordableList.SetData(items);
// 이벤트 연결 (콜백 참조 보관하여 해제 가능하게)
var capturedName = tabName;
var capturedIndex = _tabNames.Count - 1;
Action onOrder = () => OnOrderChanged?.Invoke(capturedName, capturedIndex);
Action onData = () => OnDataChanged?.Invoke(capturedName, capturedIndex);
reordableList.OnOrderChanged += onOrder;
reordableList.OnDataChanged += onData;
_callbackInfos[tabName] = new TabListCallbackInfo(onOrder, onData);
_tabLists[tabName] = reordableList;
// 탭 추가
_tabView.AddUTKTab(tabName, reordableList);
}
}
/// <summary>
/// Dictionary 형식의 데이터를 변환하여 설정합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <param name="data">탭 이름 → Dictionary 목록 매핑.</param>
public void SetData(Dictionary<string, List<Dictionary<string, string>>> data)
{
if (data == null)
{
ClearAll();
return;
}
var converted = new Dictionary<string, List<ReordableListItemData>>(data.Count);
foreach (var kvp in data)
{
var items = new List<ReordableListItemData>();
if (kvp.Value != null)
{
for (int i = 0; i < kvp.Value.Count; i++)
{
var dict = kvp.Value[i];
var item = new ReordableListItemData();
item.Order = dict.TryGetValue("order", out var orderStr) && int.TryParse(orderStr, out var order)
? order
: i;
item.IsActive = dict.TryGetValue("active", out var activeStr) && bool.TryParse(activeStr, out var active)
? active
: true;
item.DisplayText = dict.TryGetValue("text", out var text)
? text ?? ""
: "";
items.Add(item);
}
}
converted[kvp.Key] = items;
}
SetData(converted);
}
#endregion
#region Public API - GetData
/// <summary>
/// 모든 탭의 데이터를 반환합니다.
/// </summary>
/// <returns>탭 이름 → 아이템 데이터 목록 매핑.</returns>
public Dictionary<string, List<ReordableListItemData>> GetData()
{
var result = new Dictionary<string, List<ReordableListItemData>>(_tabLists.Count);
foreach (var tabName in _tabNames)
{
if (_tabLists.TryGetValue(tabName, out var list))
{
result[tabName] = list.GetData();
}
}
return result;
}
/// <summary>
/// 특정 탭의 데이터를 이름으로 반환합니다.
/// </summary>
/// <param name="tabName">탭 이름.</param>
/// <returns>아이템 데이터 목록 또는 null.</returns>
public List<ReordableListItemData>? GetData(string tabName)
{
return _tabLists.TryGetValue(tabName, out var list) ? list.GetData() : null;
}
/// <summary>
/// 특정 탭의 데이터를 인덱스로 반환합니다.
/// </summary>
/// <param name="tabIndex">탭 인덱스.</param>
/// <returns>아이템 데이터 목록 또는 null.</returns>
public List<ReordableListItemData>? GetData(int tabIndex)
{
if (tabIndex < 0 || tabIndex >= _tabNames.Count) return null;
return GetData(_tabNames[tabIndex]);
}
/// <summary>
/// 모든 탭의 데이터를 Dictionary 형식으로 변환하여 반환합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <returns>탭 이름 → Dictionary 목록 매핑.</returns>
public Dictionary<string, List<Dictionary<string, string>>> ToDictionary()
{
var result = new Dictionary<string, List<Dictionary<string, string>>>(_tabLists.Count);
foreach (var tabName in _tabNames)
{
if (_tabLists.TryGetValue(tabName, out var list))
{
result[tabName] = list.ToDictionary();
}
}
return result;
}
#endregion
#region Internal
/// <summary>
/// 기존 탭과 리스트를 모두 제거합니다.
/// </summary>
private void ClearAll()
{
// 이벤트 콜백 해제 후 리스트 Dispose
foreach (var tabName in _tabNames)
{
if (_tabLists.TryGetValue(tabName, out var list))
{
if (_callbackInfos.TryGetValue(tabName, out var info))
{
list.OnOrderChanged -= info.OnOrderHandler;
list.OnDataChanged -= info.OnDataHandler;
}
list.Dispose();
}
}
_tabLists.Clear();
_tabNames.Clear();
_callbackInfos.Clear();
// 탭 제거
_tabView?.ClearTabs();
}
#endregion
#region Internal Types
/// <summary>리스트 이벤트 콜백 참조 추적</summary>
private class TabListCallbackInfo
{
public readonly Action OnOrderHandler;
public readonly Action OnDataHandler;
public TabListCallbackInfo(Action onOrderHandler, Action onDataHandler)
{
OnOrderHandler = onOrderHandler;
OnDataHandler = onDataHandler;
}
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// 테마 구독 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
// 리스트 정리
ClearAll();
// TabView 정리
_tabView?.Dispose();
_tabView = null;
// 이벤트 정리
OnOrderChanged = null;
OnDataChanged = null;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9aff9465689e6be42a8814509b1dc848

View File

@@ -1,193 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 설정 표시 정보 탭 뷰.
/// UTKReordableList를 사용하여 표시 항목의 순서, 사용 유무, 내용을 관리합니다.
/// </summary>
[UxmlElement]
public partial class UTKSettingDisplayInfoTabView : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/Setting/UTKSettingDisplayInfoTabViewUss";
#endregion
#region Fields
private bool _disposed;
private UTKReordableList? _reordableList;
#endregion
#region Events
/// <summary>순서 변경 시 발생</summary>
public event Action? OnOrderChanged;
/// <summary>데이터(체크/텍스트) 변경 시 발생</summary>
public event Action? OnDataChanged;
#endregion
#region Constructor
public UTKSettingDisplayInfoTabView() : base()
{
// 1. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. USS 로드
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. UI 생성
CreateUI();
// 4. 테마 변경 구독
SubscribeToThemeChanges();
SampleSetAndGetWithDictionary();
}
#endregion
#region Setup
private void CreateUI()
{
AddToClassList("setting-display-tab-view");
_reordableList = new UTKReordableList();
_reordableList.style.flexGrow = 1;
_reordableList.OnOrderChanged += () => OnOrderChanged?.Invoke();
_reordableList.OnDataChanged += () => OnDataChanged?.Invoke();
Add(_reordableList);
// 하단 버튼 영역
var buttonContainer = new VisualElement();
buttonContainer.style.flexDirection = FlexDirection.Row;
buttonContainer.style.justifyContent = Justify.FlexEnd;
buttonContainer.style.paddingTop = 8;
var saveBtn = new UTKButton("저장", variant: UTKButton.ButtonVariant.Primary);
saveBtn.OnClicked += OnSaveButtonClicked;
buttonContainer.Add(saveBtn);
Add(buttonContainer);
}
private void OnSaveButtonClicked()
{
List<Dictionary<string, string>> result = ToDictionary();
foreach (var dict in result)
{
Debug.Log($"order={dict["order"]}, active={dict["active"]}, text={dict["text"]}");
}
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
}
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Public API
/// <summary>
/// 데이터를 설정하고 리스트를 갱신합니다.
/// </summary>
/// <param name="items">표시할 아이템 데이터 목록.</param>
public void SetData(List<ReordableListItemData> items)
{
_reordableList?.SetData(items);
}
/// <summary>
/// List<Dictionary>로부터 데이터를 변환하여 설정합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <param name="listDict">변환할 Dictionary 목록.</param>
public void SetData(List<Dictionary<string, string>> listDict)
{
_reordableList?.SetData(listDict);
}
/// <summary>
/// Order 값이 동기화된 데이터 목록을 반환합니다.
/// </summary>
/// <returns>현재 리스트 순서가 반영된 데이터 목록.</returns>
public List<ReordableListItemData> GetData()
{
return _reordableList?.GetData() ?? new List<ReordableListItemData>();
}
/// <summary>
/// 전체 아이템을 List<Dictionary<string, string>>로 변환하여 반환합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <returns>각 아이템을 Dictionary로 변환한 목록.</returns>
public List<Dictionary<string, string>> ToDictionary()
{
return _reordableList?.ToDictionary() ?? new List<Dictionary<string, string>>();
}
#endregion
#region Sample
/// <summary>
/// Dictionary 기반 데이터 설정 및 조회 샘플.
/// </summary>
public void SampleSetAndGetWithDictionary()
{
// 1. Dictionary 데이터로 설정
var listDict = new List<Dictionary<string, string>>
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "온도" },
new() { ["order"] = "1", ["active"] = "False", ["text"] = "습도" },
new() { ["order"] = "2", ["active"] = "True", ["text"] = "압력" },
};
SetData(listDict);
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// 테마 구독 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
// ReordableList 정리
_reordableList?.Dispose();
_reordableList = null;
// 이벤트 정리
OnOrderChanged = null;
OnDataChanged = null;
}
#endregion
}
}

View File

@@ -0,0 +1,464 @@
#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 회전하면서 호가 길어졌다 짧아지는 원형 로딩 스피너 컴포넌트.
/// Painter2D Drawing API로 자연스러운 원호를 직접 그립니다.
/// </summary>
/// <remarks>
/// <para><b>두 가지 사용 방식 비교:</b></para>
/// <list type="table">
/// <listheader>
/// <term>항목</term>
/// <description>Static API / UXML 태그</description>
/// </listheader>
/// <item>
/// <term>배치 위치</term>
/// <description>Static: panel.visualTree 최상단 / UXML: 레이아웃 내 인라인</description>
/// </item>
/// <item>
/// <term>Blocker</term>
/// <description>Static: 있음 (전체 클릭 차단) / UXML: 없음</description>
/// </item>
/// <item>
/// <term>위치</term>
/// <description>Static: 화면 정중앙 고정 / UXML: CSS 레이아웃 따름</description>
/// </item>
/// <item>
/// <term>시작</term>
/// <description>Static: Show() 호출 시 / UXML: AttachToPanel 자동 (auto-play=true)</description>
/// </item>
/// <item>
/// <term>정지</term>
/// <description>Static: Hide() 호출 시 / UXML: DetachFromPanel 자동</description>
/// </item>
/// </list>
///
/// <para><b>색상·두께·크기 설정:</b></para>
/// <list type="bullet">
/// <item><description>기본 색상은 현재 테마의 <c>--color-primary</c>를 자동 적용합니다.</description></item>
/// <item><description><c>arc-color</c> 속성을 지정하면 테마 색상을 무시하고 해당 색상을 사용합니다.</description></item>
/// <item><description><c>thickness</c>: 선 두께 (px, 기본값: 4)</description></item>
/// <item><description><c>size</c>: 스피너 크기 (px, 기본값: 48)</description></item>
/// <item><description><c>speed</c>: 회전 속도 배율 (기본값: 1.0)</description></item>
/// </list>
/// </remarks>
/// <example>
/// <para><b>Static API (전체 화면 로딩 + Blocker):</b></para>
/// <code>
/// UTKLoading.SetRoot(rootVisualElement);
/// UTKLoading.Show(); // 기본 속도
/// UTKLoading.Show(speed: 2f); // 빠른 회전
/// UTKLoading.Hide();
/// </code>
/// <para><b>UXML 태그 (인라인 배치, Blocker 없음):</b></para>
/// <code>
/// &lt;utk:UTKLoading speed="1.5" auto-play="true" /&gt;
/// &lt;utk:UTKLoading arc-color="#73C991" thickness="6" size="64" auto-play="true" /&gt;
/// </code>
/// </example>
[UxmlElement]
public partial class UTKLoading : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/UTKLoadingUss";
// 애니메이션 업데이트 간격 (16ms ≈ 60fps)
private const long UPDATE_INTERVAL_MS = 16;
// 기본 회전 속도 (도/초) — 180도/초 = 0.5바퀴/초
private const float DEFAULT_SPEED_DEG = 270f;
// 호 최대 길이 (도) — 360도
private const float ARC_MAX_DEG = 360f;
// 호 길이 변화 주기 (라디안/초) — 0~2π 사이클용 시간 누적 (내부 phase 계산용)
// 0.8π ≈ 한 사이클 약 2.5초
private const float ARC_CYCLE_SPEED = Mathf.PI * 0.8f;
// 기본 크기/두께
private const float DEFAULT_SIZE = 48f;
private const float DEFAULT_THICKNESS = 4f;
#endregion
#region Fields
private static VisualElement? _root;
private static UTKLoading? _activeInstance;
private bool _disposed;
private bool _isAnimating;
private float _speedDeg = DEFAULT_SPEED_DEG;
private bool _autoPlay = true;
// 애니메이션 상태
private float _rotation = 0f; // 현재 회전 각도 (도, 계속 누적)
private float _cycleTime = 0f; // 0~2π 범위 phase 누적 (라디안)
// 렌더링 파라미터
private Color? _arcColorOverride; // null이면 테마 색상 사용
private Color _resolvedThemeColor = UTKStyleGuide.Blue05; // OnCustomStyleResolved에서 갱신
private float _thickness = DEFAULT_THICKNESS;
private float _size = DEFAULT_SIZE;
// 클릭 차단 레이어 (Static Show()에서만 사용)
private VisualElement? _blocker;
// 스케줄 핸들
private IVisualElementScheduledItem? _animationSchedule;
#endregion
#region UxmlAttributes
/// <summary>회전 속도 배율. 1.0 = 기본 속도</summary>
[UxmlAttribute("speed")]
public float Speed
{
get => _speedDeg / DEFAULT_SPEED_DEG;
set => _speedDeg = DEFAULT_SPEED_DEG * Mathf.Max(0.1f, value);
}
/// <summary>패널에 연결될 때 자동으로 애니메이션을 시작할지 여부</summary>
[UxmlAttribute("auto-play")]
public bool AutoPlay
{
get => _autoPlay;
set => _autoPlay = value;
}
/// <summary>
/// 스피너 색상. 지정하지 않으면 현재 테마의 --color-primary가 적용됩니다.
/// </summary>
[UxmlAttribute("arc-color")]
public Color ArcColor
{
get => _arcColorOverride ?? _resolvedThemeColor;
set
{
_arcColorOverride = value;
MarkDirtyRepaint();
}
}
/// <summary>선 두께 (px)</summary>
[UxmlAttribute("thickness")]
public float Thickness
{
get => _thickness;
set
{
_thickness = Mathf.Max(1f, value);
MarkDirtyRepaint();
}
}
/// <summary>스피너 전체 크기 (px)</summary>
[UxmlAttribute("size")]
public float Size
{
get => _size;
set
{
_size = Mathf.Max(8f, value);
style.width = _size;
style.height = _size;
MarkDirtyRepaint();
}
}
#endregion
#region Constructor
/// <summary>
/// UTKLoading 기본 생성자.
/// UXML 태그로 배치하면 패널에 연결될 때 auto-play 설정에 따라 자동 시작됩니다.
/// </summary>
public UTKLoading()
{
// 테마 스타일시트 적용 (--color-primary 변수 상속용)
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
styleSheets.Add(uss);
AddToClassList("utk-loading");
// 고정 크기 지정 (Drawing API는 contentRect 기준)
style.width = _size;
style.height = _size;
style.flexShrink = 0;
// customStyle 변경 시 테마 색상 갱신
RegisterCallback<CustomStyleResolvedEvent>(OnCustomStyleResolved);
// Drawing API 콜백 등록
generateVisualContent += OnGenerateVisualContent;
SubscribeToThemeChanges();
}
#endregion
#region Static API
/// <summary>
/// 기본 루트 요소 설정.
/// 로딩 스피너는 이 루트의 panel.visualTree에 표시됩니다.
/// </summary>
public static void SetRoot(VisualElement root) => _root = root;
/// <summary>기본 루트 요소 반환</summary>
public static VisualElement? GetRoot() => _root;
/// <summary>
/// 로딩 스피너 표시.
/// 이미 표시 중이면 중복 생성하지 않고 기존 인스턴스를 반환합니다.
/// </summary>
/// <param name="speed">회전 속도 배율 (기본값 1.0)</param>
public static UTKLoading Show(float speed = 1.0f)
{
ValidateRoot();
if (_activeInstance != null)
return _activeInstance;
var loading = new UTKLoading
{
_speedDeg = DEFAULT_SPEED_DEG * Mathf.Max(0.1f, speed),
_blocker = CreateBlocker(),
};
var visualTree = _root!.panel?.visualTree ?? _root!;
visualTree.Add(loading._blocker);
visualTree.Add(loading);
// 화면 중앙 고정
loading.style.position = Position.Absolute;
loading.style.left = Length.Percent(50);
loading.style.top = Length.Percent(50);
loading.style.translate = new Translate(Length.Percent(-50), Length.Percent(-50));
loading.StartAnimation();
_activeInstance = loading;
return loading;
}
/// <summary>
/// 로딩 스피너 숨기기 및 제거.
/// </summary>
public static void Hide()
{
if (_activeInstance == null) return;
var instance = _activeInstance;
_activeInstance = null;
instance.Close();
}
/// <summary>현재 로딩이 표시 중인지 여부</summary>
public static bool IsShowing => _activeInstance != null;
#endregion
#region Public Methods
/// <summary>이 인스턴스를 닫고 계층에서 제거합니다.</summary>
public void Close()
{
StopAnimation();
if (_blocker is not null)
{
_blocker.RemoveFromHierarchy();
_blocker = null;
}
RemoveFromHierarchy();
Dispose();
if (ReferenceEquals(_activeInstance, this))
_activeInstance = null;
}
#endregion
#region Drawing
/// <summary>
/// Painter2D로 트랙 원과 로딩 호를 직접 그립니다.
/// </summary>
private void OnGenerateVisualContent(MeshGenerationContext ctx)
{
var painter = ctx.painter2D;
var rect = contentRect;
var center = new Vector2(rect.width * 0.5f, rect.height * 0.5f);
float radius = Mathf.Min(rect.width, rect.height) * 0.5f - _thickness * 0.5f;
if (radius <= 0f) return;
// arc-color 미지정 시 OnCustomStyleResolved에서 캐싱된 테마 색상 사용
var color = _arcColorOverride ?? _resolvedThemeColor;
// --- 로딩 호 ---
// _cycleTime을 0~2π 한 사이클로 사용:
// Phase 1 (0~π): head가 0→1로 달려나감, tail은 0 고정 → 호가 늘어남
// Phase 2 (π~2π): head는 1 고정, tail이 0→1로 따라잡음 → 호가 줄어듦
// 두 페이즈 경계(0, π, 2π)에서 arc length = 0 이 보장됨
float cycle = _cycleTime; // 이미 0~2π 범위로 유지됨
float headT, tailT;
if (cycle < Mathf.PI)
{
// Phase 1: 호 늘어남
headT = EaseInOut(cycle / Mathf.PI);
tailT = 0f;
}
else
{
// Phase 2: 호 줄어듦
headT = 1f;
tailT = EaseInOut((cycle - Mathf.PI) / Mathf.PI);
}
float baseAngle = _rotation - 90f; // 12시 방향 기준 (도)
float startRad = baseAngle + tailT * ARC_MAX_DEG;
float endRad = baseAngle + headT * ARC_MAX_DEG;
painter.BeginPath();
painter.Arc(center, radius, startRad, endRad);
painter.strokeColor = color;
painter.lineWidth = _thickness;
painter.lineCap = LineCap.Round;
painter.Stroke();
}
// cubic ease-in-out: 0→0, 1→1, 가운데 S커브
private static float EaseInOut(float t) => t * t * (3f - 2f * t);
#endregion
#region Animation
private void StartAnimation()
{
if (_isAnimating) return;
_isAnimating = true;
_rotation = 0f;
_cycleTime = 0f;
_animationSchedule = schedule
.Execute(UpdateAnimation)
.Every(UPDATE_INTERVAL_MS);
}
private void StopAnimation()
{
if (!_isAnimating) return;
_isAnimating = false;
_animationSchedule?.Pause();
_animationSchedule = null;
}
private void UpdateAnimation(TimerState timerState)
{
if (!_isAnimating) return;
// timerState.deltaTime: 이전 콜백 이후 실제 경과 시간 (ms, long) 16ms 나옴
float deltaSeconds = timerState.deltaTime / 1000f;
// 회전 각도 누적 (도, 범위 제한 없이 누적해도 float 정밀도 안전)
_rotation += _speedDeg * deltaSeconds;
_cycleTime += ARC_CYCLE_SPEED * deltaSeconds;
// 오버플로우 방지
if (_rotation > 360f * 1000f) _rotation -= 360f * 1000f;
// _cycleTime은 0~2π 범위로 유지 (% 연산으로 순간 점프 방지)
if (_cycleTime >= Mathf.PI * 2f) _cycleTime -= Mathf.PI * 2f;
// 다시 그리기 요청
MarkDirtyRepaint();
}
#endregion
#region Blocker
private static VisualElement CreateBlocker()
{
var blocker = new VisualElement();
blocker.style.position = Position.Absolute;
blocker.style.left = 0;
blocker.style.top = 0;
blocker.style.right = 0;
blocker.style.bottom = 0;
blocker.style.backgroundColor = new Color(0, 0, 0, 0);
blocker.RegisterCallback<ClickEvent>(evt => evt.StopPropagation());
blocker.RegisterCallback<PointerDownEvent>(evt => evt.StopPropagation());
blocker.RegisterCallback<PointerUpEvent>(evt => evt.StopPropagation());
return blocker;
}
#endregion
#region Theme
private void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
{
// --color-primary 값을 읽어 캐싱. arc-color 미지정 시 다음 프레임부터 반영됨
if (evt.customStyle.TryGetValue(UTKStyleGuide.VarColorPrimary, out var color))
_resolvedThemeColor = color;
if (_arcColorOverride == null)
MarkDirtyRepaint();
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
}
private void OnAttachToPanel(AttachToPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
UTKThemeManager.Instance.ApplyThemeToElement(this);
if (_autoPlay)
StartAnimation();
}
private void OnDetachFromPanel(DetachFromPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
StopAnimation();
}
private void OnThemeChanged(UTKTheme theme)
{
// ApplyThemeToElement가 스타일시트를 교체하면
// UI Toolkit이 CustomStyleResolvedEvent를 자동 발생 → OnCustomStyleResolved에서 색상 갱신
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Static Helper
private static void ValidateRoot()
{
if (_root == null)
throw new InvalidOperationException(
"UTKLoading.SetRoot()를 먼저 호출하여 기본 루트 요소를 설정해야 합니다.");
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
StopAnimation();
generateVisualContent -= OnGenerateVisualContent;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel);
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
UnregisterCallback<CustomStyleResolvedEvent>(OnCustomStyleResolved);
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c5a3987766008cd40a03cb1c615f03a6

View File

@@ -34,6 +34,9 @@ namespace UVC.UIToolkit
public static class UTKStyleGuide
{
#region USS Variable Names - for customStyle access
// Primary Colors
public static readonly CustomStyleProperty<Color> VarColorPrimary = new("--color-primary");
// Text Colors
public static readonly CustomStyleProperty<Color> VarTextPrimary = new("--color-text-primary");
public static readonly CustomStyleProperty<Color> VarTextSecondary = new("--color-text-secondary");

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cd76a1d460524d746bee1cf9a5a2151e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f9b8ea68f545b1442b81375cc40c1d22
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,167 @@
/*
┌──────────────────────────────────────────────────────────────────┐
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
│ Copyright (c) 2025 Ivan Murzak │
│ Licensed under the Apache License, Version 2.0. │
│ See the LICENSE file in the project root for more information. │
└──────────────────────────────────────────────────────────────────┘
*/
#nullable enable
using System.IO;
using System.Linq;
using UnityEngine;
using com.IvanMurzak.Unity.MCP.Installer.SimpleJSON;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("com.IvanMurzak.Unity.MCP.Installer.Tests")]
namespace com.IvanMurzak.Unity.MCP.Installer
{
public static partial class Installer
{
static string ManifestPath => Path.Combine(Application.dataPath, "../Packages/manifest.json");
// Property names
public const string Dependencies = "dependencies";
public const string ScopedRegistries = "scopedRegistries";
public const string Name = "name";
public const string Url = "url";
public const string Scopes = "scopes";
// Property values
public const string RegistryName = "package.openupm.com";
public const string RegistryUrl = "https://package.openupm.com";
public static readonly string[] PackageIds = new string[] {
"com.ivanmurzak", // Ivan Murzak's OpenUPM packages
"extensions.unity", // Ivan Murzak's OpenUPM packages (older)
"org.nuget.com.ivanmurzak", // Ivan Murzak's NuGet packages
"org.nuget.microsoft", // Microsoft NuGet packages
"org.nuget.system", // Microsoft NuGet packages
"org.nuget.r3" // R3 package NuGet package
};
/// <summary>
/// Determines if the version should be updated. Only update if installer version is higher than current version.
/// </summary>
/// <param name="currentVersion">Current package version string</param>
/// <param name="installerVersion">Installer version string</param>
/// <returns>True if version should be updated (installer version is higher), false otherwise</returns>
internal static bool ShouldUpdateVersion(string currentVersion, string installerVersion)
{
if (string.IsNullOrEmpty(currentVersion))
return true; // No current version, should install
if (string.IsNullOrEmpty(installerVersion))
return false; // No installer version, don't change
try
{
// Try to parse as System.Version (semantic versioning)
var current = new System.Version(currentVersion);
var installer = new System.Version(installerVersion);
// Only update if installer version is higher than current version
return installer > current;
}
catch (System.Exception)
{
Debug.LogWarning($"Failed to parse versions '{currentVersion}' or '{installerVersion}' as System.Version.");
// If version parsing fails, fall back to string comparison
// This ensures we don't break if version format is unexpected
return string.Compare(installerVersion, currentVersion, System.StringComparison.OrdinalIgnoreCase) > 0;
}
}
public static void AddScopedRegistryIfNeeded(string manifestPath, int indent = 2)
{
if (!File.Exists(manifestPath))
{
Debug.LogError($"{manifestPath} not found!");
return;
}
var jsonText = File.ReadAllText(manifestPath)
.Replace("{ }", "{\n}")
.Replace("{}", "{\n}")
.Replace("[ ]", "[\n]")
.Replace("[]", "[\n]");
var manifestJson = JSONObject.Parse(jsonText);
if (manifestJson == null)
{
Debug.LogError($"Failed to parse {manifestPath} as JSON.");
return;
}
var modified = false;
// --- Add scoped registries if needed
var scopedRegistries = manifestJson[ScopedRegistries];
if (scopedRegistries == null)
{
manifestJson[ScopedRegistries] = new JSONArray();
modified = true;
}
// --- Add OpenUPM registry if needed
var openUpmRegistry = scopedRegistries!.Linq
.Select(kvp => kvp.Value)
.Where(r => r.Linq
.Any(p => p.Key == Name && p.Value == RegistryName))
.FirstOrDefault();
if (openUpmRegistry == null)
{
scopedRegistries.Add(openUpmRegistry = new JSONObject
{
[Name] = RegistryName,
[Url] = RegistryUrl,
[Scopes] = new JSONArray()
});
modified = true;
}
// --- Add missing scopes
var scopes = openUpmRegistry[Scopes];
if (scopes == null)
{
openUpmRegistry[Scopes] = scopes = new JSONArray();
modified = true;
}
foreach (var packageId in PackageIds)
{
var existingScope = scopes!.Linq
.Select(kvp => kvp.Value)
.Where(value => value == packageId)
.FirstOrDefault();
if (existingScope == null)
{
scopes.Add(packageId);
modified = true;
}
}
// --- Package Dependency (Version-aware installation)
// Only update version if installer version is higher than current version
// This prevents downgrades when users manually update to newer versions
var dependencies = manifestJson[Dependencies];
if (dependencies == null)
{
manifestJson[Dependencies] = dependencies = new JSONObject();
modified = true;
}
// Only update version if installer version is higher than current version
var currentVersion = dependencies[PackageId];
if (currentVersion == null || ShouldUpdateVersion(currentVersion, Version))
{
dependencies[PackageId] = Version;
modified = true;
}
// --- Write changes back to manifest
if (modified)
File.WriteAllText(manifestPath, manifestJson.ToString(indent).Replace("\" : ", "\": "));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 888edfe7699cca7478a863e50be3566d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
/*
┌──────────────────────────────────────────────────────────────────┐
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
│ Copyright (c) 2025 Ivan Murzak │
│ Licensed under the Apache License, Version 2.0. │
│ See the LICENSE file in the project root for more information. │
└──────────────────────────────────────────────────────────────────┘
*/
#nullable enable
using UnityEditor;
namespace com.IvanMurzak.Unity.MCP.Installer
{
[InitializeOnLoad]
public static partial class Installer
{
public const string PackageId = "com.ivanmurzak.unity.mcp";
public const string Version = "0.48.1";
static Installer()
{
#if !IVAN_MURZAK_INSTALLER_PROJECT
AddScopedRegistryIfNeeded(ManifestPath);
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b81812993f383a34a950af84d081945d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using UnityEngine;
using UnityEditor;
using System.IO;
namespace com.IvanMurzak.Unity.MCP.Installer
{
public static class PackageExporter
{
public static void ExportPackage()
{
var packagePath = "Assets/com.IvanMurzak/AI Game Dev Installer";
var outputPath = "build/AI-Game-Dev-Installer.unitypackage";
// Ensure build directory exists
var buildDir = Path.GetDirectoryName(outputPath);
if (!Directory.Exists(buildDir))
{
Directory.CreateDirectory(buildDir);
}
// Export the package
AssetDatabase.ExportPackage(packagePath, outputPath, ExportPackageOptions.Recurse);
Debug.Log($"Package exported to: {outputPath}");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1b6d684272a1e24097921598fd2cd0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,289 @@
<div align="center">
<h1>✨ AI Game Developer — <i>Unity MCP</i></h1>
[![Docker Image](https://img.shields.io/docker/image-size/ivanmurzakdev/unity-mcp-server/latest?label=Docker%20Image&logo=docker&labelColor=333A41 'Docker Image')](https://hub.docker.com/r/ivanmurzakdev/unity-mcp-server)
[![MCP](https://badge.mcpx.dev?type=server 'MCP Server')](https://modelcontextprotocol.io/introduction)
[![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg 'Tests Passed')](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml)
[![Unity Asset Store](https://img.shields.io/badge/Asset%20Store-View-blue?logo=unity&labelColor=333A41 'Asset Store')](https://u3d.as/3wsw)
[![Unity Editor](https://img.shields.io/badge/Editor-X?style=flat&logo=unity&labelColor=333A41&color=49BC5C 'Unity Editor supported')](https://unity.com/releases/editor/archive)
[![Unity Runtime](https://img.shields.io/badge/Runtime-X?style=flat&logo=unity&labelColor=333A41&color=49BC5C 'Unity Runtime supported')](https://unity.com/releases/editor/archive)
[![OpenUPM](https://img.shields.io/npm/v/com.ivanmurzak.unity.mcp?label=OpenUPM&registry_uri=https://package.openupm.com&labelColor=333A41 'OpenUPM package')](https://openupm.com/packages/com.ivanmurzak.unity.mcp/)</br>
[![Discord](https://img.shields.io/badge/Discord-Join-7289da?logo=discord&logoColor=white&labelColor=333A41 'Join')](https://discord.gg/cfbdMZX99G)
[![Stars](https://img.shields.io/github/stars/IvanMurzak/Unity-MCP 'Stars')](https://github.com/IvanMurzak/Unity-MCP/stargazers)
[![License](https://img.shields.io/github/license/IvanMurzak/Unity-MCP?label=License&labelColor=333A41)](https://github.com/IvanMurzak/Unity-MCP/blob/main/LICENSE)
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
AI helper which does wide range of tasks in Unity Editor and even in a running game compiled to any platform. It connects to AI using TCP connection, that is why it is so flexible.
💬 **Join our community:** [Discord Server](https://discord.gg/cfbdMZX99G) - Ask questions, showcase your work, and connect with other developers!
![AI work](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/level-building.gif 'Level building')
</div>
<details>
<summary><b>Made with AI — samples (click to see)</b></summary>
<table>
<tr>
<td><img src="https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/flying-orbs.gif" alt="Animation" title="Animation" /></td>
<td><img src="https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/golden-sphere.gif" alt="Animation" title="Animation" /></td>
</tr>
<tr>
<td><img src="https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/runner.gif" alt="Runner Game" title="Runner Game" /></td>
<td><img src="https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/procedural-terrain.gif" alt="Procedural Terrain" title="Procedural Terrain" /></td>
</tr>
<tr>
<td><img src="https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/create-material.gif" alt="Material creating" title="Material creating" /></td>
<td><img src="https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/playing-maze.gif" alt="Maze Game" title="Maze Game" /></td>
</tr>
</table>
</details>
## Features for a human
- ✅ Chat with AI like with a human
- ✅ Local and Remote usage supported
-`stdio` and `streamableHttp` protocols supported
- ✅ Wide range of default [AI tools](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/ai-tools.md)
- ✅ Use `Description` attribute in C# code to provide detailed information for `class`, `field`, `property` or `method`.
- ✅ Customizable reflection convertors, inspired by `System.Text.Json` convertors
- do you have something extremely custom in your project? Make custom reflection convertor to let LLM be able to read and write into that data
- ✅ Remote AI units setup using docker containers,
- make a team of AI workers which work on your project simultaneously
## Features for LLM
- ✅ Agent ready tools, find anything you need in 1-2 steps
- ✅ Instant C# code compilation & execution using `Roslyn`, iterate faster
- ✅ Assets access (read / write), C# scripts access (read / write)
- ✅ Well described positive and negative feedback for proper understanding of an issue
- ✅ Provide references to existed objects for the instant C# code using `Reflection`
- ✅ Get full access to entire project data in a readable shape using `Reflection`
- ✅ Populate & Modify any granular piece of data in the project using `Reflection`
- ✅ Find any `method` in the entire codebase, including compiled DLL files using `Reflection`
- ✅ Call any `method` in the entire codebase using `Reflection`
- ✅ Provide any property into `method` call, even if it is a reference to existed object in memory using `Reflection` and advanced reflection convertors
- ✅ Unity API instantly available for usage, even if Unity changes something you will get fresh API using `Reflection`.
- ✅ Get access to human readable description of any `class`, `method`, `field`, `property` by reading it's `Description` attribute.
### Stability status
| Unity Version | Editmode | Playmode | Standalone |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2022.3.62f3 | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-2022-3-62f3-editmode)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-2022-3-62f3-playmode)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-2022-3-62f3-standalone)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) |
| 2023.2.22f1 | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-2023-2-22f1-editmode)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-2023-2-22f1-playmode)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-2023-2-22f1-standalone)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) |
| 6000.3.1f1 | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-6000-3-1f1-editmode)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-6000-3-1f1-playmode)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-MCP/workflows/release/badge.svg?job=test-unity-6000-3-1f1-standalone)](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) |
## Requirements
> [!IMPORTANT]
> **Project path cannot contain spaces**
>
> - ✅ `C:/MyProjects/Project`
> - ❌ `C:/My Projects/Project`
### Install `MCP Client`
Choose `MCP Client` you prefer, don't need to install all of them. This is will be your main chat window to talk with LLM.
- [Claude Code](https://github.com/anthropics/claude-code)
- [Claude Desktop](https://claude.ai/download)
- [GitHub Copilot in VS Code](https://code.visualstudio.com/docs/copilot/overview)
- [Cursor](https://www.cursor.com/)
- [Windsurf](https://windsurf.com)
- Any other supported
> MCP protocol is quite universal, that is why you may any MCP client you prefer, it will work as smooth as anyone else. The only important thing, that the MCP client has to support dynamic tool update.
# Installation
## Step 1: Install `Unity Plugin`
- **[⬇️ Download Installer](https://github.com/IvanMurzak/Unity-MCP/releases/download/0.17.2/AI-Game-Dev-Installer.unitypackage)**
- **📂 Import installer into Unity project**
> - You may use double click on the file - Unity will open it
> - OR: You may open Unity Editor first, then click on `Assets/Import Package/Custom Package`, then choose the file
<details>
<summary><b>Alternative: Install <code>Unity Plugin</code> via OpenUPM</b></summary>
- [Install OpenUPM-CLI](https://github.com/openupm/openupm-cli#installation)
- Open command line in Unity project folder
- Run the command
```bash
openupm add com.ivanmurzak.unity.mcp
```
</details>
## Step 2: Configure `MCP Client`
### Automatic configuration
- Open Unity project
- Open `Window/AI Connector (Unity-MCP)`
- Click `Configure` at your MCP client
![Unity_AI](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/ai-connector-window.gif)
> If MCP client is not in the list, use the raw JSON below in the window, to inject it into your MCP client. Read instructions for your MCP client how to do that.
### Manual configuration
If Automatic configuration doesn't work for you for any reason. Use JSON from `AI Connector (Unity-MCP)` window to configure any `MCP Client` on your own.
<details>
<summary>Add Unity-MCP to <code>Claude Code</code> (Windows)</summary>
Replace `unityProjectPath` with your real project path
```bash
claude mcp add Unity-MCP "<unityProjectPath>/Library/mcp-server/win-x64/unity-mcp-server.exe" client-transport=stdio
```
</details>
<details>
<summary>Add Unity-MCP to <code>Claude Code</code> (MacOS Apple-Silicon)</summary>
Replace `unityProjectPath` with your real project path
```bash
claude mcp add Unity-MCP "<unityProjectPath>/Library/mcp-server/osx-arm64/unity-mcp-server" client-transport=stdio
```
</details>
<details>
<summary>Add Unity-MCP to <code>Claude Code</code> (MacOS Apple-Intel)</summary>
Replace `unityProjectPath` with your real project path
```bash
claude mcp add Unity-MCP "<unityProjectPath>/Library/mcp-server/osx-x64/unity-mcp-server" client-transport=stdio
```
</details>
<details>
<summary>Add Unity-MCP to <code>Claude Code</code> (Linux x64)</summary>
Replace `unityProjectPath` with your real project path
```bash
claude mcp add Unity-MCP "<unityProjectPath>/Library/mcp-server/linux-x64/unity-mcp-server" client-transport=stdio
```
</details>
<details>
<summary>Add Unity-MCP to <code>Claude Code</code> (Linux arm64)</summary>
Replace `unityProjectPath` with your real project path
```bash
claude mcp add Unity-MCP "<unityProjectPath>/Library/mcp-server/linux-arm64/unity-mcp-server" client-transport=stdio
```
</details>
---
# Use AI
Talk with AI (LLM) in your `MCP Client`. Ask it to do anything you want. As better you describe your task / idea - as better it will do the job.
Some `MCP Clients` allow to chose different LLM models. Take an eye on it, some model may work much better.
```text
Explain my scene hierarchy
```
```text
Create 3 cubes in a circle with radius 2
```
```text
Create metallic golden material and attach it to a sphere gameObject
```
> Make sure `Agent` mode is turned on in MCP client
---
# How it works
**[Unity-MCP](https://github.com/IvanMurzak/Unity-MCP)** is a bridge between LLM and Unity. It exposes and explains to LLM Unity's tools. LLM understands the interface and utilizes the tools in the way a user asks.
Connect **[Unity-MCP](https://github.com/IvanMurzak/Unity-MCP)** to LLM client such as [Claude](https://claude.ai/download) or [Cursor](https://www.cursor.com/) using integrated `AI Connector` window. Custom clients are supported as well.
The project is designed to let developers to add custom tools soon. After that the next goal is to enable the same features in player's build. For not it works only in Unity Editor.
The system is extensible: you can define custom `tool`s directly in your Unity project codebase, exposing new capabilities to the AI or automation clients. This makes Unity-MCP a flexible foundation for building advanced workflows, rapid prototyping, or integrating AI-driven features into your development process.
---
# Advanced MCP server setup
Unity-MCP server supports many different launch options and docker docker deployment. Both transport protocol are supported `streamableHttp` and `stdio`. [Read more...](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/mcp-server.md)
# Add custom `tool`
> ⚠️ It only works with MCP client that supports dynamic tool list update.
Unity-MCP is designed to support custom `tool` development by project owner. MCP server takes data from Unity plugin and exposes it to a Client. So anyone in the MCP communication chain would receive the information about a new `tool`. Which LLM may decide to call at some point.
To add a custom `tool` you need:
1. To have a class with attribute `McpPluginToolType`.
2. To have a method in the class with attribute `McpPluginTool`.
3. [optional] Add `Description` attribute to each method argument to let LLM to understand it.
4. [optional] Use `string? optional = null` properties with `?` and default value to mark them as `optional` for LLM.
> Take a look that the line `MainThread.Instance.Run(() =>` it allows to run the code in Main thread which is needed to interact with Unity API. If you don't need it and running the tool in background thread is fine for the tool, don't use Main thread for efficiency purpose.
```csharp
[McpPluginToolType]
public class Tool_GameObject
{
[McpPluginTool
(
"MyCustomTask",
Title = "Create a new GameObject"
)]
[Description("Explain here to LLM what is this, when it should be called.")]
public string CustomTask
(
[Description("Explain to LLM what is this.")]
string inputData
)
{
// do anything in background thread
return MainThread.Instance.Run(() =>
{
// do something in main thread if needed
return $"[Success] Operation completed.";
});
}
}
```
# Add custom in-game `tool`
> ⚠️ Not yet supported. The work is in progress
---
# Contribution 💙💛
Contribution is highly appreciated. Brings your ideas and lets make the game development as simple as never before! Do you have an idea of a new `tool`, feature or did you spot a bug and know how to fix it.
1. 👉 [Fork the project](https://github.com/IvanMurzak/Unity-MCP/fork)
2. Clone the fork and open the `./Unity-MCP-Plugin` folder in Unity
3. Implement new things in the project, commit, push it to GitHub
4. Create Pull Request targeting original [Unity-MCP](https://github.com/IvanMurzak/Unity-MCP) repository, `main` branch.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: badb67057628e444fb9ecfbf2246ad10
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9fbdd5383371516498e4fd0ba51f3845
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75e3d8b42387c80479161a91b0a310f8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a6babde583d2f114c9f1756e71e0ac7d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d82937626fc136e489d066690bcccd3d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak",
"extensions.unity",
"org.nuget.com.ivanmurzak",
"org.nuget.microsoft",
"org.nuget.system",
"org.nuget.r3"
]
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3d5b79cfeddb2f04f8bb70ff73000dd7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 16c14e4a40eafcb418d9966639fefded
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ddb8439b851dacf458f1c25cf9895ddc
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
}
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0dc4a1c3c2fa1b141b5f60b9f16fd725
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": []
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 43ffd2c523a1fa643b984aeb51def7d9
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com"
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 30d120107a842394eb6b6d35500b70c0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak",
"extensions.unity",
"org.nuget.com.ivanmurzak",
"org.nuget.microsoft",
"org.nuget.system"
]
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 610ca5c6100bb9042937f4d5cf7c6160
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak",
"extensions.unity",
"org.nuget.com.ivanmurzak",
"org.nuget.microsoft"
]
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9d0579095e1400946814f12371bae605
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak",
"extensions.unity",
"org.nuget.com.ivanmurzak"
]
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 69acf9fb81183764fabb07cae63a55a1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak",
"extensions.unity"
]
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6ba3279b66694744399935ac96421b6f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
{
"dependencies": {
"com.unity.ide.visualstudio": "2.0.23",
"com.unity.test-framework": "1.1.33",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
"org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
"org.nuget.microsoft.bcl.memory": "9.0.7",
"org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
"org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.hosting": "9.0.7",
"org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
"org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "9.0.7",
"PACKAGE_ID": "PACKAGE_VERSION"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak"
]
}
]
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 112f17f3f25d40e469604e858090eacd
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
/*
┌──────────────────────────────────────────────────────────────────┐
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
│ Copyright (c) 2025 Ivan Murzak │
│ Licensed under the Apache License, Version 2.0. │
│ See the LICENSE file in the project root for more information. │
└──────────────────────────────────────────────────────────────────┘
*/
using System.IO;
using NUnit.Framework;
using UnityEngine;
namespace com.IvanMurzak.Unity.MCP.Installer.Tests
{
public class ManifestInstallerTests
{
const string PackageIdTag = "PACKAGE_ID";
const string PackageVersionTag = "PACKAGE_VERSION";
const string FilesRoot = "Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files";
const string FilesCopyRoot = "Temp/com.IvanMurzak/AI Game Dev Installer/Tests/Files";
static string CorrectManifestPath => $"{FilesRoot}/Correct/correct_manifest.json";
[SetUp]
public void SetUp()
{
Debug.Log($"[{nameof(ManifestInstallerTests)}] SetUp");
Directory.CreateDirectory(FilesCopyRoot);
}
[TearDown]
public void TearDown()
{
Debug.Log($"[{nameof(ManifestInstallerTests)}] TearDown");
// var files = Directory.GetFiles(FilesCopyRoot, "*.json", SearchOption.TopDirectoryOnly);
// foreach (var file in files)
// File.Delete(file);
}
[Test]
public void All()
{
var files = Directory.GetFiles(FilesRoot, "*.json", SearchOption.TopDirectoryOnly);
var correctManifest = File.ReadAllText(CorrectManifestPath)
.Replace(PackageVersionTag, Installer.Version)
.Replace(PackageIdTag, Installer.PackageId);
foreach (var file in files)
{
Debug.Log($"Found JSON file: {file}");
// Copy the file
var fileCopy = Path.Combine(FilesCopyRoot, Path.GetFileName(file));
File.Copy(file, fileCopy, overwrite: true);
// Arrange
File.WriteAllText(fileCopy, File.ReadAllText(fileCopy)
.Replace(PackageVersionTag, Installer.Version)
.Replace(PackageIdTag, Installer.PackageId));
// Act
Installer.AddScopedRegistryIfNeeded(fileCopy);
// Assert
var modifiedManifest = File.ReadAllText(fileCopy);
Assert.AreEqual(correctManifest, modifiedManifest, $"Modified manifest from {file} does not match the correct manifest.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f0df7dc33708f34ca0ab943a7176b3d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@
/*
┌──────────────────────────────────────────────────────────────────┐
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
│ Copyright (c) 2025 Ivan Murzak │
│ Licensed under the Apache License, Version 2.0. │
│ See the LICENSE file in the project root for more information. │
└──────────────────────────────────────────────────────────────────┘
*/
using System.IO;
using NUnit.Framework;
using com.IvanMurzak.Unity.MCP.Installer.SimpleJSON;
namespace com.IvanMurzak.Unity.MCP.Installer.Tests
{
public class VersionComparisonTests
{
const string TestManifestPath = "Temp/com.IvanMurzak/Unity.MCP.Installer.Tests/test_manifest.json";
const string PackageId = "com.ivanmurzak.unity.mcp";
[SetUp]
public void SetUp()
{
var dir = Path.GetDirectoryName(TestManifestPath);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
[TearDown]
public void TearDown()
{
if (File.Exists(TestManifestPath))
File.Delete(TestManifestPath);
}
[Test]
public void ShouldUpdateVersion_PatchVersionHigher_ReturnsTrue()
{
// Act & Assert
Assert.IsTrue(
condition: Installer.ShouldUpdateVersion(
currentVersion: "1.5.1",
installerVersion: "1.5.2"
),
message: "Should update when patch version is higher"
);
}
[Test]
public void ShouldUpdateVersion_PatchVersionLower_ReturnsFalse()
{
// Act & Assert
Assert.IsFalse(
condition: Installer.ShouldUpdateVersion(
currentVersion: "1.5.2",
installerVersion: "1.5.1"
),
message: "Should not downgrade when patch version is lower"
);
}
[Test]
public void ShouldUpdateVersion_MinorVersionHigher_ReturnsTrue()
{
// Act & Assert
Assert.IsTrue(
condition: Installer.ShouldUpdateVersion(
currentVersion: "1.5.0",
installerVersion: "1.6.0"
),
message: "Should update when minor version is higher"
);
}
[Test]
public void ShouldUpdateVersion_MinorVersionLower_ReturnsFalse()
{
// Act & Assert
Assert.IsFalse(
condition: Installer.ShouldUpdateVersion(
currentVersion: "1.6.0",
installerVersion: "1.5.0"
),
message: "Should not downgrade when minor version is lower"
);
}
[Test]
public void ShouldUpdateVersion_MajorVersionHigher_ReturnsTrue()
{
// Act & Assert
Assert.IsTrue(
condition: Installer.ShouldUpdateVersion(
currentVersion: "1.5.0",
installerVersion: "2.0.0"
),
message: "Should update when major version is higher"
);
}
[Test]
public void ShouldUpdateVersion_MajorVersionLower_ReturnsFalse()
{
// Act & Assert
Assert.IsFalse(
condition: Installer.ShouldUpdateVersion(
currentVersion: "2.0.0",
installerVersion: "1.5.0"
),
message: "Should not downgrade when major version is lower"
);
}
[Test]
public void ShouldUpdateVersion_SameVersion_ReturnsFalse()
{
// Act & Assert
Assert.IsFalse(
condition: Installer.ShouldUpdateVersion(
currentVersion: "1.5.2",
installerVersion: "1.5.2"
),
message: "Should not update when versions are the same"
);
}
[Test]
public void ShouldUpdateVersion_EmptyCurrentVersion_ReturnsTrue()
{
// Act & Assert
Assert.IsTrue(
condition: Installer.ShouldUpdateVersion(
currentVersion: "",
installerVersion: "1.5.2"
),
message: "Should install when no current version exists"
);
}
[Test]
public void ShouldUpdateVersion_NullCurrentVersion_ReturnsTrue()
{
// Act & Assert
Assert.IsTrue(
condition: Installer.ShouldUpdateVersion(
currentVersion: null,
installerVersion: "1.5.2"
),
message: "Should install when current version is null"
);
}
[Test]
public void AddScopedRegistryIfNeeded_PreventVersionDowngrade_Integration()
{
// Arrange - Create manifest with higher version
var versionParts = Installer.Version.Split('.');
var majorVersion = int.Parse(versionParts[0]);
var higherVersion = $"{majorVersion + 10}.0.0";
var manifest = new JSONObject
{
[Installer.Dependencies] = new JSONObject
{
[PackageId] = higherVersion
},
[Installer.ScopedRegistries] = new JSONArray()
};
File.WriteAllText(TestManifestPath, manifest.ToString(2));
// Act - Run installer (should NOT downgrade)
Installer.AddScopedRegistryIfNeeded(TestManifestPath);
// Assert - Version should remain unchanged
var updatedContent = File.ReadAllText(TestManifestPath);
var updatedManifest = JSONObject.Parse(updatedContent);
var actualVersion = updatedManifest[Installer.Dependencies][PackageId];
Assert.AreEqual(higherVersion, actualVersion.ToString().Trim('"'),
"Version should not be downgraded from higher version");
}
[Test]
public void AddScopedRegistryIfNeeded_AllowVersionUpgrade_Integration()
{
// Arrange - Create manifest with lower version (0.0.1 is always lower)
var lowerVersion = "0.0.1";
var manifest = new JSONObject
{
[Installer.Dependencies] = new JSONObject
{
[PackageId] = lowerVersion
},
[Installer.ScopedRegistries] = new JSONArray()
};
File.WriteAllText(TestManifestPath, manifest.ToString(2));
// Act - Run installer (should upgrade)
Installer.AddScopedRegistryIfNeeded(TestManifestPath);
// Assert - Version should be upgraded to installer version
var updatedContent = File.ReadAllText(TestManifestPath);
var updatedManifest = JSONObject.Parse(updatedContent);
var actualVersion = updatedManifest[Installer.Dependencies][PackageId];
Assert.AreEqual(Installer.Version, actualVersion.ToString().Trim('"'),
"Version should be upgraded to installer version");
}
[Test]
public void AddScopedRegistryIfNeeded_NoExistingDependency_InstallsNewVersion()
{
// Arrange - Create manifest without the package
var manifest = new JSONObject
{
[Installer.Dependencies] = new JSONObject(),
[Installer.ScopedRegistries] = new JSONArray()
};
File.WriteAllText(TestManifestPath, manifest.ToString(2));
// Act - Run installer
Installer.AddScopedRegistryIfNeeded(TestManifestPath);
// Assert - Package should be added with installer version
var updatedContent = File.ReadAllText(TestManifestPath);
var updatedManifest = JSONObject.Parse(updatedContent);
var actualVersion = updatedManifest[Installer.Dependencies][PackageId];
Assert.AreEqual(Installer.Version, actualVersion.ToString().Trim('"'),
"New package should be installed with installer version");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ece21aef0b677a246b2e4d410e769d3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
{
"name": "com.IvanMurzak.Unity.MCP.Installer.Tests",
"rootNamespace": "com.IvanMurzak.Unity.MCP.Installer.Tests",
"references": [
"com.IvanMurzak.Unity.MCP.Installer"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 62688e1a637d08f4583b4de5fdce7fc4
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
{
"name": "com.IvanMurzak.Unity.MCP.Installer",
"rootNamespace": "com.IvanMurzak.Unity.MCP.Installer",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ba51295843cc38b4ab50bce94c164333
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,6 +1,5 @@
{
"dependencies": {
"com.coplaydev.unity-mcp": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity",
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
"com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
"com.unity.2d.sprite": "1.0.0",
@@ -48,6 +47,21 @@
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",
"com.unity.modules.wind": "1.0.0",
"com.unity.modules.xr": "1.0.0"
}
}
"com.unity.modules.xr": "1.0.0",
"com.ivanmurzak.unity.mcp": "0.48.1"
},
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.ivanmurzak",
"extensions.unity",
"org.nuget.com.ivanmurzak",
"org.nuget.microsoft",
"org.nuget.system",
"org.nuget.r3"
]
}
]
}

View File

@@ -1,15 +1,5 @@
{
"dependencies": {
"com.coplaydev.unity-mcp": {
"version": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity",
"depth": 0,
"source": "git",
"dependencies": {
"com.unity.nuget.newtonsoft-json": "3.0.2",
"com.unity.test-framework": "1.1.31"
},
"hash": "12dd9bd516aba822a9206209880a0b650f60ccfa"
},
"com.cysharp.unitask": {
"version": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
"depth": 0,
@@ -24,6 +14,28 @@
"dependencies": {},
"hash": "a7c6b49a0141a5bff9b1983e38137522ef61977d"
},
"com.ivanmurzak.unity.mcp": {
"version": "0.48.1",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.modules.uielements": "1.0.0",
"com.unity.test-framework": "1.1.33",
"extensions.unity.playerprefsex": "2.1.2",
"org.nuget.microsoft.aspnetcore.signalr.client": "10.0.3",
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "10.0.3",
"org.nuget.microsoft.bcl.memory": "10.0.3",
"org.nuget.microsoft.codeanalysis.csharp": "4.14.0",
"org.nuget.microsoft.extensions.caching.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.hosting.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.logging": "10.0.3",
"org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
"org.nuget.r3": "1.3.0",
"org.nuget.system.text.json": "10.0.3"
},
"url": "https://package.openupm.com"
},
"com.unity.2d.sprite": {
"version": "1.0.0",
"depth": 0,
@@ -247,6 +259,450 @@
},
"url": "https://packages.unity.com"
},
"extensions.unity.playerprefsex": {
"version": "2.1.2",
"depth": 1,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.connections.abstractions": {
"version": "10.0.3",
"depth": 3,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.features": "10.0.3",
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.system.io.pipelines": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.http.connections.client": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.aspnetcore.http.connections.common": "10.0.3",
"org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.options": "10.0.3",
"org.nuget.system.net.serversentevents": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.http.connections.common": {
"version": "10.0.3",
"depth": 3,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.aspnetcore.connections.abstractions": "10.0.3",
"org.nuget.system.text.json": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.signalr.client": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.aspnetcore.signalr.client.core": "10.0.3",
"org.nuget.microsoft.aspnetcore.http.connections.client": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.signalr.client.core": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": "10.0.3",
"org.nuget.microsoft.aspnetcore.signalr.common": "10.0.3",
"org.nuget.microsoft.bcl.timeprovider": "10.0.3",
"org.nuget.microsoft.extensions.dependencyinjection": "10.0.3",
"org.nuget.microsoft.extensions.logging": "10.0.3",
"org.nuget.system.threading.channels": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.signalr.common": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.aspnetcore.connections.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.options": "10.0.3",
"org.nuget.system.text.json": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.aspnetcore.signalr.protocols.json": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.aspnetcore.signalr.common": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.bcl.asyncinterfaces": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.bcl.memory": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.bcl.timeprovider": {
"version": "10.0.3",
"depth": 3,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.codeanalysis.analyzers": {
"version": "3.11.0",
"depth": 2,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.codeanalysis.common": {
"version": "4.14.0",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.codeanalysis.analyzers": "3.11.0",
"org.nuget.system.collections.immutable": "9.0.0",
"org.nuget.system.memory": "4.5.5",
"org.nuget.system.reflection.metadata": "9.0.0",
"org.nuget.system.runtime.compilerservices.unsafe": "6.0.0",
"org.nuget.system.text.encoding.codepages": "7.0.0",
"org.nuget.system.threading.tasks.extensions": "4.5.4",
"org.nuget.system.buffers": "4.5.1",
"org.nuget.system.numerics.vectors": "4.5.0"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.codeanalysis.csharp": {
"version": "4.14.0",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.codeanalysis.common": "4.14.0",
"org.nuget.microsoft.codeanalysis.analyzers": "3.11.0",
"org.nuget.system.buffers": "4.5.1",
"org.nuget.system.collections.immutable": "9.0.0",
"org.nuget.system.memory": "4.5.5",
"org.nuget.system.numerics.vectors": "4.5.0",
"org.nuget.system.reflection.metadata": "9.0.0",
"org.nuget.system.runtime.compilerservices.unsafe": "6.0.0",
"org.nuget.system.text.encoding.codepages": "7.0.0",
"org.nuget.system.threading.tasks.extensions": "4.5.4"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.caching.abstractions": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.primitives": "10.0.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.configuration.abstractions": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.primitives": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.dependencyinjection": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.diagnostics.abstractions": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.options": "10.0.3",
"org.nuget.system.diagnostics.diagnosticsource": "10.0.3",
"org.nuget.system.buffers": "4.6.1",
"org.nuget.system.memory": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.features": {
"version": "10.0.3",
"depth": 4,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.fileproviders.abstractions": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.primitives": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.hosting.abstractions": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.microsoft.extensions.configuration.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.diagnostics.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.fileproviders.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.logging": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.microsoft.extensions.dependencyinjection": "10.0.3",
"org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.options": "10.0.3",
"org.nuget.system.diagnostics.diagnosticsource": "10.0.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.logging.abstractions": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
"org.nuget.system.diagnostics.diagnosticsource": "10.0.3",
"org.nuget.system.buffers": "4.6.1",
"org.nuget.system.memory": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.options": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
"org.nuget.microsoft.extensions.primitives": "10.0.3",
"org.nuget.system.componentmodel.annotations": "5.0.0"
},
"url": "https://package.openupm.com"
},
"org.nuget.microsoft.extensions.primitives": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
},
"url": "https://package.openupm.com"
},
"org.nuget.r3": {
"version": "1.3.0",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.timeprovider": "8.0.0",
"org.nuget.system.buffers": "4.5.1",
"org.nuget.system.componentmodel.annotations": "5.0.0",
"org.nuget.system.memory": "4.5.5",
"org.nuget.system.runtime.compilerservices.unsafe": "6.0.0",
"org.nuget.system.threading.channels": "8.0.0"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.buffers": {
"version": "4.6.1",
"depth": 2,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.system.collections.immutable": {
"version": "9.0.0",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.memory": "4.5.5",
"org.nuget.system.runtime.compilerservices.unsafe": "6.0.0"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.componentmodel.annotations": {
"version": "5.0.0",
"depth": 2,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.system.diagnostics.diagnosticsource": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.io.pipelines": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.buffers": "4.6.1",
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.memory": {
"version": "4.6.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.buffers": "4.6.1",
"org.nuget.system.numerics.vectors": "4.6.1",
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.net.serversentevents": {
"version": "10.0.3",
"depth": 3,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.numerics.vectors": {
"version": "4.6.1",
"depth": 3,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.system.reflection.metadata": {
"version": "9.0.0",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.collections.immutable": "9.0.0",
"org.nuget.system.memory": "4.5.5"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.runtime.compilerservices.unsafe": {
"version": "6.1.2",
"depth": 2,
"source": "registry",
"dependencies": {},
"url": "https://package.openupm.com"
},
"org.nuget.system.text.encoding.codepages": {
"version": "7.0.0",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.memory": "4.5.5",
"org.nuget.system.runtime.compilerservices.unsafe": "6.0.0"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.text.encodings.web": {
"version": "10.0.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.buffers": "4.6.1",
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.text.json": {
"version": "10.0.3",
"depth": 1,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.system.io.pipelines": "10.0.3",
"org.nuget.system.text.encodings.web": "10.0.3",
"org.nuget.system.buffers": "4.6.1",
"org.nuget.system.memory": "4.6.3",
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.threading.channels": {
"version": "10.0.3",
"depth": 3,
"source": "registry",
"dependencies": {
"org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
"org.nuget.system.threading.tasks.extensions": "4.6.3"
},
"url": "https://package.openupm.com"
},
"org.nuget.system.threading.tasks.extensions": {
"version": "4.6.3",
"depth": 2,
"source": "registry",
"dependencies": {
"org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
},
"url": "https://package.openupm.com"
},
"com.unity.modules.accessibility": {
"version": "1.0.0",
"depth": 0,

View File

@@ -12,11 +12,13 @@ MonoBehaviour:
m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
m_Name:
m_EditorClassIdentifier:
m_EnablePreviewPackages: 0
m_EnablePackageDependencies: 0
m_EnablePreReleasePackages: 0
m_AdvancedSettingsExpanded: 1
m_ScopedRegistriesSettingsExpanded: 1
m_SeeAllPackageVersions: 0
m_DismissPreviewPackagesInUse: 0
oneTimeWarningShown: 0
oneTimeDeprecatedPopUpShown: 0
m_Registries:
- m_Id: main
m_Name:
@@ -24,20 +26,31 @@ MonoBehaviour:
m_Scopes: []
m_IsDefault: 1
m_Capabilities: 7
m_UserSelectedRegistryName:
m_ConfigSource: 0
m_Compliance:
m_Status: 0
m_Violations: []
- m_Id: scoped:project:package.openupm.com
m_Name: package.openupm.com
m_Url: https://package.openupm.com
m_Scopes:
- com.ivanmurzak
- extensions.unity
- org.nuget.com.ivanmurzak
- org.nuget.microsoft
- org.nuget.system
- org.nuget.r3
m_IsDefault: 0
m_Capabilities: 0
m_ConfigSource: 4
m_Compliance:
m_Status: 0
m_Violations: []
m_UserSelectedRegistryName: package.openupm.com
m_UserAddingNewScopedRegistry: 0
m_RegistryInfoDraft:
m_ErrorMessage:
m_Original:
m_Id:
m_Name:
m_Url:
m_Scopes: []
m_IsDefault: 0
m_Capabilities: 0
m_Modified: 0
m_Name:
m_Url:
m_Scopes:
-
m_SelectedScopeIndex: 0
m_ErrorMessage:
m_UserModificationsInstanceId: -868
m_OriginalInstanceId: -870
m_LoadAssets: 0

View File

@@ -9,10 +9,12 @@
<Project Path="AssetUsageDetector.Editor.csproj" />
<Project Path="com.Tivadar.Best.WebSockets.csproj" />
<Project Path="EPODemo.csproj" />
<Project Path="com.IvanMurzak.Unity.MCP.Installer.Tests.csproj" />
<Project Path="Assembly-CSharp-firstpass.csproj" />
<Project Path="EPOUtilities.csproj" />
<Project Path="EPOEditor.csproj" />
<Project Path="SimpleFileBrowser.Runtime.csproj" />
<Project Path="com.IvanMurzak.Unity.MCP.Installer.csproj" />
<Project Path="Assembly-CSharp-Editor-firstpass.csproj" />
<Project Path="com.Tivadar.Best.HTTP.Profiler.Editor.csproj" />
<Project Path="EPOURP.csproj" />