167 lines
5.4 KiB
C#
167 lines
5.4 KiB
C#
|
|
using System.IO;
|
||
|
|
using LiveOpsExamples;
|
||
|
|
using TMPro;
|
||
|
|
using UnityEditor;
|
||
|
|
using UnityEngine;
|
||
|
|
|
||
|
|
namespace LiveOpsExamples.Editor
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Bakes the Cozy Collector gameplay prefabs. Idempotent: every prefab is
|
||
|
|
/// rebuilt from code and saved over the same fixed asset path.
|
||
|
|
/// </summary>
|
||
|
|
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<CircleCollider2D>();
|
||
|
|
collider.isTrigger = true;
|
||
|
|
collider.radius = 0.3f;
|
||
|
|
|
||
|
|
var renderer = go.AddComponent<SpriteRenderer>();
|
||
|
|
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(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<Snack>();
|
||
|
|
return go;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static GameObject BuildScorePopupPrefab()
|
||
|
|
{
|
||
|
|
var go = new GameObject("ScorePopup");
|
||
|
|
|
||
|
|
var label = go.AddComponent<TextMeshPro>();
|
||
|
|
AssignDefaultFont(label);
|
||
|
|
label.text = "+10";
|
||
|
|
label.fontSize = 2.5f;
|
||
|
|
label.alignment = TextAlignmentOptions.Center;
|
||
|
|
label.color = Color.white;
|
||
|
|
|
||
|
|
var view = go.AddComponent<ScorePopupView>();
|
||
|
|
WireObjectFields(view, ("label", label));
|
||
|
|
return go;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static GameObject BuildCollectBurstPrefab()
|
||
|
|
{
|
||
|
|
var go = new GameObject("CollectBurst");
|
||
|
|
var particles = go.AddComponent<ParticleSystem>();
|
||
|
|
|
||
|
|
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<ParticleSystemRenderer>();
|
||
|
|
renderer.renderMode = ParticleSystemRenderMode.Billboard;
|
||
|
|
var material = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Particle.mat");
|
||
|
|
if (material == null)
|
||
|
|
material = AssetDatabase.GetBuiltinExtraResource<Material>("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.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|