角色能力,存档
This commit is contained in:
@@ -197,11 +197,10 @@ namespace BaseGames.Editor.Combat
|
||||
_detailRoot.Add(btnRow);
|
||||
}
|
||||
|
||||
/// <summary>attack1 → attack2 → attack3 连击链数值横排预览。</summary>
|
||||
/// <summary>连击序列数值横排预览。</summary>
|
||||
private void BuildComboPreview(WeaponSO weapon)
|
||||
{
|
||||
// 只在有至少一个连击数据时显示
|
||||
if (weapon.attack1Source == null && weapon.attack2Source == null && weapon.attack3Source == null)
|
||||
if (weapon.groundComboSteps == null || weapon.groundComboSteps.Length == 0)
|
||||
return;
|
||||
|
||||
var section = new Label("连击链预览") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginBottom = 4 } };
|
||||
@@ -210,8 +209,11 @@ namespace BaseGames.Editor.Combat
|
||||
var chain = new VisualElement();
|
||||
chain.AddToClassList("stats-preview");
|
||||
|
||||
void AddSegment(string label, ClipTransition clip, DamageSourceSO src, bool addArrow)
|
||||
for (int i = 0; i < weapon.groundComboSteps.Length; i++)
|
||||
{
|
||||
var step = weapon.groundComboSteps[i];
|
||||
bool addArrow = i < weapon.groundComboSteps.Length - 1;
|
||||
|
||||
var cell = new VisualElement
|
||||
{
|
||||
style =
|
||||
@@ -230,24 +232,21 @@ namespace BaseGames.Editor.Combat
|
||||
}
|
||||
};
|
||||
|
||||
// 段名
|
||||
cell.Add(new Label(label)
|
||||
cell.Add(new Label($"攻击{i + 1}")
|
||||
{
|
||||
style = { fontSize = 10, color = new Color(0.65f, 0.65f, 0.65f) }
|
||||
});
|
||||
|
||||
// Clip 名称
|
||||
string clipName = clip?.Clip != null ? clip.Clip.name : "<无动画>";
|
||||
string clipName = step.clip?.Clip != null ? step.clip.Clip.name : "<无动画>";
|
||||
cell.Add(new Label(clipName)
|
||||
{
|
||||
style = { fontSize = 11, unityFontStyleAndWeight = FontStyle.Bold }
|
||||
});
|
||||
|
||||
// 伤害数值
|
||||
if (src != null)
|
||||
if (step.damageSource != null)
|
||||
{
|
||||
int dmg = Mathf.RoundToInt(src.BaseDamage * src.DamageMultiplier);
|
||||
cell.Add(new Label($"伤害 {dmg} [{src.BreakLevel}]")
|
||||
int dmg = Mathf.RoundToInt(step.damageSource.BaseDamage * step.damageSource.DamageMultiplier);
|
||||
cell.Add(new Label($"伤害 {dmg} [{step.damageSource.BreakLevel}]")
|
||||
{
|
||||
style = { fontSize = 10, color = new Color(1f, 0.7f, 0.3f) }
|
||||
});
|
||||
@@ -266,10 +265,6 @@ namespace BaseGames.Editor.Combat
|
||||
chain.Add(new Label("→") { style = { alignSelf = Align.Center, marginLeft = 2, marginRight = 2 } });
|
||||
}
|
||||
|
||||
AddSegment("攻击1", weapon.attack1Clip, weapon.attack1Source, true);
|
||||
AddSegment("攻击2", weapon.attack2Clip, weapon.attack2Source, true);
|
||||
AddSegment("攻击3", weapon.attack3Clip, weapon.attack3Source, false);
|
||||
|
||||
_detailRoot.Add(chain);
|
||||
|
||||
// 追加空中/上/下攻击的简要行
|
||||
@@ -288,9 +283,9 @@ namespace BaseGames.Editor.Combat
|
||||
});
|
||||
}
|
||||
|
||||
ExtraStat("空中", weapon.airAttackSource);
|
||||
ExtraStat("上挑", weapon.upAttackSource);
|
||||
ExtraStat("下砸", weapon.downAttackSource);
|
||||
ExtraStat("空中", weapon.airComboSteps?[0].damageSource);
|
||||
ExtraStat("上劈", weapon.upStep.damageSource);
|
||||
ExtraStat("下劈", weapon.downStep.damageSource);
|
||||
|
||||
if (extraRow.childCount > 0)
|
||||
_detailRoot.Add(extraRow);
|
||||
|
||||
118
Assets/_Game/Scripts/Editor/Combat/WeaponSOEditor.cs
Normal file
118
Assets/_Game/Scripts/Editor/Combat/WeaponSOEditor.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Player;
|
||||
|
||||
namespace BaseGames.Editor.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// WeaponSO 自定义 Inspector:在 hitBoxPrefab 下方提供一键生成按钮。
|
||||
/// 等同于菜单 BaseGames / Create / Weapon HitBox Prefab,但已预填 weaponId 并自动赋值。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(WeaponSO))]
|
||||
public class WeaponSOEditor : UnityEditor.Editor
|
||||
{
|
||||
private const string OutputFolder = "Assets/_Game/Prefabs/Weapons";
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
var weapon = (WeaponSO)target;
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
EditorGUILayout.LabelField("HitBox Prefab 工具", EditorStyles.boldLabel);
|
||||
|
||||
bool hasId = !string.IsNullOrWhiteSpace(weapon.weaponId);
|
||||
bool hasPrefab = weapon.hitBoxPrefab != null;
|
||||
|
||||
if (!hasId)
|
||||
{
|
||||
EditorGUILayout.HelpBox("请先填写 weaponId,再生成 HitBox Prefab。", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
string prefabPath = $"{OutputFolder}/WPN_{weapon.weaponId}_HitBox.prefab";
|
||||
EditorGUILayout.HelpBox($"输出路径:{prefabPath}", MessageType.None);
|
||||
|
||||
string btnLabel = hasPrefab ? "重新生成 HitBox Prefab" : "一键生成 HitBox Prefab";
|
||||
if (GUILayout.Button(btnLabel))
|
||||
{
|
||||
if (hasPrefab && !EditorUtility.DisplayDialog(
|
||||
"确认重新生成",
|
||||
$"hitBoxPrefab 已有引用,是否覆盖并重新赋值?\n\n{prefabPath}",
|
||||
"覆盖", "取消"))
|
||||
return;
|
||||
|
||||
var prefab = CreateHitBoxPrefab(weapon.weaponId, prefabPath);
|
||||
if (prefab != null)
|
||||
{
|
||||
Undo.RecordObject(weapon, "Assign HitBox Prefab");
|
||||
weapon.hitBoxPrefab = prefab;
|
||||
EditorUtility.SetDirty(weapon);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 创建逻辑 ──────────────────────────────────────────────────────────
|
||||
|
||||
private static GameObject CreateHitBoxPrefab(string weaponId, string assetPath)
|
||||
{
|
||||
EditorScaffoldUtils.EnsureFolder(OutputFolder);
|
||||
|
||||
int layer = LayerMask.NameToLayer("PlayerHitBox");
|
||||
if (layer < 0)
|
||||
{
|
||||
Debug.LogWarning("[WeaponSOEditor] 未找到 Physics Layer 'PlayerHitBox',子节点 Layer 将设为 Default。");
|
||||
layer = 0;
|
||||
}
|
||||
|
||||
string prefabName = $"WPN_{weaponId}_HitBox";
|
||||
var root = new GameObject(prefabName);
|
||||
var instance = root.AddComponent<WeaponHitBoxInstance>();
|
||||
var so = new SerializedObject(instance);
|
||||
|
||||
AddHitBoxChild(root, so, "HitBox_Ground", "_hitBoxGround", layer, new Vector2(1f, 0.5f));
|
||||
AddHitBoxChild(root, so, "HitBox_Up", "_hitBoxUp", layer, new Vector2(0.5f, 1f ));
|
||||
AddHitBoxChild(root, so, "HitBox_Down", "_hitBoxDown", layer, new Vector2(1f, 0.5f));
|
||||
AddHitBoxChild(root, so, "HitBox_Air", "_hitBoxAir", layer, new Vector2(0.5f, 1f ));
|
||||
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
var prefab = PrefabUtility.SaveAsPrefabAsset(root, assetPath);
|
||||
Object.DestroyImmediate(root);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
EditorScaffoldUtils.PingAndSelect(prefab);
|
||||
Debug.Log($"[WeaponSOEditor] 已创建:{assetPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[WeaponSOEditor] Prefab 保存失败:{assetPath}");
|
||||
}
|
||||
|
||||
return prefab;
|
||||
}
|
||||
|
||||
private static void AddHitBoxChild(GameObject root, SerializedObject so,
|
||||
string nodeName, string fieldName,
|
||||
int layer, Vector2 boxSize)
|
||||
{
|
||||
var child = new GameObject(nodeName);
|
||||
child.transform.SetParent(root.transform, false);
|
||||
child.layer = layer;
|
||||
|
||||
var col = child.AddComponent<BoxCollider2D>();
|
||||
col.isTrigger = true;
|
||||
col.size = boxSize;
|
||||
|
||||
var hb = child.AddComponent<HitBox>();
|
||||
var prop = so.FindProperty(fieldName);
|
||||
if (prop != null)
|
||||
prop.objectReferenceValue = hb;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Editor/Combat/WeaponSOEditor.cs.meta
Normal file
11
Assets/_Game/Scripts/Editor/Combat/WeaponSOEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df9abfb2b89aa244bbcc1f4e62694dd6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user