Files
Cielonos/Assets/Scripts/MainGame/Effects/VFX/VFXData.cs

154 lines
6.4 KiB
C#
Raw Normal View History

2025-11-25 08:19:33 -05:00
using System;
using System.Collections.Generic;
using Cielonos.MainGame.Characters;
using Lean.Pool;
using Sirenix.OdinInspector;
using SoftCircuits.Collections;
using UnityEngine;
using UnityEngine.Serialization;
namespace Cielonos.MainGame
{
[CreateAssetMenu(fileName = "VFXData", menuName = "Cielonos/VFXData")]
public partial class VFXData : SerializedScriptableObject
{
[DictionaryDrawerSettings(KeyLabel = "VFX Name", DisplayMode = DictionaryDisplayOptions.ExpandedFoldout)]
[Searchable]
public OrderedDictionary<string, VFXUnit> collection = new OrderedDictionary<string, VFXUnit>();
2026-06-13 18:43:40 -04:00
public VFXUnit Get(string effectName)
{
if (string.IsNullOrEmpty(effectName)) return null;
return collection.GetValueOrDefault(effectName);
}
2025-11-25 08:19:33 -05:00
}
public partial class VFXData
{
2026-05-23 08:27:50 -04:00
public GameObject SpawnVFX(string vfxName, CharacterBase executor, Vector3 position, Quaternion rotation)
2025-11-25 08:19:33 -05:00
{
2026-05-23 08:27:50 -04:00
VFXUnit vfxUnit = Get(vfxName);
GameObject vfxInstance = VFXObject.Spawn(vfxUnit.mainVFX, executor, position, rotation);
vfxInstance.transform.localScale = vfxUnit.localScale;
return vfxInstance;
2025-11-25 08:19:33 -05:00
}
2026-05-23 08:27:50 -04:00
/// <summary>
/// 生成特效。executor 和 startTransform 必须显式传入,避免共享 ScriptableObject 缓存错误引用。
/// </summary>
public GameObject SpawnVFX(string vfxName, CharacterBase executor, Transform startTransform = null)
2025-11-25 08:19:33 -05:00
{
2026-05-23 08:27:50 -04:00
startTransform ??= executor.transform;
2025-11-25 08:19:33 -05:00
VFXUnit vfxUnit = Get(vfxName);
2025-12-17 04:19:38 -05:00
GameObject vfxInstance = VFXObject.Spawn(vfxUnit.mainVFX, executor, startTransform);
2025-11-25 08:19:33 -05:00
Transform vfxTransform = vfxInstance.transform;
vfxTransform.localPosition = vfxUnit.localPosition;
vfxTransform.localEulerAngles = vfxUnit.localEulerAngles;
vfxTransform.localScale = vfxUnit.localScale;
if (!vfxUnit.keepLocalTransform)
{
vfxTransform.parent = null;
}
return vfxInstance;
}
2026-05-23 08:27:50 -04:00
/// <summary>
/// 生成枪口特效。
/// </summary>
public GameObject SpawnMuzzleVFX(string effectName, CharacterBase executor, Transform muzzleTransform)
2025-11-25 08:19:33 -05:00
{
2026-05-23 08:27:50 -04:00
return VFXObject.Spawn(Get(effectName).muzzleVFX, executor, muzzleTransform);
2025-11-25 08:19:33 -05:00
}
2026-05-23 08:27:50 -04:00
/// <summary>
/// 生成击中特效。
/// </summary>
public GameObject SpawnHitVFX(string effectName, CharacterBase executor, Vector3 hitPosition)
2025-11-25 08:19:33 -05:00
{
2026-05-23 08:27:50 -04:00
return VFXObject.Spawn(Get(effectName).muzzleVFX, executor, hitPosition, Quaternion.identity);
2025-11-25 08:19:33 -05:00
}
}
[Serializable]
2026-01-17 11:35:49 -05:00
[HideReferenceObjectPicker]
2025-11-25 08:19:33 -05:00
public class VFXUnit
{
[BoxGroup("VFX"), LabelWidth(150)]
[PropertyTooltip("特效预制体")]
public GameObject mainVFX;
[BoxGroup("VFX"), LabelWidth(150)]
[PropertyTooltip("枪口特效(可空)")]
public GameObject muzzleVFX;
[BoxGroup("VFX"), LabelWidth(150)]
[PropertyTooltip("击中特效可空通常用于AttackArea获取不需要使用VFXData生成")]
public GameObject hitVFX;
[BoxGroup("VFX"), LabelWidth(150)]
[PropertyTooltip("附加特效(可空)")]
public List<GameObject> otherVFXList;
[BoxGroup("Transform"), LabelWidth(150)]
[PropertyTooltip("特效生成时的位置偏移")]
public Vector3 localPosition = Vector3.zero;
[BoxGroup("Transform"), LabelWidth(150)]
[PropertyTooltip("特效生成时的旋转角度")]
public Vector3 localEulerAngles = Vector3.zero;
[BoxGroup("Transform"), LabelWidth(150)]
[PropertyTooltip("特效生成时的缩放比例")]
public Vector3 localScale = Vector3.one;
[BoxGroup("Transform"), LabelWidth(150)]
[PropertyTooltip("是否在生成后保持特效与父级Transform的联系")]
public bool keepLocalTransform = false;
2026-03-20 12:07:44 -04:00
[Title("Tools")]
[ShowInInspector]
public Vector2 slashScreenDirection => GetSlashScreenDirection(true);
[ShowInInspector]
public Vector2 slashScreenPosition => GetSlashScreenDirection(false);
[OnInspectorInit]
public Vector2 GetSlashScreenDirection(bool isRotation)
{
// 1. 定义屏幕(摄像机)的法线。
// 因为假设了角色和摄像机朝向一致,我们可以直接把这里当做 View Space。
// 摄像机看向 Z+,屏幕平面垂直于 Z 轴,所以法线是 Vector3.forward。
Vector3 screenNormal = Vector3.forward;
// 2. 计算刀光平面的法线。
// 刀光原本是 XZ 平面,法线是 Y+ (Vector3.up)。
// 我们通过欧拉角旋转这个法线。
Quaternion rotation = Quaternion.Euler(localEulerAngles);
Vector3 slashNormal = rotation * Vector3.up;
// 3. 计算交线向量(叉积)。
// 几何原理:两个平面的交线垂直于这两个平面的法线。
Vector3 intersectionLine = Vector3.Cross(slashNormal, screenNormal);
// 4. 转换为 2D 向量。
// 因为 intersectionLine 必定在屏幕平面上(垂直于 screenNormal
// 它的 Z 分量理论上应该是 0或者非常接近 0我们直接取 XY。
Vector2 screenDir = isRotation ?
new Vector2(-intersectionLine.y, intersectionLine.x).normalized :
new Vector2(intersectionLine.x, intersectionLine.y).normalized;
// 5. 处理特殊情况:平行。
// 如果刀光平面和屏幕平行(比如刀光垂直立起来正对着摄像机),
// 叉积结果会是 (0,0,0)。这时候没有“方向”可言。
if (screenDir.sqrMagnitude < 0.0001f)
{
return Vector2.zero; // 或者根据需求返回默认值
}
// 6. 返回结果。
// 将screenDir的x和y都乘以10之后保留小数点后2位得到最终的刀光屏幕方向。
return new Vector2(
(float)Math.Round(screenDir.x * 10f, 2),
(float)Math.Round(screenDir.y * 10f, 2)
);
}
2025-11-25 08:19:33 -05:00
}
}