Files
AppleHillsProduction/Assets/Editor/CardSystem/CardEditorWindow.cs

659 lines
24 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using System.IO;
using System.Linq;
using AppleHills.Data.CardSystem;
2025-11-06 21:31:16 +01:00
using AppleHills.Editor.Utilities;
using UI.CardSystem;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Audio;
namespace Editor.CardSystem
{
/// <summary>
/// Editor utility for managing card definitions with live preview using the same UI as in-game.
/// </summary>
public class CardEditorWindow : EditorWindow
{
// Paths
private const string CardDefinitionsPath = "Assets/Data/Cards";
private const string MenuPath = "AppleHills/Cards/Card Editor";
2025-11-06 21:31:16 +01:00
private const string CardUIPrefabPath = "Assets/Prefabs/UI/CardsSystem/Cards/Card.prefab";
private const string CardVisualConfigPath = CardDefinitionsPath + "/CardVisualConfig.asset";
// Preview settings
private static readonly Vector2 PreviewCardSize = new Vector2(200f, 300f);
private const float CameraPaddingFactor = 1.2f;
// Editor state
private List<CardDefinition> _cards = new List<CardDefinition>();
private CardDefinition _selectedCard;
private CardDefinition _editingCard;
private Vector2 _cardListScrollPosition;
private Vector2 _cardEditScrollPosition;
private string _searchQuery = "";
private bool _showPreview = true;
private bool _isDirty = false;
// Preview state
private PreviewRenderUtility _previewUtility;
private GameObject _previewRoot;
private CardDisplay _previewCardDisplay;
private GameObject _cardUIPrefab;
private CardVisualConfig _cardVisualConfig;
private float _zoomLevel = 1.0f;
[MenuItem(MenuPath)]
public static void ShowWindow()
{
var window = GetWindow<CardEditorWindow>("Card Editor");
window.minSize = new Vector2(1000, 700);
window.Show();
}
private void OnEnable()
{
LoadCardDefinitions();
InitializePreview();
Undo.undoRedoPerformed += OnUndoRedo;
}
private void OnDisable()
{
CleanupPreview();
Undo.undoRedoPerformed -= OnUndoRedo;
}
private void OnUndoRedo()
{
LoadCardDefinitions();
Repaint();
}
private void LoadCardDefinitions()
{
_cards.Clear();
// Ensure directory exists
if (!Directory.Exists(CardDefinitionsPath))
{
Directory.CreateDirectory(CardDefinitionsPath);
AssetDatabase.Refresh();
}
// Find all card definitions
string[] guids = AssetDatabase.FindAssets("t:CardDefinition", new[] { CardDefinitionsPath });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
CardDefinition card = AssetDatabase.LoadAssetAtPath<CardDefinition>(path);
if (card != null)
{
_cards.Add(card);
}
}
_cards = _cards.OrderBy(c => c.Name).ToList();
// Restore selection if possible
if (_selectedCard != null)
{
_selectedCard = _cards.FirstOrDefault(c => c.Id == _selectedCard.Id);
if (_selectedCard != null)
{
_editingCard = CloneCard(_selectedCard);
UpdatePreview();
}
}
}
private void InitializePreview()
{
if (_previewUtility == null)
{
_previewUtility = new PreviewRenderUtility();
var cam = _previewUtility.camera;
cam.clearFlags = CameraClearFlags.SolidColor;
cam.backgroundColor = new Color(0.2f, 0.2f, 0.2f);
cam.orthographic = true;
// Calculate ortho size based on card height with padding
float baseOrthoSize = (PreviewCardSize.y / 2f) * CameraPaddingFactor;
cam.orthographicSize = baseOrthoSize;
cam.nearClipPlane = 0.01f;
cam.farClipPlane = 100f;
cam.transform.position = new Vector3(0, 0, -10);
cam.transform.rotation = Quaternion.identity;
}
// Load prefab and config
_cardUIPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(CardUIPrefabPath);
if (_cardUIPrefab == null)
{
Debug.LogError($"[CardEditorWindow] Card UI prefab not found at {CardUIPrefabPath}");
}
_cardVisualConfig = AssetDatabase.LoadAssetAtPath<CardVisualConfig>(CardVisualConfigPath);
if (_cardVisualConfig == null)
{
Debug.LogWarning($"[CardEditorWindow] Card visual config not found at {CardVisualConfigPath}");
}
CreatePreviewCardObject();
}
private void CreatePreviewCardObject()
{
CleanupPreviewInstance();
if (_cardUIPrefab == null || _previewUtility == null)
return;
try
{
// Create root and canvas
_previewRoot = new GameObject("PreviewRoot");
_previewRoot.hideFlags = HideFlags.HideAndDontSave;
GameObject canvasGO = new GameObject("PreviewCanvas");
canvasGO.transform.SetParent(_previewRoot.transform, false);
Canvas canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
canvas.worldCamera = _previewUtility.camera;
RectTransform canvasRect = canvasGO.GetComponent<RectTransform>();
canvasRect.sizeDelta = new Vector2(20f, 20f);
canvasRect.localPosition = Vector3.zero;
CanvasScaler scaler = canvasGO.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize;
scaler.scaleFactor = 1f;
canvasGO.AddComponent<GraphicRaycaster>();
// Instantiate card prefab
GameObject cardInstance = Instantiate(_cardUIPrefab, canvasGO.transform, false);
RectTransform cardRect = cardInstance.GetComponent<RectTransform>();
if (cardRect != null)
{
// Set explicit size for preview
cardRect.sizeDelta = PreviewCardSize;
// Anchor to center
cardRect.anchorMin = new Vector2(0.5f, 0.5f);
cardRect.anchorMax = new Vector2(0.5f, 0.5f);
cardRect.pivot = new Vector2(0.5f, 0.5f);
cardRect.anchoredPosition = Vector2.zero;
}
// Get CardDisplay component
_previewCardDisplay = cardInstance.GetComponent<CardDisplay>();
if (_previewCardDisplay == null)
{
_previewCardDisplay = cardInstance.GetComponentInChildren<CardDisplay>();
}
if (_previewCardDisplay != null && _cardVisualConfig != null)
{
_previewCardDisplay.SetVisualConfig(_cardVisualConfig);
}
_previewUtility.AddSingleGO(_previewRoot);
// Update camera
UpdatePreviewCamera();
}
catch (System.Exception e)
{
Debug.LogError($"[CardEditorWindow] Error creating preview: {e.Message}");
}
}
private void UpdatePreviewCamera()
{
if (_previewUtility == null) return;
Camera cam = _previewUtility.camera;
// Calculate base ortho size from card height with padding
float baseOrthoSize = (PreviewCardSize.y / 2f) * CameraPaddingFactor;
// Apply zoom as divisor (higher zoom = smaller ortho size = more zoomed in)
cam.orthographicSize = baseOrthoSize / _zoomLevel;
}
private void CleanupPreviewInstance()
{
if (_previewRoot != null)
{
DestroyImmediate(_previewRoot);
_previewRoot = null;
}
_previewCardDisplay = null;
}
private void CleanupPreview()
{
CleanupPreviewInstance();
if (_previewUtility != null)
{
_previewUtility.Cleanup();
_previewUtility = null;
}
}
private void UpdatePreview()
{
if (_editingCard == null || _previewCardDisplay == null || !_showPreview)
return;
try
{
// Create CardData from definition and setup the display
CardData previewData = _editingCard.CreateCardData();
_previewCardDisplay.SetupCard(previewData);
Repaint();
}
catch (System.Exception ex)
{
Debug.LogError($"[CardEditorWindow] Error updating preview: {ex.Message}");
}
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal();
// Left panel - Card list
DrawCardList();
// Middle panel - Card editor
DrawCardEditor();
// Right panel - Preview
if (_showPreview)
{
DrawPreview();
}
EditorGUILayout.EndHorizontal();
}
private void DrawCardList()
{
EditorGUILayout.BeginVertical(GUILayout.Width(250));
EditorGUILayout.LabelField("Card List", EditorStyles.boldLabel);
// Search bar
EditorGUILayout.BeginHorizontal();
_searchQuery = EditorGUILayout.TextField(_searchQuery, EditorStyles.toolbarSearchField);
if (GUILayout.Button("X", GUILayout.Width(20)))
{
_searchQuery = "";
GUI.FocusControl(null);
}
EditorGUILayout.EndHorizontal();
// New card button
if (GUILayout.Button("Create New Card"))
{
CreateNewCard();
}
// Card list
_cardListScrollPosition = EditorGUILayout.BeginScrollView(_cardListScrollPosition);
var filteredCards = string.IsNullOrEmpty(_searchQuery)
? _cards
: _cards.Where(c => c.Name.ToLower().Contains(_searchQuery.ToLower())).ToList();
foreach (var card in filteredCards)
{
bool isSelected = _selectedCard == card;
GUI.backgroundColor = isSelected ? Color.cyan : Color.white;
if (GUILayout.Button(card.Name, EditorStyles.miniButton))
{
SelectCard(card);
}
GUI.backgroundColor = Color.white;
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void DrawCardEditor()
{
EditorGUILayout.BeginVertical(GUILayout.Width(350));
if (_editingCard == null)
{
EditorGUILayout.HelpBox("Select a card from the list or create a new one.", MessageType.Info);
EditorGUILayout.EndVertical();
return;
}
EditorGUILayout.LabelField("Card Editor", EditorStyles.boldLabel);
_cardEditScrollPosition = EditorGUILayout.BeginScrollView(_cardEditScrollPosition);
EditorGUI.BeginChangeCheck();
// Basic Info
EditorGUILayout.LabelField("Basic Information", EditorStyles.boldLabel);
_editingCard.Name = EditorGUILayout.TextField("Name", _editingCard.Name);
// Custom file name option
_editingCard.UseCustomFileName = EditorGUILayout.Toggle("Use Custom File Name", _editingCard.UseCustomFileName);
GUI.enabled = _editingCard.UseCustomFileName;
_editingCard.CustomFileName = EditorGUILayout.TextField("Custom File Name", _editingCard.CustomFileName);
GUI.enabled = true;
_editingCard.Description = EditorGUILayout.TextArea(_editingCard.Description, GUILayout.Height(60));
EditorGUILayout.Space();
// Properties
EditorGUILayout.LabelField("Properties", EditorStyles.boldLabel);
_editingCard.Rarity = (CardRarity)EditorGUILayout.EnumPopup("Rarity", _editingCard.Rarity);
_editingCard.Zone = (CardZone)EditorGUILayout.EnumPopup("Zone", _editingCard.Zone);
EditorGUILayout.Space();
// Visuals
EditorGUILayout.LabelField("Visuals", EditorStyles.boldLabel);
_editingCard.CardImage = (Sprite)EditorGUILayout.ObjectField("Card Image", _editingCard.CardImage, typeof(Sprite), false);
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Card visuals (frames, overlays, backgrounds, shapes) are configured in CardVisualConfig asset based on rarity and zone.", MessageType.Info);
// Collection
EditorGUILayout.Space();
EditorGUILayout.LabelField("Collection", EditorStyles.boldLabel);
_editingCard.CollectionIndex = EditorGUILayout.IntField("Collection Index", _editingCard.CollectionIndex);
EditorGUILayout.Space();
// Identification
EditorGUILayout.LabelField("Identification", EditorStyles.boldLabel);
GUI.enabled = false;
EditorGUILayout.TextField("ID", _editingCard.Id);
GUI.enabled = true;
// Audio
EditorGUILayout.LabelField("Audio", EditorStyles.boldLabel);
_editingCard.reactionVoiceClip = (AudioResource)EditorGUILayout.ObjectField("Reaction audio clip", _editingCard.reactionVoiceClip, typeof(AudioResource),false);
if (EditorGUI.EndChangeCheck())
{
_isDirty = true;
UpdatePreview();
}
EditorGUILayout.EndScrollView();
// Action buttons
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
GUI.enabled = _isDirty;
if (GUILayout.Button("Apply Changes"))
{
ApplyChanges();
}
GUI.enabled = true;
if (GUILayout.Button("Revert"))
{
RevertChanges();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Duplicate"))
{
DuplicateCard();
}
GUI.backgroundColor = Color.red;
if (GUILayout.Button("Delete"))
{
DeleteCard();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawPreview()
{
EditorGUILayout.BeginVertical();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
_zoomLevel = EditorGUILayout.Slider("Zoom", _zoomLevel, 0.5f, 2.0f);
EditorGUILayout.EndHorizontal();
if (Event.current.type == EventType.Repaint && _zoomLevel != _previewUtility.camera.orthographicSize)
{
UpdatePreviewCamera();
}
Rect previewRect = GUILayoutUtility.GetRect(400, 600);
if (_previewUtility != null && _editingCard != null)
{
_previewUtility.BeginPreview(previewRect, GUIStyle.none);
_previewUtility.camera.Render();
Texture resultTexture = _previewUtility.EndPreview();
GUI.DrawTexture(previewRect, resultTexture, ScaleMode.ScaleToFit, false);
}
else
{
EditorGUI.DrawRect(previewRect, new Color(0.2f, 0.2f, 0.2f));
EditorGUI.LabelField(previewRect, "No Preview Available", EditorStyles.centeredGreyMiniLabel);
}
EditorGUILayout.EndVertical();
}
private void SelectCard(CardDefinition card)
{
if (_isDirty && _selectedCard != null)
{
if (EditorUtility.DisplayDialog("Unsaved Changes",
"You have unsaved changes. Apply them before switching cards?",
"Apply", "Discard"))
{
ApplyChanges();
}
}
_selectedCard = card;
_editingCard = CloneCard(card);
_isDirty = false;
UpdatePreview();
}
private void CreateNewCard()
{
CardDefinition newCard = CreateInstance<CardDefinition>();
newCard.Id = System.Guid.NewGuid().ToString();
newCard.Name = "New Card";
newCard.Description = "Card description";
newCard.Rarity = CardRarity.Normal;
newCard.Zone = CardZone.AppleHills;
newCard.CollectionIndex = _cards.Count;
string path = $"{CardDefinitionsPath}/Card_{newCard.Name}.asset";
path = AssetDatabase.GenerateUniqueAssetPath(path);
AssetDatabase.CreateAsset(newCard, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
2025-11-06 21:31:16 +01:00
// Add to Addressables group "BlokkemonCards" and apply "BlokkemonCard" label
if (AddressablesUtility.EnsureAssetInGroupWithLabel(path, "BlokkemonCards", "BlokkemonCard"))
{
AddressablesUtility.SaveAddressableAssets();
Debug.Log($"[CardEditorWindow] Added new card to Addressables with 'BlokkemonCard' label");
}
else
{
Debug.LogWarning("[CardEditorWindow] Failed to add new card to Addressables. Please ensure Addressables are set up in this project.");
}
LoadCardDefinitions();
SelectCard(newCard);
}
private void ApplyChanges()
{
if (_selectedCard == null || _editingCard == null)
return;
Undo.RecordObject(_selectedCard, "Modify Card");
string oldPath = AssetDatabase.GetAssetPath(_selectedCard);
EditorUtility.CopySerialized(_editingCard, _selectedCard);
EditorUtility.SetDirty(_selectedCard);
AssetDatabase.SaveAssets();
// Rename file if needed
string desiredFileName = _selectedCard.UseCustomFileName && !string.IsNullOrEmpty(_selectedCard.CustomFileName)
? _selectedCard.CustomFileName
: _selectedCard.Name;
2025-11-06 21:31:16 +01:00
string finalPath = oldPath;
if (!string.IsNullOrEmpty(desiredFileName))
{
// Strip spaces from file name
desiredFileName = desiredFileName.Replace(" ", "");
string directory = Path.GetDirectoryName(oldPath);
string extension = Path.GetExtension(oldPath);
string currentFileName = Path.GetFileNameWithoutExtension(oldPath);
string expectedFileName = $"Card_{desiredFileName}";
// Only rename if the current file name doesn't match the expected name
if (currentFileName != expectedFileName)
{
string newPath = Path.Combine(directory, $"{expectedFileName}{extension}");
string uniquePath = AssetDatabase.GenerateUniqueAssetPath(newPath);
string error = AssetDatabase.MoveAsset(oldPath, uniquePath);
if (!string.IsNullOrEmpty(error))
{
Debug.LogError($"[CardEditorWindow] Failed to rename asset: {error}");
}
else
2025-11-06 21:31:16 +01:00
finalPath = uniquePath;
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
}
2025-11-06 21:31:16 +01:00
// Add to Addressables group "BlokkemonCards" and apply "BlokkemonCard" label
if (!string.IsNullOrEmpty(finalPath))
{
if (AddressablesUtility.EnsureAssetInGroupWithLabel(finalPath, "BlokkemonCards", "BlokkemonCard"))
{
AddressablesUtility.SaveAddressableAssets();
Debug.Log($"[CardEditorWindow] Added {_selectedCard.Name} to Addressables with 'BlokkemonCard' label");
}
else
{
Debug.LogError("[CardEditorWindow] Failed to add card to Addressables. Please ensure Addressables are set up in this project.");
}
}
_isDirty = false;
Debug.Log($"[CardEditorWindow] Applied changes to {_selectedCard.Name}");
}
private void RevertChanges()
{
if (_selectedCard == null)
return;
_editingCard = CloneCard(_selectedCard);
_isDirty = false;
UpdatePreview();
}
private void DuplicateCard()
{
if (_selectedCard == null)
return;
CardDefinition duplicate = Instantiate(_selectedCard);
duplicate.Id = System.Guid.NewGuid().ToString();
duplicate.Name = _selectedCard.Name + " (Copy)";
string path = $"{CardDefinitionsPath}/Card_{duplicate.Name}.asset";
path = AssetDatabase.GenerateUniqueAssetPath(path);
AssetDatabase.CreateAsset(duplicate, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
LoadCardDefinitions();
SelectCard(duplicate);
}
private void DeleteCard()
{
if (_selectedCard == null)
return;
if (!EditorUtility.DisplayDialog("Delete Card",
$"Are you sure you want to delete '{_selectedCard.Name}'?",
"Delete", "Cancel"))
{
return;
}
string path = AssetDatabase.GetAssetPath(_selectedCard);
AssetDatabase.DeleteAsset(path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
_selectedCard = null;
_editingCard = null;
_isDirty = false;
LoadCardDefinitions();
CleanupPreviewInstance();
CreatePreviewCardObject();
}
private CardDefinition CloneCard(CardDefinition original)
{
CardDefinition clone = CreateInstance<CardDefinition>();
EditorUtility.CopySerialized(original, clone);
return clone;
}
}
}