게임개발

[ BLAST 개발 기록 ] Effect System 개발

vcs 2024. 11. 1. 11:09

내가 만들어야 하는 것은

이런 기능이다. 일반적인 버프도 있지만. 불이나 전기 같은 속성대미지를 입으면

그에 따른 디버프를 걸고 효과를 띄우는 기능으로도 활용할 것이다.

 

모든 Agent들이 기능을 가지고 있어야 하고 개발하고 있는 BLAST는 뱀서라이크 장르이다.

뱀서라이크는 기본적으로 에너미가 엄청나게 많이 나오고 이 개체 하나하나가 모두

가지고 있어야 하는 기능이기 때문에 나름 최적화를 필요로 하는 기능이다.

 

여러 방법을 고민해 봤으나 제네릭으로 Agent마다 달린 어떤 컨트롤러가 효과를 가지고 업데이트해 주는 방식 외에는 나은 방법이 떠오르지 않아 그 방법을 채택해서 그대로 개발을 해보기로 했다

 

우선 이 방법의 원리를 대강 설명하면

 

우선 Controller를 하나 만들고 그걸 Agent가 가지고만 있게 만들 것이다

그 컨트롤러 안에는 이펙트들을 들고 있는 Dictionary가 있다. 이 친구는

EffectType을 Key로 EffectState 클래스로 하위 구현된 각 버프, 디버프별 객체를 Value로 담고 있다.

 

Controller를 구현하기 전에 EffectState 클래스를 먼저 작성해 준다.

필요한 멤버 변수는 기본적으로 이 정도가 있는데

저 두 Action들은 UI가 컨트롤하기 위해 존재하는 이벤트이다. 아래는 순서대로

 

isResist 해당 버프에 저항을 가지고 있는가.

enabled 해당 버프가 활성화 (버프를 받은 상태) 되어있는가

level 버프의 강도, 세기

duration 남은 지속 시간 

 

그리고 이 이펙트가 붙은 Agent, 즉 owner가 필요하다.

 

또한 자식에서 많이 사용하게 될 owner의 Health와 Transform을 가지고 있으면 나중에 구현하기

편해진다. 그리고 생성자를 만들어줄 텐데.

(이렇게 해주지 않으면 Initialize 같은 함수를 또 만들어서 Controller를 통해 한꺼번에 또 owner를 설정해줘야 하는데 그게 일이 많고 어쩌고 저쩌고...)

 

Agent 객체와 저항 정보를 받아온다.

Controller에서 버프를 생성할 때 이 작업을 거칠 것이다.

 

 

약간 코드가 FSM과 유사한 모양새로 동작하는 것처럼 보일 수 있는데. 

살짝 맞는 말이다. 거기서 영감을 얻는 것인.

 

기본적으로 필요한 메서드는 Start(), Update(), Over() 함수들로 짜보았다.

대충 버프 받는 함수, 버프 갱신, 끝날 때 실행되는 함수이다.

 

Start함수인데 내부적으로는 대충 level과 duration을 적용해 주고 활성화시켜 주는 작업이 다인데.

꽤 중요한 게 이미 버프가 활성화되어 있고 duration이나 level이 할당된 상태에서

새로 들어온 값이 덮어써질 수 있으니 관련 설정은 게임 밸런싱에 따라 위처럼 해주거나 

중첩 시 로직을 짜주는 게 좋을 것이다. 일단은 중첩은 없고 값만 적용시키면 돼서 딱히 특별한 게 있진 않다.

(percent는 만들어놓고 어디 쓰려했는지 까먹은.)

 

Update(), UpdateBySecond()를 구현해 놓는다.

시간도 이 내부에서 관리해 주기 때문에 Update에서는 duration을 계속해서

deltaTime으로 차감해주어야 한다. 또한 UI를 위한 Action이벤트를 갱신해 주고

만약 duration이 0이 되었다면 지속시간이 끝나서 효과를 종료시키면 되기 때문에

Over함수를 실행시킨다.

 

UpdateBySecond는 사실상 EffectState자체에서 구현하기보다는

1초 다마 일정한 딜이 들어온다던가 그런 용도로 자식에서 구현하는 게 일반적이라 그냥 비워둔다.

 

 

Over() 함수는 위에서 언급한 것과 같이

효과가 종료될 때 호출되는 함수이기 때문에

enabled를 false로 돌려주고

레벨과 지속시간 값 초기화,

OnOverEvent를 발행해 준다.

 

 

이 정도 EffectState를 짜놓고 나서 EffectController를 만들어줄 것인데.

 

Agent용 Controller이기 때문에 Agent를 _owner로 가진 채로 이루어져 있으며

코드는 세 부분으로 나눠진다. 

 

첫 번째는 버프 정보를 초기화해 주는 부분

리플렉션으로 버프 Dictionary에 버프들을 관리해 주기 위해 추가해 준다.

배열로 구현하고 Enum으로 인덱싱 해서 하드 하게 가져오는 방법도 있지만

가독성도 안 좋긴 한데 다음 부분에서 순차적으로 가져와 작업을 해주기 때문에

나중에 바꿀지 말지 고민이 좀 있다.

 

두 번째는 버프들을 최신화(Update) 해주는 부분이다.

Update에서 버프들을 모두 Upate() 시킨다. 또한 1초를 재서 UpdateBySeccond를 실행한다.

 

 

 

세 번째는 버프를 추가, 삭제 관리 해주는 부분이다.

단순히 이펙트를 받고 제거하거나 이펙트를 가져오는 함수들인데. 한 가지 이 구조에서 중요한 부분이라면

바로 이 녀석이다

EffectController에 IEffectable이 붙어있는데 (이름 좀 구린.)

 

여기에 ApplyEffect가 붙어있다. 이걸 또 어디서 쓰기 위한 것이냐 하면.

공격과는 별개로 속성딜을 받으면 활성화되는 오브젝트를 구현하기 위해 미리 만들어 둔 것이다.

(그냥 속성 피해받기 위해서는 IEffectable을 받아서 구현하면 된다, 객체 의존성도 줄이고 확장성도 늘리고)

 

그렇게 AgentEffectController는 완성되었다.

이제 각 Agent마다 이를 하위에서 분리할 필요가 있다. 왜 그래야 하나면

플레이어와 적이 이런 식으로 UI에 적용된 효과를 띄워주게 되는데

UI위치와 방식이 다르기 때문에 이걸 위해서 PlayerEffectController와 EnemyEffectController로 분리해 준다.

 

 

PlayerEffectController에서 바로 UI가 이펙트에 효과 지속시간을 최신화해 주도록 구독해 주는 작업을 해준다.

EffectStatePanel로 넘어가는 매개변수로는 효과 타입과 EffectState효과 인스턴스를 Dictionary에서 찾아서

보내준다. 그러면 UI 측에서 구독과 추가적인 작업을 알아서 해준다.

대충 이렇게 받아서 저 for문이랑 등등은, 대충 생성했던 슬롯을 자체 풀링해주는 작업이다.

진짜 그냥 풀링이다.

슬롯은 내부에 정보를 보여줄 요소들과 IsActive로 이 슬롯이 효과가 끝나 비활성화된 상태인지 체크한다.

이런 식으로 처음 등록시켜 줄 때 활성화 시켜주며 효과에 넣어놨던 이벤트에 각각 HandleRefresh로

최신화 정보를 받아와 시각적으로 보여주고 파괴 시에 비활성화시킨다.

 

다만 몇 가지 고민했던 것은, 현재와 같이 List로 할지 활성화, 비활성화 Queue를 두 개로

나눠서 관리해 줄지 고민을 했었다. 고민 좀 하다 냅다 AI의 힘을 빌려보았는데

풀링의 개수가 많지 않으면 그냥 간결하게 List 쓰는 게 좋을 거라고 하길래 그렇게 써봤다.

어차피 동시에 들어올 효과가 많아봤자 2,3개. 무엇보다 현재까지 개발된 효과만 해도 4개밖에 안되기 때문에

현재로서는 문제 될 일이 없어 보인다.

 

어쨌든 저렇게 풀링 해서 UI까지 관리해 주면 전체적인 구조와 비주얼적으로 보여주는 것까지 모두 짰다.

 

다음엔 실질적인 효과를 구현해 볼 것이다.