2.5d test
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
"BumpCombat.Runtime",
|
||||
"Unity.2D.Sprite.Editor",
|
||||
"Unity.RenderPipelines.Universal.2D.Runtime",
|
||||
"Unity.RenderPipelines.Universal.Runtime",
|
||||
"Unity.InputSystem",
|
||||
"UnityEngine.UI"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Defense.Comparison;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace BumpCombat.Editor
|
||||
{
|
||||
public static class Defense25DComparisonSetup
|
||||
{
|
||||
public const string ScenePath = "Assets/_Project/Scenes/Defense25DComparison.unity";
|
||||
public const string SettingsPath = "Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset";
|
||||
private const string AssetFolder = "Assets/_Project/Art/Defense25DComparison";
|
||||
private const string RendererPath = AssetFolder + "/ComparisonRenderer.asset";
|
||||
|
||||
[MenuItem("BumpCombat/2.5D Comparison/Create Independent Scene")]
|
||||
public static void Generate()
|
||||
{
|
||||
// This command only owns the comparison assets. Never opens or regenerates the main scene.
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
throw new InvalidOperationException("Stop Play mode before generating comparison assets.");
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
EnsureFolder("Assets/_Project/Constants/Defense25DComparison");
|
||||
EnsureFolder(AssetFolder);
|
||||
Defense25DComparisonSettings settings = AssetDatabase.LoadAssetAtPath<Defense25DComparisonSettings>(SettingsPath);
|
||||
if (settings == null)
|
||||
{
|
||||
settings = ScriptableObject.CreateInstance<Defense25DComparisonSettings>();
|
||||
AssetDatabase.CreateAsset(settings, SettingsPath);
|
||||
}
|
||||
settings.Enemy = AssetDatabase.LoadAssetAtPath<EnemyDefinition>("Assets/_Project/Constants/Enemies/Skeleton.asset");
|
||||
settings.EnemyAttackClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(
|
||||
"Assets/_Project/Art/Characters/Skeleton/Animations/Skeleton_Attack.anim");
|
||||
settings.EnemyRadius = 0.275f; // Skeleton prefab circle: radius .22 at root scale 1.25.
|
||||
settings.EnemyColliderOffset = new Vector2(0f, -0.35f); // Skeleton prefab offset: -.28 at root scale 1.25.
|
||||
settings.EnemyVisualScale = 1.25f; // Skeleton prefab root scale.
|
||||
settings.Cyclone = AssetDatabase.FindAssets("CycloneArtifact t:ActiveArtifactDefinition", new[] { "Assets/_Project" })
|
||||
.Select(AssetDatabase.GUIDToAssetPath).Select(AssetDatabase.LoadAssetAtPath<ActiveArtifactDefinition>).FirstOrDefault();
|
||||
if (settings.Enemy == null || settings.Cyclone == null) throw new InvalidOperationException("Existing Skeleton/Cyclone definitions not found.");
|
||||
settings.PlayerArt = ReadCharacter("Swordsman", "Swordsman", "BumpAttack-v1");
|
||||
settings.EnemyArt = ReadCharacter("Skeleton", "Skeleton", "Attack");
|
||||
settings.CutoutMaterial = SpriteMaterial("SpriteCutout", false, Color.white);
|
||||
settings.TransparentMaterial = SpriteMaterial("SpriteTransparent", true, new Color(1f, 1f, 1f, .55f));
|
||||
settings.GroundMaterial = SolidMaterial("Ground", new Color(.12f, .18f, .2f));
|
||||
settings.WallMaterial = SolidMaterial("Wall", new Color(.48f, .51f, .48f));
|
||||
settings.PathMaterial = SolidMaterial("Route", new Color(.22f, .34f, .31f));
|
||||
settings.BaseMaterial = SolidMaterial("Base", new Color(.45f, .77f, .82f));
|
||||
settings.ShadowMaterial = SpriteMaterial("GroundShadow", true, new Color(0f, 0f, 0f, .35f));
|
||||
settings.BoundaryMaterial = SolidMaterial("ColliderBoundary", new Color(.38f, .71f, .55f));
|
||||
settings.HitBoundaryMaterial = SolidMaterial("CycloneHitBoundary", new Color(1f, .72f, .18f));
|
||||
EditorUtility.SetDirty(settings);
|
||||
int rendererIndex = AppendComparisonRenderer();
|
||||
if (AssetDatabase.LoadAssetAtPath<SceneAsset>(ScenePath) == null)
|
||||
{
|
||||
Scene previous = SceneManager.GetActiveScene();
|
||||
// A fresh batch Editor has an untitled scene which Unity cannot extend additively.
|
||||
// Interactive Editors retain their scenes and unsaved work.
|
||||
bool replaceBatchStartup = Application.isBatchMode && string.IsNullOrEmpty(previous.path);
|
||||
Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene,
|
||||
replaceBatchStartup ? NewSceneMode.Single : NewSceneMode.Additive);
|
||||
SceneManager.SetActiveScene(scene);
|
||||
try
|
||||
{
|
||||
GameObject root = new("Physics3D comparison (independent)");
|
||||
var controller = root.AddComponent<Defense25DComparison>();
|
||||
GameObject cameraObject = new("Comparison Camera - dedicated Universal renderer");
|
||||
Camera camera = cameraObject.AddComponent<Camera>();
|
||||
camera.orthographic = true;
|
||||
camera.orthographicSize = settings.CameraSize;
|
||||
camera.backgroundColor = new Color(.055f, .08f, .1f);
|
||||
camera.clearFlags = CameraClearFlags.SolidColor;
|
||||
camera.allowHDR = false;
|
||||
camera.allowMSAA = false;
|
||||
camera.nearClipPlane = .1f;
|
||||
camera.farClipPlane = 100f;
|
||||
camera.transform.rotation = Quaternion.Euler(settings.CameraPitch, 0f, 0f);
|
||||
camera.transform.position = new Vector3(0f, settings.GroundY, 0f) - camera.transform.forward * 25f;
|
||||
camera.GetUniversalAdditionalCameraData().SetRenderer(rendererIndex);
|
||||
cameraObject.AddComponent<AudioListener>();
|
||||
Defense25DView view = cameraObject.AddComponent<Defense25DView>();
|
||||
SerializedObject serialized = new(controller);
|
||||
serialized.FindProperty("settings").objectReferenceValue = settings;
|
||||
serialized.FindProperty("view").objectReferenceValue = view;
|
||||
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||
CreateMap(settings);
|
||||
EditorSceneManager.SaveScene(scene, ScenePath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!replaceBatchStartup)
|
||||
{
|
||||
EditorSceneManager.CloseScene(scene, true);
|
||||
if (previous.IsValid() && previous.isLoaded) SceneManager.SetActiveScene(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
stopwatch.Stop();
|
||||
Debug.Log($"DEFENSE25D_SETUP scene={ScenePath}; renderer={rendererIndex}; setupMilliseconds={stopwatch.ElapsedMilliseconds}; PNG edits=0; frame edits=0; Front/Back edits=0");
|
||||
}
|
||||
|
||||
public static void SyncSolution()
|
||||
{
|
||||
Type type = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType("UnityEditor.SyncVS")).FirstOrDefault(t => t != null);
|
||||
var method = type?.GetMethod("SyncSolution", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
|
||||
if (method == null) throw new InvalidOperationException("Unity SyncVS.SyncSolution not found.");
|
||||
method.Invoke(null, null);
|
||||
Debug.Log("DEFENSE25D_SOLUTION_SYNCED");
|
||||
}
|
||||
|
||||
public static void SyncDefenseSkeletonOnly()
|
||||
{
|
||||
Defense25DComparisonSettings settings = AssetDatabase.LoadAssetAtPath<Defense25DComparisonSettings>(SettingsPath);
|
||||
EnemyDefinition skeleton = AssetDatabase.LoadAssetAtPath<EnemyDefinition>("Assets/_Project/Constants/Enemies/Skeleton.asset");
|
||||
if (settings == null || skeleton == null)
|
||||
throw new InvalidOperationException("Existing comparison settings or Skeleton definition not found.");
|
||||
settings.Enemy = skeleton;
|
||||
settings.EnemyAttackClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(
|
||||
"Assets/_Project/Art/Characters/Skeleton/Animations/Skeleton_Attack.anim");
|
||||
settings.EnemyArt = ReadCharacter("Skeleton", "Skeleton", "Attack");
|
||||
EditorUtility.SetDirty(settings);
|
||||
AssetDatabase.SaveAssets();
|
||||
Debug.Log("DEFENSE25D_SKELETON_SETTINGS_SYNCED settingsOnly=true; sceneRegenerated=false");
|
||||
}
|
||||
|
||||
private static ComparisonSpriteFrames ReadCharacter(string folder, string prefix, string attack)
|
||||
{
|
||||
Sprite[] Read(string suffix) => AssetDatabase.LoadAllAssetsAtPath($"Assets/_Project/Art/Characters/{folder}/{prefix}_{suffix}.png")
|
||||
.OfType<Sprite>().OrderBy(s => int.TryParse(s.name[(s.name.LastIndexOf('_') + 1)..], out int n) ? n : 0).ToArray();
|
||||
var frames = new ComparisonSpriteFrames { Idle = Read("Idle"), Walk = Read("Walk"), Attack = Read(attack), Hurt = Read("Hurt"), Death = Read("Death") };
|
||||
if (frames.Idle.Length == 0) throw new InvalidOperationException($"Existing {prefix} idle sprites missing.");
|
||||
return frames;
|
||||
}
|
||||
|
||||
private static int AppendComparisonRenderer()
|
||||
{
|
||||
UniversalRendererData renderer = AssetDatabase.LoadAssetAtPath<UniversalRendererData>(RendererPath);
|
||||
if (renderer == null)
|
||||
{
|
||||
renderer = ScriptableObject.CreateInstance<UniversalRendererData>();
|
||||
AssetDatabase.CreateAsset(renderer, RendererPath);
|
||||
}
|
||||
UniversalRenderPipelineAsset pipeline = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset
|
||||
?? AssetDatabase.LoadAssetAtPath<UniversalRenderPipelineAsset>("Assets/Settings/UniversalRP.asset");
|
||||
if (pipeline == null) throw new InvalidOperationException("Active Universal pipeline not found.");
|
||||
SerializedObject serialized = new(pipeline);
|
||||
SerializedProperty list = serialized.FindProperty("m_RendererDataList");
|
||||
for (int i = 0; i < list.arraySize; i++)
|
||||
if (list.GetArrayElementAtIndex(i).objectReferenceValue == renderer) return i;
|
||||
int index = list.arraySize;
|
||||
list.InsertArrayElementAtIndex(index);
|
||||
list.GetArrayElementAtIndex(index).objectReferenceValue = renderer;
|
||||
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||
return index;
|
||||
}
|
||||
|
||||
private static Material SpriteMaterial(string name, bool alpha, Color color)
|
||||
{
|
||||
Material material = GetMaterial(name, "BumpCombat/Comparison Sprite Depth");
|
||||
material.SetColor("_BaseColor", color);
|
||||
material.SetFloat("_Cutoff", alpha ? .001f : .4f);
|
||||
material.SetFloat("_ZWrite", alpha ? 0f : 1f);
|
||||
material.SetFloat("_SrcBlend", alpha ? (float)BlendMode.SrcAlpha : (float)BlendMode.One);
|
||||
material.SetFloat("_DstBlend", alpha ? (float)BlendMode.OneMinusSrcAlpha : (float)BlendMode.Zero);
|
||||
material.renderQueue = alpha ? (int)RenderQueue.Transparent : (int)RenderQueue.AlphaTest;
|
||||
EditorUtility.SetDirty(material);
|
||||
return material;
|
||||
}
|
||||
|
||||
private static Material SolidMaterial(string name, Color color)
|
||||
{
|
||||
Material material = GetMaterial(name, "Universal Render Pipeline/Unlit");
|
||||
material.SetColor("_BaseColor", color);
|
||||
EditorUtility.SetDirty(material);
|
||||
return material;
|
||||
}
|
||||
|
||||
private static Material GetMaterial(string name, string shaderName)
|
||||
{
|
||||
string path = $"{AssetFolder}/{name}.mat";
|
||||
Material material = AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
if (material != null) return material;
|
||||
Shader shader = Shader.Find(shaderName);
|
||||
if (shader == null) throw new InvalidOperationException($"Shader missing: {shaderName}");
|
||||
material = new Material(shader) { name = name };
|
||||
AssetDatabase.CreateAsset(material, path);
|
||||
return material;
|
||||
}
|
||||
|
||||
private static void CreateMap(Defense25DComparisonSettings settings)
|
||||
{
|
||||
Transform map = new GameObject("Ground and Physics3D obstacles").transform;
|
||||
GameObject Box(string name, Vector3 center, Vector3 size, Material material, bool collision)
|
||||
{
|
||||
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
go.name = name;
|
||||
go.transform.SetParent(map, false);
|
||||
go.transform.position = center + Vector3.up * settings.GroundY;
|
||||
go.transform.localScale = size;
|
||||
go.GetComponent<Renderer>().sharedMaterial = material;
|
||||
if (collision) go.layer = Defense25DComparisonSettings.TerrainLayer;
|
||||
else UnityEngine.Object.DestroyImmediate(go.GetComponent<Collider>());
|
||||
return go;
|
||||
}
|
||||
Vector2 extents = settings.MapHalfExtents;
|
||||
Box("XZ ground", new Vector3(0f, -.12f, 0f), new Vector3(extents.x * 2f, .2f, extents.y * 2f), settings.GroundMaterial, false);
|
||||
Box("North boundary", new Vector3(0f, .6f, extents.y), new Vector3(extents.x * 2f + .5f, 1.2f, .5f), settings.WallMaterial, true);
|
||||
Box("South boundary", new Vector3(0f, .6f, -extents.y), new Vector3(extents.x * 2f + .5f, 1.2f, .5f), settings.WallMaterial, true);
|
||||
Box("West boundary", new Vector3(-extents.x, .6f, 0f), new Vector3(.5f, 1.2f, extents.y * 2f), settings.WallMaterial, true);
|
||||
Box("East boundary", new Vector3(extents.x, .6f, 0f), new Vector3(.5f, 1.2f, extents.y * 2f), settings.WallMaterial, true);
|
||||
Box("Central wall - slide and occlusion", new Vector3(-1f, .55f, -.15f), new Vector3(3.2f, 1.1f, 1.5f), settings.WallMaterial, true);
|
||||
Box("South obstacle", new Vector3(4f, .75f, -2.5f), new Vector3(2f, 1.5f, 1.5f), settings.WallMaterial, true);
|
||||
for (int i = 0; i < settings.Route.Length - 1; i++)
|
||||
{
|
||||
Vector2 a = settings.Route[i], b = settings.Route[i + 1];
|
||||
GameObject path = Box($"Route segment {i + 1}", Defense25DActor.OnGround((a + b) * .5f, -.005f),
|
||||
new Vector3(.65f, .015f, Vector2.Distance(a, b)), settings.PathMaterial, false);
|
||||
path.transform.rotation = Quaternion.LookRotation(Defense25DActor.OnGround(b - a));
|
||||
}
|
||||
Box("Portal", Defense25DActor.OnGround(settings.Route[0], .06f), new Vector3(1f, .12f, 1f), settings.PathMaterial, false);
|
||||
Vector2 end = settings.Route[^1];
|
||||
Box("Base - arrival marker", Defense25DActor.OnGround(end, .2f), new Vector3(1f, .4f, 1f), settings.BaseMaterial, false);
|
||||
}
|
||||
|
||||
private static void EnsureFolder(string path)
|
||||
{
|
||||
if (AssetDatabase.IsValidFolder(path)) return;
|
||||
string parent = Path.GetDirectoryName(path).Replace('\\', '/');
|
||||
EnsureFolder(parent);
|
||||
AssetDatabase.CreateFolder(parent, Path.GetFileName(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3353e1ca60584c7591e353d3dbf7568c
|
||||
Reference in New Issue
Block a user