Bezi回来了

This commit is contained in:
SoulliesOfficial
2026-04-28 15:46:32 -04:00
parent 7379583165
commit 0902ca8a9e
56 changed files with 3285 additions and 3803 deletions

View File

@@ -74,7 +74,7 @@ namespace Cielonos.MainGame.Characters.AI
if (willTurnToTargetAtStart)
{
self.movementSc.TurnToTarget(target);
self.movementSc.SmartTurnToTarget(target);
}
if (willAdjustAdsorption)

View File

@@ -83,7 +83,7 @@ namespace Cielonos.MainGame.Characters.AI
if (willTurnToTarget)
{
self.movementSc.TurnToTarget(target);
self.movementSc.SmartTurnToTarget(target);
}
if (willAdjustAdsorption)

View File

@@ -47,9 +47,28 @@ namespace Cielonos.MainGame.Characters
}
}
public void TurnToTarget(CharacterBase target, float duration = 0.2f)
public void SmartTurnToTarget(CharacterBase target, float maxTurnAngle = 150f)
{
characterTransform.DOLookAt(target.transform.position, duration, AxisConstraint.Y).Play();
Vector3 directionToTarget = (target.centerPoint.position - owner.centerPoint.position).Flatten();
if (directionToTarget.sqrMagnitude < 0.001f) return;
float angleToTarget = Vector3.SignedAngle(owner.transform.forward, directionToTarget, Vector3.up);
float absAngle = Mathf.Abs(angleToTarget);
// 超过最大角度不转身
if (absAngle > maxTurnAngle) return;
// 小角度瞬间转身
if (absAngle <= 45f)
{
owner.transform.rotation = Quaternion.LookRotation(directionToTarget);
}
// 大角度平滑转身
else
{
float duration = Mathf.Lerp(0.1f, 0.2f, (absAngle - 45f) / 135f);
owner.transform.DORotateQuaternion(Quaternion.LookRotation(directionToTarget), duration).Play();
}
}
public void TurnToTargetByAngle(CharacterBase target, float angularSpeed)

View File

@@ -1,5 +1,6 @@
using Cielonos.MainGame.Effects.Feedback;
using SLSUtilities.General;
using Unity.Mathematics;
using UnityEngine;
namespace Cielonos.MainGame.Characters
@@ -22,9 +23,6 @@ namespace Cielonos.MainGame.Characters
{
player.landMovementSc.TurnToDirection(direction, 0f);
}
// 计算冲刺方向用于相机震动方向设置
// Dash反馈的方向设置将在DashStart中通过新Feedback系统统一处理
}
}
@@ -32,10 +30,15 @@ namespace Cielonos.MainGame.Characters
{
player.landMovementSc.isDashing = true;
player.audioSc.PlayDashSound();
// 使用新Feedback系统播放Dash反馈
player.feedbackSc.PlayFeedback("Dash");
Vector3 playerForward = player.transform.forward.Flatten();
Vector3 cameraForward = player.viewSc.playerCamera.transform.forward.Flatten();
float forwardDot = Vector3.Dot(playerForward, cameraForward);
//如果角色和相机的角度相差120度以上
//播放普通Dash反馈
//播放背向Dash反馈
player.feedbackSc.PlayFeedback(forwardDot < -0.5f ? "Dash_NoCameraRotation" : "Dash");
//player.renderSc.dashTrails.ForEach(ds => ds.active = true);
//player.renderSc.dashTrails.ForEach(ds => ds.Restart());

View File

@@ -117,11 +117,20 @@ namespace Cielonos.MainGame.Characters
}
};
inputActions.Player.LockOnTarget.performed += ctx =>
inputActions.Player.LockonTarget.performed += ctx =>
{
if (ctx.performed && isCursorLocked.Value)
{
operation.LockOnTarget();
operation.LockonTarget();
}
};
inputActions.Player.SelectLockonTarget.performed += ctx =>
{
if (ctx.performed && isCursorLocked.Value)
{
Debug.Log("Value: " + ctx.ReadValue<float>());
operation.SelectLockonTarget(ctx.ReadValue<float>());
}
};

View File

@@ -159,7 +159,12 @@ namespace Cielonos.MainGame.Characters.Inventory
comboSm?[comboTreeName].SuspendThenSetup(actionCoolDownTime);
if (target != null)
{
if (autoRotate) player.landMovementSc.TurnToTarget(target);
if (autoRotate)
{
float angleLimit = player.viewSc.lockTargetModule.isLocking ? 240 : 150;
player.landMovementSc.SmartTurnToTarget(target, angleLimit);
}
if (keepAdsorption)
{
funcAnimSm.currentRuntimeFuncAnim.AddUpdateEvent(new SetRootAdsorptionAdjustment.Keep(target, adsorptionMinDistance));
@@ -185,13 +190,15 @@ namespace Cielonos.MainGame.Characters.Inventory
Debug.LogWarning($"没有找到名为 'Camera' 的轨道,请检查 FeedbackData '{feedBackName}' 的设置。");
return;
}
if (cameraTrack.clips.Find(clip => clip.action is CameraPositionShakeAction)?.action is CameraPositionShakeAction positionShakeAction)
var positionShakeAction = feedBackData.Action<CameraPositionShakeAction>("Camera");
if (positionShakeAction != null)
{
positionShakeAction.amplitude = swingPosition;
}
if (cameraTrack.clips.Find(clip => clip.action is CameraRotationShakeAction)?.action is CameraRotationShakeAction rotationShakeAction)
var rotationShakeAction = feedBackData.Action<CameraRotationShakeAction>("Camera");
if (rotationShakeAction != null)
{
rotationShakeAction.amplitude = swingRotation;
}

View File

@@ -54,7 +54,7 @@ namespace Cielonos.MainGame.Characters
eventSm.onHealthChanged.InsertByPriority("UI_HealthBarUpdate", new PrioritizedAction<float>(UI_HealthBarUpdate));
eventSm.onEnergyChanged.InsertByPriority("UI_EnergyBarUpdate", new PrioritizedAction<float>(UI_EnergyBarUpdate));
eventSm.onDodgeStart.InsertByPriority("Feedback_DodgeStart", new PrioritizedAction(() => { feedbackSc.PlayFeedback("PerfectDodge"); }));
//eventSm.onDodgeStart.InsertByPriority("Feedback_DodgeStart", new PrioritizedAction(() => { feedbackSc.PlayFeedback("PerfectDodge"); }));
eventSm.onNormalDodgeSuccess.InsertByPriority("Feedback_NormalDodge", new PrioritizedAction<AttackAreaBase, DodgeSource>(Feedback_NormalDodge));
eventSm.onPerfectDodgeSuccess.InsertByPriority("Feedback_PerfectDodge", new PrioritizedAction<AttackAreaBase, DodgeSource>(Feedback_PerfectDodge));
}
@@ -113,14 +113,14 @@ namespace Cielonos.MainGame.Characters
BreakthroughType breakthroughType = attackArea.attackSm.attackValue.breakthroughType;
if(breakthroughType == BreakthroughType.None) return;
string feedbackName = "GetHit" + breakthroughType.ToString();
feedbackSc[feedbackName]?.Play();
feedbackSc.PlayFeedback(feedbackName);
}
private void Feedback_GetAttacked(AttackAreaBase attackArea, AttackResult attackResult)
{
float ratio = attackResult.finalDamage / (attributeSm["MaximumHealth"] * 0.2f);
float intensity = Mathf.Lerp(0.25f, 1f, ratio);
feedbackSc["GetAttacked"]?.Play();
//feedbackSc.GetFeedbackData("GetAttacked")
}
private void Feedback_PerfectDodge(AttackAreaBase attackArea, DodgeSource dodgeSource)

View File

@@ -1,8 +1,10 @@
using System;
using Cielonos.MainGame.Effects.Feedback;
using DG.Tweening;
using SLSUtilities.General;
using SLSUtilities.FunctionalAnimation;
using UniRx;
using Unity.Cinemachine;
using UnityEngine;
namespace Cielonos.MainGame.Characters
@@ -249,6 +251,8 @@ namespace Cielonos.MainGame.Characters
float horizontalSpeed = horizontalMovement.magnitude / DeltaTime;
float remapFactor = Mathf.InverseLerp(10f, 15f, horizontalSpeed);
PostProcessingManager.Instance.speedLinesSm.SetRemap(1 - remapFactor);
//player.viewSc.cameraFovSm.UpdateFovBySpeed(horizontalSpeed, DeltaTime);
}
else
{

View File

@@ -23,7 +23,8 @@ namespace Cielonos.MainGame.Characters
public event Action<Vector3, float> OnDash;
public event Action<float> OnDodge;
public event Action OnLockOnTarget;
public event Action OnLockonTarget;
public event Action<float> OnSelectLockonTarget;
public event Action OnWalkPress;
public event Action OnWalkRelease;
@@ -70,7 +71,9 @@ namespace Cielonos.MainGame.Characters
public void Dodge(float length = -1) => OnDodge?.Invoke(length);
public void LockOnTarget() => OnLockOnTarget?.Invoke();
public void LockonTarget() => OnLockonTarget?.Invoke();
public void SelectLockonTarget(float direction) => OnSelectLockonTarget?.Invoke(direction);
public void WalkPress() => OnWalkPress?.Invoke();

View File

@@ -0,0 +1,134 @@
using Cielonos.MainGame.Effects.Feedback;
using DG.Tweening;
using Unity.Cinemachine;
using UnityEngine;
namespace Cielonos.MainGame.Characters
{
public class CameraFovSubmodule : SubmoduleBase<PlayerViewSubcontroller>
{
private Player player => owner.player;
private PlayerViewSubcontroller viewSc => owner;
[Header("基础FOV配置")] [Tooltip("静止时的基础FOV")]
public float baseFov = 45f;
[Tooltip("FOV最小值")] public float minFov = 40f;
[Tooltip("FOV最大值疾奔时")] public float maxFov = 55f;
[Header("速度阈值")] [Tooltip("开始FOV变化的最小速度")]
public float speedThreshold = 3f;
[Tooltip("达到最大FOV的速度")] public float maxSpeedThreshold = 15f;
[Header("过渡设置")] [Tooltip("FOV变化的速度")] public float fovChangeSpeed = 5f;
[Tooltip("是否平滑过渡FOV")] public bool useSmoothTransition = true;
private float currentFov;
private float targetFov;
private CinemachineCamera currentCamera => viewSc.currentCamera;
public CameraFovSubmodule(PlayerViewSubcontroller owner, float baseFov, float minFov,
float maxFov, float speedThreshold, float maxSpeedThreshold, float fovChangeSpeed, bool useSmoothTransition) : base(owner)
{
this.baseFov = baseFov;
this.minFov = minFov;
this.maxFov = maxFov;
this.speedThreshold = speedThreshold;
this.maxSpeedThreshold = maxSpeedThreshold;
this.fovChangeSpeed = fovChangeSpeed;
this.useSmoothTransition = useSmoothTransition;
this.currentFov = baseFov;
this.targetFov = baseFov;
}
/// <summary>
/// 根据水平速度更新FOV
/// </summary>
/// <param name="horizontalSpeed">水平速度(单位/秒)</param>
/// <param name="deltaTime">帧时间</param>
public void UpdateFovBySpeed(float horizontalSpeed, float deltaTime)
{
// 计算目标FOV
targetFov = CalculateTargetFov(horizontalSpeed);
// 平滑过渡
if (useSmoothTransition)
{
currentFov = Mathf.Lerp(currentFov, targetFov, deltaTime * fovChangeSpeed);
}
else
{
currentFov = targetFov;
}
// 应用FOV
ApplyFov(currentFov);
}
/// <summary>
/// 根据速度计算目标FOV
/// </summary>
private float CalculateTargetFov(float speed)
{
if (speed < speedThreshold)
{
return baseFov;
}
// 使用InverseLerp将速度映射到0-1范围
float t = Mathf.InverseLerp(speedThreshold, maxSpeedThreshold, speed);
// 使用SmoothStep使过渡更平滑
t = Mathf.SmoothStep(0f, 1f, t);
return Mathf.Lerp(baseFov, maxFov, t);
}
/// <summary>
/// 应用FOV到相机
/// </summary>
private void ApplyFov(float fov)
{
fov = Mathf.Clamp(fov, minFov, maxFov);
LensSettings lens = currentCamera.Lens;
lens.FieldOfView = fov;
currentCamera.Lens = lens;
}
/// <summary>
/// 立即设置FOV不使用平滑过渡
/// </summary>
public void SetFovImmediate(float fov)
{
currentFov = fov;
targetFov = fov;
ApplyFov(fov);
}
/// <summary>
/// 平滑过渡到指定FOV
/// </summary>
public void SetFovSmooth(float targetFovValue, float duration)
{
DOTween.To(() => currentFov, x =>
{
currentFov = x;
ApplyFov(x);
}, targetFovValue, duration)
.SetEase(Ease.OutQuad);
targetFov = targetFovValue;
}
/// <summary>
/// 重置为默认FOV
/// </summary>
public void ResetToBaseFov()
{
targetFov = baseFov;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1a007afcee9fd864cb29a8405fdc4d71

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using DG.Tweening;
using SickscoreGames.HUDNavigationSystem;
using UniRx;
@@ -31,8 +32,13 @@ namespace Cielonos.MainGame.Characters
/// 是否正在使用锁定目标摄像机
/// </summary>
public bool isUsingLockTargetCamera => isLocking && isAutoRotate;
public bool isDuringSwitch;
public bool isDuringCameraSwitch;
private const float CameraSwitchCooldown = 0.25f;
public CharacterBase lockTarget;
private float lastTargetSwitchTime;
private const float TargetSwitchCooldown = 0.25f;
public Transform targetPoint;
private Tweener iconTween;
@@ -40,20 +46,27 @@ namespace Cielonos.MainGame.Characters
{
isLocking = false;
isAutoRotate = false;
isDuringSwitch = false;
isDuringCameraSwitch = false;
lockTarget = null;
targetPoint = null;
}
public void Update()
{
if (isUsingLockTargetCamera && !isDuringSwitch)
if (isUsingLockTargetCamera && !isDuringCameraSwitch)
{
viewSc.cameraRoot.LookAt(targetPoint);
float distance = (targetPoint.position - viewSc.cameraRoot.transform.position).Flatten().magnitude;
if (isUsingLockTargetCamera && distance < 1f)
if (targetPoint != null)
{
UnlockTarget();
// 平滑跟随目标
Vector3 currentRotation = viewSc.cameraRoot.eulerAngles;
Vector3 targetDirection = targetPoint.position - viewSc.cameraRoot.position;
Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
viewSc.cameraRoot.rotation = Quaternion.Slerp(
viewSc.cameraRoot.rotation,
targetRotation,
1f - Mathf.Exp(-10f * Time.deltaTime)
);
}
}
}
@@ -75,7 +88,7 @@ namespace Cielonos.MainGame.Characters
public void LockTarget(bool isAutoRotate)
{
if(isDuringSwitch) return;
if(isDuringCameraSwitch) return;
CharacterBase target = BattleManager.EnemySm.GetNearestEnemy(50f);
@@ -84,7 +97,7 @@ namespace Cielonos.MainGame.Characters
this.isLocking = true;
this.isAutoRotate = isAutoRotate;
this.lockTarget = target;
this.isDuringSwitch = true;
this.isDuringCameraSwitch = true;
if (isAutoRotate)
{
@@ -94,14 +107,14 @@ namespace Cielonos.MainGame.Characters
viewSc.stateDrivenCamera.GetComponent<Animator>().SetBool("isLockTarget", true);
viewSc.cameraRoot.DOLookAt(targetPoint.position, 0.5f)
.SetEase(Ease.InOutSine)
.OnComplete(() => { isDuringSwitch = false; })
.OnComplete(() => { isDuringCameraSwitch = false; })
.Play();
}
else
{
Observable.Timer(TimeSpan.FromSeconds(0.5f)).First().Subscribe(_ =>
{
isDuringSwitch = false;
isDuringCameraSwitch = false;
});
}
@@ -114,7 +127,7 @@ namespace Cielonos.MainGame.Characters
public void UnlockTarget()
{
if(isDuringSwitch) return;
if(isDuringCameraSwitch) return;
Vector3 currentEuler = viewSc.playerCamera.transform.rotation.eulerAngles;
@@ -134,12 +147,76 @@ namespace Cielonos.MainGame.Characters
orbitalFollow.HorizontalAxis.Value = newYaw;
orbitalFollow.VerticalAxis.Value = newPitch;
if (lockTarget != null)
{
lockTarget.navigationElement.showIndicator = false;
}
this.isLocking = false;
this.isAutoRotate = false;
this.lockTarget.navigationElement.showIndicator = false;
this.lockTarget = null;
this.targetPoint = null;
viewSc.stateDrivenCamera.GetComponent<Animator>().SetBool("isLockTarget", false);
}
/// <summary>
/// 切换锁定目标
/// </summary>
public void SwitchTarget(float direction)
{
if (!isLocking || isDuringCameraSwitch) return;
if (Time.time - lastTargetSwitchTime < TargetSwitchCooldown) return;
List<CharacterBase> sortedEnemies = BattleManager.EnemySm.GetVisibleEnemiesSortedByScreenX();
if (sortedEnemies.Count <= 1) return;
int currentIndex = sortedEnemies.IndexOf(lockTarget);
if (currentIndex < 0)
{
currentIndex = 0;
}
int dir = direction > 0 ? -1 : 1;
int newIndex = currentIndex + dir;
// 边界检查(无循环)
if (newIndex < 0 || newIndex >= sortedEnemies.Count)
{
return;
}
CharacterBase newTarget = sortedEnemies[newIndex];
// 目标相同检查
if (newTarget == lockTarget)
{
return;
}
lastTargetSwitchTime = Time.time;
SetNewTarget(sortedEnemies[newIndex]);
}
private void SetNewTarget(CharacterBase newTarget)
{
if (lockTarget != null)
{
lockTarget.navigationElement.showIndicator = false;
}
lockTarget = newTarget;
targetPoint = newTarget.bodyPartsSc.cameraLockingPoint ?? newTarget.bodyPartsSc.staticCenterPoint;
if (isUsingLockTargetCamera)
{
viewSc.lockingTargetCamera.LookAt = targetPoint;
}
lockTarget.navigationElement.showIndicator = true;
Image icon = lockTarget.navigationElement.Indicator.OnscreenIcon;
iconTween?.Kill(true);
iconTween = icon.GetComponent<RectTransform>().DOScale(1f, 0.3f).From(0.5f).SetEase(Ease.OutQuart).Play();
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Cielonos.MainGame.Characters
public CinemachineCamera lockingTargetCamera;
public CharacterBase testEnemy;
public CameraFovSubmodule cameraFovSm;
public CameraRotationSubmodule cameraRotationSm;
public OcclusionFadeSubmodule occlusionFadeSm;
public LockTargetSubmodule lockTargetModule;
@@ -30,10 +31,14 @@ namespace Cielonos.MainGame.Characters
public override void Initialize()
{
base.Initialize();
cameraFovSm = new CameraFovSubmodule(this, 30f, 30f, 40f, 0.1f, 10f, 5f, true);
cameraRotationSm = new CameraRotationSubmodule(this, player.transform.eulerAngles.y);
occlusionFadeSm = new OcclusionFadeSubmodule(this);
lockTargetModule = new LockTargetSubmodule(this);
cameraFOV = new LerpFloat(30f, 2f);
player.operationSc.OnLockonTarget += lockTargetModule.SwitchLockState;
player.operationSc.OnSelectLockonTarget += lockTargetModule.SwitchTarget;
}
private void Start()
@@ -43,11 +48,6 @@ namespace Cielonos.MainGame.Characters
private void Update()
{
if (Keyboard.current.tabKey.wasPressedThisFrame)
{
lockTargetModule.SwitchLockState();
}
/*cameraFOV.targetValue = owner.landMovementSc.isSprinting ? 40f : 30f;
cameraFOV.Update(owner.selfTimeSm.DeltaTime);
freeLookCamera.Lens.FieldOfView = cameraFOV.currentValue;*/

View File

@@ -12,29 +12,21 @@ namespace Cielonos.MainGame.Effects.Feedback
[Serializable]
public struct FloatCurveChannel
{
/// <summary>
/// 是否启用此通道。
/// </summary>
public bool active;
/// <summary>
/// 震动曲线X轴为归一化时间[0,1]Y轴为强度[0,1]。
/// </summary>
[ShowIf("active")]
[ShakeCurvePreset]
public AnimationCurve curve;
/// <summary>
/// 曲线值0对应的实际数值。
/// </summary>
[ShowIf("active")]
[LabelText("Remap Min")]
public float remapMin;
/// <summary>
/// 曲线值1对应的实际数值。
/// </summary>
[ShowIf("active")]
[LabelText("Remap Max")]
public float remapMax;
@@ -47,11 +39,10 @@ namespace Cielonos.MainGame.Effects.Feedback
/// <summary>
/// 创建默认的曲线通道。
/// </summary>
public static FloatCurveChannel CreateDefault(bool active = true, float remapMin = 0f, float remapMax = 1f, bool relativeToInitial = true)
public static FloatCurveChannel CreateDefault(float remapMin = 0f, float remapMax = 1f, bool relativeToInitial = true)
{
return new FloatCurveChannel
{
active = active,
curve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.5f, 1f),
@@ -68,7 +59,7 @@ namespace Cielonos.MainGame.Effects.Feedback
/// </summary>
public readonly float Evaluate(float normalizedTime)
{
if (!active || curve == null) return 0f;
if (curve == null) return 0f;
float t = Mathf.Clamp01(normalizedTime);
float curveValue = curve.Evaluate(t);
return Mathf.LerpUnclamped(remapMin, remapMax, curveValue);
@@ -81,15 +72,9 @@ namespace Cielonos.MainGame.Effects.Feedback
[Serializable]
public struct ColorCurveChannel
{
/// <summary>
/// 是否启用此通道。
/// </summary>
public bool active;
/// <summary>
/// 颜色渐变。
/// </summary>
[ShowIf("active")]
[LabelText("颜色渐变")]
public Gradient gradient;
@@ -100,7 +85,6 @@ namespace Cielonos.MainGame.Effects.Feedback
{
return new ColorCurveChannel
{
active = true,
gradient = new Gradient()
};
}
@@ -110,7 +94,7 @@ namespace Cielonos.MainGame.Effects.Feedback
/// </summary>
public Color Evaluate(float normalizedTime)
{
if (!active || gradient == null) return Color.white;
if (gradient == null) return Color.white;
return gradient.Evaluate(Mathf.Clamp01(normalizedTime));
}
}
@@ -121,23 +105,17 @@ namespace Cielonos.MainGame.Effects.Feedback
[Serializable]
public struct Vector2CurveChannel
{
public bool active;
[ShowIf("active")]
[LabelText("曲线 X")]
[ShakeCurvePreset]
public AnimationCurve curveX;
[ShowIf("active")]
[LabelText("曲线 Y")]
[ShakeCurvePreset]
public AnimationCurve curveY;
[ShowIf("active")]
[LabelText("Remap Min")]
public Vector2 remapMin;
[ShowIf("active")]
[LabelText("Remap Max")]
public Vector2 remapMax;
@@ -153,7 +131,6 @@ namespace Cielonos.MainGame.Effects.Feedback
{
return new Vector2CurveChannel
{
active = true,
curveX = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)),
curveY = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)),
remapMin = Vector2.zero,
@@ -164,9 +141,8 @@ namespace Cielonos.MainGame.Effects.Feedback
/// <summary>
/// 根据归一化时间计算Vector2值。
/// </summary>
public Vector2 Evaluate(float normalizedTime, Vector2 initialValue)
public readonly Vector2 Evaluate(float normalizedTime, Vector2 initialValue)
{
if (!active) return Vector2.zero;
float t = Mathf.Clamp01(normalizedTime);
float x = curveX?.Evaluate(t) ?? 0f;
float y = curveY?.Evaluate(t) ?? 0f;

View File

@@ -39,26 +39,19 @@ namespace Cielonos.MainGame.Effects.Feedback
/// 将给定的本地空间向量根据当前设置转换到世界空间。
/// 如果两个方向都开启,角色方向优先。
/// </summary>
/// <param name="localAmplitude">本地空间的振幅向量</param>
/// <param name="standardScreenVector">在摄像机和角色相同朝向时,屏幕空间的振幅向量</param>
/// <param name="ownerTransform">角色 Transform可能为 null</param>
/// <returns>经方向变换后的振幅向量</returns>
public Vector3 TransformAmplitude(Vector3 localAmplitude, Transform ownerTransform)
public Vector3 TransformAmplitude(Vector3 standardScreenVector, Transform ownerTransform)
{
if (affectedByCharacterDirection && ownerTransform != null)
{
return ownerTransform.TransformDirection(localAmplitude);
}
Camera mainCamera = MainGameManager.Instance?.player?.viewSc?.playerCamera;
if (mainCamera == null) return standardScreenVector;
if (affectedByCameraDirection)
{
Camera mainCamera = MainGameManager.Instance.player.viewSc.playerCamera;
if (mainCamera != null)
{
return mainCamera.transform.TransformDirection(localAmplitude);
}
}
Quaternion characterRotation = ownerTransform?.rotation ?? Quaternion.identity;
Quaternion cameraRotation = mainCamera.transform.rotation;
return localAmplitude;
Quaternion deltaRotation = Quaternion.Inverse(cameraRotation) * characterRotation;
return deltaRotation * standardScreenVector;
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Cielonos.MainGame.Effects.Feedback
[TitleGroup("FOV设置")]
[LabelText("FOV曲线")]
public FloatCurveChannel fovCurve = FloatCurveChannel.CreateDefault(remapMax: 10f);
public FloatCurveChannel fovCurve = FloatCurveChannel.CreateDefault();
protected override void TriggerEvent(FeedbackContext context)
{
@@ -30,11 +30,6 @@ namespace Cielonos.MainGame.Effects.Feedback
public override bool Validate(out string error)
{
if (!fovCurve.active)
{
error = "FOV curve is not enabled.";
return false;
}
error = null;
return true;
}

View File

@@ -44,7 +44,7 @@ namespace Cielonos.MainGame.Effects.Feedback
{
Vector3 finalAmplitude = directionSettings.TransformAmplitude(amplitude, context.owner);
float intensityMultiplier = ComputeAttenuation(context);
CameraRotationShakeEvent.Trigger(context, intensityCurve, finalAmplitude * intensityMultiplier);
CameraPositionShakeEvent.Trigger(context, intensityCurve, finalAmplitude * intensityMultiplier);
}
protected override void StopEvent(FeedbackContext context)
@@ -66,11 +66,6 @@ namespace Cielonos.MainGame.Effects.Feedback
public override bool Validate(out string error)
{
if (!intensityCurve.active)
{
error = "Intensity curve is not enabled.";
return false;
}
error = null;
return true;
}

View File

@@ -17,8 +17,7 @@ namespace Cielonos.MainGame.Effects.Feedback
public Vector3 amplitude;
[LabelText("X轴曲线")]
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 5f);
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault();
public CameraDirectionSettings directionSettings = new CameraDirectionSettings();
@@ -40,8 +39,8 @@ namespace Cielonos.MainGame.Effects.Feedback
protected override void TriggerEvent(FeedbackContext context)
{
Vector3 finalAmplitude = directionSettings.TransformAmplitude(amplitude, context.owner);
float intensityMultiplier = ComputeAttenuation(context);
CameraRotationShakeEvent.Trigger(context, intensityCurve, finalAmplitude * intensityMultiplier);
float attenuation = ComputeAttenuation(context);
CameraRotationShakeEvent.Trigger(context, intensityCurve, finalAmplitude * attenuation);
}
protected override void StopEvent(FeedbackContext context)

View File

@@ -58,11 +58,11 @@ namespace Cielonos.MainGame.Effects.Feedback
{
AnimeACESShakeEvent.Trigger(
context,
exposureChannel,
contrastChannel,
saturationChannel,
hueChannel,
colorFilterChannel
modifyExposure, exposureChannel,
modifyContrast, contrastChannel,
modifySaturation, saturationChannel,
modifyHue, hueChannel,
modifyColorFilter, colorFilterChannel
);
}

View File

@@ -3,8 +3,16 @@ using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
public class BloomAction : CurveShakeAction
public class BloomAction : PostprocessingActionBase
{
protected override void TriggerEvent(FeedbackContext context)
{
throw new System.NotImplementedException();
}
protected override void StopEvent(FeedbackContext context)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// RGB分离故障反馈动作通过 RGBSplitGlitchShakeEvent 触发 RGBSplitGlitchShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(1f, 0.3f, 0.8f)]
public class RGBSplitGlitchAction : PostprocessingActionBase
{
public override string DisplayName => "RGB Split Glitch";
[LabelText("强度曲线")]
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
[LabelText("修改速度")]
public bool modifySpeed;
[ShowIf("modifySpeed")]
[LabelText("速度曲线")]
public FloatCurveChannel speedCurve = FloatCurveChannel.CreateDefault(remapMax: 50f);
[HideIf("modifySpeed")]
[LabelText("速度")]
[Range(0f, 100f)]
public float speed = 30f;
protected override void TriggerEvent(FeedbackContext context)
{
FloatCurveChannel finalSpeedCurve = modifySpeed
? speedCurve
: new FloatCurveChannel
{
curve = AnimationCurve.Constant(0f, 1f, speed),
relativeToInitial = false
};
RGBSplitGlitchShakeEvent.Trigger(
context,
intensityCurve,
finalSpeedCurve
);
}
protected override void StopEvent(FeedbackContext context)
{
RGBSplitGlitchShakeEvent.Trigger(context, intensityCurve, stop: true);
}
public override bool Validate(out string error)
{
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dc20b82d80b720247a0ef6db8513af02

View File

@@ -14,73 +14,53 @@ namespace Cielonos.MainGame.Effects.Feedback
{
public override string DisplayName => "Vignette";
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
/// <summary>
/// 是否修改暗角中心点。
/// </summary>
[LabelText("修改中心点")]
public bool modifyCenter;
/// <summary>
/// 模糊中心的屏幕坐标 (0-1)。(0.5, 0.5) 为屏幕正中心。
/// </summary>
[HideIf("modifyCenter")]
[LabelText("中心点")]
public Vector2 center = new Vector2(0.5f, 0.5f);
[ShowIf("modifyCenter")]
[LabelText("中心点曲线")]
public Vector2CurveChannel centerCurve = Vector2CurveChannel.CreateDefault();
/// <summary>
/// 是否修改颜色。
/// </summary>
[LabelText("修改颜色")]
public bool modifyColors;
[HideIf("modifyColors")]
public Color outColor;
[HideIf("modifyColors")]
public Color innerColor;
/// <summary>
/// 外圈颜色。
/// </summary>
[ShowIf("modifyColors")]
[LabelText("外圈颜色")]
public Color outColor = Color.black;
[HideIf("modifyColors")]
[LabelText("内圈颜色")]
public Color innerColor = Color.black;
[ShowIf("modifyColors")]
[LabelText("外圈颜色曲线")]
public ColorCurveChannel outerColorCurve = ColorCurveChannel.CreateDefault();
/// <summary>
/// 内圈颜色。
/// </summary>
[ShowIf("modifyColors")]
[LabelText("内圈颜色")]
[LabelText("内圈颜色曲线")]
public ColorCurveChannel innerColorCurve = ColorCurveChannel.CreateDefault();
/// <summary>
/// 是否修改形状。
/// </summary>
[LabelText("修改形状")]
public bool modifyShape;
[HideIf("modifyShape")]
public float smoothness;
[LabelText("柔和度")]
public float smoothness = 0.5f;
[HideIf("modifyShape")]
public float roundness;
[LabelText("圆度")]
public float roundness = 1f;
/// <summary>
/// 柔和度曲线。
/// </summary>
[ShowIf("modifyShape")]
[LabelText("柔和度曲线")]
public FloatCurveChannel smoothnessCurve = FloatCurveChannel.CreateDefault(remapMax: 0.5f);
/// <summary>
/// 圆度曲线。
/// </summary>
[ShowIf("modifyShape")]
[LabelText("圆度曲线")]
public FloatCurveChannel roundnessCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
@@ -91,13 +71,18 @@ namespace Cielonos.MainGame.Effects.Feedback
context,
intensityCurve,
modifyCenter,
center,
modifyCenter ? centerCurve : default,
modifyCenter ? center : default(Vector2),
modifyColors,
outerColorCurve,
innerColorCurve,
modifyColors ? outerColorCurve : default,
modifyColors ? innerColorCurve : default,
modifyColors ? outColor : default,
modifyColors ? innerColor : default,
modifyShape,
smoothnessCurve,
roundnessCurve
modifyShape ? smoothnessCurve : default,
modifyShape ? roundnessCurve : default,
modifyShape ? smoothness : 0f,
modifyShape ? roundness : 0f
);
}

View File

@@ -17,10 +17,15 @@ namespace Cielonos.MainGame.Effects.Feedback
public delegate void ShakeDelegate(
FeedbackContext feedbackContext,
bool modifyExposure,
FloatCurveChannel exposureCurve,
bool modifyContrast,
FloatCurveChannel contrastCurve,
bool modifySaturation,
FloatCurveChannel saturationCurve,
bool modifyHue,
FloatCurveChannel hueCurve,
bool modifyColorFilter,
ColorCurveChannel colorFilterCurve,
bool stop
);
@@ -30,14 +35,32 @@ namespace Cielonos.MainGame.Effects.Feedback
public static void Trigger(
FeedbackContext feedbackContext,
bool modifyExposure = false,
FloatCurveChannel exposureCurve = default,
bool modifyContrast = false,
FloatCurveChannel contrastCurve = default,
bool modifySaturation = false,
FloatCurveChannel saturationCurve = default,
bool modifyHue = false,
FloatCurveChannel hueCurve = default,
bool modifyColorFilter = false,
ColorCurveChannel colorFilterCurve = default,
bool stop = false)
{
OnEvent?.Invoke(feedbackContext, exposureCurve, contrastCurve, saturationCurve, hueCurve, colorFilterCurve, stop);
OnEvent?.Invoke(
feedbackContext,
modifyExposure,
exposureCurve,
modifyContrast,
contrastCurve,
modifySaturation,
saturationCurve,
modifyHue,
hueCurve,
modifyColorFilter,
colorFilterCurve,
stop
);
}
}
@@ -46,26 +69,45 @@ namespace Cielonos.MainGame.Effects.Feedback
/// </summary>
public class AnimeACESShakeInstance : ShakeInstanceBase
{
public readonly FloatCurveChannel ExposureCurve;
public readonly FloatCurveChannel ContrastCurve;
public readonly FloatCurveChannel SaturationCurve;
public readonly FloatCurveChannel HueCurve;
public readonly ColorCurveChannel ColorFilterCurve;
public readonly bool modifyExposure;
public readonly FloatCurveChannel exposureCurve;
public readonly bool modifyContrast;
public readonly FloatCurveChannel contrastCurve;
public readonly bool modifySaturation;
public readonly FloatCurveChannel saturationCurve;
public readonly bool modifyHue;
public readonly FloatCurveChannel hueCurve;
public readonly bool modifyColorFilter;
public readonly ColorCurveChannel colorFilterCurve;
public AnimeACESShakeInstance(
FeedbackContext feedbackContext,
bool modifyExposure,
FloatCurveChannel exposureCurve,
bool modifyContrast,
FloatCurveChannel contrastCurve,
bool modifySaturation,
FloatCurveChannel saturationCurve,
bool modifyHue,
FloatCurveChannel hueCurve,
bool modifyColorFilter,
ColorCurveChannel colorFilterCurve)
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
{
ExposureCurve = exposureCurve;
ContrastCurve = contrastCurve;
SaturationCurve = saturationCurve;
HueCurve = hueCurve;
ColorFilterCurve = colorFilterCurve;
this.modifyExposure = modifyExposure;
this.modifyContrast = modifyContrast;
this.modifySaturation = modifySaturation;
this.modifyHue = modifyHue;
this.modifyColorFilter = modifyColorFilter;
this.exposureCurve = exposureCurve;
this.contrastCurve = contrastCurve;
this.saturationCurve = saturationCurve;
this.hueCurve = hueCurve;
this.colorFilterCurve = colorFilterCurve;
}
}
@@ -115,38 +157,39 @@ namespace Cielonos.MainGame.Effects.Feedback
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
AnimeACESShakeInstance shake = _activeShakes[i];
shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings);
shake.Tick();
float normalizedTime = shake.timer / shake.duration;
// Exposure
if (shake.ExposureCurve.active)
if (shake.modifyExposure)
{
additiveExposure += shake.ExposureCurve.Evaluate(normalizedTime);
additiveExposure += shake.exposureCurve.Evaluate(normalizedTime);
Debug.Log($"Exposure shake: {additiveExposure}");
}
// Contrast
if (shake.ContrastCurve.active)
if (shake.modifyContrast)
{
additiveContrast += shake.ContrastCurve.Evaluate(normalizedTime);
additiveContrast += shake.contrastCurve.Evaluate(normalizedTime);
}
// Saturation
if (shake.SaturationCurve.active)
if (shake.modifySaturation)
{
additiveSaturation += shake.SaturationCurve.Evaluate(normalizedTime);
additiveSaturation += shake.saturationCurve.Evaluate(normalizedTime);
}
// Hue
if (shake.HueCurve.active)
if (shake.modifyHue)
{
additiveHue += shake.HueCurve.Evaluate(normalizedTime);
additiveHue += shake.hueCurve.Evaluate(normalizedTime);
}
// Color Filter
if (shake.ColorFilterCurve.active)
if (shake.modifyColorFilter)
{
colorFilterAccum = shake.ColorFilterCurve.Evaluate(normalizedTime);
colorFilterAccum = shake.colorFilterCurve.Evaluate(normalizedTime);
hasColorFilter = true;
}
@@ -171,10 +214,15 @@ namespace Cielonos.MainGame.Effects.Feedback
private void OnShakeEvent(
FeedbackContext feedbackContext,
bool modifyExposure,
FloatCurveChannel exposureCurve,
bool modifyContrast,
FloatCurveChannel contrastCurve,
bool modifySaturation,
FloatCurveChannel saturationCurve,
bool modifyHue,
FloatCurveChannel hueCurve,
bool modifyColorFilter,
ColorCurveChannel colorFilterCurve,
bool stop)
{
@@ -184,12 +232,11 @@ namespace Cielonos.MainGame.Effects.Feedback
var instance = new AnimeACESShakeInstance(
feedbackContext,
exposureCurve,
contrastCurve,
saturationCurve,
hueCurve,
colorFilterCurve
);
modifyExposure, exposureCurve,
modifyContrast, contrastCurve,
modifySaturation, saturationCurve,
modifyHue, hueCurve,
modifyColorFilter, colorFilterCurve);
_activeShakes.Add(instance);
}

View File

@@ -118,21 +118,18 @@ namespace Cielonos.MainGame.Effects.Feedback
float normalizedTime = shake.timer / shake.duration;
if (shake.intensityCurve.active)
float curveValue = shake.intensityCurve.Evaluate(normalizedTime);
if (shake.intensityCurve.relativeToInitial)
{
float curveValue = shake.intensityCurve.Evaluate(normalizedTime);
if (shake.intensityCurve.relativeToInitial)
{
additiveIntensity += curveValue;
}
else
{
absoluteIntensity = curveValue;
hasAbsolute = true;
}
additiveIntensity += curveValue;
}
else
{
absoluteIntensity = curveValue;
hasAbsolute = true;
}
if (shake.modifyJitter && shake.jitterCurve.active)
if (shake.modifyJitter)
{
additiveJitter += shake.jitterCurve.Evaluate(normalizedTime);
}

View File

@@ -56,13 +56,13 @@ namespace Cielonos.MainGame.Effects.Feedback
public class CameraFovShaker : MonoBehaviour
{
private CinemachineCamera _camera;
private float _initialFov;
public float initialFov;
private readonly List<CameraFovShakeInstance> _activeShakes = new List<CameraFovShakeInstance>();
private void Awake()
{
_camera = GetComponent<CinemachineCamera>();
_initialFov = _camera.Lens.FieldOfView;
initialFov = _camera.Lens.FieldOfView;
}
private void OnEnable()
@@ -82,7 +82,7 @@ namespace Cielonos.MainGame.Effects.Feedback
if (_activeShakes.Count == 0)
{
SetFov(_initialFov);
SetFov(initialFov);
return;
}
@@ -112,7 +112,7 @@ namespace Cielonos.MainGame.Effects.Feedback
}
}
float finalFov = hasAbsolute ? absoluteFov : _initialFov + additiveFov;
float finalFov = hasAbsolute ? absoluteFov : initialFov + additiveFov;
SetFov(finalFov);
}
@@ -144,7 +144,7 @@ namespace Cielonos.MainGame.Effects.Feedback
_activeShakes.Clear();
if (_camera != null)
{
SetFov(_initialFov);
SetFov(initialFov);
}
}
}

View File

@@ -109,7 +109,6 @@ namespace Cielonos.MainGame.Effects.Feedback
}
Vector3 totalOffset = Vector3.zero;
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
CameraRotationShakeInstance shake = _activeShakes[i];
@@ -121,7 +120,6 @@ namespace Cielonos.MainGame.Effects.Feedback
_activeShakes.RemoveAt(i);
}
}
_rotationOffset.rotationOffset = _initialRotation + totalOffset;
}

View File

@@ -0,0 +1,173 @@
using System.Collections.Generic;
using SLSUtilities.Feedback;
using SLSUtilities.Rendering.PostProcessing;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// RGB分离故障震动事件。
/// </summary>
public struct RGBSplitGlitchShakeEvent
{
private static event ShakeDelegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RuntimeInitialization() { OnEvent = null; }
public delegate void ShakeDelegate(
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
FloatCurveChannel speedCurve,
bool stop
);
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
public static void Trigger(
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
FloatCurveChannel speedCurve = default,
bool stop = false)
{
OnEvent?.Invoke(feedbackContext, intensityCurve, speedCurve, stop);
}
}
/// <summary>
/// RGB分离故障震动实例。
/// </summary>
public class RGBSplitGlitchShakeInstance : ShakeInstanceBase
{
public readonly FloatCurveChannel intensityCurve;
public readonly FloatCurveChannel speedCurve;
public RGBSplitGlitchShakeInstance(
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
FloatCurveChannel speedCurve)
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
{
this.intensityCurve = intensityCurve;
this.speedCurve = speedCurve;
}
}
/// <summary>
/// RGBSplitGlitch 的震动聚合器。
/// </summary>
[AddComponentMenu("SLS Utilities/Feedback Shakers/RGB Split Glitch Shaker")]
public class RGBSplitGlitchShaker : MonoBehaviour
{
private RGBSplitGlitch _component;
private float _initialIntensity;
private float _initialSpeed;
private bool _resolved;
private readonly List<RGBSplitGlitchShakeInstance> _activeShakes = new List<RGBSplitGlitchShakeInstance>();
private void Awake()
{
_resolved = TryResolve();
}
private void OnEnable()
{
RGBSplitGlitchShakeEvent.Register(OnShakeEvent);
}
private void OnDisable()
{
RGBSplitGlitchShakeEvent.Unregister(OnShakeEvent);
StopAll();
}
private void Update()
{
if (!_resolved || _activeShakes.Count == 0) return;
float additiveIntensity = 0f;
float absoluteIntensity = 0f;
bool hasAbsolute = false;
float additiveSpeed = 0f;
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
RGBSplitGlitchShakeInstance shake = _activeShakes[i];
shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings);
float normalizedTime = shake.timer / shake.duration;
float intensityValue = shake.intensityCurve.Evaluate(normalizedTime);
if (shake.intensityCurve.relativeToInitial)
{
additiveIntensity += intensityValue;
}
else
{
absoluteIntensity = intensityValue;
hasAbsolute = true;
}
additiveSpeed += shake.speedCurve.Evaluate(normalizedTime);
if (shake.IsFinished)
{
_activeShakes.RemoveAt(i);
}
}
float finalIntensity = hasAbsolute ? absoluteIntensity : _initialIntensity + additiveIntensity;
_component.intensity.value = finalIntensity;
_component.speed.value = _initialSpeed + additiveSpeed;
if (_activeShakes.Count == 0)
{
Restore();
}
}
private void OnShakeEvent(
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
FloatCurveChannel speedCurve,
bool stop)
{
if (stop) { StopAll(); return; }
if (!_resolved) _resolved = TryResolve();
if (!_resolved) return;
var instance = new RGBSplitGlitchShakeInstance(
feedbackContext,
intensityCurve,
speedCurve
);
_activeShakes.Add(instance);
}
private bool TryResolve()
{
if (_component != null) return true;
if (PostProcessingManager.Instance == null) return false;
if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false;
_initialIntensity = _component.intensity.value;
_initialSpeed = _component.speed.value;
return true;
}
private void Restore()
{
if (!_resolved) return;
_component.intensity.value = _initialIntensity;
_component.speed.value = _initialSpeed;
}
private void StopAll()
{
_activeShakes.Clear();
Restore();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e720e1219abdd234b8c87ce75d5815cb

View File

@@ -19,13 +19,18 @@ namespace Cielonos.MainGame.Effects.Feedback
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
bool modifyCenter,
Vector2CurveChannel centerCurve,
Vector2 center,
bool modifyColors,
ColorCurveChannel colorOuter,
ColorCurveChannel colorInner,
Color outColor,
Color innerColor,
bool modifyShape,
FloatCurveChannel smoothnessCurve,
FloatCurveChannel roundnessCurve,
float smoothness,
float roundness,
bool stop
);
@@ -36,18 +41,23 @@ namespace Cielonos.MainGame.Effects.Feedback
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
bool modifyCenter = false,
Vector2CurveChannel centerCurve = default,
Vector2 center = default,
bool modifyColors = false,
ColorCurveChannel colorOuter = default,
ColorCurveChannel colorInner = default,
Color outColor = default,
Color innerColor = default,
bool modifyShape = false,
FloatCurveChannel smoothnessCurve = default,
FloatCurveChannel roundnessCurve = default,
float smoothness = 0f,
float roundness = 0f,
bool stop = false)
{
OnEvent?.Invoke(feedbackContext, intensityCurve, modifyCenter, center,
modifyColors, colorOuter, colorInner, modifyShape,
smoothnessCurve, roundnessCurve, stop);
OnEvent?.Invoke(feedbackContext, intensityCurve, modifyCenter, centerCurve, center,
modifyColors, colorOuter, colorInner, outColor, innerColor, modifyShape,
smoothnessCurve, roundnessCurve, smoothness, roundness, stop);
}
}
@@ -58,36 +68,51 @@ namespace Cielonos.MainGame.Effects.Feedback
{
public readonly FloatCurveChannel intensityCurve;
public readonly bool modifyCenter;
public readonly Vector2CurveChannel centerCurve;
public readonly Vector2 center;
public readonly bool modifyColors;
public readonly ColorCurveChannel colorOuter;
public readonly ColorCurveChannel colorInner;
public readonly Color outColor;
public readonly Color innerColor;
public readonly bool modifyShape;
public readonly FloatCurveChannel smoothnessCurve;
public readonly FloatCurveChannel roundnessCurve;
public readonly float smoothness;
public readonly float roundness;
public VignetteShakeInstance(
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
bool modifyCenter,
Vector2CurveChannel centerCurve,
Vector2 center,
bool modifyColors,
ColorCurveChannel colorOuter,
ColorCurveChannel colorInner,
Color outColor,
Color innerColor,
bool modifyShape,
FloatCurveChannel smoothnessCurve,
FloatCurveChannel roundnessCurve)
FloatCurveChannel roundnessCurve,
float smoothness,
float roundness)
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
{
this.intensityCurve = intensityCurve;
this.modifyCenter = modifyCenter;
this.centerCurve = centerCurve;
this.center = center;
this.modifyColors = modifyColors;
this.colorOuter = colorOuter;
this.colorInner = colorInner;
this.outColor = outColor;
this.innerColor = innerColor;
this.modifyShape = modifyShape;
this.smoothnessCurve = smoothnessCurve;
this.roundnessCurve = roundnessCurve;
this.smoothness = smoothness;
this.roundness = roundness;
}
}
@@ -148,37 +173,44 @@ namespace Cielonos.MainGame.Effects.Feedback
float normalizedTime = shake.timer / shake.duration;
if (shake.intensityCurve.active)
float curveValue = shake.intensityCurve.Evaluate(normalizedTime);
if (shake.intensityCurve.relativeToInitial)
{
float curveValue = shake.intensityCurve.Evaluate(normalizedTime);
if (shake.intensityCurve.relativeToInitial)
{
additiveIntensity += curveValue;
}
else
{
absoluteIntensity = curveValue;
hasAbsolute = true;
}
additiveIntensity += curveValue;
}
else
{
absoluteIntensity = curveValue;
hasAbsolute = true;
}
if (shake.modifyCenter)
{
latestCenter = shake.center;
latestCenter = shake.modifyCenter
? shake.centerCurve.Evaluate(normalizedTime, _initialCenter)
: shake.center;
hasCenter = true;
}
if (shake.modifyColors)
{
latestColorOuter = shake.colorOuter.Evaluate(normalizedTime);
latestColorInner = shake.colorInner.Evaluate(normalizedTime);
latestColorOuter = shake.modifyColors
? shake.colorOuter.Evaluate(normalizedTime)
: shake.outColor;
latestColorInner = shake.modifyColors
? shake.colorInner.Evaluate(normalizedTime)
: shake.innerColor;
hasColors = true;
}
if (shake.modifyShape)
{
latestSmoothness = shake.smoothnessCurve.Evaluate(normalizedTime);
latestRoundness = shake.roundnessCurve.Evaluate(normalizedTime);
latestSmoothness = shake.modifyShape
? shake.smoothnessCurve.Evaluate(normalizedTime)
: shake.smoothness;
latestRoundness = shake.modifyShape
? shake.roundnessCurve.Evaluate(normalizedTime)
: shake.roundness;
hasShape = true;
}
@@ -213,13 +245,18 @@ namespace Cielonos.MainGame.Effects.Feedback
FeedbackContext feedbackContext,
FloatCurveChannel intensityCurve,
bool modifyCenter,
Vector2CurveChannel centerCurve,
Vector2 center,
bool modifyColors,
ColorCurveChannel colorOuter,
ColorCurveChannel colorInner,
Color outColor,
Color innerColor,
bool modifyShape,
FloatCurveChannel smoothnessCurve,
FloatCurveChannel roundnessCurve,
float smoothness,
float roundness,
bool stop)
{
if (stop) { StopAll(); return; }
@@ -227,9 +264,21 @@ namespace Cielonos.MainGame.Effects.Feedback
if (!_resolved) return;
var instance = new VignetteShakeInstance(
feedbackContext, intensityCurve, modifyCenter, center,
modifyColors, colorOuter, colorInner, modifyShape,
smoothnessCurve, roundnessCurve
feedbackContext,
intensityCurve,
modifyCenter,
centerCurve,
center,
modifyColors,
colorOuter,
colorInner,
outColor,
innerColor,
modifyShape,
smoothnessCurve,
roundnessCurve,
smoothness,
roundness
);
_activeShakes.Add(instance);
}

View File

@@ -79,6 +79,49 @@ namespace Cielonos.MainGame
return enemiesInRadius;
}
/// <summary>
/// 获取所有可见敌人
/// </summary>
public List<CharacterBase> GetVisibleEnemies(float radius = 50f)
{
List<CharacterBase> result = new List<CharacterBase>();
List<CharacterBase> enemies = GetEnemiesInRadius(Player.transform.position, radius);
foreach (CharacterBase enemy in enemies)
{
if (enemy == null || enemy.statusSm.isDead) continue;
Vector3 screenPos = Player.viewSc.playerCamera.WorldToScreenPoint(enemy.centerPoint.position);
bool isInScreen = screenPos.z > 0 &&
screenPos.x > 0 && screenPos.x < Screen.width &&
screenPos.y > 0 && screenPos.y < Screen.height;
if (isInScreen)
{
result.Add(enemy);
}
}
return result;
}
/// <summary>
/// 获取按屏幕X坐标从左到右排序的可见敌人列表
/// </summary>
public List<CharacterBase> GetVisibleEnemiesSortedByScreenX(float radius = 50f)
{
List<CharacterBase> visibleEnemies = GetVisibleEnemies(radius);
visibleEnemies.Sort((a, b) =>
{
Vector3 screenA = Player.viewSc.playerCamera.WorldToScreenPoint(a.centerPoint.position);
Vector3 screenB = Player.viewSc.playerCamera.WorldToScreenPoint(b.centerPoint.position);
return screenA.x.CompareTo(screenB.x);
});
return visibleEnemies;
}
}
}
}

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 383b31d5ddcc89e4692157c4cc81b9aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,50 +0,0 @@
using System;
using Sirenix.OdinInspector;
using UnityEngine;
namespace SLSUtilities.Feedback
{
/// <summary>
/// 通用的"按曲线震动数值"基类,提供曲线采样、初始值记录/复位等通用逻辑。
/// RadialBlur、ChromaticAberration、Vignette 等后处理效果均继承此类。
/// </summary>
[Serializable]
public abstract class CurveShakeAction : FeedbackActionBase
{
/// <summary>
/// 震动曲线X 轴为归一化时间 [0,1]Y 轴为震动强度 [0,1]。
/// </summary>
[Title("Curve Shake")]
[LabelText("Shake Curve")]
[ShakeCurvePreset]
public AnimationCurve shakeCurve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.5f, 1f),
new Keyframe(1f, 0f)
);
/// <summary>
/// 曲线值 0 对应的实际数值。
/// </summary>
[LabelText("Remap Min")]
public float remapMin;
/// <summary>
/// 曲线值 1 对应的实际数值。
/// </summary>
[LabelText("Remap Max")]
public float remapMax = 1f;
/// <summary>
/// 是否在初始值上叠加(而非替换)。
/// </summary>
[LabelText("Relative to Initial")]
public bool relativeToInitial;
protected virtual float EvaluateShake(float normalizedTime, float initialValue)
{
return base.EvaluateShake(shakeCurve, remapMin, remapMax, relativeToInitial, normalizedTime, initialValue);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 26b145321a43fe44d899db3f2178cb0e