协程

协程的概念

使用协程可以让一个函数分散在好几帧或者一段时间内执行完成

例子

加入我们需要实现一个让人物透明度逐渐降到0消失的功能需要实现

1
2
3
4
5
6
7
8
9
void Fade()
{
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
{
c.a = alpha;
renderer.material.color = c;
}
}

如果将这个函数在Update中调用,由于Update在一帧内执行完,因此并不会有人物逐渐透明的效果,而是一瞬间消失。因此,使用协程让函数循环分散到每一帧执行

1
2
3
4
5
6
7
8
9
10
IEnumerator Fade()
{
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
{
c.a = alpha;
renderer.material.color = c;
yield return null;
}
}
  • 协程需要以IEnumerator声明
  • 协程内需要有yield return语句
  • 使用return null会让函数在这一句暂停,等到下一帧再执行后面的语句
  • 如果用的是return new WaitForSeconds(),则会让后面的语句在若干秒后执行

应用:添加打击顿帧&抖动效果

创建一个AttackSense脚本挂载到MainCamera下,由于AttackSense作为工具类使用,采用单例模式编写。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AttackSense : MonoBehaviour
{
private static AttackSense instance;
public static AttackSense Instance
{
get
{
if(instance == null)
instance = Transform.FindAnyObjectByType<AttackSense>();
return instance;
}
}
}

这段代码实现了一个单例模式(Singleton)的静态属性Instance,用于在Unity中全局访问唯一的AttackSense实例。以下是关键解析:


1. 语法与设计模式

  • 静态属性(Static Property):通过public static AttackSense Instance定义了一个类级别的属性,无需实例化即可通过AttackSense.Instance访问。

  • 延迟初始化(Lazy Initialization):在get访问器中,首次调用时检查instance是否为null,若为空则通过Transform.FindAnyObjectByType()

    查找场景中的现有实例。


2. 关键点

  • 单例模式核心:
    • 确保全局唯一实例。
    • 提供全局访问点(Instance属性)。
  • Unity特定方法
    FindAnyObjectByType()是Unity的API,用于快速查找场景中的组件实例。

3. 实现顿帧

AttackSense内的函数

1
2
3
4
5
6
7
8
9
10
11
12
public void HitPause(int duration)
{
StartCoroutine(Pause(duration));
}

IEnumerator Pause(int duration)
{
float pauseTime = duration / 60; //duration是帧数,计算暂停时间
Time.timeScale = 0; //游戏暂停
yield return new WaitForSeconds(pauseTime); //等一段时间后恢复正常游戏速度
Time.timeScale = 1;
}
  • Time.timeScale 是 Unity 引擎中控制游戏时间流逝速度的核心属性,其默认值为 1.0,表示游戏时间与现实时间同步。

  • Time.deltaTime 变为 0,导致所有基于它的运动、动画计算停止(如 transform.position += speed * Time.deltaTime

实现镜头震动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private bool isShake;
public void CameraShake(float duration,float power)
{
if (!isShake) //避免频繁震动
{
StartCoroutine(Shake(duration,power));
}
}

IEnumerator Shake(float duration,float power )
{
isShake = true;
Transform camera = Camera.main.transform;
Vector3 startPosition = camera.position;

while (duration>0)
{
camera.position = Random.insideUnitSphere*power + startPosition;
duration -= Time.timeScale;
yield return null;
}
camera.position = startPosition; //震动完镜头恢复原始位置
isShake = false;
}

如何触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Attack : MonoBehaviour
{
[Header("基本属性")]
public int damage;
public int power;
public int range;
public int duration;
// Start is called before the first frame update

private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log(collision.name);
collision.GetComponent<Character>()?.TakeDamage(this);
AttackSense.Instance.HitPause(duration);
AttackSense.Instance.CameraShake(duration, power*0.01f);
}
}