Game Development, 게임개발/개발

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

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

 

이벤트를 배워놓고 어디다 써먹는지 몰랐다.

이제 왜 이벤트를 써야하는지 알아 볼 차례다.

 

알아보자

 


 

문제 상황

플레이어가 총을 쏘는데 

총알이 스크린을 벗어나거나 적에게 맞지 않는다면 총알을 쏠 수 없게 만들고 싶다.

즉, 총알을 쏘는 속도를 제어하고 싶다는 말이다.

 

그래서 생각해낸 생각이 아래와 같다.

 

플레이어 스크립트에서 

Disable, 과 Enable로 총을 쏠 수 있게 하고

총알 자체에서 스크린 밖으로 벗어난 것을 감지하면 그것을 플레이어에게 알려줌으로 

Scene에 활성화 된 총알을 하나만 만들려고 한다.

 


 

여기 플레이어의 스크립트가 있다.

그렇게 구현하고자 한다면

 

#region Projectile Management

    public void EnableProjectile()
    {
        projectileEnabled = true;
        availableBullet.SetActive(projectileEnabled);
    }

    public void DisableProjectile()
    {
        projectileEnabled = false;
        availableBullet.SetActive(projectileEnabled);
    }

    private void FireProjectile()
    {
    	if(projectileEnabled){
        
          Vector2 spawnPosition = availableBullet.transform.position;

          ProjectileController projectile =
              Instantiate(projectilePrefab, spawnPosition, Quaternion.AngleAxis(90, Vector3.forward));

         projectile.gameObject.GetComponent<SpriteRenderer>().color = Color.green;
          projectile.gameObject.layer = LayerMask.NameToLayer("PlayerProjectile");
          projectile.isPlayers = true;
          projectile.projectileSpeed = 4;
          projectile.projectileDirection = Vector2.up;
          
          DisableProjectile();
        }
        
    }

 

FireProjectile에서

단순히 projectileEnabled를 체크해서 발사를 결정한다.

그러므로 실행 후 DisableProjectile 메서드를 실행해서 발사를 못하게 막으면 된다.

그렇다면 다시 총알이 밖으로 나갔을 때 enable을 시켜야 한다.

 

 

그럼 projectile 스크립트를 보자

    private void MoveProjectile()
    {
        transform.Translate(projectileDirection * Time.deltaTime * projectileSpeed, Space.World);

        if (ScreenBounds.OutOfBounds(transform.position))
        {
        	if(isPlayer == true){
            	PlayerController playerShip = FindObjectOfType<PlayerController>();
                playerShip.EnableProjectile();
            
            }
            Destroy(gameObject);
        }
    }

 

여기서 OutOfBounds로 위치를 체크하고 스크린 밖으로 나가면 Destroy를 함으로 오브젝트를 없앤다.

하지만 그냥 없애면 플레이어 스크립트가 알 수 없다.

알려줘야 한다.

 

그래서 PlayerController 클래스를 참조하는 객체를 만들어서 EnableProjectile 메서드를 실행해줘야한다.

그렇기에 EnableProjectile은 Public으로 선언되어야 한다.

 


 

 

솔직히 이정도로 구현했다??

나쁘지 않다. 하지만 이런 방법은 큰 프로젝트에 어울리지 않는 방법이다.

 

 

우리가 당장 PlayerController를 찾기위해 Find 메서드를 쓴 것도 꽤나 cost가 큰 작업이다.

** find는 거의 cost가 큰 작업이다.

더군다나  만약 PlayerController를 가지고 있는 오브젝트가 많다고 생각해보자.

MultiPlayer 게임이라면 이 메서드로는 해결이 불가능하다.

가장 처음 오브젝트만 반환하기 때문이다.

 

또한 PlayerController의 메서드를 public으로 바꿔야 한다.

당장 총알만 해도

projectile이 PlayerController 객체의 메서드를 사용하는 의존성이 높은 것을 보여준다.

 

 

이는 결합도(Coupling)을 올리기 때문이다.

결합도는 다른 모듈과 의존성을 말한다.

 

결합도가 올라가면 문제가 많이 발생한다.

** 응집도,Cohesion과 다른 개념이다.

 


 

아.. 그러면 어떻게 해결하라는 말이냐?

바로 Delegates와  Event이다.

 

Delegation을 다시 정의해보자면 

메서드를 참조하거나 캡슐화하기 위한 타입이다.

Delegation은 동적으로 작동해서 의존성을 줄여주는데 큰 도움이 된다.

-> 결합도를 줄여준다는 말이다.

 

이런 식으로 ProjectileController 스크립트에다 선언한다.

public delegate void OutOfBoundsHandler();

 

하지만 우린 Delegate로 선언한 Event를 여기서 쓸 예정이다.

Event를 다시 정의하자면

Event는 클래스나 객체가 해당 클래스나 객체에 관계된 사건이 일어났을 때 다른 클래스나 객체에게 알려주는 역할을 한다. 

 

delegate을 선언했으니 이렇게 해당 타입의 이벤트도 선언이 가능하다.

public event OutOfBoundsHandler ProjectileOutOfBounds;

 

 

그럼 이렇게 바꿀 수 있다.

    private void MoveProjectile()
    {
        transform.Translate(projectileDirection * Time.deltaTime * projectileSpeed, Space.World);

        if (ScreenBounds.OutOfBounds(transform.position))
        {
        	if(isPlayer == true)
            {
            	if(ProjectileOutOfBounds != null) // null check
			ProjectileOutOfBounds();                
            }
            Destroy(gameObject);
        }
    }

 

이제 ProjectileController는 PlayerController와 의존적이지 않다.

이제 만약 총알이 스크린에서 벗어나면 ProjectileOutOfBounds 이벤트가 실행된다.

 


 

?? 뭐야 저걸로 바꾸면 정상작동 안 할것 같은데???

물론 더 바꿔줘야할 것들이 남았다.

 

 

FireProjectile 메서드를 보자

private void FireProjectile()
    {
    	if(projectileEnabled){
        
          Vector2 spawnPosition = availableBullet.transform.position;
		  
          //여기서 PronjectileController를 참조함
          ProjectileController projectile =
              Instantiate(projectilePrefab, spawnPosition, Quaternion.AngleAxis(90, Vector3.forward));

         projectile.gameObject.GetComponent<SpriteRenderer>().color = Color.green;
          projectile.gameObject.layer = LayerMask.NameToLayer("PlayerProjectile");
          projectile.isPlayers = true;
          projectile.projectileSpeed = 4;
          projectile.projectileDirection = Vector2.up;
          
          //이벤트를 받아옴.
          prjectile.ProjectileOutOfBounds += EnableProjectile;
          
          DisableProjectile();
        }
        

 

저기 위에 프리팹을 Instantiate하기 위해서 ProjectileConroller를 참조하는 부분이 있다.

그렇다면 여기서 우리가 ProjectileController에서 선언한 이벤트를 받을 수 있다.

즉, ProjectileController에서 직접 PlayerController를 참조하지 않아도 되니까

public으로 선언할 필요도 없어졌다.

 

 

 

**주의할 점은 우리가 이벤트를 선언할 때

우리가 이용할 메서드의 반환 형식과 파라미터의 여부를 확인하고 선언해야한다는 것이다.

 

여기서는 delegate에서 void 타입, 파라미터 없이 선언했고

우리도 쓰려는 메서드가 void 타입에 파라미터가 없기 때문에 되는 것이다.

 

 


 

정리하면

직접적으로 객체를 불러와서 이용하는 것은

큰 프로젝트에서 좋지 않은 방향이다.

 

다른 방법을 찾아봐라

그 방법에는 이벤트를 이용할 수 있다 정도 되겠다.

 

 

 


 

참고링크

 

learn.unity.com/tutorial/handling-events

728x90
반응형
그리드형