Files
Cielonos/Assets/Scripts/MainGame/Effects/VFX/VFXData.cs
SoulliesOfficial 649b7a5ddc 更新
2026-05-23 08:27:50 -04:00

150 lines
6.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>();
public VFXUnit Get(string effectName) => collection[effectName];
}
public partial class VFXData
{
public GameObject SpawnVFX(string vfxName, CharacterBase executor, Vector3 position, Quaternion rotation)
{
VFXUnit vfxUnit = Get(vfxName);
GameObject vfxInstance = VFXObject.Spawn(vfxUnit.mainVFX, executor, position, rotation);
vfxInstance.transform.localScale = vfxUnit.localScale;
return vfxInstance;
}
/// <summary>
/// 生成特效。executor 和 startTransform 必须显式传入,避免共享 ScriptableObject 缓存错误引用。
/// </summary>
public GameObject SpawnVFX(string vfxName, CharacterBase executor, Transform startTransform = null)
{
startTransform ??= executor.transform;
VFXUnit vfxUnit = Get(vfxName);
GameObject vfxInstance = VFXObject.Spawn(vfxUnit.mainVFX, executor, startTransform);
Transform vfxTransform = vfxInstance.transform;
vfxTransform.localPosition = vfxUnit.localPosition;
vfxTransform.localEulerAngles = vfxUnit.localEulerAngles;
vfxTransform.localScale = vfxUnit.localScale;
if (!vfxUnit.keepLocalTransform)
{
vfxTransform.parent = null;
}
return vfxInstance;
}
/// <summary>
/// 生成枪口特效。
/// </summary>
public GameObject SpawnMuzzleVFX(string effectName, CharacterBase executor, Transform muzzleTransform)
{
return VFXObject.Spawn(Get(effectName).muzzleVFX, executor, muzzleTransform);
}
/// <summary>
/// 生成击中特效。
/// </summary>
public GameObject SpawnHitVFX(string effectName, CharacterBase executor, Vector3 hitPosition)
{
return VFXObject.Spawn(Get(effectName).muzzleVFX, executor, hitPosition, Quaternion.identity);
}
}
[Serializable]
[HideReferenceObjectPicker]
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;
[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)
);
}
}
}