구글 시트 연동 기능 확장
This commit is contained in:
parent
4bde2ee226
commit
624b4373b4
@ -285,11 +285,15 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 2631101f894592945a1c50aed7048e66, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
isAccessGoogleSheet: 1
|
||||
googleSheetUrl: https://script.google.com/macros/s/AKfycbw8TRSl_OuY2S-RX0yvOJi1SqNqoflG0R3pWxk9GC9u_wvGQeuABZc0VH7YJ5lMrAl4/exec
|
||||
availSheets: Test/Monster
|
||||
generateFolderPath: /0.Datas/02.Scripts/GenerateGoogleSheet
|
||||
googleSheetSO: {fileID: 11400000, guid: a205d54a1d0f6b447986268e3fe1d668, type: 2}
|
||||
_persistent: 1
|
||||
_isAccessGoogleSheet: 1
|
||||
_googleSheetUrl: https://script.google.com/macros/s/AKfycbw8TRSl_OuY2S-RX0yvOJi1SqNqoflG0R3pWxk9GC9u_wvGQeuABZc0VH7YJ5lMrAl4/exec
|
||||
_availSheets: Food/Monster
|
||||
_generateFolderPath: /0.Datas/02.Scripts/GenerateGoogleSheet/AutoCreated
|
||||
_lastUpdated: 2025-05-12 03:32:27
|
||||
_restoreIndex: 0
|
||||
_editorName:
|
||||
_refreshTrigger: 1
|
||||
--- !u!4 &383092898
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -298,12 +302,12 @@ Transform:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 383092896}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_Father: {fileID: 1788462582}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &410087039
|
||||
GameObject:
|
||||
@ -475,6 +479,38 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1788462581
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1788462582}
|
||||
m_Layer: 0
|
||||
m_Name: GameObject
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1788462582
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1788462581}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 383092898}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -482,4 +518,4 @@ SceneRoots:
|
||||
- {fileID: 330585546}
|
||||
- {fileID: 410087041}
|
||||
- {fileID: 832575519}
|
||||
- {fileID: 383092898}
|
||||
- {fileID: 1788462582}
|
||||
|
@ -1,35 +0,0 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class GoogleSheetDiffWindow : EditorWindow
|
||||
{
|
||||
private GoogleSheetDiffResult _diff;
|
||||
|
||||
public static void ShowWithDiff(GoogleSheetDiffResult diff)
|
||||
{
|
||||
var window = GetWindow<GoogleSheetDiffWindow>("Sheet Diff");
|
||||
window._diff = diff;
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (_diff == null)
|
||||
{
|
||||
GUILayout.Label("No diff result yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label("\ud83d\udd3c Added", EditorStyles.boldLabel);
|
||||
foreach (var id in _diff.Added)
|
||||
GUILayout.Label($" + {id}");
|
||||
|
||||
GUILayout.Label("\u270f\ufe0f Modified", EditorStyles.boldLabel);
|
||||
foreach (var id in _diff.Modified)
|
||||
GUILayout.Label($" * {id}");
|
||||
|
||||
GUILayout.Label("\ud83d\udd3d Removed", EditorStyles.boldLabel);
|
||||
foreach (var id in _diff.Removed)
|
||||
GUILayout.Label($" - {id}");
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbe9541a39cf4284babed409264c92ab
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 140c5b11dd5e2c243b7ddc10ddc5b30b
|
||||
guid: fb4a8c3ebcd8b9040b65650bcb10ed6c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
@ -0,0 +1,14 @@
|
||||
// <auto-generated>
|
||||
using System;
|
||||
|
||||
public enum Taste
|
||||
{
|
||||
None = 0,
|
||||
Bitter = 1,
|
||||
Sweet = 2,
|
||||
Spicy = 3,
|
||||
Fresh = 4,
|
||||
Sour = 5,
|
||||
Salty = 6,
|
||||
}
|
||||
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2714e362cc5f51346a94b6da1f2a68f8
|
@ -0,0 +1,31 @@
|
||||
// <auto-generated>
|
||||
using System;
|
||||
using UnityEngine;
|
||||
[Serializable]
|
||||
public class Food
|
||||
{
|
||||
/// <summary>식별ID</summary>
|
||||
[Tooltip("식별ID")]
|
||||
public string Id;
|
||||
|
||||
/// <summary>이름</summary>
|
||||
[Tooltip("이름")]
|
||||
public string Name;
|
||||
|
||||
/// <summary>재료1</summary>
|
||||
[Tooltip("재료1")]
|
||||
public string Ingredient1;
|
||||
|
||||
/// <summary>재료2</summary>
|
||||
[Tooltip("재료2")]
|
||||
public string Ingredient2;
|
||||
|
||||
/// <summary>맛1</summary>
|
||||
[Tooltip("맛1")]
|
||||
public Taste Taste1;
|
||||
|
||||
/// <summary>맛2</summary>
|
||||
[Tooltip("맛2")]
|
||||
public Taste Taste2;
|
||||
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8693b42dca1bad640aad102399faa440
|
@ -0,0 +1,9 @@
|
||||
// <auto-generated> File: FoodSo.cs
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "FoodSo", menuName = "GoogleSheet/FoodSo")]
|
||||
public class FoodSo : ScriptableObject
|
||||
{
|
||||
public List<Food> FoodList;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9a7e15d3d3df5f4a989d1e2e92682e6
|
@ -0,0 +1,76 @@
|
||||
{
|
||||
"$개요": [
|
||||
{
|
||||
"": "시트 생성"
|
||||
}
|
||||
],
|
||||
"Food": [
|
||||
{
|
||||
"Id": "식별ID",
|
||||
"Name": "이름",
|
||||
"Ingredient1": "재료1",
|
||||
"Ingredient2": "재료2",
|
||||
"Taste1:Taste_Enum": "맛1",
|
||||
"Taste2:Taste_Enum": "맛2"
|
||||
},
|
||||
{
|
||||
"Id": "Food001",
|
||||
"Name": "햇빛수프",
|
||||
"Ingredient1": "극락쌀",
|
||||
"Ingredient2": "햇빛당근",
|
||||
"Taste1:Taste_Enum": "Bitter",
|
||||
"Taste2:Taste_Enum": "Sweet"
|
||||
},
|
||||
{
|
||||
"Id": "Food002",
|
||||
"Name": "B",
|
||||
"Ingredient1": 1,
|
||||
"Ingredient2": 4,
|
||||
"Taste1:Taste_Enum": "Spicy",
|
||||
"Taste2:Taste_Enum": "Bitter"
|
||||
},
|
||||
{
|
||||
"Id": "Food003",
|
||||
"Name": "C",
|
||||
"Ingredient1": 2,
|
||||
"Ingredient2": 5,
|
||||
"Taste1:Taste_Enum": "Fresh",
|
||||
"Taste2:Taste_Enum": "None"
|
||||
},
|
||||
{
|
||||
"Id": "Food004",
|
||||
"Name": "D",
|
||||
"Ingredient1": 3,
|
||||
"Ingredient2": 6,
|
||||
"Taste1:Taste_Enum": "Sour",
|
||||
"Taste2:Taste_Enum": "Salty"
|
||||
}
|
||||
],
|
||||
"Monster": [
|
||||
{
|
||||
"Id": "식별번호",
|
||||
"Name": "이름",
|
||||
"T1": "테스트1"
|
||||
},
|
||||
{
|
||||
"Id": "Test001",
|
||||
"Name": "A",
|
||||
"T1": 1
|
||||
},
|
||||
{
|
||||
"Id": "Test002",
|
||||
"Name": "B",
|
||||
"T1": 2
|
||||
},
|
||||
{
|
||||
"Id": "Test003",
|
||||
"Name": "C",
|
||||
"T1": 3
|
||||
},
|
||||
{
|
||||
"Id": "Test004",
|
||||
"Name": "D",
|
||||
"T1": 4
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f01fadb863b05574a964a6fd1b8da2f9
|
||||
guid: 70eea4fed7f314a4492ac4a97380d4b1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
@ -0,0 +1,19 @@
|
||||
// <auto-generated>
|
||||
using System;
|
||||
using UnityEngine;
|
||||
[Serializable]
|
||||
public class Monster
|
||||
{
|
||||
/// <summary>식별번호</summary>
|
||||
[Tooltip("식별번호")]
|
||||
public string Id;
|
||||
|
||||
/// <summary>이름</summary>
|
||||
[Tooltip("이름")]
|
||||
public string Name;
|
||||
|
||||
/// <summary>테스트1</summary>
|
||||
[Tooltip("테스트1")]
|
||||
public int T1;
|
||||
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26a6429de94146a43826ee8860767011
|
@ -0,0 +1,9 @@
|
||||
// <auto-generated> File: MonsterSo.cs
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "MonsterSo", menuName = "GoogleSheet/MonsterSo")]
|
||||
public class MonsterSo : ScriptableObject
|
||||
{
|
||||
public List<Monster> MonsterList;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a377050a15fa114cb0d0bd8d0f6bafc
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d30016576075fac4e84ec728aaeb2806
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,39 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d9a7e15d3d3df5f4a989d1e2e92682e6, type: 3}
|
||||
m_Name: FoodSo
|
||||
m_EditorClassIdentifier:
|
||||
FoodList:
|
||||
- Id: Food001
|
||||
Name: "\uD587\uBE5B\uC218\uD504"
|
||||
Ingredient1: "\uADF9\uB77D\uC300"
|
||||
Ingredient2: "\uD587\uBE5B\uB2F9\uADFC"
|
||||
Taste1: 1
|
||||
Taste2: 2
|
||||
- Id: Food002
|
||||
Name: B
|
||||
Ingredient1: 1
|
||||
Ingredient2: 4
|
||||
Taste1: 3
|
||||
Taste2: 1
|
||||
- Id: Food003
|
||||
Name: C
|
||||
Ingredient1: 2
|
||||
Ingredient2: 5
|
||||
Taste1: 4
|
||||
Taste2: 0
|
||||
- Id: Food004
|
||||
Name: D
|
||||
Ingredient1: 3
|
||||
Ingredient2: 6
|
||||
Taste1: 5
|
||||
Taste2: 6
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a205d54a1d0f6b447986268e3fe1d668
|
||||
guid: 8f6a170dcd0a88d47939e70db303af14
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
@ -9,33 +9,16 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 52c5476425215a341bc2722f655d24fe, type: 3}
|
||||
m_Name: GoogleSheetSO
|
||||
m_Script: {fileID: 11500000, guid: 8a377050a15fa114cb0d0bd8d0f6bafc, type: 3}
|
||||
m_Name: MonsterSo
|
||||
m_EditorClassIdentifier:
|
||||
TestList:
|
||||
- Id: Test001
|
||||
Name: A
|
||||
T1: 1
|
||||
T2: 234
|
||||
- Id: Test002
|
||||
Name: B
|
||||
T1: 2
|
||||
T2: 2
|
||||
- Id: Test003
|
||||
Name: C
|
||||
T1: 3
|
||||
T2: 24142
|
||||
- Id: Test004
|
||||
Name: D
|
||||
T1: 4
|
||||
T2: 2
|
||||
MonsterList:
|
||||
- Id: Test001
|
||||
Name: A
|
||||
T1: 1
|
||||
- Id: Test002
|
||||
Name: B
|
||||
T1: 5345
|
||||
T1: 2
|
||||
- Id: Test003
|
||||
Name: C
|
||||
T1: 3
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a1b86f3356293441bcfca705fff3b85
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89a023f100586884990e16b137a5b30e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,76 @@
|
||||
{
|
||||
"$개요": [
|
||||
{
|
||||
"": "시트 생성"
|
||||
}
|
||||
],
|
||||
"Food": [
|
||||
{
|
||||
"Id": "식별ID",
|
||||
"Name": "이름",
|
||||
"Ingredient1": "재료1",
|
||||
"Ingredient2": "재료2",
|
||||
"Taste1:Taste_Enum": "맛1",
|
||||
"Taste2:Taste_Enum": "맛2"
|
||||
},
|
||||
{
|
||||
"Id": "Food001",
|
||||
"Name": "햇빛수프",
|
||||
"Ingredient1": "극락쌀",
|
||||
"Ingredient2": "햇빛당근",
|
||||
"Taste1:Taste_Enum": "Bitter",
|
||||
"Taste2:Taste_Enum": "Sweet"
|
||||
},
|
||||
{
|
||||
"Id": "Food002",
|
||||
"Name": "B",
|
||||
"Ingredient1": 1,
|
||||
"Ingredient2": 11,
|
||||
"Taste1:Taste_Enum": "Spicy",
|
||||
"Taste2:Taste_Enum": "Bitter"
|
||||
},
|
||||
{
|
||||
"Id": "Food003",
|
||||
"Name": "C",
|
||||
"Ingredient1": 2,
|
||||
"Ingredient2": 22,
|
||||
"Taste1:Taste_Enum": "Fresh",
|
||||
"Taste2:Taste_Enum": "None"
|
||||
},
|
||||
{
|
||||
"Id": "Food004",
|
||||
"Name": "D",
|
||||
"Ingredient1": 3,
|
||||
"Ingredient2": 33,
|
||||
"Taste1:Taste_Enum": "Sour",
|
||||
"Taste2:Taste_Enum": "Salty"
|
||||
}
|
||||
],
|
||||
"Monster": [
|
||||
{
|
||||
"Id": "식별번호",
|
||||
"Name": "이름",
|
||||
"T1": "테스트1"
|
||||
},
|
||||
{
|
||||
"Id": "Test001",
|
||||
"Name": "A",
|
||||
"T1": 1
|
||||
},
|
||||
{
|
||||
"Id": "Test002",
|
||||
"Name": "B",
|
||||
"T1": 2
|
||||
},
|
||||
{
|
||||
"Id": "Test003",
|
||||
"Name": "C",
|
||||
"T1": 3
|
||||
},
|
||||
{
|
||||
"Id": "Test004",
|
||||
"Name": "D",
|
||||
"T1": 4
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5657779a278b799488c5a53a4ae7d508
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,76 @@
|
||||
{
|
||||
"$개요": [
|
||||
{
|
||||
"": "시트 생성"
|
||||
}
|
||||
],
|
||||
"Food": [
|
||||
{
|
||||
"Id": "식별ID",
|
||||
"Name": "이름",
|
||||
"Ingredient1": "재료1",
|
||||
"Ingredient2": "재료2",
|
||||
"Taste1:Taste_Enum": "맛1",
|
||||
"Taste2:Taste_Enum": "맛2"
|
||||
},
|
||||
{
|
||||
"Id": "Food001",
|
||||
"Name": "햇빛수프",
|
||||
"Ingredient1": "극락쌀",
|
||||
"Ingredient2": "햇빛당근",
|
||||
"Taste1:Taste_Enum": "Bitter",
|
||||
"Taste2:Taste_Enum": "Sweet"
|
||||
},
|
||||
{
|
||||
"Id": "Food002",
|
||||
"Name": "B",
|
||||
"Ingredient1": 1,
|
||||
"Ingredient2": 4,
|
||||
"Taste1:Taste_Enum": "Spicy",
|
||||
"Taste2:Taste_Enum": "Bitter"
|
||||
},
|
||||
{
|
||||
"Id": "Food003",
|
||||
"Name": "C",
|
||||
"Ingredient1": 2,
|
||||
"Ingredient2": 5,
|
||||
"Taste1:Taste_Enum": "Fresh",
|
||||
"Taste2:Taste_Enum": "None"
|
||||
},
|
||||
{
|
||||
"Id": "Food004",
|
||||
"Name": "D",
|
||||
"Ingredient1": 3,
|
||||
"Ingredient2": 6,
|
||||
"Taste1:Taste_Enum": "Sour",
|
||||
"Taste2:Taste_Enum": "Salty"
|
||||
}
|
||||
],
|
||||
"Monster": [
|
||||
{
|
||||
"Id": "식별번호",
|
||||
"Name": "이름",
|
||||
"T1": "테스트1"
|
||||
},
|
||||
{
|
||||
"Id": "Test001",
|
||||
"Name": "A",
|
||||
"T1": 1
|
||||
},
|
||||
{
|
||||
"Id": "Test002",
|
||||
"Name": "B",
|
||||
"T1": 2
|
||||
},
|
||||
{
|
||||
"Id": "Test003",
|
||||
"Name": "C",
|
||||
"T1": 3
|
||||
},
|
||||
{
|
||||
"Id": "Test004",
|
||||
"Name": "D",
|
||||
"T1": 4
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5ce26639cbf46447a2b1298c436a485
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/0.Datas/02.Scripts/GenerateGoogleSheet/Core.meta
Normal file
8
Assets/0.Datas/02.Scripts/GenerateGoogleSheet/Core.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66addae216bafdf40931054ab35bae80
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,710 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEditor;
|
||||
|
||||
public class GoogleSheetManager : Singleton<GoogleSheetManager>
|
||||
{
|
||||
[BoxGroup("기본 설정")] [Tooltip("true: google sheet, false: local json")] [SerializeField]
|
||||
private bool _isAccessGoogleSheet = true;
|
||||
|
||||
[BoxGroup("기본 설정")]
|
||||
[Tooltip("구글 시트 -> 확장 프로그램 -> Apps Script -> 새 배포(웹 앱) or 배포 관리 -> 웹 앱 URL(~~~/exec)")]
|
||||
[SerializeField]
|
||||
private string _googleSheetUrl;
|
||||
|
||||
[BoxGroup("기본 설정")]
|
||||
[Tooltip("적용시킬 시트의 이름을 적고, 여러 개의 시트를 적는 경우 '/'로 구분지어 시트 나열\nex) \"Sheet1/Sheet2\"")]
|
||||
[SerializeField]
|
||||
private string _availSheets = "Sheet1/Sheet2";
|
||||
|
||||
[BoxGroup("기본 설정")] [Tooltip("Class, Json, So 생성 위치 \"/GenerateGoogleSheet\"")] [SerializeField]
|
||||
private string _generateFolderPath = "/0.Datas/02.Scripts/GenerateGoogleSheet/AutoCreated";
|
||||
|
||||
[Title("버전 복구")] [BoxGroup("버전 복구")] [Tooltip("마지막 업데이트 시간")] [SerializeField, ReadOnly]
|
||||
private string _lastUpdated;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[BoxGroup("버전 복구")] [SerializeField, ValueDropdown(nameof(GetVersionOptions))]
|
||||
private int _restoreIndex;
|
||||
#endif
|
||||
|
||||
[Title("데이터 변경")]
|
||||
[BoxGroup("데이터 변경")]
|
||||
[LabelText("수정자 이름")]
|
||||
[SerializeField, Required("반드시 수정자 이름을 입력해야 합니다\n이력을 남길 때 표시될 사용자 이름입니다.")]
|
||||
private string _editorName;
|
||||
|
||||
private string JsonPath => $"{Application.dataPath}{_generateFolderPath}/GoogleSheetJson.json";
|
||||
|
||||
private const string ChangeLogPath = "Assets/0.Datas/02.Scripts/GenerateGoogleSheet/Logs/GoogleSheetChangeLog.asset";
|
||||
|
||||
private string[] _availSheetArray;
|
||||
private string _json;
|
||||
|
||||
[SerializeField, ReadOnly]
|
||||
private bool _refreshTrigger;
|
||||
private bool _alreadyCreatedSo;
|
||||
|
||||
public static T So<T>() where T : ScriptableObject
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
string soName = typeof(T).Name;
|
||||
string path = $"Assets{Instance._generateFolderPath}/So/{soName}.asset";
|
||||
T so = AssetDatabase.LoadAssetAtPath<T>(path);
|
||||
if (so == null)
|
||||
{
|
||||
Debug.LogError($"[GoogleSheetManager] {soName}.asset 파일을 찾을 수 없습니다. 먼저 데이터를 생성하세요.");
|
||||
}
|
||||
|
||||
return so;
|
||||
#else
|
||||
// 런타임 시에는 Resources 로 로드하거나 Addressables 등을 사용할 수 있음
|
||||
Debug.LogError("[GoogleSheetManager] 런타임에서 SO를 로드하려면 별도 로딩 로직이 필요합니다.");
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시트 이름으로 개별 SO를 불러옵니다.
|
||||
/// 사용 예: GoogleSheetManager.LoadSo<Sheet1So>("Sheet1")
|
||||
/// </summary>
|
||||
public static T LoadSo<T>(string sheetName) where T : ScriptableObject
|
||||
{
|
||||
string path = $"Assets{Instance._generateFolderPath}/So/{sheetName}So.asset";
|
||||
T so = AssetDatabase.LoadAssetAtPath<T>(path);
|
||||
if (so == null)
|
||||
{
|
||||
Debug.LogError($"[GoogleSheetManager] 해당 SO를 찾을 수 없습니다: {sheetName}So.asset");
|
||||
}
|
||||
|
||||
return so;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
[BoxGroup("데이터 변경")]
|
||||
[Button("데이터 최신화"), EnableIf(nameof(CanFetchData))]
|
||||
private async void FetchGoogleSheet()
|
||||
{
|
||||
_availSheetArray = _availSheets.Split('/');
|
||||
|
||||
var prevLog = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
||||
string previousJson = prevLog?.Logs.LastOrDefault()?.JsonSnapshot ?? "";
|
||||
|
||||
if (_isAccessGoogleSheet)
|
||||
{
|
||||
if (!IsValidGoogleSheetUrl(_googleSheetUrl))
|
||||
{
|
||||
Debug.LogError("Google Sheet URL이 유효하지 않습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("구글 시트 데이터 읽는 중...");
|
||||
_json = await LoadDataGoogleSheet(_googleSheetUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Local Json 파일 읽는 중...");
|
||||
_json = LoadDataLocalJson();
|
||||
}
|
||||
|
||||
if (_json == null)
|
||||
{
|
||||
Debug.Log("Json is null. 최신화 실패");
|
||||
return;
|
||||
}
|
||||
|
||||
var diffs = GoogleSheetFetchHelper.CompareJsonDiff(previousJson, _json);
|
||||
if (diffs.Count > 0)
|
||||
GoogleSheetDiffViewer.ShowWindow(diffs);
|
||||
|
||||
bool isJsonSaved = SaveFileOrSkip(JsonPath, _json);
|
||||
GenerateClassFilesPerSheet(_json);
|
||||
|
||||
_lastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
if (isJsonSaved)
|
||||
{
|
||||
_refreshTrigger = true;
|
||||
SaveChangeLog(_json);
|
||||
EditorPrefs.SetBool("GoogleSheetManager_ShouldCreateSO", true);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanFetchData()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(_editorName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json 로그 저장
|
||||
/// </summary>
|
||||
private void SaveChangeLog(string json)
|
||||
{
|
||||
string logsDirectory = Path.GetDirectoryName(ChangeLogPath);
|
||||
if (!Directory.Exists(logsDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(logsDirectory);
|
||||
AssetDatabase.ImportAsset(logsDirectory);
|
||||
}
|
||||
|
||||
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
||||
if (log == null)
|
||||
{
|
||||
log = ScriptableObject.CreateInstance<GoogleSheetChangeLog>();
|
||||
AssetDatabase.CreateAsset(log, ChangeLogPath);
|
||||
}
|
||||
|
||||
string previousJson = log.Logs.Count > 0 ? log.Logs[^1].JsonSnapshot : null;
|
||||
|
||||
// 차이 비교
|
||||
if (!string.IsNullOrEmpty(previousJson))
|
||||
{
|
||||
string diffResult = GoogleSheetDiffHelper.GenerateDiff(previousJson, json);
|
||||
Debug.Log(diffResult);
|
||||
}
|
||||
|
||||
string saveTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
log.Logs.Add(new GoogleSheetChangeLog.LogEntry
|
||||
{
|
||||
Editor = _editorName,
|
||||
Timestamp = saveTime,
|
||||
JsonSnapshot = json
|
||||
});
|
||||
|
||||
EditorUtility.SetDirty(log);
|
||||
AssetDatabase.SaveAssets();
|
||||
SaveJsonBackup(json, saveTime);
|
||||
_editorName = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json 백업
|
||||
/// </summary>
|
||||
private void SaveJsonBackup(string json, string saveTime)
|
||||
{
|
||||
string safeSaveTime = saveTime.Replace(":", "-"); // 윈도우 파일 이름 안전 처리
|
||||
string folderPath = Path.Combine(Application.dataPath, "0.Datas/02.Scripts/GenerateGoogleSheet/Backups");
|
||||
|
||||
if (!Directory.Exists(folderPath))
|
||||
Directory.CreateDirectory(folderPath);
|
||||
|
||||
string fileName = $"{safeSaveTime} by {_editorName}.json";
|
||||
string filePath = Path.Combine(folderPath, fileName);
|
||||
|
||||
File.WriteAllText(filePath, json);
|
||||
}
|
||||
|
||||
[BoxGroup("버전 복구")]
|
||||
[Button("선택한 버전과 현재 비교")]
|
||||
private void CompareWithSelectedVersion()
|
||||
{
|
||||
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
||||
if (log == null || _restoreIndex < 0 || _restoreIndex >= log.Logs.Count)
|
||||
{
|
||||
Debug.LogWarning("비교할 수 있는 로그가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
string restoreJson = log.Logs[_restoreIndex].JsonSnapshot;
|
||||
string currentJson = File.Exists(JsonPath) ? File.ReadAllText(JsonPath) : "";
|
||||
|
||||
List<(string Sheet, string Field, int RowIndex, string OldValue, string NewValue)> diffs =
|
||||
GoogleSheetFetchHelper.CompareJsonDiff(currentJson, restoreJson);
|
||||
|
||||
if (diffs.Count > 0)
|
||||
{
|
||||
GoogleSheetDiffViewer.ShowWindow(diffs);
|
||||
Debug.Log("[GoogleSheetManager] 선택한 버전과 현재 버전 간의 변경점을 표시합니다.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[GoogleSheetManager] 변경점 없음.");
|
||||
}
|
||||
}
|
||||
|
||||
[BoxGroup("버전 복구")]
|
||||
[Button("선택한 버전으로 복구")]
|
||||
private void RestoreSelectedVersion()
|
||||
{
|
||||
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
||||
if (log == null || _restoreIndex < 0 || _restoreIndex >= log.Logs.Count)
|
||||
{
|
||||
Debug.LogWarning("복원할 수 있는 로그가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
string restoreJson = log.Logs[_restoreIndex].JsonSnapshot;
|
||||
string currentJson = File.Exists(JsonPath) ? File.ReadAllText(JsonPath) : "";
|
||||
|
||||
// ✅ 변경점 시각화
|
||||
List<(string Sheet, string Field, int RowIndex, string OldValue, string NewValue)> diffs =
|
||||
GoogleSheetFetchHelper.CompareJsonDiff(currentJson, restoreJson);
|
||||
|
||||
if (diffs.Count > 0)
|
||||
GoogleSheetDiffViewer.ShowWindow(diffs);
|
||||
|
||||
// 복원 처리
|
||||
_json = restoreJson;
|
||||
|
||||
SaveFileOrSkip(JsonPath, _json);
|
||||
CreateGoogleSheetSo();
|
||||
_lastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
Debug.Log($"[{log.Logs[_restoreIndex].Editor}]의 버전으로 복원 완료");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 버전 로그 드롭다운 함수
|
||||
/// </summary>
|
||||
private IEnumerable<ValueDropdownItem<int>> GetVersionOptions()
|
||||
{
|
||||
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
||||
if (log == null)
|
||||
yield break;
|
||||
|
||||
for (int i = 0; i < log.Logs.Count; i++)
|
||||
{
|
||||
yield return new ValueDropdownItem<int>(
|
||||
$"{i} - {log.Logs[i].Timestamp} by {log.Logs[i].Editor}", i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 구글 시트 데이터 읽어오기
|
||||
/// </summary>
|
||||
private async Task<string> LoadDataGoogleSheet(string url)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] dataBytes = await client.GetByteArrayAsync(url);
|
||||
return Encoding.UTF8.GetString(dataBytes);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Debug.LogError($"Request error: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// jSON 데이터 파일 읽어오기
|
||||
/// </summary>
|
||||
private string LoadDataLocalJson()
|
||||
{
|
||||
if (File.Exists(JsonPath))
|
||||
{
|
||||
return File.ReadAllText(JsonPath);
|
||||
}
|
||||
|
||||
Debug.Log($"Json 파일이 존재하지 않습니다\n{JsonPath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파일 생성 및 비교
|
||||
/// </summary>
|
||||
private bool SaveFileOrSkip(string path, string contents)
|
||||
{
|
||||
string directoryPath = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
if (File.Exists(path) && File.ReadAllText(path).Equals(contents))
|
||||
return false;
|
||||
|
||||
File.WriteAllText(path, contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsExistAvailSheets(string sheetName)
|
||||
{
|
||||
return Array.Exists(_availSheetArray, x => x == sheetName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 유효한 구글 웹 앱 URL인지 확인
|
||||
/// </summary>
|
||||
private bool IsValidGoogleSheetUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url)
|
||||
&& url.StartsWith("https://script.google.com/macros/")
|
||||
&& url.EndsWith("/exec");
|
||||
}
|
||||
|
||||
private void GenerateClassFilesPerSheet(string jsonInput)
|
||||
{
|
||||
JObject jsonObject = JObject.Parse(jsonInput);
|
||||
string basePath = $"Assets{_generateFolderPath}";
|
||||
|
||||
// enum 후보 수집
|
||||
Dictionary<string, HashSet<string>> enumCandidates = new();
|
||||
foreach (var jObject in jsonObject)
|
||||
{
|
||||
string className = jObject.Key;
|
||||
if (!IsExistAvailSheets(className)) continue;
|
||||
var items = (JArray)jObject.Value;
|
||||
if (items.Count < 2) continue;
|
||||
|
||||
for (int i = 1; i < items.Count; i++)
|
||||
{
|
||||
foreach (var property in ((JObject)items[i]).Properties())
|
||||
{
|
||||
string rawName = property.Name;
|
||||
if (!rawName.Contains("_Enum")) continue;
|
||||
|
||||
string[] parts = rawName.Split(':');
|
||||
string enumTypeName =
|
||||
parts.Length > 1 ? parts[1].Replace("_Enum", "") : rawName.Replace("_Enum", "");
|
||||
string enumValue = NormalizeEnumKey(property.Value.ToString());
|
||||
|
||||
if (!enumCandidates.ContainsKey(enumTypeName))
|
||||
enumCandidates[enumTypeName] = new();
|
||||
|
||||
enumCandidates[enumTypeName].Add(enumValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EnumTypes.cs 생성
|
||||
StringBuilder enumCode = new();
|
||||
enumCode.AppendLine("// <auto-generated>");
|
||||
enumCode.AppendLine("using System;\n");
|
||||
|
||||
foreach (var kvp in enumCandidates)
|
||||
{
|
||||
enumCode.AppendLine($"public enum {kvp.Key} \n{{");
|
||||
enumCode.AppendLine(" None = 0,");
|
||||
int index = 1;
|
||||
foreach (string value in kvp.Value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value) && value != "None")
|
||||
enumCode.AppendLine($" {value} = {index++},");
|
||||
}
|
||||
|
||||
enumCode.AppendLine("}\n");
|
||||
}
|
||||
|
||||
File.WriteAllText($"{basePath}/EnumTypes.cs", enumCode.ToString());
|
||||
AssetDatabase.ImportAsset($"{basePath}/EnumTypes.cs");
|
||||
|
||||
// 시트별 클래스 파일 생성
|
||||
foreach (var jObject in jsonObject)
|
||||
{
|
||||
string className = jObject.Key;
|
||||
if (!IsExistAvailSheets(className)) continue;
|
||||
var items = (JArray)jObject.Value;
|
||||
if (items.Count < 2) continue;
|
||||
|
||||
string dataCode = GenerateDataClassCode(className, items);
|
||||
string soCode = GenerateSoClassCode(className);
|
||||
|
||||
string dataPath = $"{basePath}/{className}.cs";
|
||||
string soPath = $"{basePath}/{className}So.cs";
|
||||
|
||||
File.WriteAllText(dataPath, dataCode);
|
||||
File.WriteAllText(soPath, soCode);
|
||||
|
||||
AssetDatabase.ImportAsset(dataPath);
|
||||
AssetDatabase.ImportAsset(soPath);
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateSoClassCode(string className)
|
||||
{
|
||||
return
|
||||
$"// <auto-generated> File: {className}So.cs\n" +
|
||||
"using System.Collections.Generic;\n" +
|
||||
"using UnityEngine;\n\n" +
|
||||
$"[CreateAssetMenu(fileName = \"{className}So\", menuName = \"GoogleSheet/{className}So\")]\n" +
|
||||
$"public class {className}So : ScriptableObject \n" +
|
||||
$"{{\n public List<{className}> {className}List;\n}}\n";
|
||||
}
|
||||
|
||||
private string GenerateDataClassCode(string className, JArray items)
|
||||
{
|
||||
var commentRow = (JObject)items[0];
|
||||
var sampleRow = (JObject)items[1];
|
||||
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("// <auto-generated>");
|
||||
sb.AppendLine("using System;");
|
||||
sb.AppendLine("using UnityEngine;");
|
||||
|
||||
sb.AppendLine("[Serializable]");
|
||||
sb.AppendLine($"public class {className} \n{{");
|
||||
|
||||
int count = sampleRow.Properties().Count();
|
||||
string[] types = new string[count];
|
||||
string[] names = new string[count];
|
||||
string[] tooltips = new string[count];
|
||||
|
||||
foreach (JToken item in items.Skip(1))
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var prop in ((JObject)item).Properties())
|
||||
{
|
||||
string rawName = prop.Name;
|
||||
string propType = GetCSharpType(prop.Value.Type);
|
||||
string fieldName, enumName;
|
||||
|
||||
if (rawName.Contains(":") && rawName.EndsWith("_Enum"))
|
||||
{
|
||||
string[] parts = rawName.Split(':');
|
||||
fieldName = parts[0];
|
||||
enumName = parts[1].Replace("_Enum", "");
|
||||
types[i] = enumName;
|
||||
names[i] = fieldName;
|
||||
}
|
||||
else if (rawName.EndsWith("_Enum"))
|
||||
{
|
||||
fieldName = rawName.Replace("_Enum", "");
|
||||
enumName = fieldName;
|
||||
types[i] = enumName;
|
||||
names[i] = fieldName;
|
||||
}
|
||||
else
|
||||
{
|
||||
types[i] ??= propType;
|
||||
names[i] ??= rawName;
|
||||
}
|
||||
|
||||
tooltips[i] ??= commentRow.TryGetValue(rawName, out var tip) ? tip.ToString() : "";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tooltips[i]))
|
||||
{
|
||||
sb.AppendLine($" /// <summary>{tooltips[i]}</summary>");
|
||||
sb.AppendLine($" [Tooltip(\"{tooltips[i]}\")]");
|
||||
}
|
||||
|
||||
sb.AppendLine($" public {types[i]} {names[i]};\n");
|
||||
}
|
||||
|
||||
sb.AppendLine("}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GetCSharpType(JTokenType jsonType)
|
||||
{
|
||||
switch (jsonType)
|
||||
{
|
||||
case JTokenType.Integer:
|
||||
return "int";
|
||||
case JTokenType.Float:
|
||||
return "float";
|
||||
case JTokenType.Boolean:
|
||||
return "bool";
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateGoogleSheetSo()
|
||||
{
|
||||
JObject jsonObject = JObject.Parse(_json);
|
||||
bool allSuccess = true;
|
||||
|
||||
foreach (var sheetPair in jsonObject)
|
||||
{
|
||||
string sheetName = sheetPair.Key;
|
||||
if (!IsExistAvailSheets(sheetName))
|
||||
continue;
|
||||
|
||||
// 1. 데이터 클래스 및 SO 클래스 타입 찾기
|
||||
Type dataType = FindTypeByName(sheetName);
|
||||
Type soType = FindTypeByName($"{sheetName}So");
|
||||
|
||||
if (dataType == null || soType == null)
|
||||
{
|
||||
Debug.LogError($"[GoogleSheetManager] 타입을 찾을 수 없습니다: {sheetName} 또는 {sheetName}So");
|
||||
allSuccess = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. SO 경로 설정 및 불러오기 / 생성
|
||||
string soDirectory = $"Assets{_generateFolderPath}/So";
|
||||
if (!Directory.Exists(soDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(soDirectory);
|
||||
AssetDatabase.ImportAsset(soDirectory);
|
||||
}
|
||||
|
||||
string soPath = $"{soDirectory}/{sheetName}So.asset";
|
||||
|
||||
ScriptableObject soInstance = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath);
|
||||
if (soInstance == null)
|
||||
{
|
||||
soInstance = ScriptableObject.CreateInstance(soType);
|
||||
AssetDatabase.CreateAsset(soInstance, soPath);
|
||||
}
|
||||
|
||||
// 3. 데이터 파싱
|
||||
IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(dataType));
|
||||
var dataArray = (JArray)sheetPair.Value;
|
||||
|
||||
for (int i = 1; i < dataArray.Count; i++) // 0번은 주석이므로 제외
|
||||
{
|
||||
JObject item = (JObject)dataArray[i];
|
||||
object dataInstance = Activator.CreateInstance(dataType);
|
||||
|
||||
foreach (var prop in item.Properties())
|
||||
{
|
||||
string rawName = prop.Name;
|
||||
string fieldName = rawName.Contains(":") ? rawName.Split(':')[0] : rawName;
|
||||
|
||||
FieldInfo field = dataType.GetField(fieldName,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (field == null)
|
||||
{
|
||||
Debug.LogWarning($"[GoogleSheetManager] 필드 누락: {dataType.Name}.{fieldName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object value;
|
||||
if (field.FieldType.IsEnum)
|
||||
{
|
||||
string enumRaw = prop.Value.ToString();
|
||||
string formatted = NormalizeEnumKey(enumRaw);
|
||||
value = Enum.TryParse(field.FieldType, formatted, out var parsed)
|
||||
? parsed
|
||||
: Activator.CreateInstance(field.FieldType);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = Convert.ChangeType(prop.Value.ToString(), field.FieldType);
|
||||
}
|
||||
|
||||
field.SetValue(dataInstance, value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[GoogleSheetManager] 값 할당 실패: {fieldName} = {prop.Value} ({field.FieldType}) → {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(dataInstance);
|
||||
}
|
||||
|
||||
// 4. SO의 필드에 리스트 할당
|
||||
FieldInfo listField = soType.GetField($"{sheetName}List",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (listField != null)
|
||||
{
|
||||
listField.SetValue(soInstance, list);
|
||||
EditorUtility.SetDirty(soInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[GoogleSheetManager] {soType.Name}에 {sheetName}List 필드가 없습니다.");
|
||||
allSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log("✅ 시트별 ScriptableObject 생성 및 데이터 반영 완료");
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
private Type FindTypeByName(string name)
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.FirstOrDefault(t => t.Name == name);
|
||||
}
|
||||
|
||||
private string NormalizeEnumKey(string input)
|
||||
{
|
||||
string validName = System.Text.RegularExpressions.Regex.Replace(input, @"[^a-zA-Z0-9_]", "_");
|
||||
if (char.IsDigit(validName[0]))
|
||||
validName = "_" + validName;
|
||||
return char.ToUpper(validName[0]) + validName.Substring(1);
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_refreshTrigger && !_alreadyCreatedSo)
|
||||
{
|
||||
_refreshTrigger = false;
|
||||
_alreadyCreatedSo = true;
|
||||
DelayedSoCreation();
|
||||
}
|
||||
}
|
||||
|
||||
private void DelayedSoCreation()
|
||||
{
|
||||
if (CreateGoogleSheetSo())
|
||||
{
|
||||
Debug.Log("Fetch done. SO 업데이트 완료");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[GoogleSheetManager] SO 생성 실패. 수동으로 Fetch를 다시 시도하세요.");
|
||||
}
|
||||
}
|
||||
|
||||
[BoxGroup("데이터 변경")]
|
||||
[Button("런타임 중 데이터 재적용")]
|
||||
public void ReloadRuntimeData()
|
||||
{
|
||||
StartCoroutine(ReloadRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator ReloadRoutine()
|
||||
{
|
||||
_availSheetArray = _availSheets.Split('/');
|
||||
|
||||
if (_isAccessGoogleSheet)
|
||||
{
|
||||
var task = LoadDataGoogleSheet(_googleSheetUrl);
|
||||
yield return new WaitUntil(() => task.IsCompleted);
|
||||
_json = task.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
_json = LoadDataLocalJson();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_json))
|
||||
{
|
||||
CreateGoogleSheetSo();
|
||||
Debug.Log("런타임 데이터 재적용 완료");
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateSoAfterScriptReload()
|
||||
{
|
||||
if (_json != null)
|
||||
{
|
||||
Debug.Log("[GoogleSheetManager] Script Reload 이후 SO 생성 실행");
|
||||
CreateGoogleSheetSo();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
// GoogleSheetChangeLog.cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "GoogleSheetChangeLog", menuName = "GoogleSheet/ChangeLog", order = 0)]
|
||||
public class GoogleSheetChangeLog : ScriptableObject
|
||||
{
|
||||
[Serializable]
|
||||
public class LogEntry
|
||||
{
|
||||
public string Editor;
|
||||
public string Timestamp;
|
||||
[TextArea(5, 20)] public string JsonSnapshot;
|
||||
}
|
||||
|
||||
[SerializeField] private List<LogEntry> _logs = new();
|
||||
public List<LogEntry> Logs => _logs;
|
||||
|
||||
public int MaxLogs = 100;
|
||||
|
||||
public void AddEntry(LogEntry entry)
|
||||
{
|
||||
if (_logs.Count >= MaxLogs)
|
||||
{
|
||||
_logs.RemoveAt(0);
|
||||
}
|
||||
_logs.Add(entry);
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47d33f999b3dd2f44a64b3c7b6262376
|
@ -0,0 +1,67 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static class GoogleSheetDiffHelper
|
||||
{
|
||||
public static List<(string Sheet, string Field, int RowIndex, string OldValue, string NewValue)> CompareJsonDiff(string oldJson, string newJson)
|
||||
{
|
||||
var diffs = new List<(string, string, int, string, string)>();
|
||||
|
||||
if (string.IsNullOrEmpty(oldJson) || string.IsNullOrEmpty(newJson))
|
||||
return diffs;
|
||||
|
||||
var oldObj = JObject.Parse(oldJson);
|
||||
var newObj = JObject.Parse(newJson);
|
||||
|
||||
foreach (var sheet in newObj)
|
||||
{
|
||||
var sheetName = sheet.Key;
|
||||
if (!oldObj.TryGetValue(sheetName, out var oldSheetToken))
|
||||
continue;
|
||||
|
||||
var oldArray = oldSheetToken as JArray;
|
||||
var newArray = sheet.Value as JArray;
|
||||
|
||||
// Row-by-row 비교 (1번 줄부터 데이터 시작)
|
||||
for (int i = 1; i < newArray.Count; i++)
|
||||
{
|
||||
if (i >= oldArray.Count)
|
||||
break;
|
||||
|
||||
var newRow = (JObject)newArray[i];
|
||||
var oldRow = (JObject)oldArray[i];
|
||||
|
||||
foreach (var prop in newRow.Properties())
|
||||
{
|
||||
var field = prop.Name;
|
||||
string newValue = prop.Value.ToString();
|
||||
string oldValue = oldRow.TryGetValue(field, out var oldVal) ? oldVal.ToString() : "";
|
||||
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
diffs.Add((sheetName, field, i, oldValue, newValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diffs;
|
||||
}
|
||||
|
||||
public static string GenerateDiff(string oldJson, string newJson)
|
||||
{
|
||||
var diffs = CompareJsonDiff(oldJson, newJson);
|
||||
if (diffs.Count == 0)
|
||||
return "No differences found.";
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("[GoogleSheetManager] 변경된 필드들:");
|
||||
|
||||
foreach (var (sheet, field, rowIndex, oldVal, newVal) in diffs)
|
||||
{
|
||||
sb.AppendLine($"{sheet} / Row {rowIndex} / {field} : '{oldVal}' → '{newVal}'");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1cb07de998538442b14e113ff8da86e
|
@ -0,0 +1,46 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class GoogleSheetDiffViewer : EditorWindow
|
||||
{
|
||||
private List<(string Sheet, string Field, int RowIndex, string OldValue, string NewValue) > _diffs;
|
||||
|
||||
public static void ShowWindow(List<(string Sheet, string Field, int RowIndex, string OldValue, string NewValue) > diffs)
|
||||
{
|
||||
var window = GetWindow<GoogleSheetDiffViewer>("Google Sheet 변경점");
|
||||
window._diffs = diffs;
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private Vector2 _scroll;
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (_diffs == null || _diffs.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("변경 사항이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("변경된 항목", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
_scroll = EditorGUILayout.BeginScrollView(_scroll);
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
|
||||
foreach (var diff in _diffs)
|
||||
{
|
||||
EditorGUILayout.LabelField($"시트: {diff.Sheet}");
|
||||
EditorGUILayout.LabelField($"행: {diff.RowIndex + 1}");
|
||||
EditorGUILayout.LabelField($"필드: {diff.Field}");
|
||||
EditorGUILayout.LabelField($"기존 값: {diff.OldValue} → 변경 값: {diff.NewValue}", EditorStyles.helpBox);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: feabf00e706fddc49af4692fbfc85632
|
@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public static class GoogleSheetFetchHelper
|
||||
{
|
||||
public static List<(string Sheet, string Field, int RowIndex, string OldValue, string NewValue)> CompareJsonDiff(string oldJson, string newJson)
|
||||
{
|
||||
var result = new List<(string, string, int, string, string)>();
|
||||
|
||||
if (string.IsNullOrEmpty(oldJson) || string.IsNullOrEmpty(newJson))
|
||||
return result;
|
||||
|
||||
var oldObj = JObject.Parse(oldJson);
|
||||
var newObj = JObject.Parse(newJson);
|
||||
|
||||
foreach (var sheet in newObj)
|
||||
{
|
||||
if (!oldObj.TryGetValue(sheet.Key, out var oldSheetData))
|
||||
continue;
|
||||
|
||||
var newSheetData = (JArray)sheet.Value;
|
||||
var oldSheetArray = (JArray)oldSheetData;
|
||||
|
||||
int minCount = Mathf.Min(oldSheetArray.Count, newSheetData.Count);
|
||||
|
||||
for (int i = 1; i < minCount; i++) // i = 1부터: 첫 줄은 주석
|
||||
{
|
||||
var oldRow = (JObject)oldSheetArray[i];
|
||||
var newRow = (JObject)newSheetData[i];
|
||||
|
||||
foreach (var prop in newRow.Properties())
|
||||
{
|
||||
string oldVal = oldRow.TryGetValue(prop.Name, out var val) ? val.ToString() : "";
|
||||
string newVal = prop.Value.ToString();
|
||||
|
||||
if (oldVal != newVal)
|
||||
result.Add((sheet.Key, prop.Name, i + 1, oldVal, newVal)); // i+1: 실제 시트에서의 행 번호 (1-based)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3062282d3af306458153938166b72ce
|
@ -0,0 +1,18 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
|
||||
public static class GoogleSheetPostProcessor
|
||||
{
|
||||
[DidReloadScripts]
|
||||
private static void OnScriptsReloaded()
|
||||
{
|
||||
if (EditorPrefs.GetBool("GoogleSheetManager_ShouldCreateSO", false))
|
||||
{
|
||||
EditorPrefs.DeleteKey("GoogleSheetManager_ShouldCreateSO");
|
||||
GoogleSheetManager.Instance.CreateSoAfterScriptReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acef684d9f9959e4dbbf34cd1c9dfb37
|
@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>You must approach through `GoogleSheetManager.SO<GoogleSheetSO>()`</summary>
|
||||
public class GoogleSheetSO : ScriptableObject
|
||||
{
|
||||
public List<Test> TestList;
|
||||
public List<Monster> MonsterList;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Test
|
||||
{
|
||||
public string Id;
|
||||
public string Name;
|
||||
public int T1;
|
||||
public int T2;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Monster
|
||||
{
|
||||
public string Id;
|
||||
public string Name;
|
||||
public int T1;
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52c5476425215a341bc2722f655d24fe
|
@ -1,50 +0,0 @@
|
||||
{
|
||||
"Test": [
|
||||
{
|
||||
"Id": "Test001",
|
||||
"Name": "A",
|
||||
"T1": 1,
|
||||
"T2": 234
|
||||
},
|
||||
{
|
||||
"Id": "Test002",
|
||||
"Name": "B",
|
||||
"T1": 2,
|
||||
"T2": 2
|
||||
},
|
||||
{
|
||||
"Id": "Test003",
|
||||
"Name": "C",
|
||||
"T1": 3,
|
||||
"T2": 24142
|
||||
},
|
||||
{
|
||||
"Id": "Test004",
|
||||
"Name": "D",
|
||||
"T1": 4,
|
||||
"T2": 2
|
||||
}
|
||||
],
|
||||
"Monster": [
|
||||
{
|
||||
"Id": "Test001",
|
||||
"Name": "A",
|
||||
"T1": 1
|
||||
},
|
||||
{
|
||||
"Id": "Test002",
|
||||
"Name": "B",
|
||||
"T1": 5345
|
||||
},
|
||||
{
|
||||
"Id": "Test003",
|
||||
"Name": "C",
|
||||
"T1": 3
|
||||
},
|
||||
{
|
||||
"Id": "Test004",
|
||||
"Name": "D",
|
||||
"T1": 4
|
||||
}
|
||||
]
|
||||
}
|
8
Assets/0.Datas/02.Scripts/GenerateGoogleSheet/Logs.meta
Normal file
8
Assets/0.Datas/02.Scripts/GenerateGoogleSheet/Logs.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f5408d8dd9191c469b5e4717d4da831
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,62 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 47d33f999b3dd2f44a64b3c7b6262376, type: 3}
|
||||
m_Name: GoogleSheetChangeLog
|
||||
m_EditorClassIdentifier:
|
||||
_logs:
|
||||
- Editor: NTG
|
||||
Timestamp: 2025-05-12 03:31:27
|
||||
JsonSnapshot: "{\n \"$\uAC1C\uC694\": [\n {\n \"\": \"\uC2DC\uD2B8 \uC0DD\uC131\"\n
|
||||
}\n ],\n \"Food\": [\n {\n \"Id\": \"\uC2DD\uBCC4ID\",\n \"Name\":
|
||||
\"\uC774\uB984\",\n \"Ingredient1\": \"\uC7AC\uB8CC1\",\n \"Ingredient2\":
|
||||
\"\uC7AC\uB8CC2\",\n \"Taste1:Taste_Enum\": \"\uB9DB1\",\n \"Taste2:Taste_Enum\":
|
||||
\"\uB9DB2\"\n },\n {\n \"Id\": \"Food001\",\n \"Name\": \"\uD587\uBE5B\uC218\uD504\",\n
|
||||
\"Ingredient1\": \"\uADF9\uB77D\uC300\",\n \"Ingredient2\": \"\uD587\uBE5B\uB2F9\uADFC\",\n
|
||||
\"Taste1:Taste_Enum\": \"Bitter\",\n \"Taste2:Taste_Enum\": \"Sweet\"\n
|
||||
},\n {\n \"Id\": \"Food002\",\n \"Name\": \"B\",\n \"Ingredient1\":
|
||||
1,\n \"Ingredient2\": 11,\n \"Taste1:Taste_Enum\": \"Spicy\",\n
|
||||
\"Taste2:Taste_Enum\": \"Bitter\"\n },\n {\n \"Id\": \"Food003\",\n
|
||||
\"Name\": \"C\",\n \"Ingredient1\": 2,\n \"Ingredient2\": 22,\n
|
||||
\"Taste1:Taste_Enum\": \"Fresh\",\n \"Taste2:Taste_Enum\": \"None\"\n
|
||||
},\n {\n \"Id\": \"Food004\",\n \"Name\": \"D\",\n \"Ingredient1\":
|
||||
3,\n \"Ingredient2\": 33,\n \"Taste1:Taste_Enum\": \"Sour\",\n
|
||||
\"Taste2:Taste_Enum\": \"Salty\"\n }\n ],\n \"Monster\": [\n {\n
|
||||
\"Id\": \"\uC2DD\uBCC4\uBC88\uD638\",\n \"Name\": \"\uC774\uB984\",\n
|
||||
\"T1\": \"\uD14C\uC2A4\uD2B81\"\n },\n {\n \"Id\": \"Test001\",\n
|
||||
\"Name\": \"A\",\n \"T1\": 1\n },\n {\n \"Id\": \"Test002\",\n
|
||||
\"Name\": \"B\",\n \"T1\": 2\n },\n {\n \"Id\": \"Test003\",\n
|
||||
\"Name\": \"C\",\n \"T1\": 3\n },\n {\n \"Id\": \"Test004\",\n
|
||||
\"Name\": \"D\",\n \"T1\": 4\n }\n ]\n}"
|
||||
- Editor: NTG
|
||||
Timestamp: 2025-05-12 03:32:27
|
||||
JsonSnapshot: "{\n \"$\uAC1C\uC694\": [\n {\n \"\": \"\uC2DC\uD2B8 \uC0DD\uC131\"\n
|
||||
}\n ],\n \"Food\": [\n {\n \"Id\": \"\uC2DD\uBCC4ID\",\n \"Name\":
|
||||
\"\uC774\uB984\",\n \"Ingredient1\": \"\uC7AC\uB8CC1\",\n \"Ingredient2\":
|
||||
\"\uC7AC\uB8CC2\",\n \"Taste1:Taste_Enum\": \"\uB9DB1\",\n \"Taste2:Taste_Enum\":
|
||||
\"\uB9DB2\"\n },\n {\n \"Id\": \"Food001\",\n \"Name\": \"\uD587\uBE5B\uC218\uD504\",\n
|
||||
\"Ingredient1\": \"\uADF9\uB77D\uC300\",\n \"Ingredient2\": \"\uD587\uBE5B\uB2F9\uADFC\",\n
|
||||
\"Taste1:Taste_Enum\": \"Bitter\",\n \"Taste2:Taste_Enum\": \"Sweet\"\n
|
||||
},\n {\n \"Id\": \"Food002\",\n \"Name\": \"B\",\n \"Ingredient1\":
|
||||
1,\n \"Ingredient2\": 4,\n \"Taste1:Taste_Enum\": \"Spicy\",\n
|
||||
\"Taste2:Taste_Enum\": \"Bitter\"\n },\n {\n \"Id\": \"Food003\",\n
|
||||
\"Name\": \"C\",\n \"Ingredient1\": 2,\n \"Ingredient2\": 5,\n
|
||||
\"Taste1:Taste_Enum\": \"Fresh\",\n \"Taste2:Taste_Enum\": \"None\"\n
|
||||
},\n {\n \"Id\": \"Food004\",\n \"Name\": \"D\",\n \"Ingredient1\":
|
||||
3,\n \"Ingredient2\": 6,\n \"Taste1:Taste_Enum\": \"Sour\",\n
|
||||
\"Taste2:Taste_Enum\": \"Salty\"\n }\n ],\n \"Monster\": [\n {\n
|
||||
\"Id\": \"\uC2DD\uBCC4\uBC88\uD638\",\n \"Name\": \"\uC774\uB984\",\n
|
||||
\"T1\": \"\uD14C\uC2A4\uD2B81\"\n },\n {\n \"Id\": \"Test001\",\n
|
||||
\"Name\": \"A\",\n \"T1\": 1\n },\n {\n \"Id\": \"Test002\",\n
|
||||
\"Name\": \"B\",\n \"T1\": 2\n },\n {\n \"Id\": \"Test003\",\n
|
||||
\"Name\": \"C\",\n \"T1\": 3\n },\n {\n \"Id\": \"Test004\",\n
|
||||
\"Name\": \"D\",\n \"T1\": 4\n }\n ]\n}"
|
||||
MaxLogs: 100
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70eb4ff0fb5057744a569e016975ded0
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,323 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
public class GoogleSheetManager : MonoBehaviour
|
||||
{
|
||||
[FormerlySerializedAs("isAccessGoogleSheet")]
|
||||
[Tooltip("true: google sheet, false: local json")]
|
||||
[SerializeField] private bool _isAccessGoogleSheet = true;
|
||||
|
||||
[FormerlySerializedAs("googleSheetUrl")]
|
||||
[Tooltip("Google sheet appsscript webapp url")]
|
||||
[SerializeField] private string _googleSheetUrl;
|
||||
|
||||
[FormerlySerializedAs("availSheets")]
|
||||
[Tooltip("Google sheet avail sheet tabs. seperate `/`. For example `Sheet1/Sheet2`")]
|
||||
[SerializeField] private string _availSheets = "Sheet1/Sheet2";
|
||||
|
||||
[FormerlySerializedAs("generateFolderPath")]
|
||||
[Tooltip("For example `/GenerateGoogleSheet`")]
|
||||
[SerializeField] private string _generateFolderPath = "/GenerateGoogleSheet";
|
||||
|
||||
[FormerlySerializedAs("googleSheetSO")] [Tooltip("You must approach through `GoogleSheetManager.SO<GoogleSheetSO>()`"), ReadOnly]
|
||||
public ScriptableObject GoogleSheetSo;
|
||||
|
||||
[SerializeField, ReadOnly]
|
||||
private string _lastUpdated;
|
||||
|
||||
private string JsonPath => $"{Application.dataPath}{_generateFolderPath}/GoogleSheetJson.json";
|
||||
private string ClassPath => $"{Application.dataPath}{_generateFolderPath}/GoogleSheetClass.cs";
|
||||
private string SoPath => $"Assets{_generateFolderPath}/GoogleSheetSO.asset";
|
||||
|
||||
private string[] _availSheetArray;
|
||||
private string _json;
|
||||
private bool _refeshTrigger;
|
||||
private static GoogleSheetManager _instance;
|
||||
|
||||
public static T So<T>() where T : ScriptableObject
|
||||
{
|
||||
if (GetInstance().GoogleSheetSo == null)
|
||||
{
|
||||
Debug.Log($"googleSheetSO is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetInstance().GoogleSheetSo as T;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[ContextMenu("FetchGoogleSheet")]
|
||||
private async void FetchGoogleSheet()
|
||||
{
|
||||
//Init
|
||||
_availSheetArray = _availSheets.Split('/');
|
||||
|
||||
if (_isAccessGoogleSheet)
|
||||
{
|
||||
Debug.Log($"Loading from google sheet..");
|
||||
_json = await LoadDataGoogleSheet(_googleSheetUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Loading from local json..");
|
||||
_json = LoadDataLocalJson();
|
||||
}
|
||||
|
||||
if (_json == null)
|
||||
{
|
||||
Debug.Log("Json is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isJsonSaved = SaveFileOrSkip(JsonPath, _json);
|
||||
string allClassCode = GenerateCSharpClass(_json);
|
||||
bool isClassSaved = SaveFileOrSkip(ClassPath, allClassCode);
|
||||
|
||||
if (isJsonSaved || isClassSaved)
|
||||
{
|
||||
_refeshTrigger = true;
|
||||
UnityEditor.AssetDatabase.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateGoogleSheetSo();
|
||||
Debug.Log($"Fetch done.");
|
||||
}
|
||||
|
||||
_lastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
private async Task<string> LoadDataGoogleSheet(string url)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] dataBytes = await client.GetByteArrayAsync(url);
|
||||
return Encoding.UTF8.GetString(dataBytes);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Debug.LogError($"Request error: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string LoadDataLocalJson()
|
||||
{
|
||||
if (File.Exists(JsonPath))
|
||||
{
|
||||
return File.ReadAllText(JsonPath);
|
||||
}
|
||||
|
||||
Debug.Log($"File not exist.\n{JsonPath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool SaveFileOrSkip(string path, string contents)
|
||||
{
|
||||
string directoryPath = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
if (File.Exists(path) && File.ReadAllText(path).Equals(contents))
|
||||
return false;
|
||||
|
||||
File.WriteAllText(path, contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsExistAvailSheets(string sheetName)
|
||||
{
|
||||
return Array.Exists(_availSheetArray, x => x == sheetName);
|
||||
}
|
||||
|
||||
private string GenerateCSharpClass(string jsonInput)
|
||||
{
|
||||
JObject jsonObject = JObject.Parse(jsonInput);
|
||||
StringBuilder classCode = new();
|
||||
|
||||
// Scriptable Object
|
||||
classCode.AppendLine("using System;\nusing System.Collections.Generic;\nusing UnityEngine;\n");
|
||||
classCode.AppendLine("/// <summary>You must approach through `GoogleSheetManager.SO<GoogleSheetSO>()`</summary>");
|
||||
classCode.AppendLine("public class GoogleSheetSO : ScriptableObject\n{");
|
||||
|
||||
foreach (var sheet in jsonObject)
|
||||
{
|
||||
string className = sheet.Key;
|
||||
if (!IsExistAvailSheets(className))
|
||||
continue;
|
||||
|
||||
classCode.AppendLine($"\tpublic List<{className}> {className}List;");
|
||||
}
|
||||
classCode.AppendLine("}\n");
|
||||
|
||||
// Class
|
||||
foreach (var jObject in jsonObject)
|
||||
{
|
||||
string className = jObject.Key;
|
||||
|
||||
if (!IsExistAvailSheets(className))
|
||||
continue;
|
||||
|
||||
var items = (JArray)jObject.Value;
|
||||
var firstItem = (JObject)items[0];
|
||||
classCode.AppendLine($"[Serializable]\npublic class {className}\n{{");
|
||||
|
||||
// int > float > bool > string
|
||||
int itemIndex = 0;
|
||||
int propertyCount = ((JObject)items[0]).Properties().Count();
|
||||
string[] propertyTypes = new string[propertyCount];
|
||||
|
||||
foreach (JToken item in items)
|
||||
{
|
||||
itemIndex = 0;
|
||||
foreach (var property in ((JObject)item).Properties())
|
||||
{
|
||||
string propertyType = GetCSharpType(property.Value.Type);
|
||||
string oldPropertyType = propertyTypes[itemIndex];
|
||||
|
||||
if (oldPropertyType == null)
|
||||
{
|
||||
propertyTypes[itemIndex] = propertyType;
|
||||
}
|
||||
else if (oldPropertyType == "int")
|
||||
{
|
||||
if (propertyType == "int") propertyTypes[itemIndex] = "int";
|
||||
else if (propertyType == "float") propertyTypes[itemIndex] = "float";
|
||||
else if (propertyType == "bool") propertyTypes[itemIndex] = "string";
|
||||
else if (propertyType == "string") propertyTypes[itemIndex] = "string";
|
||||
}
|
||||
else if (oldPropertyType == "float")
|
||||
{
|
||||
if (propertyType == "int") propertyTypes[itemIndex] = "float";
|
||||
else if (propertyType == "float") propertyTypes[itemIndex] = "float";
|
||||
else if (propertyType == "bool") propertyTypes[itemIndex] = "string";
|
||||
else if (propertyType == "string") propertyTypes[itemIndex] = "string";
|
||||
}
|
||||
else if (oldPropertyType == "bool")
|
||||
{
|
||||
if (propertyType == "int") propertyTypes[itemIndex] = "string";
|
||||
else if (propertyType == "float") propertyTypes[itemIndex] = "string";
|
||||
else if (propertyType == "bool") propertyTypes[itemIndex] = "bool";
|
||||
else if (propertyType == "string") propertyTypes[itemIndex] = "string";
|
||||
}
|
||||
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
itemIndex = 0;
|
||||
foreach (var property in firstItem.Properties())
|
||||
{
|
||||
string propertyName = property.Name;
|
||||
string propertyType = propertyTypes[itemIndex];
|
||||
classCode.AppendLine($"\tpublic {propertyType} {propertyName};");
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
classCode.AppendLine("}\n");
|
||||
}
|
||||
|
||||
return classCode.ToString();
|
||||
}
|
||||
|
||||
private string GetCSharpType(JTokenType jsonType)
|
||||
{
|
||||
switch (jsonType)
|
||||
{
|
||||
case JTokenType.Integer:
|
||||
return "int";
|
||||
case JTokenType.Float:
|
||||
return "float";
|
||||
case JTokenType.Boolean:
|
||||
return "bool";
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateGoogleSheetSo()
|
||||
{
|
||||
if (Type.GetType("GoogleSheetSO") == null)
|
||||
return false;
|
||||
|
||||
GoogleSheetSo = ScriptableObject.CreateInstance("GoogleSheetSO");
|
||||
JObject jsonObject = JObject.Parse(_json);
|
||||
try
|
||||
{
|
||||
foreach (var jObject in jsonObject)
|
||||
{
|
||||
string className = jObject.Key;
|
||||
if (!IsExistAvailSheets(className))
|
||||
continue;
|
||||
|
||||
Type classType = Type.GetType(className);
|
||||
Type listType = typeof(List<>).MakeGenericType(classType);
|
||||
IList listInst = (IList)Activator.CreateInstance(listType);
|
||||
var items = (JArray)jObject.Value;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
object classInst = Activator.CreateInstance(classType);
|
||||
|
||||
foreach (var property in ((JObject)item).Properties())
|
||||
{
|
||||
FieldInfo fieldInfo = classType.GetField(property.Name);
|
||||
object value = Convert.ChangeType(property.Value.ToString(), fieldInfo.FieldType);
|
||||
fieldInfo.SetValue(classInst, value);
|
||||
}
|
||||
|
||||
listInst.Add(classInst);
|
||||
}
|
||||
|
||||
GoogleSheetSo.GetType().GetField($"{className}List").SetValue(GoogleSheetSo, listInst);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"CreateGoogleSheetSO error: {e.Message}");
|
||||
}
|
||||
print("CreateGoogleSheetSO");
|
||||
UnityEditor.AssetDatabase.CreateAsset(GoogleSheetSo, SoPath);
|
||||
UnityEditor.AssetDatabase.SaveAssets();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_refeshTrigger)
|
||||
{
|
||||
bool isCompleted = CreateGoogleSheetSo();
|
||||
if (isCompleted)
|
||||
{
|
||||
_refeshTrigger = false;
|
||||
Debug.Log($"Fetch done.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static GoogleSheetManager GetInstance()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = FindFirstObjectByType<GoogleSheetManager>();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
public static class GoogleSheetComparer
|
||||
{
|
||||
public static GoogleSheetDiffResult Compare<T>(List<T> oldList, List<T> newList) where T : class
|
||||
{
|
||||
var result = new GoogleSheetDiffResult();
|
||||
|
||||
var oldDict = oldList.ToDictionary(x => GetId(x));
|
||||
var newDict = newList.ToDictionary(x => GetId(x));
|
||||
|
||||
foreach (var newId in newDict.Keys)
|
||||
{
|
||||
if (!oldDict.ContainsKey(newId)) result.Added.Add(newId);
|
||||
else if (!IsSame(oldDict[newId], newDict[newId])) result.Modified.Add(newId);
|
||||
}
|
||||
|
||||
foreach (var oldId in oldDict.Keys)
|
||||
{
|
||||
if (!newDict.ContainsKey(oldId)) result.Removed.Add(oldId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetId(object obj)
|
||||
{
|
||||
var field = obj.GetType().GetField("Id");
|
||||
return field?.GetValue(obj)?.ToString() ?? "";
|
||||
}
|
||||
|
||||
private static bool IsSame(object a, object b)
|
||||
{
|
||||
foreach (var field in a.GetType().GetFields())
|
||||
{
|
||||
var valA = field.GetValue(a);
|
||||
var valB = field.GetValue(b);
|
||||
if (!Equals(valA, valB)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78a69b75420adc941ada254e71932c01
|
@ -1,8 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class GoogleSheetDiffResult
|
||||
{
|
||||
public List<string> Added = new();
|
||||
public List<string> Removed = new();
|
||||
public List<string> Modified = new();
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c8081f426b1b4944985305c853b3062
|
101
Assets/0.Datas/02.Scripts/Singletone.cs
Normal file
101
Assets/0.Datas/02.Scripts/Singletone.cs
Normal file
@ -0,0 +1,101 @@
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
[DefaultExecutionOrder(-2)]
|
||||
public abstract class Singleton<T> : Singleton where T : MonoBehaviour
|
||||
{
|
||||
#region Fields
|
||||
[CanBeNull]
|
||||
private static T _instance;
|
||||
|
||||
[NotNull]
|
||||
private static readonly object _lock = new();
|
||||
|
||||
[SerializeField]
|
||||
private bool _persistent;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
[NotNull]
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Quitting)
|
||||
{
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
var instances = FindObjectsByType<T>(FindObjectsSortMode.None);
|
||||
var count = instances.Length;
|
||||
if (count > 0)
|
||||
{
|
||||
if (count == 1)
|
||||
return _instance = instances[0];
|
||||
//Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] There should never be more than one {nameof(Singleton)} of type {typeof(T)} in the scene, but {count} were found. The first instance found will be used, and all others will be destroyed.");
|
||||
for (var i = 1; i < instances.Length; i++)
|
||||
Destroy(instances[i]);
|
||||
return _instance = instances[0];
|
||||
}
|
||||
//Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] Instance will not be returned because the application is quitting.");
|
||||
return null;
|
||||
}
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
var instances = FindObjectsByType<T>(FindObjectsSortMode.None);
|
||||
var count = instances.Length;
|
||||
if (count > 0)
|
||||
{
|
||||
if (count == 1)
|
||||
return _instance = instances[0];
|
||||
Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] There should never be more than one {nameof(Singleton)} of type {typeof(T)} in the scene, but {count} were found. The first instance found will be used, and all others will be destroyed.");
|
||||
for (var i = 1; i < instances.Length; i++)
|
||||
Destroy(instances[i]);
|
||||
return _instance = instances[0];
|
||||
}
|
||||
|
||||
Debug.Log($"[{nameof(Singleton)}<{typeof(T)}>] An instance is needed in the scene and no existing instances were found, so a new instance will be created.");
|
||||
return _instance = new GameObject($"({nameof(Singleton)}){typeof(T)}")
|
||||
.AddComponent<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (_persistent)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = this as T;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else if (_instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
OnAwake();
|
||||
}
|
||||
|
||||
protected virtual void OnAwake() { }
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class Singleton : MonoBehaviour
|
||||
{
|
||||
#region Properties
|
||||
public static bool Quitting { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
protected virtual void OnApplicationQuit()
|
||||
{
|
||||
Quitting = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
2
Assets/0.Datas/02.Scripts/Singletone.cs.meta
Normal file
2
Assets/0.Datas/02.Scripts/Singletone.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4108fe16fc07d54d84c51afc87938cc
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f859a6911fd7cbf4680f4923c9e176fb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b21c32f42ddfd445ab54465dbe714e1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,22 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 1549551891, guid: 74721b9f0af448f5ae2e91102a1a5edd, type: 3}
|
||||
m_Name: GlobalSerializationConfig
|
||||
m_EditorClassIdentifier:
|
||||
HideSerializationCautionaryMessage: 0
|
||||
HidePrefabCautionaryMessage: 0
|
||||
HideOdinSerializeAttributeWarningMessages: 0
|
||||
HideNonSerializedShowInInspectorWarningMessages: 0
|
||||
buildSerializationFormat: 0
|
||||
editorSerializationFormat: 2
|
||||
loggingPolicy: 0
|
||||
errorHandlingPolicy: 0
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c476d6e989d53f243a19df879920a83d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -53,6 +53,7 @@ MonoBehaviour:
|
||||
m_AdditionalLightsShadowResolutionTierHigh: 1024
|
||||
m_ReflectionProbeBlending: 1
|
||||
m_ReflectionProbeBoxProjection: 1
|
||||
m_ReflectionProbeAtlas: 1
|
||||
m_ShadowDistance: 50
|
||||
m_ShadowCascadeCount: 4
|
||||
m_Cascade2Split: 0.25
|
||||
@ -78,11 +79,11 @@ MonoBehaviour:
|
||||
m_UseAdaptivePerformance: 1
|
||||
m_ColorGradingMode: 0
|
||||
m_ColorGradingLutSize: 32
|
||||
m_AllowPostProcessAlphaOutput: 0
|
||||
m_UseFastSRGBLinearConversion: 0
|
||||
m_SupportDataDrivenLensFlare: 1
|
||||
m_SupportScreenSpaceLensFlare: 1
|
||||
m_GPUResidentDrawerMode: 0
|
||||
m_UseLegacyLightmaps: 0
|
||||
m_SmallMeshScreenPercentage: 0
|
||||
m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0
|
||||
m_ShadowType: 1
|
||||
@ -100,15 +101,16 @@ MonoBehaviour:
|
||||
m_Keys: []
|
||||
m_Values:
|
||||
m_PrefilteringModeMainLightShadows: 3
|
||||
m_PrefilteringModeAdditionalLight: 4
|
||||
m_PrefilteringModeAdditionalLightShadows: 0
|
||||
m_PrefilteringModeAdditionalLight: 0
|
||||
m_PrefilteringModeAdditionalLightShadows: 2
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 1
|
||||
m_PrefilteringModeForwardPlus: 2
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 1
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 2
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 0
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterAlphaOutput: 1
|
||||
m_PrefilterSSAODepthNormals: 0
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
@ -120,14 +122,15 @@ MonoBehaviour:
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 0
|
||||
m_PrefilterSoftShadowsQualityLow: 0
|
||||
m_PrefilterSoftShadowsQualityMedium: 0
|
||||
m_PrefilterSoftShadowsQualityHigh: 0
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_PrefilterUseLegacyLightmaps: 0
|
||||
m_PrefilterBicubicLightmapSampling: 1
|
||||
m_ShaderVariantLogLevel: 0
|
||||
m_ShadowCascades: 0
|
||||
m_Textures:
|
||||
|
@ -62,7 +62,20 @@ MonoBehaviour:
|
||||
- rid: 4427513590311550980
|
||||
- rid: 4427513590311550981
|
||||
m_RuntimeSettings:
|
||||
m_List: []
|
||||
m_List:
|
||||
- rid: 6852985685364965378
|
||||
- rid: 6852985685364965379
|
||||
- rid: 6852985685364965380
|
||||
- rid: 6852985685364965381
|
||||
- rid: 6852985685364965384
|
||||
- rid: 6852985685364965385
|
||||
- rid: 6852985685364965392
|
||||
- rid: 6852985685364965394
|
||||
- rid: 8712630790384254976
|
||||
- rid: 4427513590171566080
|
||||
- rid: 4427513590311550977
|
||||
- rid: 4427513590311550978
|
||||
- rid: 4427513590311550980
|
||||
m_AssetVersion: 8
|
||||
m_ObsoleteDefaultVolumeProfile: {fileID: 0}
|
||||
m_RenderingLayerNames:
|
||||
|
@ -36,10 +36,8 @@ GraphicsSettings:
|
||||
- {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_PreloadedShaders: []
|
||||
m_PreloadShadersBatchTimeLimit: -1
|
||||
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
|
||||
type: 0}
|
||||
m_CustomRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd,
|
||||
type: 2}
|
||||
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_CustomRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, type: 2}
|
||||
m_TransparencySortMode: 0
|
||||
m_TransparencySortAxis: {x: 0, y: 0, z: 1}
|
||||
m_DefaultRenderingPath: 1
|
||||
@ -60,8 +58,7 @@ GraphicsSettings:
|
||||
m_FogKeepExp2: 1
|
||||
m_AlbedoSwatchInfos: []
|
||||
m_RenderPipelineGlobalSettingsMap:
|
||||
UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa,
|
||||
type: 2}
|
||||
UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa, type: 2}
|
||||
m_LightsUseLinearIntensity: 1
|
||||
m_LightsUseColorTemperature: 1
|
||||
m_LogWhenShaderIsCompiled: 0
|
||||
|
@ -12,7 +12,7 @@ PlayerSettings:
|
||||
targetDevice: 2
|
||||
useOnDemandResources: 0
|
||||
accelerometerFrequency: 60
|
||||
companyName: DefaultCompany
|
||||
companyName: Capers
|
||||
productName: ProjectDDD
|
||||
defaultCursor: {fileID: 0}
|
||||
cursorHotspot: {x: 0, y: 0}
|
||||
@ -141,7 +141,8 @@ PlayerSettings:
|
||||
visionOSBundleVersion: 1.0
|
||||
tvOSBundleVersion: 1.0
|
||||
bundleVersion: 0.1.0
|
||||
preloadedAssets: []
|
||||
preloadedAssets:
|
||||
- {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3}
|
||||
metroInputSource: 0
|
||||
wsaTransparentSwapchain: 0
|
||||
m_HolographicPauseOnTrackingLoss: 1
|
||||
@ -516,7 +517,10 @@ PlayerSettings:
|
||||
m_Height: 720
|
||||
m_Kind: 1
|
||||
m_SubKind:
|
||||
m_BuildTargetBatching: []
|
||||
m_BuildTargetBatching:
|
||||
- m_BuildTarget: Standalone
|
||||
m_StaticBatching: 1
|
||||
m_DynamicBatching: 0
|
||||
m_BuildTargetShaderSettings: []
|
||||
m_BuildTargetGraphicsJobs: []
|
||||
m_BuildTargetGraphicsJobMode: []
|
||||
@ -825,6 +829,7 @@ PlayerSettings:
|
||||
platformArchitecture: {}
|
||||
scriptingBackend:
|
||||
Android: 1
|
||||
Standalone: 1
|
||||
il2cppCompilerConfiguration: {}
|
||||
il2cppCodeGeneration: {}
|
||||
il2cppStacktraceInformation: {}
|
||||
|
Loading…
Reference in New Issue
Block a user