diff --git a/unity/Assets/Editor/ElementsAligner.cs b/unity/Assets/Editor/ElementsAligner.cs
index 51a68cc..4feb777 100644
--- a/unity/Assets/Editor/ElementsAligner.cs
+++ b/unity/Assets/Editor/ElementsAligner.cs
@@ -2,11 +2,123 @@ using UnityEngine;
using UnityEditor;
///
-/// Elements底对齐工具 - 将Elements下所有物体底对齐到Y=0平面
+/// Elements底对齐工具 - 将选中的物体底对齐到同一Y坐标
///
public class ElementsAligner : Editor
{
- [MenuItem("Tools/Elements/底对齐到地面 (Y=0)")]
+ [MenuItem("Tools/Elements/选中物体底对齐")]
+ public static void AlignSelectedToBottom()
+ {
+ var selected = Selection.gameObjects;
+
+ if (selected == null || selected.Length < 2)
+ {
+ EditorUtility.DisplayDialog("提示", "请至少选中2个物体进行底对齐", "确定");
+ return;
+ }
+
+ // 找到所有选中物体中最低的底部Y坐标
+ float lowestBottom = float.MaxValue;
+ var boundsData = new (Transform transform, Bounds bounds)[selected.Length];
+ int validCount = 0;
+
+ for (int i = 0; i < selected.Length; i++)
+ {
+ Transform t = selected[i].transform;
+ Bounds bounds = GetObjectBounds(t);
+
+ if (bounds.size == Vector3.zero)
+ {
+ Debug.LogWarning($"跳过 {t.name} - 无法计算包围盒");
+ continue;
+ }
+
+ boundsData[validCount] = (t, bounds);
+ validCount++;
+
+ if (bounds.min.y < lowestBottom)
+ lowestBottom = bounds.min.y;
+ }
+
+ if (validCount < 2)
+ {
+ EditorUtility.DisplayDialog("提示", "有效物体不足2个,无法对齐", "确定");
+ return;
+ }
+
+ // 将所有物体的底部对齐到最低点
+ Undo.SetCurrentGroupName("选中物体底对齐");
+ int count = 0;
+
+ for (int i = 0; i < validCount; i++)
+ {
+ var (t, bounds) = boundsData[i];
+ Undo.RecordObject(t, $"Align {t.name}");
+
+ float bottomY = bounds.min.y;
+ Vector3 newPosition = t.position;
+ newPosition.y = t.position.y - bottomY + lowestBottom;
+ t.position = newPosition;
+
+ count++;
+ Debug.Log($"✓ 对齐 {t.name}: 底部Y {bottomY:F3} → {lowestBottom:F3}");
+ }
+
+ if (count > 0)
+ {
+ UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
+ UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()
+ );
+ Debug.Log($"完成!底对齐了 {count} 个物体到 Y={lowestBottom:F3}");
+ }
+ }
+
+ [MenuItem("Tools/Elements/选中物体底对齐到Y=0")]
+ public static void AlignSelectedToGround()
+ {
+ var selected = Selection.gameObjects;
+
+ if (selected == null || selected.Length == 0)
+ {
+ EditorUtility.DisplayDialog("提示", "请选中至少1个物体", "确定");
+ return;
+ }
+
+ int count = 0;
+ Undo.SetCurrentGroupName("选中物体底对齐到Y=0");
+
+ for (int i = 0; i < selected.Length; i++)
+ {
+ Transform t = selected[i].transform;
+ Undo.RecordObject(t, $"Align {t.name}");
+
+ Bounds bounds = GetObjectBounds(t);
+
+ if (bounds.size == Vector3.zero)
+ {
+ Debug.LogWarning($"跳过 {t.name} - 无法计算包围盒");
+ continue;
+ }
+
+ float bottomY = bounds.min.y;
+ Vector3 newPosition = t.position;
+ newPosition.y = t.position.y - bottomY;
+ t.position = newPosition;
+
+ count++;
+ Debug.Log($"✓ 对齐 {t.name}: 底部Y {bottomY:F3} → 0");
+ }
+
+ if (count > 0)
+ {
+ UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
+ UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()
+ );
+ Debug.Log($"完成!底对齐了 {count} 个物体到 Y=0");
+ }
+ }
+
+ [MenuItem("Tools/Elements/底对齐到地面 (Y=0) (Elements全部)")]
public static void AlignElementsToGround()
{
GameObject elements = GameObject.Find("Elements");
@@ -23,11 +135,8 @@ public class ElementsAligner : Editor
for (int i = 0; i < elements.transform.childCount; i++)
{
Transform child = elements.transform.GetChild(i);
-
- // 记录到Undo栈
Undo.RecordObject(child, $"Align {child.name}");
- // 获取物体的包围盒
Bounds bounds = GetObjectBounds(child);
if (bounds.size == Vector3.zero)
@@ -36,11 +145,9 @@ public class ElementsAligner : Editor
continue;
}
- // 计算需要移动的距离:让物体底部对齐到Y=0
float bottomY = bounds.min.y;
Vector3 newPosition = child.position;
newPosition.y = child.position.y - bottomY;
-
child.position = newPosition;
count++;
@@ -52,69 +159,10 @@ public class ElementsAligner : Editor
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()
);
-
- EditorUtility.DisplayDialog("完成", $"已对齐 {count} 个物体到地面 (Y=0)", "确定");
Debug.Log($"完成!对齐了 {count} 个物体");
}
}
- [MenuItem("Tools/Elements/底对齐到指定高度")]
- public static void AlignElementsToCustomHeight()
- {
- string input = EditorInputDialog.Show("对齐到指定高度", "请输入目标Y坐标:", "0");
-
- if (input == null) return; // 用户取消
-
- if (!float.TryParse(input, out float targetY))
- {
- EditorUtility.DisplayDialog("错误", "请输入有效的数字", "确定");
- return;
- }
-
- GameObject elements = GameObject.Find("Elements");
-
- if (elements == null)
- {
- EditorUtility.DisplayDialog("错误", "找不到场景中的 'Elements' 物体", "确定");
- return;
- }
-
- int count = 0;
- Undo.SetCurrentGroupName("Align Elements to Custom Height");
-
- for (int i = 0; i < elements.transform.childCount; i++)
- {
- Transform child = elements.transform.GetChild(i);
- Undo.RecordObject(child, $"Align {child.name}");
-
- Bounds bounds = GetObjectBounds(child);
-
- if (bounds.size == Vector3.zero)
- {
- Debug.LogWarning($"跳过 {child.name} - 无法计算包围盒");
- continue;
- }
-
- float bottomY = bounds.min.y;
- Vector3 newPosition = child.position;
- newPosition.y = child.position.y - bottomY + targetY;
-
- child.position = newPosition;
-
- count++;
- Debug.Log($"✓ 对齐 {child.name}: 底部Y从 {bottomY:F3} 到 {targetY:F3}");
- }
-
- if (count > 0)
- {
- UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(
- UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()
- );
-
- EditorUtility.DisplayDialog("完成", $"已对齐 {count} 个物体到 Y={targetY}", "确定");
- }
- }
-
///
/// 获取物体的世界空间包围盒(包括所有子物体和渲染器)
///
diff --git a/unity/Assets/Editor/EnemyBatchCreator.cs b/unity/Assets/Editor/EnemyBatchCreator.cs
new file mode 100644
index 0000000..c751320
--- /dev/null
+++ b/unity/Assets/Editor/EnemyBatchCreator.cs
@@ -0,0 +1,73 @@
+using UnityEngine;
+using UnityEditor;
+using IndianOceanAssets.Engine2_5D;
+
+///
+/// 编辑器工具:在场景中批量创建敌人实例。
+/// 每个敌人使用对应的新预制体(enemy_01~05),保留各自的外观和参数。
+/// 菜单路径:Assets > 创建5个敌人 (enemy_01~05)
+///
+public static class EnemyBatchCreator
+{
+ [MenuItem("Assets/创建5个敌人 (enemy_01~05)")]
+ public static void Create()
+ {
+ // 5个新预制体路径(对应各自的外观)
+ string[] prefabPaths = {
+ "Assets/enemy/Prefabs/enemy_01.prefab",
+ "Assets/enemy/Prefabs/enemy_02.prefab",
+ "Assets/enemy/Prefabs/enemy_03.prefab",
+ "Assets/enemy/Prefabs/enemy_04.prefab",
+ "Assets/enemy/Prefabs/enemy_05.prefab",
+ };
+ string[] names = { "enemy_01", "enemy_02", "enemy_03", "enemy_04", "enemy_05" };
+
+ // 查找当前场景中的玩家位置,围绕玩家分布敌人
+ var player = GameObject.FindWithTag("Player");
+ Vector3 center = player != null ? player.transform.position : Vector3.zero;
+
+ // 5个敌人的偏移位置(围绕玩家分布)
+ Vector3[] offsets = new Vector3[]
+ {
+ new Vector3(5f, 0f, 3f),
+ new Vector3(-5f, 0f, 3f),
+ new Vector3(5f, 0f, -3f),
+ new Vector3(-5f, 0f, -3f),
+ new Vector3(0f, 0f, 6f),
+ };
+
+ Undo.SetCurrentGroupName("创建5个敌人");
+
+ int created = 0;
+ for (int i = 0; i < 5; i++)
+ {
+ var prefab = AssetDatabase.LoadAssetAtPath(prefabPaths[i]);
+ if (prefab == null)
+ {
+ Debug.LogWarning($"[EnemyBatchCreator] 找不到预制体: {prefabPaths[i]},跳过");
+ continue;
+ }
+
+ // 实例化对应的 Prefab 到场景中
+ GameObject enemy = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
+ enemy.name = names[i];
+ enemy.transform.position = center + offsets[i];
+
+ // 注册 Undo
+ Undo.RegisterCreatedObjectUndo(enemy, $"创建 {names[i]}");
+ created++;
+ }
+
+ // 选中刚创建的敌人
+ var all = new GameObject[created];
+ int idx = 0;
+ for (int i = 0; i < 5; i++)
+ {
+ var go = GameObject.Find(names[i]);
+ if (go != null) all[idx++] = go;
+ }
+ Selection.objects = all;
+
+ Debug.Log($"[EnemyBatchCreator] 已创建 {created} 个敌人: enemy_01 ~ enemy_05,使用各自对应的新预制体");
+ }
+}
diff --git a/unity/Assets/Editor/EnemyBatchCreator.cs.meta b/unity/Assets/Editor/EnemyBatchCreator.cs.meta
new file mode 100644
index 0000000..0874659
--- /dev/null
+++ b/unity/Assets/Editor/EnemyBatchCreator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: C3kYvH6qWy5FgJeh7I+nxvf1cNKzdmR6Bo0TQSyK6Lu2aV9lwrgFOmc=
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/unity/Assets/Editor/EnemyPrefabBatchCreator.cs b/unity/Assets/Editor/EnemyPrefabBatchCreator.cs
new file mode 100644
index 0000000..e8142df
--- /dev/null
+++ b/unity/Assets/Editor/EnemyPrefabBatchCreator.cs
@@ -0,0 +1,93 @@
+using UnityEngine;
+using UnityEditor;
+using IndianOceanAssets.Engine2_5D;
+
+///
+/// 编辑器工具:基于原始 Enemy Prefab 批量创建 5 个不同外观的敌人预制体。
+/// 每个预制体使用不同的 Sprite,保留所有原有组件和参数。
+/// 菜单路径:Assets > 创建5个敌人预制体
+///
+public static class EnemyPrefabBatchCreator
+{
+ [MenuItem("Assets/创建5个敌人预制体")]
+ public static void Create()
+ {
+ // 加载原始敌人 Prefab
+ string originalPath = "Assets/2.5D Engine/Enemy.prefab";
+ var originalPrefab = AssetDatabase.LoadAssetAtPath(originalPath);
+ if (originalPrefab == null)
+ {
+ EditorUtility.DisplayDialog("错误", "找不到原始敌人 Prefab: " + originalPath, "确定");
+ return;
+ }
+
+ // 确保输出目录存在
+ string outputDir = "Assets/enemy/Prefabs";
+ if (!AssetDatabase.IsValidFolder(outputDir))
+ {
+ string parent = "Assets/enemy";
+ AssetDatabase.CreateFolder(parent, "Prefabs");
+ }
+
+ // 5个敌人的 Sprite 路径和名称
+ string[] spritePaths = {
+ "Assets/enemy/sprites/enemy1.png",
+ "Assets/enemy/sprites/enemy2.png",
+ "Assets/enemy/sprites/enemy3.png",
+ "Assets/enemy/sprites/enemy4.png",
+ "Assets/enemy/sprites/enemy5.png",
+ };
+ string[] prefabNames = { "enemy_01", "enemy_02", "enemy_03", "enemy_04", "enemy_05" };
+
+ int created = 0;
+
+ for (int i = 0; i < 5; i++)
+ {
+ // 加载 Sprite
+ Sprite sprite = AssetDatabase.LoadAssetAtPath(spritePaths[i]);
+ if (sprite == null)
+ {
+ Debug.LogWarning($"[EnemyPrefabCreator] 找不到 Sprite: {spritePaths[i]},跳过");
+ continue;
+ }
+
+ // 实例化原始 Prefab
+ GameObject instance = Object.Instantiate(originalPrefab);
+ instance.name = prefabNames[i];
+
+ // 替换主 Sprite(查找根物体或 GFX 子物体的 SpriteRenderer)
+ SpriteRenderer mainSR = instance.GetComponentInChildren();
+ if (mainSR != null)
+ {
+ mainSR.sprite = sprite;
+ }
+ else
+ {
+ // 如果没有 SpriteRenderer,添加一个
+ mainSR = instance.AddComponent();
+ mainSR.sprite = sprite;
+ mainSR.sortingOrder = 1;
+ }
+
+ // 保存为新 Prefab
+ string prefabPath = $"{outputDir}/{prefabNames[i]}.prefab";
+ PrefabUtility.SaveAsPrefabAsset(instance, prefabPath);
+ Object.DestroyImmediate(instance);
+
+ created++;
+ Debug.Log($"[EnemyPrefabCreator] 创建: {prefabPath} (Sprite: {sprite.name})");
+ }
+
+ AssetDatabase.Refresh();
+
+ // 高亮输出目录
+ var folder = AssetDatabase.LoadAssetAtPath