public class GameFeelManager : MonoBehaviour
{
public static GameFeelManager Instance { get; private set; }
[Header("屏幕震动")]
private Camera mainCamera;
private Vector3 originalCameraPos;
private Coroutine shakeCoroutine;
[Header("时间控制")]
private float defaultTimeScale = 1.0f;
private Coroutine hitstopCoroutine;
void Awake()
{
\text{if} (Instance == null)
{
Instance = this;
mainCamera = Camera.main;
originalCameraPos = mainCamera.transform.localPosition;
}
else
{
Destroy(gameObject);
}
}
public void TriggerHitFeedback(HitFeedbackConfig config, Vector3 hitPosition)
{
// 1. 特效
\text{if} (config.hitEffectPrefab != null)
{
SpawnHitEffect(config, hitPosition);
}
// 2. 屏幕震动
\text{if} (config.enableScreenShake)
{
ScreenShake(config.shakeIntensity, config.shakeFrequency,
config.shakeDuration, config.shakeDecay);
}
// 3. 顿帧
\text{if} (config.enableHitstop)
{
Hitstop(config.hitstopFrames);
}
// 4. 音效
\text{if} (config.hitSound != null)
{
PlayHitSound(config);
}
// 5. 震动反馈
\text{if} (config.enableHapticFeedback && Application.isMobilePlatform)
{
TriggerHaptic(config.hapticType);
}
}
private void SpawnHitEffect(HitFeedbackConfig config, Vector3 position)
{
var effect = Instantiate(config.hitEffectPrefab, position, Quaternion.identity);
effect.transform.localScale = config.effectScale;
Destroy(effect, config.effectDuration);
}
public void ScreenShake(float intensity, float frequency, float duration, AnimationCurve decay = null)
{
\text{if} (shakeCoroutine != null)
{
StopCoroutine(shakeCoroutine);
}
shakeCoroutine = StartCoroutine(ScreenShakeCoroutine(intensity, frequency, duration, decay));
}
private IEnumerator ScreenShakeCoroutine(float intensity, float frequency, float duration, AnimationCurve decay)
{
float elapsed = 0f;
while (elapsed < duration)
{
float progress = elapsed / duration;
// 应用衰减曲线
float currentIntensity = intensity;
\text{if} (decay != null)
{
currentIntensity *= decay.Evaluate(progress);
}
else
{
// 默认指数衰减
currentIntensity *= Mathf.Exp(-5 * progress);
}
// 计算震动偏移
float offsetX = Mathf.PerlinNoise(elapsed * frequency, 0f) * 2f - 1f;
float offsetY = Mathf.PerlinNoise(0f, elapsed * frequency) * 2f - 1f;
Vector3 shakeOffset = new Vector3(
offsetX * currentIntensity * 0.01f,
offsetY * currentIntensity * 0.01f,
0f
);
mainCamera.transform.localPosition = originalCameraPos + shakeOffset;
elapsed += Time.unscaledDeltaTime;
yield return null;
}
// 恢复原位
mainCamera.transform.localPosition = originalCameraPos;
}
public void Hitstop(int frames)
{
\text{if} (hitstopCoroutine != null)
{
StopCoroutine(hitstopCoroutine);
}
hitstopCoroutine = StartCoroutine(HitstopCoroutine(frames));
}
private IEnumerator HitstopCoroutine(int frames)
{
// 保存当前时间缩放
float originalTimeScale = Time.timeScale;
// 冻结时间
Time.timeScale = 0f;
// 等待指定帧数(使用 unscaled time)
float frameDuration = 1f / 60f; // 假设 60fps
yield return new WaitForSecondsRealtime(frames * frameDuration);
// 恢复时间
Time.timeScale = originalTimeScale;
}
private void PlayHitSound(HitFeedbackConfig config)
{
\text{if} (config.hitSound == null) return;
// 随机音调变化
float randomPitch = 1.0f + Random.Range(-config.pitchVariation, config.pitchVariation);
// 播放音效
AudioSource.PlayClipAtPoint(
config.hitSound,
mainCamera.transform.position,
config.volumeScale
);
// 注意:PlayClipAtPoint 不支持 pitch,这里需要用 AudioSource 对象池
}
private void TriggerHaptic(HapticFeedbackType type)
{
#if UNITY_ANDROID || UNITY_IOS
switch (type)
{
case HapticFeedbackType.Light:
Handheld.Vibrate();
break;
case HapticFeedbackType.Medium:
Handheld.Vibrate();
break;
case HapticFeedbackType.Heavy:
Handheld.Vibrate();
break;
}
#endif
}
// 慢动作效果
public void SlowMotion(float timeScale, float duration)
{
StartCoroutine(SlowMotionCoroutine(timeScale, duration));
}
private IEnumerator SlowMotionCoroutine(float targetTimeScale, float duration)
{
float originalTimeScale = Time.timeScale;
// 渐入慢动作
float elapsed = 0f;
while (elapsed < 0.1f)
{
Time.timeScale = Mathf.Lerp(originalTimeScale, targetTimeScale, elapsed / 0.1f);
elapsed += Time.unscaledDeltaTime;
yield return null;
}
Time.timeScale = targetTimeScale;
// 维持慢动作
yield return new WaitForSecondsRealtime(duration);
// 渐出慢动作
elapsed = 0f;
while (elapsed < 0.1f)
{
Time.timeScale = Mathf.Lerp(targetTimeScale, originalTimeScale, elapsed / 0.1f);
elapsed += Time.unscaledDeltaTime;
yield return null;
}
Time.timeScale = originalTimeScale;
}
}