using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using UnityEditor; using UnityEngine; using UnityEngine.Networking; public class AssetManagerWindow : EditorWindow { private const string ApiUrl = "https://dangrygames.uk/API/assets.php?status=approved"; private const string ImportedRoot = "Assets/AssetManager/ImportedAssets"; private const string SiteAssetUrlBase = "https://dangrygames.uk/AssetHub/asset.php?id="; private AssetListResponse _assetListResponse; private Vector2 _scrollPosition; private string _statusMessage = "Press Refresh Assets to load data."; private bool _isLoading = false; private string _searchText = ""; private string _typeFilter = "all"; private string _categoryFilter = "all"; private readonly string[] _typeOptions = { "all", "texture", "model", "audio", "material", "prefab", "document", "other" }; private AssetMetadataStore _metadataStore; private string MetadataPath => "Assets/AssetManager/asset_metadata.json"; [MenuItem("Tools/Asset Manager/Open Asset Manager")] public static void OpenWindow() { AssetManagerWindow window = GetWindow("Asset Manager"); window.minSize = new Vector2(980, 520); } private void OnEnable() { LoadMetadata(); } private void OnGUI() { DrawHeader(); DrawToolbar(); DrawStatusMessage(); DrawAssetList(); } private void DrawHeader() { EditorGUILayout.Space(10); EditorGUILayout.LabelField("Asset Manager", EditorStyles.boldLabel); EditorGUILayout.LabelField("Browse approved assets from your website and import them into this Unity project."); EditorGUILayout.Space(8); } private void DrawToolbar() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); GUI.enabled = !_isLoading; if (GUILayout.Button("Refresh Assets", GUILayout.Height(28), GUILayout.Width(140))) { RefreshAssets(); } GUILayout.Space(10); if (GUILayout.Button("Import All Visible", GUILayout.Height(28), GUILayout.Width(140))) { ImportAllVisibleAssets(); } GUI.enabled = true; GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(8); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Search", GUILayout.Width(50)); _searchText = EditorGUILayout.TextField(_searchText, GUILayout.MinWidth(180)); GUILayout.Space(12); EditorGUILayout.LabelField("Type", GUILayout.Width(35)); int currentTypeIndex = Array.IndexOf(_typeOptions, _typeFilter); if (currentTypeIndex < 0) { currentTypeIndex = 0; } int selectedTypeIndex = EditorGUILayout.Popup(currentTypeIndex, _typeOptions, GUILayout.Width(140)); _typeFilter = _typeOptions[selectedTypeIndex]; GUILayout.Space(12); EditorGUILayout.LabelField("Category", GUILayout.Width(55)); string[] categoryOptions = GetCategoryOptions(); int currentCategoryIndex = Array.IndexOf(categoryOptions, _categoryFilter); if (currentCategoryIndex < 0) { currentCategoryIndex = 0; } int selectedCategoryIndex = EditorGUILayout.Popup(currentCategoryIndex, categoryOptions, GUILayout.Width(160)); _categoryFilter = categoryOptions[selectedCategoryIndex]; GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.Space(10); } private void DrawStatusMessage() { MessageType messageType = _statusMessage.StartsWith("Failed", StringComparison.OrdinalIgnoreCase) || _statusMessage.StartsWith("Import failed", StringComparison.OrdinalIgnoreCase) ? MessageType.Error : MessageType.Info; EditorGUILayout.HelpBox(_statusMessage, messageType); EditorGUILayout.Space(6); } private void DrawAssetList() { if (_assetListResponse == null || _assetListResponse.assets == null || _assetListResponse.assets.Length == 0) { EditorGUILayout.LabelField("No assets loaded."); return; } List visibleAssets = GetVisibleAssets(); EditorGUILayout.LabelField($"Visible Assets: {visibleAssets.Count}", EditorStyles.miniBoldLabel); EditorGUILayout.Space(6); _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); foreach (AssetData asset in visibleAssets) { DrawAssetCard(asset); EditorGUILayout.Space(10); } EditorGUILayout.EndScrollView(); } private void DrawAssetCard(AssetData asset) { if (asset == null) { return; } string importState = GetAssetState(asset); EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(asset.name ?? "Unnamed Asset", EditorStyles.boldLabel); EditorGUILayout.LabelField($"Type: {SafeString(asset.asset_type)}"); EditorGUILayout.LabelField($"Category: {SafeString(asset.category)}"); EditorGUILayout.LabelField($"Status: {SafeString(asset.status)}"); EditorGUILayout.LabelField($"Import State: {importState}"); EditorGUILayout.EndVertical(); GUILayout.FlexibleSpace(); EditorGUILayout.BeginVertical(GUILayout.Width(220)); if (asset.latest_version != null) { EditorGUILayout.LabelField($"Latest Version: v{asset.latest_version.version_number}"); EditorGUILayout.LabelField($"Extension: {SafeString(asset.latest_version.file_extension)}"); EditorGUILayout.LabelField($"File: {SafeString(asset.latest_version.original_filename)}"); } else { EditorGUILayout.LabelField("No version data available."); } EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); if (!string.IsNullOrWhiteSpace(asset.description)) { EditorGUILayout.Space(4); EditorGUILayout.LabelField("Description:"); EditorGUILayout.HelpBox(asset.description, MessageType.None); } EditorGUILayout.Space(6); EditorGUILayout.BeginHorizontal(); GUI.enabled = !_isLoading && asset.latest_version != null && !string.IsNullOrWhiteSpace(asset.latest_version.download_url); string importButtonLabel = importState == "Not Imported" ? "Import Asset" : "Reimport Asset"; if (GUILayout.Button(importButtonLabel, GUILayout.Height(26), GUILayout.Width(120))) { ImportAsset(asset); } GUI.enabled = true; if (GUILayout.Button("View on Site", GUILayout.Height(26), GUILayout.Width(120))) { OpenAssetOnSite(asset); } GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } private string SafeString(string value) { return string.IsNullOrWhiteSpace(value) ? "—" : value; } private async void RefreshAssets() { _isLoading = true; _statusMessage = "Loading assets from API..."; Repaint(); try { string json = await GetTextFromUrl(ApiUrl); Debug.Log("Asset API JSON: " + json); if (string.IsNullOrWhiteSpace(json)) { _statusMessage = "Failed to load assets: API returned empty JSON."; _isLoading = false; Repaint(); return; } AssetListResponse response = JsonUtility.FromJson(json); if (response == null) { _statusMessage = "Failed to load assets: JSON parsed to null."; _isLoading = false; Repaint(); return; } _assetListResponse = response; EnsureCategoryFilterIsValid(); int count = response.assets != null ? response.assets.Length : 0; _statusMessage = $"Loaded {count} asset(s) successfully."; } catch (Exception ex) { Debug.LogError("RefreshAssets error: " + ex); _statusMessage = "Failed to load assets: " + ex.Message; } _isLoading = false; Repaint(); } private async void ImportAsset(AssetData asset) { if (asset == null || asset.latest_version == null || string.IsNullOrWhiteSpace(asset.latest_version.download_url)) { _statusMessage = "Asset is missing download information."; Repaint(); return; } _isLoading = true; _statusMessage = $"Downloading {asset.name}..."; Repaint(); try { byte[] fileData = await GetBytesFromUrl(asset.latest_version.download_url); if (fileData == null || fileData.Length == 0) { _statusMessage = "Import failed: downloaded file was empty."; _isLoading = false; Repaint(); return; } string fileName = asset.latest_version.original_filename; if (string.IsNullOrWhiteSpace(fileName)) { fileName = asset.name + "." + asset.latest_version.file_extension; } string targetFolder = GetImportFolderForAssetType(asset.asset_type); EnsureFolderExists(targetFolder); string targetPath = Path.Combine(targetFolder, fileName).Replace("\\", "/"); File.WriteAllBytes(targetPath, fileData); AssetDatabase.ImportAsset(targetPath, ImportAssetOptions.ForceUpdate); ApplyImportSettings(targetPath, asset.asset_type); AssetDatabase.Refresh(); UpsertMetadataRecord(asset, targetPath); SaveMetadata(); _statusMessage = $"Imported successfully: {targetPath}"; } catch (Exception ex) { Debug.LogError("ImportAsset error: " + ex); _statusMessage = "Import failed: " + ex.Message; } _isLoading = false; Repaint(); } private async void ImportAllVisibleAssets() { List visibleAssets = GetVisibleAssets(); if (visibleAssets.Count == 0) { _statusMessage = "No visible assets to import."; Repaint(); return; } bool proceed = EditorUtility.DisplayDialog( "Import All Visible Assets", $"Import {visibleAssets.Count} visible asset(s)?", "Import All", "Cancel"); if (!proceed) { return; } _isLoading = true; int successCount = 0; int failCount = 0; for (int i = 0; i < visibleAssets.Count; i++) { AssetData asset = visibleAssets[i]; if (asset == null || asset.latest_version == null || string.IsNullOrWhiteSpace(asset.latest_version.download_url)) { failCount++; continue; } _statusMessage = $"Importing {i + 1}/{visibleAssets.Count}: {asset.name}"; Repaint(); try { byte[] fileData = await GetBytesFromUrl(asset.latest_version.download_url); if (fileData == null || fileData.Length == 0) { failCount++; continue; } string fileName = asset.latest_version.original_filename; if (string.IsNullOrWhiteSpace(fileName)) { fileName = asset.name + "." + asset.latest_version.file_extension; } string targetFolder = GetImportFolderForAssetType(asset.asset_type); EnsureFolderExists(targetFolder); string targetPath = Path.Combine(targetFolder, fileName).Replace("\\", "/"); File.WriteAllBytes(targetPath, fileData); AssetDatabase.ImportAsset(targetPath, ImportAssetOptions.ForceUpdate); ApplyImportSettings(targetPath, asset.asset_type); UpsertMetadataRecord(asset, targetPath); successCount++; } catch (Exception ex) { Debug.LogError($"Bulk import failed for asset {asset.name}: {ex}"); failCount++; } } SaveMetadata(); AssetDatabase.Refresh(); _statusMessage = $"Bulk import complete. Success: {successCount}, Failed: {failCount}."; _isLoading = false; Repaint(); } private void OpenAssetOnSite(AssetData asset) { if (asset == null) { return; } Application.OpenURL(SiteAssetUrlBase + asset.id); } private List GetVisibleAssets() { if (_assetListResponse == null || _assetListResponse.assets == null) { return new List(); } return _assetListResponse.assets .Where(asset => asset != null) .Where(PassesFilter) .ToList(); } private bool PassesFilter(AssetData asset) { if (asset == null) { return false; } if (_typeFilter != "all" && !string.Equals(asset.asset_type, _typeFilter, StringComparison.OrdinalIgnoreCase)) { return false; } if (_categoryFilter != "all" && !string.Equals(SafeString(asset.category), _categoryFilter, StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.IsNullOrWhiteSpace(_searchText)) { string search = _searchText.Trim(); bool matchesName = !string.IsNullOrWhiteSpace(asset.name) && asset.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; bool matchesCategory = !string.IsNullOrWhiteSpace(asset.category) && asset.category.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; bool matchesDescription = !string.IsNullOrWhiteSpace(asset.description) && asset.description.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; if (!matchesName && !matchesCategory && !matchesDescription) { return false; } } return true; } private string[] GetCategoryOptions() { if (_assetListResponse == null || _assetListResponse.assets == null) { return new[] { "all" }; } List categories = _assetListResponse.assets .Where(a => a != null && !string.IsNullOrWhiteSpace(a.category)) .Select(a => a.category.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(c => c) .ToList(); categories.Insert(0, "all"); return categories.ToArray(); } private void EnsureCategoryFilterIsValid() { string[] categories = GetCategoryOptions(); if (!categories.Contains(_categoryFilter)) { _categoryFilter = "all"; } } private string GetAssetState(AssetData asset) { if (asset == null || asset.latest_version == null) { return "Unknown"; } if (_metadataStore == null) { return "Not Imported"; } AssetLocalRecord record = _metadataStore.records.Find(r => r.asset_id == asset.id); if (record == null) { return "Not Imported"; } if (record.version_id == asset.latest_version.version_id) { return "Up To Date"; } return "Update Available"; } private void UpsertMetadataRecord(AssetData asset, string targetPath) { if (_metadataStore == null) { _metadataStore = new AssetMetadataStore(); } AssetLocalRecord existing = _metadataStore.records.Find(r => r.asset_id == asset.id); if (existing != null) { existing.version_id = asset.latest_version.version_id; existing.local_path = targetPath; } else { _metadataStore.records.Add(new AssetLocalRecord { asset_id = asset.id, version_id = asset.latest_version.version_id, local_path = targetPath }); } } private void LoadMetadata() { if (!File.Exists(MetadataPath)) { _metadataStore = new AssetMetadataStore(); return; } try { string json = File.ReadAllText(MetadataPath); _metadataStore = JsonUtility.FromJson(json); if (_metadataStore == null) { _metadataStore = new AssetMetadataStore(); } if (_metadataStore.records == null) { _metadataStore.records = new List(); } } catch (Exception ex) { Debug.LogError("Failed to load asset metadata: " + ex.Message); _metadataStore = new AssetMetadataStore(); } } private void SaveMetadata() { if (_metadataStore == null) { _metadataStore = new AssetMetadataStore(); } if (_metadataStore.records == null) { _metadataStore.records = new List(); } try { string directory = Path.GetDirectoryName(MetadataPath); if (!string.IsNullOrWhiteSpace(directory)) { EnsureFolderExists(directory.Replace("\\", "/")); } string json = JsonUtility.ToJson(_metadataStore, true); File.WriteAllText(MetadataPath, json); AssetDatabase.Refresh(); } catch (Exception ex) { Debug.LogError("Failed to save asset metadata: " + ex.Message); } } private async Task GetTextFromUrl(string url) { using (UnityWebRequest request = UnityWebRequest.Get(url)) { UnityWebRequestAsyncOperation operation = request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } #if UNITY_2020_1_OR_NEWER if (request.result != UnityWebRequest.Result.Success) #else if (request.isNetworkError || request.isHttpError) #endif { throw new Exception($"{request.error} | Response Code: {request.responseCode}"); } return request.downloadHandler.text; } } private async Task GetBytesFromUrl(string url) { using (UnityWebRequest request = UnityWebRequest.Get(url)) { request.downloadHandler = new DownloadHandlerBuffer(); UnityWebRequestAsyncOperation operation = request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } #if UNITY_2020_1_OR_NEWER if (request.result != UnityWebRequest.Result.Success) #else if (request.isNetworkError || request.isHttpError) #endif { throw new Exception($"{request.error} | Response Code: {request.responseCode}"); } return request.downloadHandler.data; } } private void ApplyImportSettings(string assetPath, string assetType) { if (string.IsNullOrWhiteSpace(assetPath)) { return; } string lowerType = string.IsNullOrWhiteSpace(assetType) ? string.Empty : assetType.ToLowerInvariant(); if (lowerType == "texture") { TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter; if (importer != null) { importer.textureType = TextureImporterType.Default; importer.mipmapEnabled = false; importer.SaveAndReimport(); } } else if (lowerType == "audio") { AudioImporter importer = AssetImporter.GetAtPath(assetPath) as AudioImporter; if (importer != null) { importer.forceToMono = false; importer.loadInBackground = true; AudioImporterSampleSettings settings = importer.defaultSampleSettings; settings.loadType = AudioClipLoadType.DecompressOnLoad; importer.defaultSampleSettings = settings; importer.SaveAndReimport(); } } else if (lowerType == "model") { ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter; if (importer != null) { importer.globalScale = 1f; importer.importVisibility = true; importer.importCameras = false; importer.importLights = false; importer.SaveAndReimport(); } } } private string GetImportFolderForAssetType(string assetType) { string lowerType = string.IsNullOrWhiteSpace(assetType) ? string.Empty : assetType.ToLowerInvariant(); switch (lowerType) { case "texture": return $"{ImportedRoot}/Textures"; case "model": return $"{ImportedRoot}/Models"; case "audio": return $"{ImportedRoot}/Audio"; case "material": return $"{ImportedRoot}/Materials"; case "prefab": return $"{ImportedRoot}/Prefabs"; case "document": return $"{ImportedRoot}/Documents"; default: return $"{ImportedRoot}/Other"; } } private void EnsureFolderExists(string assetPath) { string[] split = assetPath.Split('/'); if (split.Length < 2) { return; } string currentPath = split[0]; for (int i = 1; i < split.Length; i++) { string nextFolder = split[i]; string combined = currentPath + "/" + nextFolder; if (!AssetDatabase.IsValidFolder(combined)) { AssetDatabase.CreateFolder(currentPath, nextFolder); } currentPath = combined; } } [Serializable] public class AssetListResponse { public int count; public AssetData[] assets; } [Serializable] public class AssetData { public int id; public string name; public string slug; public string asset_type; public string category; public string description; public string status; public string created_at; public string updated_at; public LatestVersion latest_version; } [Serializable] public class LatestVersion { public int version_id; public int version_number; public string original_filename; public string stored_filename; public string file_path; public string file_extension; public int file_size; public string file_hash; public string changelog; public string uploaded_at; public string download_url; } [Serializable] public class AssetLocalRecord { public int asset_id; public int version_id; public string local_path; } [Serializable] public class AssetMetadataStore { public List records = new List(); } }