完整实现脚本:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
[RequireComponent(typeof(Image))]
public class HitEffectController : MonoBehaviour
{
[Header("基础设置")]
public float hitDuration = 0.5f; // 打击效果总时长
[Header("缩放效果")]
public float maxScale = 1.3f; // 最大缩放值
public float scaleInDuration = 0.05f; // 缩放进入时间
public float scaleOutDuration = 0.2f; // 缩放恢复时间
public AnimationCurve scaleCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("震动效果")]
public float shakeIntensity = 10f; // 震动强度
public float shakeFrequency = 25f; // 震动频率
public AnimationCurve shakeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("闪光效果")]
public Color flashColor = Color.white; // 闪光颜色
public float flashDuration = 0.1f; // 闪光持续时间
public float flashIntensity = 5f; // 闪光强度
[Header("运动模糊")]
public bool useMotionBlur = true;
public float stretchAmount = 0.3f; // 拉伸量
public float stretchDuration = 0.08f; // 拉伸持续时间
[Header("粒子效果")]
public ParticleSystem hitParticles;
public int particleCount = 15; // 粒子数量
[Header("时间效果")]
public float freezeDuration = 0.05f; // 时间冻结时长
private Image targetImage;
private RectTransform rectTransform;
private Vector3 originalScale;
private Color originalColor;
private Material originalMaterial;
private Coroutine currentEffect;
void Awake()
{
targetImage = GetComponent<Image>();
rectTransform = GetComponent<RectTransform>();
originalScale = rectTransform.localScale;
originalColor = targetImage.color;
originalMaterial = targetImage.material;
}
// 触发打击效果
public void TriggerHitEffect()
{
if (currentEffect != null) StopCoroutine(currentEffect);
currentEffect = StartCoroutine(HitEffectRoutine());
}
private IEnumerator HitEffectRoutine()
{
// 1. 时间冻结(强调击中瞬间)
Time.timeScale = 0f;
yield return new WaitForSecondsRealtime(freezeDuration);
Time.timeScale = 1f;
// 2. 初始缩放(表现冲击)
float scaleTimer = 0f;
while (scaleTimer < scaleInDuration)
{
scaleTimer += Time.deltaTime;
float t = scaleCurve.Evaluate(scaleTimer / scaleInDuration);
float scale = Mathf.Lerp(1f, maxScale, t);
rectTransform.localScale = originalScale * scale;
yield return null;
}
// 3. 同时触发闪光和粒子效果
StartCoroutine(FlashEffect());
EmitParticles();
// 4. 运动模糊(可选)
if (useMotionBlur)
{
StartCoroutine(StretchEffect());
}
// 5. 缩放恢复+震动效果
float shakeTimer = 0f;
Vector3 originalPosition = rectTransform.localPosition;
while (shakeTimer < hitDuration)
{
shakeTimer += Time.deltaTime;
// 缩放恢复
if (shakeTimer < scaleOutDuration)
{
float scaleT = scaleCurve.Evaluate(shakeTimer / scaleOutDuration);
float scale = Mathf.Lerp(maxScale, 1f, scaleT);
rectTransform.localScale = originalScale * scale;
}
// 震动效果
float shakeT = shakeCurve.Evaluate(shakeTimer / hitDuration);
float shakeMagnitude = shakeIntensity * (1f - shakeT);
float offsetX = Mathf.Sin(Time.time * shakeFrequency) * shakeMagnitude;
float offsetY = Mathf.Cos(Time.time * shakeFrequency) * shakeMagnitude;
rectTransform.localPosition = originalPosition + new Vector3(offsetX, offsetY, 0);
yield return null;
}
// 恢复原始状态
rectTransform.localScale = originalScale;
rectTransform.localPosition = originalPosition;
}
// 闪光效果协程
private IEnumerator FlashEffect()
{
// 创建临时材质实现闪光
Material flashMaterial = new Material(Shader.Find("UI/Unlit/Transparent"));
targetImage.material = flashMaterial;
float timer = 0f;
while (timer < flashDuration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / flashDuration);
// 强度曲线:快速达到峰值然后衰减
float intensity = flashIntensity * Mathf.Sin(t * Mathf.PI);
// 混合原始颜色和闪光颜色
Color blendedColor = Color.Lerp(originalColor, flashColor, intensity);
flashMaterial.color = blendedColor;
yield return null;
}
// 恢复原始材质
targetImage.material = originalMaterial;
Destroy(flashMaterial);
}
// 运动模糊拉伸效果
private IEnumerator StretchEffect()
{
Vector3 originalScale = rectTransform.localScale;
Vector3 stretchedScale = new Vector3(
originalScale.x * (1f + stretchAmount),
originalScale.y * (1f - stretchAmount * 0.5f),
originalScale.z
);
float timer = 0f;
while (timer < stretchDuration)
{
timer += Time.deltaTime;
float t = timer / stretchDuration;
// 使用缓动函数实现快速拉伸然后恢复
float stretchT = Mathf.Sin(t * Mathf.PI * 0.5f);
rectTransform.localScale = Vector3.Lerp(originalScale, stretchedScale, stretchT);
yield return null;
}
rectTransform.localScale = originalScale;
}
// 发射粒子
private void EmitParticles()
{
if (hitParticles != null)
{
// 在图片位置发射粒子
hitParticles.transform.position = rectTransform.position;
hitParticles.Emit(particleCount);
}
}
}
参数优化建议
缩放效果
csharp
maxScale = 1.2f - 1.5f; // 根据图片大小调整
scaleInDuration = 0.03f - 0.07f; // 快速冲击
scaleOutDuration = 0.15f - 0.3f; // 缓慢恢复
震动效果
csharp
shakeIntensity = 5f - 20f; // 小图片用小值,大图片用大值
shakeFrequency = 20f - 30f; // 高频震动更真实
闪光效果
csharp
// 根据图片主色调选择对比色
flashColor = ComplementaryColor(originalColor);
flashIntensity = 3f - 8f; // 强度根据场景亮度调整
高级效果增强技巧
1. 材质与着色器效果
冲击波着色器:
// 简化的冲击波着色器
Shader "Custom/HitWave"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_WaveCenter ("Wave Center", Vector) = (0.5, 0.5, 0, 0)
_WaveAmount ("Wave Amount", Range(0, 1)) = 0
_WaveIntensity ("Wave Intensity", Float) = 10
}
SubShader
{
// 着色器代码...
float2 waveOffset = (distance(i.uv, _WaveCenter.xy) * _WaveIntensity * _WaveAmount;
float2 distortedUV = i.uv + waveOffset;
fixed4 col = tex2D(_MainTex, distortedUV);
// 增加边缘发光
col.rgb += _WaveAmount * _WaveIntensity * 0.1 * (1 - smoothstep(0, 0.2, distance(i.uv, _WaveCenter.xy)));
}
}
2. 屏幕空间效果
全局屏幕震动:
public class ScreenShake : MonoBehaviour
{
public float duration = 0.5f;
public float intensity = 0.1f;
private Vector3 originalPos;
private float currentShakeTime;
void Start()
{
originalPos = transform.localPosition;
}
public void Shake()
{
currentShakeTime = duration;
}
void Update()
{
if (currentShakeTime > 0)
{
float percent = currentShakeTime / duration;
Vector2 offset = UnityEngine.Random.insideUnitCircle * intensity * percent;
transform.localPosition = originalPos + new Vector3(offset.x, offset.y, 0);
currentShakeTime -= Time.deltaTime;
}
else
{
transform.localPosition = originalPos;
}
}
}
3. 音效同步
[Header("音效设置")]
public AudioClip hitSound;
public AudioClip impactSound;
public float soundDelay = 0.02f;
private AudioSource audioSource;
void Start()
{
audioSource = gameObject.AddComponent<AudioSource>();
}
private IEnumerator PlayHitSounds()
{
audioSource.PlayOneShot(hitSound);
yield return new WaitForSeconds(soundDelay);
audioSource.PlayOneShot(impactSound);
}
4. 物理模拟碎片
[Header("碎片效果")]
public GameObject fragmentPrefab;
public int fragmentCount = 10;
public float explosionForce = 300f;
private void CreateFragments()
{
for (int i = 0; i < fragmentCount; i++)
{
GameObject fragment = Instantiate(fragmentPrefab, transform.position, Quaternion.identity);
Rigidbody2D rb = fragment.GetComponent<Rigidbody2D>();
if (rb != null)
{
Vector2 direction = Random.insideUnitCircle.normalized;
rb.AddForce(direction * explosionForce);
rb.AddTorque(Random.Range(-100f, 100f));
}
Destroy(fragment, Random.Range(0.5f, 1.5f));
}
}
参数组合方案
参数组合方案
轻度打击效果
csharp
hitDuration = 0.3f;
maxScale = 1.15f;
shakeIntensity = 5f;
flashIntensity = 3f;
freezeDuration = 0.02f;
中度打击效果
csharp
hitDuration = 0.5f;
maxScale = 1.25f;
shakeIntensity = 10f;
flashIntensity = 5f;
freezeDuration = 0.05f;
useMotionBlur = true;
重度打击效果
csharp
hitDuration = 0.7f;
maxScale = 1.4f;
shakeIntensity = 15f;
flashIntensity = 8f;
freezeDuration = 0.08f;
useMotionBlur = true;
// 启用碎片效果
CreateFragments();
其他:
性能优化建议
对象池管理:
csharp
public class FragmentPool : MonoBehaviour
{
public GameObject fragmentPrefab;
public int poolSize = 20;
private Queue<GameObject> fragmentPool = new Queue<GameObject>();
void Start()
{
for (int i = 0; i < poolSize; i++)
{
GameObject fragment = Instantiate(fragmentPrefab);
fragment.SetActive(false);
fragmentPool.Enqueue(fragment);
}
}
public GameObject GetFragment()
{
if (fragmentPool.Count > 0)
{
GameObject fragment = fragmentPool.Dequeue();
fragment.SetActive(true);
return fragment;
}
return Instantiate(fragmentPrefab);
}
public void ReturnFragment(GameObject fragment)
{
fragment.SetActive(false);
fragmentPool.Enqueue(fragment);
}
}
材质共享:
csharp
private static Material flashMaterial;
void Awake()
{
if (flashMaterial == null)
{
flashMaterial = new Material(Shader.Find("UI/Unlit/Transparent"));
}
// 使用共享材质
}
按需启用:
csharp
void OnEnable()
{
// 初始化
}
void OnDisable()
{
// 清理资源
}
使用示例
csharp
// 在UI按钮上添加点击效果
public class HitButton : MonoBehaviour
{
public HitEffectController hitEffect;
void Start()
{
Button button = GetComponent<Button>();
button.onClick.AddListener(OnButtonClick);
}
void OnButtonClick()
{
hitEffect.TriggerHitEffect();
}
}
// 在游戏角色受击时
public class CharacterHealth : MonoBehaviour
{
public HitEffectController hitEffect;
public ScreenShake screenShake;
public void TakeDamage()
{
hitEffect.TriggerHitEffect();
screenShake.Shake();
// 其他伤害处理...
}
}
效果组合策略
轻重攻击区分:
轻攻击:缩放+轻微震动
重攻击:缩放+震动+闪光+屏幕震动+粒子
元素类型反馈:
物理攻击:灰色粒子+屏幕震动
火焰攻击:红色闪光+火焰粒子
冰霜攻击:蓝色闪光+冰晶粒子
命中部位差异:
头部命中:加强屏幕震动
身体命中:标准效果
四肢命中:减弱效果
总结
要创建具有强烈打击感的图片动画,关键在于多种效果的组合运用:
时间控制:通过时间冻结强调击中瞬间
空间变形:缩放、拉伸和震动表现物理冲击
视觉特效:闪光和粒子增加细节反馈
辅助效果:屏幕震动和音效增强沉浸感
参数优化:根据不同场景调整效果强度
通过调整上述脚本中的参数和组合不同的效果模块,可以为各种游戏场景创建出令人满意的打击效果。记得在实际应用中根据游戏风格和性能要求进行适当调整。