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.");
}
}
}