C#/수업내용

2020.05.26. 수업내용 - Coroutine 으로 애니메이션 실행하기

dev_sr 2020. 5. 26. 23:18

Coroutine 특징

1. IEnumerator 형식을 반환값으로 가진다

2. yield return ~ 에서 실행을 중지하고 다음 프레임으로 넘어가서 실행을 재개한다.

3. 사용할 때 StartCoroutine 메서드를 사용한다. ex) StartCoroutine(this.Move());

- MonoBehaviour 상속받는 클래스에서만 사용 가능

 

Coroutine을 쓰는 가장 큰 목적은 최적화!

Updata함수에서 매 프레임마다 실행하지 않고 원하는 시간만큼 원하는 시점에 실행시킬 수 있음

 

1. Test1 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Test1 : MonoBehaviour
{
    public GameObject heroGo;
    public GameObject fx01Prefabs;
    public GameObject fx02Prefabs;
    public UITest1 uiTest1;
    public GameObject hudPivot;
 
 
    private Hero hero;
    void Start()
    {
        this.hero = this.heroGo.GetComponent<Hero>();
        int num = 10000;
        int criticalNum = 15000;
 
        this.hero.OnImpactNotiTest1 = () =>
        {
            this.CreateHitFx(this.fx01Prefabs);
            this.uiTest1.ShowHud(this.hudPivot.transform.position, num);
        };
 
        this.hero.OnCriticalImpactNotiTest1 = () =>
        {
            this.CreateHitFx(this.fx02Prefabs);
            this.uiTest1.ShowCriticalHud(this.hudPivot.transform.position, criticalNum);
        };
    }
 
    private void CreateHitFx(GameObject fxPrefabs)
    {
        GameObject fxGo = Instantiate(fxPrefabs) as GameObject;
        var heroPos = this.heroGo.transform.position;
        fxGo.transform.position = new Vector3(heroPos.x, heroPos.y + 0.3f, heroPos.z + 0.3f);
    }
}
 

 

2. UITest1 

크리티컬일 때 다른 hud모양을 만들어본다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class UITest1 : MonoBehaviour
{
    public GameObject uiHudTextPrefabs;
 
    public void ShowHud(Vector3 targetPos, int num)
    {
        string strNum = string.Format("{0:#,0}", num);
        Debug.LogFormat("{0}", strNum);
        GameObject hudTextGo = Instantiate<GameObject>(this.uiHudTextPrefabs);
        hudTextGo.transform.SetParent(this.transform, false);
 
        var pos = Camera.main.WorldToScreenPoint(targetPos);
        var canvasX = pos.x - 1920 / 2;
        var canvasY = pos.y - 1080 / 2;
 
        var targetLocalPos = new Vector2(canvasX, canvasY);
        var uiHudText = hudTextGo.GetComponent<UIHudText>();
        uiHudText.Init(strNum, targetLocalPos);
        //hudTextGo.transform.localScale = Vector3.zero;
 
        //연출
        var targetPosY = hudTextGo.transform.localPosition.y + 100;
 
        hudTextGo.transform.DOLocalMoveY(targetPosY, 1f).SetEase(Ease.OutQuart).OnComplete(() =>
         {
             Debug.Log("move complete");
             Object.Destroy(hudTextGo);
         });
    }


    public void ShowCriticalHud(Vector3 targetPos, int num)
    {
        string strNum = string.Format("{0:#,0}", num);
        Debug.LogFormat("{0}", strNum);
        GameObject hudTextGo = Instantiate<GameObject>(this.uiHudTextPrefabs);
        hudTextGo.transform.SetParent(this.transform, false);
 
        var pos = Camera.main.WorldToScreenPoint(targetPos);
        var canvasX = pos.x - 1920 / 2;
        var canvasY = pos.y - 1080 / 2;
 
        var targetLocalPos = new Vector2(canvasX, canvasY);
        var uiHudText = hudTextGo.GetComponent<UIHudText>();
        uiHudText.Init(strNum, targetLocalPos);
        uiHudText.text.color = Color.red;
        //hudTextGo.transform.localScale = Vector3.zero;
 
        //연출
        var targetPosY = hudTextGo.transform.localPosition.y + 100;
 
        hudTextGo.transform.DOLocalMoveY(targetPosY, 1f).SetEase(Ease.OutQuart).OnComplete(() =>
        {
            Debug.Log("move complete");
            Object.Destroy(hudTextGo);
        });
    }
}
 

 

3. UIHudText 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
public class UIHudText : MonoBehaviour
{
    public Text text;
 
    public void Init(string strNum, Vector2 targetLocalPos)
    {
        this.text.text = strNum;
        this.transform.localPosition = targetLocalPos;
    }
}
 

 

4. Hero 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
using System.Collections;
using System.Collections.Generic;
using System.Resources;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
 
public class Hero : MonoBehaviour
{
 
    private Animation targetAnim;
    private Animation anim;
    private float delayTime;
    private AnimationState animStateAttack;
    private float impactTime;
    private float elapsedTiemImpact;
    private bool isImpact;
    private Hero targetHero;
 
    public GameObject model;
    //public GameObject targetPoint;
    public GameObject targetHeroGo;
    public UnityAction OnMoveComplete;
    public UnityAction OnAttackComplete;
    public UnityAction OnImpact;
    public UnityAction OnImpactNotiTest1;
    public UnityAction OnDamageComplete;
    public UnityAction OnIdleDelayComplete;
    public UnityAction OnCriticalImpactNotiTest1;
    
    public bool isEnemy;
    public bool isShakeCamera;
 
    // Start is called before the first frame update
    void Start()
    {
 
 
        this.targetHero = this.targetHeroGo.GetComponent<Hero>();
 
        this.anim = this.model.GetComponent<Animation>();
        this.animStateAttack = this.anim["attack_sword_02"];
        float totalFrame = this.animStateAttack.length * this.animStateAttack.clip.frameRate;
 
 
        this.impactTime = this.animStateAttack.length * 18 / totalFrame;
 
        //데미지 모션 실행 완료됐으면
        this.OnDamageComplete = () =>
        {
            this.IdleMotion();
        };
 
        //임팩트 터졌으면
        this.OnImpact = () =>
        {
            //데미지 모션 실행
            this.StartCoroutine(this.Damage());
        };
 
        //idle 했으면
        this.OnIdleDelayComplete = () =>
        {
            //다시 공격 실행 -> 루틴이 됨
            this.StartCoroutine(this.Attack(0.5f));
        };
 
 
        if (isEnemy) return;
        //적이면 이 밑의 코드부터 다 동작 안함//
 
        this.StartCoroutine(this.Move());
 
        //이동을 완료했을 때
        this.OnMoveComplete = () =>
        {
            //idle 잠깐 실행하고
            this.IdleMotion();
            //공격하는데 처음에 0.5 초 딜레이를 준다.
            this.StartCoroutine(this.Attack(0.5f));
        };
 
        //공격이 완료됐으면
        this.OnAttackComplete = () =>
        {
            this.isShakeCamera = false;
            //idle루틴을 실행해줌
            this.StartCoroutine(this.Idle());
        };
 
    }
 
    IEnumerator Move()
    {
        //한번만 루프애니메이션을 실행함
        this.anim.Play("run@loop");
 
        while (true)
        {
            //position변경
            this.transform.Translate(Vector3.forward * 1f * Time.deltaTime);
 
            var dis = Vector3.Distance(this.targetHero.transform.position, this.transform.position);
 
            if (dis <= 0.5f)
            {
                //도착
                break;
            }
 
            //다음 프레임 이동
            yield return null;
        }
        Debug.Log("이동완료");
        this.anim.Play("idle@loop");
        //이동을 완료했어?
        this.OnMoveComplete();
    }
 
    IEnumerator Attack(float delay)
    {
        Debug.Log("공격 실행");
 
        //랜덤 함수
 
        //첫 프레임은 0.5초 쉬고 
        yield return new WaitForSeconds(delay);
        //어택 모션 실행
        this.anim.Play("attack_sword_02");
        //임팩트 아직 안터짐
        this.isImpact = false;
        float rand = Random.Range(0, 1f);
 
        Debug.LogFormat("랜덤 {0}", rand);
 
        while (true)
        {
            this.elapsedTiemImpact += Time.deltaTime;
            if (this.elapsedTiemImpact >= this.impactTime)
            {
                if (!isImpact)
                {
                    if(rand>=0.4f)
                    {
                        this.isImpact = true;   //임팩트 터졌음
                                                //StartCoroutine(this.targetHero.Damage());
                        this.targetHero.OnImpact(); //타겟 히어로의 임팩트 터질 시간이 됐다고 알려줌
                        this.OnImpactNotiTest1();   //테스트1에 임팩트가 터질 시간이 됐다고 알려줌
                    }
 
                    //크리티컬
                    else if(rand < 0.4f)
                    {
                        this.isImpact = true;
                        this.targetHero.OnImpact();
                        this.OnCriticalImpactNotiTest1();
                        this.isShakeCamera = true;
 
 
                        Debug.Log("크리티컬!");
                    }
                   
                }
            }
 
            //총 공격시간이 커지면
            if (elapsedTiemImpact >= this.animStateAttack.length)
            {
                this.OnAttackComplete();    //공격완료 했어    
                this.elapsedTiemImpact = 0//시간 초기화
                
                break;
            }
 
 
            yield return null;
 
        }
    }
 
    IEnumerator Damage()
    {
        //데미지 모션 실행
        this.anim.Play("damage");
        //damage 애니메이션 길이 시간 만큼 딜레이 두고 다음 프레임 호출
        yield return new WaitForSeconds(this.anim["damage"].length);
        //데이지가 완료됨
        this.OnDamageComplete();
    }
 
    IEnumerator Idle()
    {
        //idle을 실행해주는데
        this.anim.Play("idle@loop");
        //2.5초쯤 뒤에 다음 프레임을 실행해라
        //딜레이를 안주면 idle 바로 실행실행실행실행..
        yield return new WaitForSeconds(2.5f);
        //idle을 실행한 걸 알려줌
        this.OnIdleDelayComplete();
    }
 
    private void IdleMotion()
    {
        this.anim.Play("idle@loop");
    }
 
}
 

 

5. CameraShake 

*되긴 되는데 수정이 필요함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
using UnityEngine;
using System.Collections;
 
public class CameraShake : MonoBehaviour
{
    // Transform of the camera to shake. Grabs the gameObject's transform
    // if null.
    public Transform camTransform;
 
    // How long the object should shake for.
    public float shakeDuration;
 
    // Amplitude of the shake. A larger value shakes the camera harder.
    public float shakeAmount;
    public float decreaseFactor;
 
    public GameObject heroGo;
    private Hero hero;
    private bool isCritical;
 
    Vector3 originalPos;
 
    void Awake()
    {
        if (camTransform == null)
        {
            camTransform = GetComponent(typeof(Transform)) as Transform;
        }
    }
 
    void OnEnable()
    {
        originalPos = camTransform.localPosition;
    }
 
    private void Start()
    {
        
        this.hero = heroGo.GetComponent<Hero>();
        Debug.Log(this.hero);
        
 
        if (this.hero.isShakeCamera)
         {
            Debug.Log("크리티컬0");
            
         }
    }
 
    void Update()
    {
        if (this.hero.isShakeCamera)
        {
            
            if (shakeDuration > 0)
            {
 
                
                camTransform.localPosition = originalPos + Random.insideUnitSphere * shakeAmount;
 
                shakeDuration -= Time.deltaTime * decreaseFactor;
                //this.shakeDuration = 0;
                
            }
 
            else
            {
                shakeDuration = 0f;
                camTransform.localPosition = originalPos;
                
            }
        }
        
        
    }
}