스킬 아이콘을 담을 스킬 테두리를 추가했다. 이전에 게임 영역을 나누는 Bar에 적용된 효과를 그대로 적용했다. 효과는 포토샵으로 은색을 표현하는 자료를 검색해서 적용한 것이다.
다음으로는 스킬 관련 오류를 수정했다. 적을 처치할 떄 가끔 적이 정상적으로 사망 처리가 되지 않았는데, 이유는 IEnumerator 사용 방식에 있었다.
문제를 찾기 위해 디버깅을 하니 yield return new WaitForSeconds(1); 이후 부분이 실행이 되지 않는 문제가 있었다.
이에 관해 구글링을 하다가 아래 글의 PraetorBlue라는 유저의 답변에서 도움을 받았다.
Coroutine stops working after yield return - Unity Forum
Coroutine stops working after yield return
Hi there everyone!So, let me tell you about what I'm trying to achieve.Basically, I have a raycast-based interact script that recognizes objects that...
forum.unity.com
|
나한테 해당되는 내용은 1번이었다. 총알이 적과 닿으면 Active 상태를 false로 한 후에 yield return을 사용했는데, 이 경우에 코루틴을 호출한 MonoBehaviour가 disabled되어 그 지점에서 이어서 실행되지 않는 듯 하였다.
private void OnTriggerEnter2D(Collider2D other)
{
if(other.CompareTag("ShootingEnemy"))
{
StartCoroutine(ShootingGameManager.s_instance.HitByMidoriBullet(gameObject, other.gameObject));
}
}
코루틴이 실행되면 그 코루틴을 실행하고 있는 객체가 살아있으니 계속 실행될 것이라고 생각했는데, 코루틴을 실행하는 객체가 살아있어도 이를 호출한 객체가 사라지면 코루틴의 작동도 중단되는 듯 하였다. 하긴 이게 맞는 것 같다. 예를 들어 타이머를 외부 스크립트에서 가져와 사용한다고 치면, 호출한 객체가 사라졌을 때 계속 도는 등의 일이 반복된다면 메모리 낭비가 굉장히 심할 것이다.
public IEnumerator HitByMidoriBullet(GameObject midoriBullet, GameObject enemy)
{
// 이미 총알이 공격한 상태라면 공격이 작동하지 않도록 함
if (!midoriBullet.GetComponent<BulletController>().GetCanAttack()) yield break;
// 총알이 적중하면 투명하게 이미지를 바꾸고, 적중 상태를 변경 (Active를 false로 하면 yield return 이후 호출이 불가능해짐)
midoriBullet.GetComponent<Image>().color = Color.clear;
midoriBullet.GetComponent<BulletController>().SetCanAttack(false);
EnemyController enemyController = enemy.GetComponent<EnemyController>();
if(enemyController.GetEnemyHP() > 1)
{
enemyController.SetEnemyHP(enemyController.GetEnemyHP() - 1);
audioManager.PlaySFX("EnemyHit");
}
else
{
StartCoroutine(storyManager.MidoriHappy());
audioManager.PlaySFX("EnemyDestroy");
enemy.SetActive(false);
StartCoroutine(ShowExplosion(enemy));
// 스킬 사용 중 적의 Parent가 바뀌어 오류 생기는 것을 방지
if (_isActivatingSkill) yield return new WaitForSeconds(1);
enemy.transform.SetParent(_deadEnemyParent.transform);
}
}
때문에 적에게 총알이 적중하면 color를 투명하게 변경하여 눈에 보이지 않게 변경하였다. 이 경우, 날아가면서 뒤에 있는 적도 맞을 수 있기 때문에 해당 총알이 이미 적에게 적중했다면 canAttack이라는 변수를 false로 변경하여 하나의 총알이 한 명의 적만 공격할 수 있도록 제한하였다. 이렇게 수정하니 제대로 작동되었다.
적이 폭발할 때의 이미지는 아래 주소를 통해 받았다. 감사하게도 무료로 배포하고 있었다.
이 폭발 이미지는 Sprite가 변경되는 것이기 때문에 Animation을 사용했다.
private IEnumerator ShowExplosion(GameObject target)
{
GameObject explosion = GetFrefab("Explosion");
explosion.GetComponent<RectTransform>().anchoredPosition = target.GetComponent<RectTransform>().anchoredPosition;
explosion.SetActive(true);
explosion.GetComponent<Animator>().Play("Explosion");
yield return new WaitForSeconds(0.5f);
_explosionQueue.Enqueue(explosion);
}
다른 것들과 동일하게 오브젝트 풀링 기법을 적용했고, target의 위치로 폭발 이미지를 이동시켜서 애니메이션을 재생하는 식으로 구현했다. 애니메이션 재생 시간이 0.5초라 0.5초 이후에 다시 사용할 수 있도록 설계하였다.
public IEnumerator HitByEnemy(GameObject midoriPlane, GameObject enemy)
{
_isAlivePlane = false;
midoriPlane.GetComponent<Image>().color = Color.clear;
StartCoroutine(ShowExplosion(midoriPlane));
audioManager.PlaySFX("EnemyDestroy");
yield return null;
}
public IEnumerator HitByEnemyBullet(GameObject midoriPlane, GameObject enemyBullet)
{
_isAlivePlane = false;
midoriPlane.GetComponent<Image>().color = Color.clear;
StartCoroutine(ShowExplosion(midoriPlane));
audioManager.PlaySFX("EnemyDestroy");
yield return null;
}
적에게 닿으면 아군 기체가 폭발하는 기능은 총알이 닿으면 적이 죽는 것과 동일하게 구현했다. 적은 닿으면 사라지지 않고, 적의 총알은 닿으면 사라질 것이기 때문에 따로 함수를 만들었는데, 하나의 함수로 통합하고 Tag를 확인해서 해당 부분을 추가하면 될 것 같다.
마지막으로는 UI를 추가하여 우측 하단에 공격, 스킬 아이콘을 표시하고 쿨타임을 가시화했으며, 스킬 쿨타임 중 사용시 유저에게 좌측 상단의 텍스트와 경고음을 통해 알려주게끔 하였다.
private IEnumerator SkillTimer()
{
while(true)
{
if (_skillCooltime > 0)
{
_skillCooltime -= 0.1f;
skill2CooltimeText.GetComponent<TMP_Text>().text = _skillCooltime.ToString("F1");
}
else
{
skill2CooltimeText.GetComponent<TMP_Text>().text = "";
skill2Background.GetComponent<Image>().color = Color.clear;
}
yield return new WaitForSeconds(0.1f);
}
}
스킬 쿨타임은 IEnumerator를 활용했는데, Update는 프레임마다 호출하기 때문에 효율이 떨어질 것 같아 0.1초마다 쿨타임을 갱신하는 식으로 구현하였다. 다만, else 부분이 약간 비효율적인 것 같다. bool 변수를 하나 추가해서 한 번만 작동하도록 수정해야겠다.
오늘까지 완성된 부분은 다음과 같다.
* 아직 적이 총알을 날리는 기능이 구현되어 있지 않아 맵 오른쪽 중단에 적 총알을 배치하여 적 총알에 맞아 죽는 모습도 녹화되어 있음
배경음악도 변경했는데 이전 것들보다 어울리는 것 같다. 아래 영상의 BGM을 사용했다.
내일 목표는 다음과 같다.
1. HitByEnemy, HitByEnemyBullet 함수 하나로 통합
2. SkillTimer 함수 효율적인 사용을 할 수 있도록 수정
3. 기본 공격에도 쿨타임 적용하기 (0.2~0.3초 정도?)
4. 죽었을 때 부활하는 기능 구현
5. 적이 플레이어를 향해 총알 발사하는 기능 구현
6. 점수 기능 구현
아마 이 정도만 구현하면 슈팅 게임의 전반적인 기능들이 전부 구현될 것 같다. 아, 보스 스킬을 구현하는 부분이 있기는 하겠다. 근데 우선 보스 스테이지까지 진행하는 부분을 먼저 만들어야겠다. 처음엔 되게 막막하고 조잡했는데, 확실히 이제는 게임 느낌이 나는 것 같다. 그리고 적들에게 각각 다른 능력치를 적용하면 좋을 것 같다. 어떤 적은 속도가 빠른 대신 체력이 낮다든지 하는 것이다. 만들다보니 '이런 기능이 있으면 좋겠다' 싶은 생각이 들어서 추가한 부분도 있는데, 백지 상태에서 기획하는 것이 상당히 힘들구나 하는 것을 새로이 깨닫게 되었다.