TIL

2024 11 24 TIL

noc777 2024. 11. 24. 14:18

지금까지 프로젝트를 살펴보았을 때 나는 델리게이트와 이벤트 구독을 사용하지 않았다.  이유는 내가 개념만 알고

쓰는 방법을 모르고 있기에 사용하는 것을 꺼리는 게 아닌가 싶다. 

강의를 진도를 나가면서 이벤트를 어떤식으로 사용하는 지 보고 다음 팀프로젝트 때 어떤 식으로 사용할 지 구상해보는 연습이 필요할 것 같다.

private void SpawnEnemyAtPositions(int posIdx)
{
  int prefabIdx = Random.Range(0,enemyPrefabs.Count); //등록된 적 프리팹의 카운트만큼 난수를 돌린다.
  GameObject enemy = Instatntiate(enemyPrefabs[prefabIdx],spawnPosition[posIdx].position,Quaternion.identity) //지정된 위치에 해당 인덱스의 적 오브젝트를 생성;
  enemy.GetComponent<HealthSystem>().OnDeath += OnEnemyDeath;//생성된 오브젝트의 HealthSystem을 불러와서 OnDeath에 새로 함수를 구독한다. (펍썹 패턴)
  CurrentSpawnCount++ //적이 생성되었으니 카운트를 늘려준다.
}

private void OnEnemyDeath()
{
  CurrentSpawnCount-- //아까 구독해둔 함수, 적이 죽었을 때 카운트가 줄어들도록
}

 

심화강의의 스테이지 로직 챕터에서 나온 이벤트 구독 부분이다.

적을 생성하고 생성할 때 HealthSystem의 컴포넌트 기능에 접근해 적이 죽을 때 수행되는 OnDeath 이벤트에 OnEnemyDeath라는 함수를 구독해서 적의 현재 수를 알려주는 UI를 제어한다.  

 

public event Action OnHeal; //Action 으로 이벤트를 등록
 
 if (CurrentHealth <= 0f) //현재 체력이 0이 되었을 때 조건 성립
 {
     CallDeath(); //OnDeath이벤트를 실행한다.
     return true;
 }
 
 
 private void CallDeath()
 {
     OnDeath?.Invoke(); //여기서 죽을 때 수행되는 함수들을 모두 실행한다.
 }

좀 더 거슬러올라가서 HealthSystem의 OnDeath를 살펴보았을 때  이벤트 구독 방식에선 실질적인 기능보단

실행되는 위치만이 명시되어 있다. 

이는 플레이어와 적이 같은 HealthSystem을 공유하되 다르게 작동하기를 원하기 때문이다.

 

그래서 아까처럼 enemy가 생성되는 부분에서만 함수를 구독하여 플레이어에겐 적용이 안되도록 하기도 하고

 

OnDeath += OnDeath; //다른 스크립트에서 OnDeath에 OnDeath 함수를 구독

void OnDeath() //기능을 계속해서 추가할 수 있다.
{
    // 멈추도록 수정
    rigidbody.velocity = Vector3.zero;

    // 약간 반투명한 느낌으로 변경
    foreach (SpriteRenderer renderer in transform.GetComponentsInChildren<SpriteRenderer>())
    {
        Color color = renderer.color;
        color.a = 0.3f;
        renderer.color = color;
    }

    // 스크립트 더이상 작동 안하도록 함
    foreach (Behaviour component in transform.GetComponentsInChildren<Behaviour>())
    {
        component.enabled = false;
    }

    // 2초뒤에 파괴
    Destroy(gameObject, 2f);
}

추가하고 싶은 기능을 담은 스크립트를 이렇게 같이 연결

또 스크립트로 연결해서 이벤트함수에 따로 기능을 추가해준다. 이때도 적용되고 싶어하는 게임 오브젝트에만 스크립트를 붙여줌으로서 플레이어에겐 따로 영향이 안가도록 설계되어 있다.

 

 

이외에도 사용방법이 있겠지만 일단 실행하고 싶은 곳에 이벤트가 실행되도록 배치하고 또 기능적인 부분은 

따로 구현을 하는 것 자체는 동일하므로 다음 프로젝트 때는 이런 이벤트를 적절히 배치하여 좀 더 완성도 높게 코드를 

작성해보는 걸 목표로 해본다.

 

 

 

OrderBy 

 

OrderBy 는 ()안의 내용대로 오름차순으로 정렬하겠다는 뜻입니다.

foreach(CharacterStat stat in statsModifiers.OrderBy(o=>o.statsChangeType))



public enum StatsChangeType //오름차순의 기준이 되는 enum
{
	Add,
	Multiple,
	Override,
}

강의에서는 enum 안의 Add,Multiple,Override 를 오름차순의 기준으로 삼았습니다.

스탯 강화순서를 Add,Multiple,Override로 순서를 정하겠다는 의미입니다.

 

private void UpdateCharacterStat()
{
    ApplyStatModifier(baseStats);//CurrentStat에 먼저 baseStats을 적용

    foreach(CharacterStat stat in statsModifiers.OrderBy(o => o.statsChangeType))
    {
        ApplyStatModifier(stat);//이후 순차적으로 강화 스탯을 적용
    } 
}

 

 

Action에서는 반환값이 없는 메서드를 다룬다면 Func는 반환값을 다루는 메서드를 다룬다.

따라서 Func는 반환 값이 있는 Action으로 보고 있다.

 

Func<float,float,float> operation 처럼 여러개의 자료형을 가지고 있다면 마지막 값이 반환값을 표현하는 자료형이 된다.

예를 들어 Func<int,int,string> 이라면  int값과 int값을 받고 마지막 string이 반환값인 메서드를 다룬다. 

 

해당 코드는 스위치 식으로 작성되어 있는데.  

기존의 Switch Case 문과 비슷하게 사용되면서도 간편하게 사용할 수 있다.

 

//아직 어떻게 스탯을 적용시키는 건지는 잘 모르겠다. 일단 Add,Multiple,Override 모두 처리 방식이 다르다는 것은
알수있다.
Func<float, float, float> operation = modifier.statsChangeType switch
{
    StatsChangeType.Add => (current, change) => current + change, //<float,float,float> float값 두개를 받아와서 처리하고 float 값을 반환
    StatsChangeType.Multiple => (current, change) => current * change,
    StatsChangeType.Override => (current, change) => change,
};


//디폴트 지정자로 바꿔주어 임의의 값이 입력되었을 때 enum의 마지막 타입인 Override에 계산되도록 한다. 시작할 때 baseStat을 기반으로 한번 스탯을 적용시켜주기에 이것을 의식한듯 하다.
//이후 타입이 늘어난다면 Override를 적어주고 디폴트 지정자를 늘어난 타입으로 해준다.
Func<float, float, float> operation = modifier.statsChangeType switch
{
    StatsChangeType.Add => (current, change) => current + change,
    StatsChangeType.Multiple => (current, change) => current * change,
    _=> (current, change) => change,   
};

 

이후 이 Func를 매개변수로 하여 현재 스탯을 적용시키는 로직을 작성한다.

뭔가 머리속으로 이해는 되는 거 같은데 직접써보는 생각을 해보는 것은 많이 보고 많이 써봐야할 것 같다.

 private void UpdateBasicStats(Func<float, float, float> operation, CharacterStat modifier) //여기선 기본 스탯이 되는 체력과 스피드만 처리해줌
 {
     CurrentStat.maxHealth = Mathf.Max((int)operation(CurrentStat.maxHealth, modifier.maxHealth),MinMaxHealth); //현재 스탯의 최대 체력은 Func 함수에서 처리해서 반환된 maxHealth 와 5로 제한된 최소체력 중 값이 큰 것이 된다.
     CurrentStat.speed = Mathf.Max((int)operation(CurrentStat.speed,modifier.speed),MinSpeed); //마찬가지로 현재 스탯의 스피드를 둘중 큰 값으로 처리한다.
 }

 

여기서 Func의 활용점은  스위치 식을 통해 enum의 타입에 따라 다르게 동작하는 메서드를 구현 및 매개변수로 활용하는 것이다.  

'TIL' 카테고리의 다른 글

2024 11 26 TIL  (0) 2024.11.26
2024 11 25 TIL  (1) 2024.11.25
2024 11 23 TIL  (0) 2024.11.23
2024 11 22 TIL  (0) 2024.11.22
2024 11 21 TIL  (1) 2024.11.21