public IEnumerator Timer()
{
while (true)
{
CurrentTime = 0f;
GameManager.Instance.gameState = GameState.Ground;
while (CurrentTime < groundTime)
{
yield return _fixedUpdate;
CurrentTime += Time.fixedDeltaTime;
}
CurrentTime = 0f;
GameManager.Instance.gameState = GameState.Underground;
while (CurrentTime < undergroundTime)
{
yield return _fixedUpdate;
CurrentTime += Time.fixedDeltaTime;
}
}
}
낮과 밤을 분리하는 코루틴으로 시작하면 낮을 설정된 시간만큼 지속하고, 그 후 다시 밤을 설정된 시간만큼 지속한다. 낮과 밤 둘 다 지나가면 한 사이클이 끝난다.
private void DisableAllChildren()
{
for (int i = 0; i < MonsterPool.transform.childCount; i++)
{
Transform childTransform = MonsterPool.transform.GetChild(i);
childTransform.gameObject.SetActive(false);
}
}
낮 시간이 끝날 때 모든 몬스터 오브젝트들을 비활성화 하기 위해 작성한 코드이나, 비활성화 넣을 부분이 애매해서 프로젝트에서는 빠지게 되었다.
할당된 오브젝트의 하위 오브젝트들을 하나씩 찾아 비활성화하는 원리이다.
기존 강의에서는 몬스터들을 풀링하는게 아니라 계속 복제하는 방식으로 사용했는데, 풀링하는 방식으로 하려하니 여러 애로사항이 있었다.
기존 강의처럼 스폰할 때 데이터를 덮어씌우는 방식으로 적용해보려고 했는데, 이 방식으로 했을 때 계속 웨이브 진행이 되지도 않았는 데 체력이 올라가 있었다.
private void statupgrade()
{
var monsterSpawner = GameManager.Instance.monsterSpawner.GetComponent<MonsterSpawner>(); //여러번 하지말고 한 곳에 캐싱해서 재사용할 것
monsterSpawner.HealthUpgrade();
var objectPool = monsterSpawner.MonsterPool;
var bombstat = monsterSpawner.BombStat;
if (GameManager.Instance.WaveCnt != 1)
{
var monsters = objectPool.GetComponentsInChildren<Transform>(); //이런 식으로 하는게 좋다.
Transform[] Bombs = monsters.Where(t => t.name == "Bob_omb(Clone)").ToArray(); //꺼져있는 애들을 찾아오는지 봐야됨. 꺼져있는 애들은 적용 안된다.
foreach (var bomb in Bombs)
{
bomb.GetComponent<CharStatsHandler>().AddStatModifier(bombstat);
}
이유를 찾아보니 스탯의 최소치를 제한하는 구문이 있는데, 거기의 값이 내가 지정한 값보다 더 높게 설정되어 있었다. 아마 그 부분을 수정하고, 위의 꺼져있는 오브젝트들을 찾아올 수 있도록 했다면 이 방법으로도 가능했으리라 생각한다.
두번째 방법은 오브젝트풀의 딕셔너리에 직접 접근해서 데이터를 변경해주는 방법이다.
private void MonsterUpgrade()
{
var Stats = GameManager.Instance.monsterSpawner.GetComponent<MonsterSpawner>();
Stats.HealthUpgrade();
if (GameManager.Instance.WaveCnt != 1)
{
foreach (var bomb in poolDictionary["Bob_omb"])
{
bomb.GetComponent<CharStatsHandler>().AddStatModifier(Stats.BombStat);
}
}
}
이 방식이 좀 더 깔끔해보이기도 해서 선택했다. 만들어진 오브젝트들을 웨이브가 증가할 때 콜백을 주는 이벤트에 구독시켜 동작하도록 설정하였다.
딕셔너리에 foreach로 접근하려는데 태그를 찾을 수 없다고 나옴.
오브젝트풀 스크립트를 두 곳(몬스터, 투사체)에서 사용해 몬스터의 딕셔너리가 사라졌다.
그래서 투사체용 스크립트는 기본형을 그대로 채용하고, 오브젝트풀을 상속받는 몬스터용 스크립트를 하나 더 만들어 사용해 해결하였다.
병합하고 테스트해보니 애들이 점점 쎄지고 투사체를 많이 던진다.
스탯핸들러의 addmodifier 부분에 타입이 있는데, Override 기준으로 작성한 것을 Add로 적용하고 있었다.
몬스터들의 대상이 되는 Target Tag를 스크립트 내에서 수정하고 있었는데, 직렬화된 필드라 인스펙터에서 수정해주어야 했다.
분명 재설정해서 다시 1로 돌리도록 했는데 이런 현상이 발생했다.
코루틴 동작 중 해당 컴포넌트가 꺼지면 남은 동작이 이어지는 현상이라고 유추하여
페이드아웃이 종료 된 후 컴포넌트들을 종료할 수 있도록 변경해주었다.
IEnumerator disapear()
{
foreach (SpriteRenderer renderer in transform.GetComponentsInChildren<SpriteRenderer>())
{
StartCoroutine("Fadeout", renderer);
}
yield return new WaitForSeconds(2f); //여기
foreach (Behaviour component in transform.GetComponentsInChildren<Behaviour>())
{
component.enabled = false;
}
gameObject.SetActive(false);
}
하지만 이렇게 되면 몬스터들이 죽었음에도 공격을 하거나 이동하기 때문에 그와 관련된 컴포넌트들은 직접 먼저 종료해주었다.
public void die()
{
if (!isDie)
{
isDie = true;
if (_monsterMovement != null)
_monsterMovement.enabled = false;
if (_flyingMonsterMovement != null)
_flyingMonsterMovement.enabled = false;
_characterController.enabled = false;
foreach (var coll in _collider2Ds)
{
coll.enabled = false;
}
StartCoroutine("disapear");
}
}
일련의 동작들은 모두 HealthSystem의 OnDeath 이벤트에 구독시켜 몬스터가 죽으면 자동으로 동작하도록 작성되었다.
event
기본적으로는 delegate의 한종류라고 볼 수 있다.
delegate에 기반되어있으며 이를 옵저버패턴과 유사하게 활용 할 수 있게 도와줌.
델리게이트는 함수를 저장해서 실행하는게 본래 의미
- 어떤 요청을 했을 때 그 결과를 처리하는 것을 만들 수 있다.(콜백)
이벤트를 예약해두고 필요할 때 해당 기능들을 실행
둘이 사용법이 좀 다르다.
action은 c#에 내장되어있는 델리게이트
매개변수로 넘기는 부분은 Action뒤에 제네릭으로 추가함 (<>부분)
매개변수 없으면 제네릭 없이 사용하면 됨.
nullable을 실행할 때는 인보크를 통해서 실행해야됨
action?.invoke();
if(action!=null)
action();
위 두개는 같은 의미다.
튜터님도 델리게이트보다 액션을 많이 사용한다고 하심
꼭 그런건 아니지만 작업하는 구조상 동작하는 애가 정보를 뿌려주는 경우가 많기때문에 이벤트를 가지고있는 애가 싱글톤으로 사용될 수도 있다.
보통 게임 로직 관련 이벤트들 모아서 처리하고, 몬스터 이벤트끼리 모아서 처리하는 그런 느낌으로 사용
한 곳에 모으는 건 비추천 / 각각의 주체별로는 모으는 게 좋지만, 다 모을 필요는 없다.
배운 기능들을 배웠다고 막 쓰는게 아니라 필요한 곳에 적절히 사용해야 좋다.
지난번에도 포스팅했지만 델리게이트는 외부에서 실행 가능, 이벤트는 외부에서 실행 불가능.