Game Development, 게임개발/개발

Handling Event, 이벤트 활용하기 - 2 [Unity]

게임이 더 좋아 2021. 4. 3. 08:19
반응형
728x170

 

이어서 이벤트를 활용해보자

이전 글과 이어진다.

 


 

이번에는 Enemy가 죽으면 해당하는 점수를 인터페이스 보여주려고 한다.

여기선 HUD, Head Up Display로 나타난다.

총알을 맞으면 어떻게 되는지 보자

    private void OnCollisionEnter2D(Collision2D collision)
    {
        Destroy(collision.gameObject);
        
        GameObject xPlosion = Instantiate(explosion, transform.position, Quaternion.identity);
        xPlosion.transform.localScale = new Vector2(2, 2);

        Destroy(gameObject);
    }

 

이번에도 이벤트를 이용하고 싶다.

 

 

 

이번엔 이런 식으로 선언을 하였다.

public delegate void EnemyDestroyHandler(int pointValue);
public class EnemyController : MonoBehaviour

여전히 반환 타입은 void이지만 파라미터가 생겼다.

 

public event EnemyDestroydHandler EnemyDestroyed;

 또 안에다 이벤트로 선언했다.

 

그럼 또 스크립트를 바꿔보자

    private void OnCollisionEnter2D(Collision2D collision)
    {
        Destroy(collision.gameObject);

        GameObject xPlosion = Instantiate(explosion, transform.position, Quaternion.identity);
        xPlosion.transform.localScale = new Vector2(2, 2);
        
        if(EnemyDestroyed != null)
        	EnemyDestroyed(pointValue);

        Destroy(gameObject);
    }

 

이번에는 파괴되었다면 플레이어에게 알려주는 것이 아니라

점수판에 알려줘야 한다.

 

그럼 UI 스크립트, HUD 스크립트를 보자

그 중 점수를 바꾸는 메서드를 보자

    public void UpdateScore(int score)
    {
        scoreText.text = score.ToString("D5");
    }

 

이 메서드가 Enemy가 죽을 때마다 실행되어야 점수판이 바뀔 것이다.

그렇다고 Find를 쓰기엔 Enemy는 화면 상에 많아질 것이기 때문에 쓸모없어진다.

 

 

 

우리가 PlayerController에서 ProjectileController의 객체가 있었지만

HUDController에는 EnemyController의 객체가 없다.

어떻게 이벤트를 불러와야 할까??

 

그 전에 다른 스크립트 하나만 더 보자

GameSceneController이다.

적을 만드는 스크립트다.

    private IEnumerator SpawnEnemies()
    {
        WaitForSeconds wait = new WaitForSeconds(currentLevel.enemySpawnDelay);
        yield return wait;

        for (int i = 0; i < currentLevel.numberOfEnemies; i++)
        {
            Vector2 spawnPosition = ScreenBounds.RandomTopPosition();

            EnemyController enemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);
            enemy.gameObject.layer = LayerMask.NameToLayer("Enemy");
            enemy.shotSpeed = currentLevel.enemyShotSpeed;
            enemy.speed = currentLevel.enemySpeed;
            enemy.shotdelayTime = currentLevel.enemyShotDelay;
            enemy.angerdelayTime = currentLevel.enemyAngerDelay;
 
            yield return wait;
        }
    }

이 스크립트에는 Enemy를 참조하는 변수가 있다.

 

찾았다. 같은 방법을 시도해보자

 

쓰려는데 유니티가 추천해준다.

 

TAB을 눌러서 확인해보자.

 

새롭게 생겼다.

 

아무튼 이렇게 GameSceneController에서 Enemy가 파괴되었을 때 알 수 있게 되었다.

그렇다면 HUD는 ..??

게다가 HUD는 적 하나에만 국한된게 아니라

플레이어가 총 획득한 모든 점수를 보여줘야 한다.

 

GameScene의 Variable에 총 점수를 추가시키자.

    private int totalPoints;

Enemy가 파괴될 때 이벤트가 발생하니 Enemy가 죽을 때마다 totalPoints를 조작할 수 있다고 생각하면 된다.

 

사용하기 전에 우선 이벤트를 하나 더 만들자.

Total 점수가 변화했다는 것을 알리기 위해 이벤트를 하나 더 만드는 것이다.

    public event EnemyDestroyedHandler ScoreUpdated;

  이 이벤트를 통해 다른 클래스에서도 GameSceneController를 이용해서 totalPoints에 접근할 수 있게 하는 것이다.

 

그리고 우리가 아까 자동으로 만들어진 메서드를 totalPoint를 조작할 수 있게 수정하자

 

    private void Enemy_EnemyDestroyed(int pointValue)
    {
        totalPoints += pointValue;
        
        if(ScoreUpdatedOnKill != null)
        	ScoreUpdatedOnKill(totalPoints);
        
        
    }

 

 

HUD에 만약 GameSceneController를 참조하는 것이 없다면 만들어봤자 쓸모가 없다.

 

그래서 HUD에 먼저 선언해줬다.

private GameSceneController gameSceneController;

 

그리고 초기화해준다.

  private void Start()
    {
        gameSceneController = FindObjectOfType<GameSceneController>();
    }

왜 여기서 Find를 쓰냐고 물을수 있는데

얘는 유일하다. ㅎㅎ 적처럼 많지도 총알처럼 많지도 않다.

 

그래서 여기다 다시 이벤트가 참조할 메서드를 넣는다.

또 유니티가 자동으로 만들어준다.

 

그럼 GameSceneController에서 토탈스코어가 바뀌면

    private void GameSceneController_ScoreUpdatedOnKill(int pointValue)
    {
        UpdateScore(pointValue);
    }

업데이트만 하면 바뀌겠다.

 

다시 정리하면

 

Enemy가 죽으면 GameSceneConroller 가 Enemy를 참조해서 Enemy가 죽었을 때 해당 pointValue를 받아서

Total에 더해준다. 그리고 total이 바뀌면 그 이벤트를, HUDController가 받아서 텍스트를 업데이트 하게 된다.

 

그림으로 표현하자면

 

 

**참조를 어떻게 누구를 할건지를 이해하는 것은

진짜 이벤트에 대해 제일 중요한 부분이다.

 


 

또.. 만약 플레이어가 죽으면 플레이어 목숨이 사라져야 한다.

 

즉, 플레이어가 죽으면 이벤트가 발생하게 할 수 있다.

플레이어가 죽는 스크립트가 어디있나보자

PlayerController에 이런 부분이 있다.

 

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.GetComponent<ProjectileController>())
            TakeHit();
    }

    private void TakeHit()
    {
        GameObject xp = Instantiate(expolsion, transform.position, Quaternion.identity);
        xp.transform.localScale = new Vector2(2, 2);

        Destroy(gameObject);
    }

 

 TakeHit에서 이벤트 발생하면 딱 좋을 것 같다.

 

여기서 또 다른 개념이 나오는데

Action키워드다

C# Action Delegates란..?

System 네임스페이스에 있는 타입으로 확실하게 delegate를 정의하지 않고도 메서드를 캡슐화 시키는게 가능하게 해준다. 

 

**이렇게 쓰는게 편하다. 사실

 

어떻게 쓰이냐면..?

System을 써야하고 -> using System

그냥 이벤트를 선언한다.

 

 

이렇게 PlayerController에 이벤트를 만들었다. 

그럼 목숨은 어디서 관리하느냐??

 

GameSceneController에 있네?

 

오 그럼 PlayerController를 어디서 참조하는 곳이 있나?

 

 

여기 있네?? ship 이 여기있네 이런 ship ㅅ..

아무튼 얘도 이벤트 함수가 또 Tab누르면 생기겠지?

 

바꿔주자.

 

 

다시 살아나야하니까

SpawnShip도 실행 좀 해주고

 

이제는 실제로 HUD만 업데이트만 되면 끝.

 

HUD에는 직접적으로 PlayerController를 참조는 안하고 GameSceneController는 아직 남아있다.

 

GameSceneController에 아까 배운 Action을 활용해서 이벤트를 하나 만들자.

Life의 개수를 필요로 하기 때문에

int형을 받을 때는 저렇게 선언한다.

 

아까 GameScene에서 PlayerController에서 받아온 이벤트를 가져왔다.

lives를 전달할 수 있게 되었다.

 

그럼 다시 HUD로 돌아가자

 

LifeLost 이벤트가 일어나면 우리는 HIdeShip을 실행하고 싶다.

lives를 받아서 실행이 되겠지??

 

와 실행 잘된다 끝!!!

 

 

아 힘들다 힘들어.. ㅎㅎ 새벽인데 고생했다.

 

다시 마지막으로 정리하자면

이벤트 하나를 여러 클래스에서 읽을 수 있고

이벤트가 연결되어있어 최종적으로 전달할 수도 있었고

Action으로 선언을 하면 굳이 밖에다 delegate 선언을 안해도 됐고

어떤 걸 참조하느냐가 정말 중요하구나 깨달았다.

 

 

728x90
반응형
그리드형