using System.IO; using LiveOpsExamples; using TMPro; using UnityEditor; using UnityEngine; namespace LiveOpsExamples.Editor { /// /// Bakes the Cozy Collector gameplay prefabs. Idempotent: every prefab is /// rebuilt from code and saved over the same fixed asset path. /// public static partial class CozyCollectorBake { private const string PrefabFolderPath = "Assets/LiveOpsExamples/Prefabs"; private const string SnackSpritePath = "Assets/LiveOpsExamples/Sprites/snack.png"; private static readonly Color AccentColor = ParseHexColor("#FFD27D"); [MenuItem("Tools/LiveOps/Bake Cozy Collector Prefabs")] public static void BakePrefabs() { EnsureAssetFolder(PrefabFolderPath); SaveBakedPrefab(BuildSnackPrefab(), $"{PrefabFolderPath}/Snack.prefab"); SaveBakedPrefab(BuildScorePopupPrefab(), $"{PrefabFolderPath}/ScorePopup.prefab"); SaveBakedPrefab(BuildCollectBurstPrefab(), $"{PrefabFolderPath}/CollectBurst.prefab"); AssetDatabase.SaveAssets(); Debug.Log($"Cozy Collector prefabs baked under {PrefabFolderPath}"); } private static GameObject BuildSnackPrefab() { var go = new GameObject("Snack"); var collider = go.AddComponent(); collider.isTrigger = true; collider.radius = 0.3f; var renderer = go.AddComponent(); var sprite = AssetDatabase.LoadAssetAtPath(SnackSpritePath); if (sprite == null) { Debug.LogError($"CozyCollectorBake: sprite not found at {SnackSpritePath}"); } else { renderer.sprite = sprite; // Phaser setDisplaySize(52, 52) at 100 PPU -> 0.52 x 0.52 world units. var scale = new Vector3( 0.52f / sprite.bounds.size.x, 0.52f / sprite.bounds.size.y, 1f); go.transform.localScale = scale; // Phaser pickup radius = 0.42 * 52px ~= 0.22 world units; a circle // collider's world radius scales by the largest transform axis. collider.radius = 0.22f / Mathf.Max(scale.x, scale.y); } go.AddComponent(); return go; } private static GameObject BuildScorePopupPrefab() { var go = new GameObject("ScorePopup"); var label = go.AddComponent(); AssignDefaultFont(label); label.text = "+10"; label.fontSize = 2.5f; label.alignment = TextAlignmentOptions.Center; label.color = Color.white; var view = go.AddComponent(); WireObjectFields(view, ("label", label)); return go; } private static GameObject BuildCollectBurstPrefab() { var go = new GameObject("CollectBurst"); var particles = go.AddComponent(); var main = particles.main; main.duration = 0.5f; main.loop = false; main.playOnAwake = true; main.startLifetime = 0.45f; main.startSpeed = 2f; main.startSize = 0.15f; main.gravityModifier = 0f; main.startColor = AccentColor; main.stopAction = ParticleSystemStopAction.Destroy; var emission = particles.emission; emission.rateOverTime = 0f; emission.SetBursts(new[] { new ParticleSystem.Burst(0f, 9) }); var shape = particles.shape; shape.shapeType = ParticleSystemShapeType.Sphere; shape.radius = 0.1f; var renderer = particles.GetComponent(); renderer.renderMode = ParticleSystemRenderMode.Billboard; var material = AssetDatabase.GetBuiltinExtraResource("Default-Particle.mat"); if (material == null) material = AssetDatabase.GetBuiltinExtraResource("Sprites-Default.mat"); if (material == null) Debug.LogWarning("CozyCollectorBake: no builtin particle material resolved, assign one manually."); else renderer.sharedMaterial = material; return go; } private static Color ParseHexColor(string hex) { return ColorUtility.TryParseHtmlString(hex, out var color) ? color : Color.magenta; } private static void EnsureAssetFolder(string folderPath) { if (AssetDatabase.IsValidFolder(folderPath)) return; var parent = Path.GetDirectoryName(folderPath)?.Replace('\\', '/'); if (!string.IsNullOrEmpty(parent)) EnsureAssetFolder(parent); AssetDatabase.CreateFolder(parent, Path.GetFileName(folderPath)); } private static void SaveBakedPrefab(GameObject go, string path) { PrefabUtility.SaveAsPrefabAsset(go, path); Object.DestroyImmediate(go); } private static void WireObjectFields(Object component, params (string Field, Object Value)[] fields) { var serialized = new SerializedObject(component); foreach (var (field, value) in fields) { var property = serialized.FindProperty(field); if (property == null) { Debug.LogError($"CozyCollectorBake: field '{field}' not found on {component.GetType().Name}"); continue; } property.objectReferenceValue = value; } serialized.ApplyModifiedPropertiesWithoutUndo(); } private static void AssignDefaultFont(TMP_Text text) { if (TMP_Settings.defaultFontAsset != null) text.font = TMP_Settings.defaultFontAsset; else Debug.LogWarning("CozyCollectorBake: TMP default font asset is not set, assign a font manually."); } } }