150 lines
6.3 KiB
C#
150 lines
6.3 KiB
C#
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)
|
||
);
|
||
}
|
||
}
|
||
} |