Initial commit
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user