이번 포스팅에서는 코루틴(Coroutines)에 대하여 이야기하고 Player 클래스가 상속 받고 있는 Character 클래스를 보강하도록 하겠습니다. 추가로 Enemy 클래스를 추가하도록 하겠습니다.
#12 유니티 기초 종합, 클래스 구현 포스팅을 참고 하시면 되겠습니다.
포스팅은 유니티 2D 게임 개발(게임 개발 프로그래밍)에 나온 예제로 진행합니다.
Coroutine(코루틴)
코루틴은 실행 도중에 일시 정지한 뒤에 다음 프레임에 재개할 수 있는 함수라고 생각하면 됩니다.
여러 프레임에 걸쳐서 길게 실행해야 할 메서드를 코루틴으로 구현하곤 합니다.
일반적인 메서드의 모든 작업은 유니티 엔진의 한 프레임 안에 일어납니다.
한 프레임보다 길게 실행해야 할 메서드를 호출해도 유니티는 호출한 메서드 전체를 강제로 한 프레임안에 실행합니다.
using System.Collections 코드를 가장 상단에 추가해야합니다.
IEnumerator 반환 형식을 사용하여 간단하게 코루틴을 선언 할 수 있습니다.
메서드 본문안에 yield 하라는 코드를 추가하면 바로 실행을 일시 정지하고 다음 프레임에 같은 위치로 돌아가라고 엔진에 알리는 역할을 합니다.
우리는 Character 클래스와 Character 클래스를 상속받는 Player , Enemy 클래스에 코루틴을 사용하겠습니다.
Character 클래스
Character 클래스는 다른 캐릭터와 피해를 주고 받고, 죽는 기능까지 필요합니다.
하지만 현재 Character 클래스에서는 구현이 되어 있지 않습니다.
구현을 해보도록 하겠습니다.
Character 스크립트(C#)의 전체 코드입니다.
using System.Collections;
using UnityEngine;
public abstract class Character : MonoBehaviour
{
//public HitPoints HP; 삭제할 예정입니다.
public float maxHP;
public float StartingHP;
//수정한 부분 <------
public virtual void KillCharacter()
{
Destroy(gameObject);
}
public abstract void ResetCharacter();
public abstract IEnumerator DamageCharacter(int damage, float interval);
// ----->
}
코드를 확인하고 저장합니다.
수정한 부분만 확인해보겠습니다.
public HitPoints HP; 는 Player 클래스에는 필요하지만 Enemy 클래스에는 필요가 없기 때문에 Character 클래스에서 삭제를 하고, Player 클래스에만 추가를 하도록 하겠습니다.
"virtual" 키워드를 사용한 KillCharacter() 메서드를 정의하였습니다.
"virtual" 키워드의 의미는 상속한 클래스에서 재정의할 수도 있다는 뜻입니다.
KillCharacter() 메서드는 게임오브젝트를 제거하고 씬에서 삭제하는 Destroy() 메서드를 포함하고있습니다.
캐릭터가 죽을때 이 메서드가 실행이 될 것입니다.
"abstract" 키워드는 현재 클래스에서 구현할 수 없고 상속한 클래스에서 구현해야만합니다.
"abstract" 키워드는 클래스, 메서드, 변수를 선언할 때 사용합니다. 우리는 클래스와 메서드에 사용하였습니다.
Character 클래스를 상속받는 Player , Enemy 클래스에서 구현해야합니다.
ResetCharacter() 메서드는 캐릭터를 다시 사용할 수 있게 하는 메서드입니다.
IEnumerator 반환 형식의 DamageCharacter(int damage, float interval) 메서드는 코루틴의 반환 형식을 가지고 있고, 캐릭터가 피해를 받을 때 사용할 것입니다.
Enemy 클래스
Enemy클래스를 생성해보도록 하겠습니다.
Assets > Scripts > MonoBehaviour 폴더에 Enemy 스크립트(C#)을 생성하겠습니다.
방금 생성한 Enemy 스크립트(C#) 컴포넌트를 EnemyObject 프리팹에 추가합니다.
Enemy 스크립트(C#)의 전체코드 입니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : Character
{
float HP;
private void OnEnable()
{
ResetCharacter();
}
public override IEnumerator DamageCharacter(int damage, float interval)
{
while (true)
{
HP = HP - damage;
if(HP <= float.Epsilon)
{
KillCharacter();
break;
}
if(interval > float.Epsilon)
{
yield return new WaitForSeconds(interval);
}
else
{
break;
}
}
}
public override void ResetCharacter()
{
HP = StartingHP;
}
}
코드를 확인하고 저장합니다.
먼저 코루틴을 사용하고 있기 때문에 using System.Collections를 사용하는 것을 볼 수 있습니다. Character 클래스를 상속 받고 있습니다.
위에서 Character 클래스에서 HitPoints를 삭제하였기 때문에 float 형식으로 HP가 들어가 있는 것을 볼 수 있습니다.
DamageCharacter 메서드 부터 확인해보도록 하겠습니다.
public override IEnumerator DamageCharacter(int damage, float interval)
{
while (true)
{
HP = HP - damage;
if(HP <= float.Epsilon)
{
KillCharacter();
break;
}
if(interval > float.Epsilon)
{
yield return new WaitForSeconds(interval);
}
else
{
break;
}
}
}
DamageCharacter 메서드가 "override" 키워드가 들어가 있는 것을 볼 수 있습니다.
부모 클래스인 Character 클래스에서 abstract로 되어 있던 메서드이기에 자식 클래스에서 해당 메서드를 사용하려면 재정의를 해야합니다.
재정의를 한다는 것을 알리는 키워드가 "override" 입니다.
while문 루프를 만들었습니다. interval이 0 보다 작을 때 루프를 빠져나옵니다.
while문 안쪽을 보면 HP에서 피해량을 나타내는 damage를 뺀 값을 HP에 설정합니다.
if문을 확인해보겠습니다. float.Epsilon은 0보다 큰 가장 작은 양수 값을 나타낸 것입니다.
이 값보다 작으면 캐릭터의 HP값은 0인 것입니다.
HP가 0이 되었기 때문에 KillCharacter() 메서드를 호출하고 while문을 빠져나옵니다.
다음 if문입니다. interval은 피해 사이의 대기 시간입니다. 이 간격이 float.Epsilon 보다 크면 실행을 yield 하고 interval 간격만큼 대기하고 루프를 재개합니다.
else문은 위의 상황이 조건이 참이 아니라면 while문을 빠져나옵니다.
ResetCharacter() 메서드를 확인하겠습니다.
public override void ResetCharacter()
{
HP = StartingHP;
}
"override" 키워드를 사용하고 있습니다. 메서드 안쪽에는 Enemy의 현재 HP를 StartingHP로 설정합니다.
OnEnable() 메서드입니다.
private void OnEnable()
{
ResetCharacter();
}
OnEnable() 메서드는 오브젝트를 활성화할 때마다 불립니다. 적 오브젝트가 활성화 할때마다 ResetCharacter()를 호출하겠습니다.
Player 클래스
player클래스를 수정해보도록 하겠습니다.
Player 스크립트(C#)의 전체 코드입니다.
using System.Collections;
using UnityEngine;
public class Player : Character
{
//수정한 부분 <------
public HitPoints HP;
// ------>
public Inventory inventoryPrefab;
Inventory inventory;
public HealthBar healthBarPrefab;
HealthBar healthBar;
float speed = 10;
/* 삭제할 것입니다.
public void Start()
{
inventory = Instantiate(inventoryPrefab);
healthBar = Instantiate(healthBarPrefab);
HP.value = StartingHP;
healthBar.Character = this;
}
*/
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("CanBePickedUp"))
{
Item hitObject = collision.gameObject.GetComponent<Consumable>().item;
if (hitObject != null)
{
bool shouldDisappear = false;
print("Hit: " + hitObject.objectName);
switch (hitObject.itemType)
{
case Item.ItemType.COIN:
shouldDisappear = inventory.AddItem(hitObject);
break;
case Item.ItemType.HEALTH:
shouldDisappear = AdjustHP(hitObject.quantity);
break;
default:
break;
}
if (shouldDisappear)
{
collision.gameObject.SetActive(false);
}
}
}
}
public bool AdjustHP(int amount)
{
if (HP.value < maxHP)
{
HP.value += amount;
print("Adjusted HP by: " + amount + ". New Value: " + HP.value);
return true;
}
return false;
}
// 수정한 부분 <-------
private void OnEnable()
{
ResetCharacter();
}
public override void ResetCharacter()
{
inventory = Instantiate(inventoryPrefab);
healthBar = Instantiate(healthBarPrefab);
HP.value = StartingHP;
healthBar.Character = this;
}
public override IEnumerator DamageCharacter(int damage, float interval)
{
while (true)
{
HP.value = HP.value - damage;
if(HP.value <= float.Epsilon)
{
KillCharacter();
break;
}
if(interval > float.Epsilon)
{
yield return new WaitForSeconds(interval);
}
else
{
break;
}
}
}
public override void KillCharacter()
{
base.KillCharacter();
Destroy(healthBar.gameObject);
Destroy(inventory.gameObject);
}
// -------->
}
코드를 확인하고 저장합니다.
public HitPonts HP;는 Chracter 클래스에서 삭제하였던 코드입니다. Enemy 클래스에는 필요없지만 Player 클래스에는 필요하기 때문에 적었습니다.
Start() 메소드를 삭제할 것입니다.
수정한 부분을 확인해보도록 하겠습니다.
먼저 ResetCharacter() 메서드입니다.
public override void ResetCharacter()
{
inventory = Instantiate(inventoryPrefab);
healthBar = Instantiate(healthBarPrefab);
HP.value = StartingHP;
healthBar.Character = this;
}
"override" 키워드를 사용하고 있습니다.
Start() 메서드의 내용과 똑같은 코드입니다.
Start() 메서드 안의 내용을 삭제하여도 좋습니다.
캐릭터가 재생성 될때 Enemy 오브젝트와 다르게 인벤토리나 체력바를 가지게 됩니다.
OnEnable() 메서드의 내용은 Enemy 클래스와 동일합니다.
DamageCharacter() 메서드의 내용 또한 Enemy 클래스와 거의 유사합니다.
KillCaracter() 메서드입니다.
public override void KillCharacter()
{
base.KillCharacter();
Destroy(healthBar.gameObject);
Destroy(inventory.gameObject);
}
"override" 키워드를 사용하고 있습니다.
"base"키워드는 상속받은 클래스 즉 부모클래스 또는 기본클래스를 참조할 때 사용합니다.
base.KillCharacter() 메서드는 Character 클래스의 KillCharacter()메서드입니다.
다음은 체력바와 인벤토리를 제거합니다.
이번포스팅에서는 많은 내용을 다루었습니다. 다음 포스팅에서는 적(Enemy) 캐릭터와 플레이어(Player) 캐릭터가 부딪혔을때 플레이어의 HP가 감소하도록 하겠습니다.
마지막으로 Ctrl + S를 눌러 Scene을 저장합니다!
감사합니다! :)
'유니티2D' 카테고리의 다른 글
#20 유니티 인공지는 적(Enemy) 만들기 (9) | 2020.12.23 |
---|---|
#19 유니티 OnCollisionEnter2D , OnCollisionExit2D (3) | 2020.12.22 |
#17 유니티 카메라 매니저(플레이어 추적) (7) | 2020.12.19 |
#16 유니티 스폰위치, 게임 매니저(Singleton) (5) | 2020.12.18 |
#15-2 유니티 인벤토리 만들기_스크립트(C#) (0) | 2020.12.17 |