목차
모의면접 2차, EffectData SO 관리, 오브젝트 풀 매니저
모의면접 2차
갑자기? 단체 면접을 보게 되었다.
진짜 갑자기...
일단 하단에는 내가 자기소개 준비하면서 작성했던 것.
>>>자기소개<<<
안녕하십니까. 게임 클라이언트 개발 직무에 지원한 ㅇㅇㅇ입니다.
저는 '사용자 경험'을 깊이 이해하는 개발자로서, 게임의 기획과 기술을 모두 아우를 수 있는 강점을 가지고 있습니다.
게임 기획자로 일하며 사용자가 게임을 통해 느끼는 재미에 대해 깊이 고민할 기회가 있었습니다. 이 경험은 비록 오래되었지만, 제가 코드를 작성할 때에도 '이 기능이 유저에게 어떤 경험을 주게 될까?'를 먼저 생각하게 하는 중요한 자산이 되었습니다.
이후 캐나다에서 소프트웨어 엔지니어링을 전공하고, 은행에서 웹 어플리케이션 개발자로 8개월간 근무하며 실무 역량을 쌓았습니다. 게임 프로그래밍 학사를 취득하며 이론적 기반을 다졌고, 현재 '내일배움캠프 유니티 11기' 과정을 통해 클라이언트 개발에 필요한 실무 기술을 집중적으로 배우고 있습니다.
이처럼 저는 기획자의 시선으로 게임의 재미를 이해하고, 개발자로서 이를 현실로 구현해내는 데 필요한 종합적인 역량을 갖추었습니다. 저의 다양한 경험과 끊임없는 배움에 대한 열정으로 더 많은 유저에게 사랑받을 수 있는 게임을 개발하는 데 기여하고 싶습니다.
경청해주셔서 감사합니다.
은행에서 어플리케이션 개발자로 일하며 구체적으로 어떤 업무를 하셨나요?
Q. 은행에서 웹 개발자로 근무하셨는데, 게임 클라이언트 개발과 어떤 유사점 및 차이점이 있다고 생각하십니까?
A. 웹 개발과 게임 클라이언트 개발은 사용자에게 쾌적한 경험을 제공하고, 효율적인 데이터 통신을 구현한다는 점에서 유사하다고 생각합니다. 두 분야 모두 사용자 중심의 사고와 유지보수가 쉬운 코드를 작성하는 능력이 중요합니다.
하지만 가장 큰 차이점은 '성능'이라고 생각합니다. 웹은 요청에 따라 페이지를 갱신하는 비동기적인 방식이 주를 이루지만, 게임은 초당 60프레임 이상의 실시간으로 모든 물리와 로직, 렌더링을 처리해야 합니다. 이 때문에 게임 클라이언트 개발은 CPU, GPU, 메모리 등 시스템 자원을 효율적으로 관리하고 실시간 성능을 최적화하는 것에 더욱 집중해야 합니다. 이러한 차이를 명확히 이해하고, 웹 개발 경험에서 쌓은 기본기를 바탕으로 게임 개발에 필요한 기술 역량을 키워나가고 있습니다.
Q. 10년 전 게임 기획자 경험이 현재 클라이언트 개발에 어떻게 도움이 된다고 생각하나요?
A. 저는 단순히 기능 구현에 그치지 않고, 유저가 받게 될 피드백이 기획 의도에 부합한가를 생각합니다. 예를 들어, 공격 스킬을 구현할 때 데미지 계산 로직뿐만 아니라, 스킬이 적중했을 때의 타격감이나 시각적 효과가 기획된 의도대로 유저에게 '타격의 재미'를 주고 있는지 고민하며 작업합니다. 이처럼 유저 경험을 먼저 생각하는 개발 태도는 기획자 경력을 통해 자연스럽게 습득한 저의 강점입니다.
Q. 내일배움캠프에서 유니티를 활용한 프로젝트 경험이 있나요? 있다면 어떤 프로젝트를 하셨나요?
A.
Q. 사용자 경험'을 깊이 이해한다고 말씀하셨는데, 본인이 생각하는 좋은 게임이란 무엇이며, 어떤 경험을 제공해야 한다고 생각하십니까?
A. 조작이나 성능 때문에 게임 진행에 지장을 준다면, 그것은 유저 경험을 해치는 요소라고 생각합니다. 따라서 좋은 개발자란, 기획의도를 온전히 구현하여 유저가 온전히 재미에만 몰입할 수 있도록 만드는 사람이라고 생각합니다.
그리고 주신 피드백!!
자기소개 개선을 해봐라고 한이유: 서로 보고 배우라고.
자신의 경험들을 예시로 녹여낸 자기소개가 좋았다. -> 자기소개 1분 정도만.
질문이 막혔을때 정리할 시간을 달라고 하고 답변을 했었음. -> 같은 모르겠다여도 최대한 생각해보고 모르겠다고 하는게 나음.
예시를 들어서 답변한 부분. 보통은 꼬리물기 질문으로 들어오는데 그럴 필요가 없었다. 좋았음.
대답할 수 있는 부분 있는지 물어봤을 때 나서서 답변하는 것은 엄청난 어드밴티지가 된다.
인터셉트는 면접관이 물어볼때만!
다른 면접자의 질문도 듣고 있는지 확인하려고 다른 면접자에게 이부분 답변주실수 있나요? 하고 물어볼 수도 있다.
~입니다. 라고 답변. ~라고 이해하고 있습니다.라고 해도 되지만 최대한 지양하도록.
(실질적인 관련 경력이 없더라도)최종프로젝트에서 맡았던 부분들을 위주로 설명하거나, 팀플 협업 같은거 잘한다, 최적화하는 데 자신있습니다. 스펀지 같은 사람입니다 지식을 빨리 배웁니다, 등
성격을 살리는 방법?? 커뮤니케이션, 리더십을 살리는 경우도 많다.
끝나고 느낀 점은 생각보다 나쁘지 않다!
자기소개 부분에서 긍정적인 피드백을 받았고,
다른 분들이 답변 못한 것을 다른 분들 중에 답변하실 분 있으신가요? 에서 인터셉트해서 답변도 해볼 수 있었다.
이 부분에서 내가 면접자로서 경쟁력이 부족하지는 않겠구나 라는 자신감이 생긴 것 같다.
다만 기술적인 부분에서 조금 더 준비를 하면 더 좋을 것 같다.
EffectData SO 관리

위 이미지와 같이 SO를 통합하여 관리하기로 했다.
EffectManager에서 SO들을 담은 List를 갖고 있는 SO(EffectDatabase)를 갖고 거기에서 호출된 id와 동일한 EffectData들의 ApplyEffect()함수가 호출되는 것이다.
SO로 관리하는 이유
1. prefab이라거나 soundclip, color 값 같은 것들을 기획자가 더욱 손 쉽게 관리할 수 있을 것이라 생각했음. +유효성(Null 참조 등)을 시각적으로 확인할 수 있음.
2. 연출 부분은 미세하게 조정해야할 부분들이 있을 것이라 예상, 이 경우 데이터 테이블을 그때마다 갱신하고 테스트해보는 것보다는 인스펙터에서 조절만 하면 되는 SO가 간단함.
3. SO는 상속이 가능하고, 메소드 구현부를 정의할 수 있음. (BaseEffectData에서 abstract PlayEffect()를 정의하고 이를 상속을 하여 SoundEffectData에서의 PlayEffect, CameraEffectData에서의 PlayEffect 들을 따로 정의할 수 있음.
4. 3의 연장선인데 상속을 이용한 다형성 덕분에 추후 TimeEffectData 등을 추가하여 구현하기가 편해짐.
SO의 단점
SO List를 갖고 있는 SO Database를 싱글톤인 EffectManager가 갖고 있게 된다.
이 경우, Prefab이나 SoundClip을 갖고 있게 되면 메모리상으로 부하가 생길까 걱정이 된다고 팀원 분이 이야기 해주셨다.
시도: 기획테이블에 추가하자!
기획 테이블에 사운드 아이디를 추가하자는 아이디어였다.
예를 들어, SkillData에서 SoundId 열을 추가해서 관리하는 것이다.
이 경우 CameraShake등의 id가 필요할 경우, 진폭 주기 등의 데이터 또한 추가되어야한다.
게다가, 실제로 효과가 발생하는 스킬도 있고 발생하지 않는 스킬도 있다.
이 경우 기획 테이블이 굉장히 커지고, CameraEffectId를 관리하는 또 다른 시트를 만들어 관리하자니,
기획자가 시트를 번갈아가면서 id를 맵핑해줘야하는 단점이 그대로 유지된다.
결론적으로 기술적 우려를 해소하기 위해 설계적인 퇴보를 초래하는 방식이라고 생각했고,
장기적으로 유지보수가 어려워진다고 판단했다.
시도: 일단 프로파일링해보고 그때 결정하자
이미 구현된 오디오 매니저를 수정해야했다.
또한 프로파일링을 제대로 써본적이 없기 때문에 사용하느라 시간이 지체될 것 같았다.
구현하고 나서 또 수정을 하게 되면 또 다시 수정사항이 생기는 셈이다.
중간 발표 이전까지 일정이 빠듯하기 때문에 그럴 시간이 없을 것 같았다.
그래서 차선책인 제일 간단한 오디오 매니저를 수정하고 진행한 뒤 중간 발표가 끝난 뒤 프로파일링을 돌려보기로 결정이 되는 것 같았다...
어차피 Addressable 쓸 거면 path만 저장하면 되는거 아냐!?
TIL을 쓰려다가 갑자기 떠오른 생각
어차피 Addressable을 쓸 거면 path만 저장하면 되는 거 아냐!?
인스펙터에서 연결된 프리팹이나 오디오 클립의 위치를 어떻게 알 수 있을까?를 검색해보다가 방법을 찾았다!
[CreateAssetMenu(fileName = "SoundEffectData", menuName = "ScriptableObjects/Effects/SoundEffectData", order = 3)]
public class SoundEffectData : BaseEffectData
{
[field: SerializeField] public AudioClip SoundClip { get; private set; }
[field: SerializeField] public string prefabPath { get; private set; }
#if UNITY_EDITOR
[field: SerializeField] public AudioClip AudioClip { get; private set; }
private void OnValidate()
{
if (AudioClip != null)
{
prefabPath = AssetDatabase.GetAssetPath(AudioClip).Replace("Assets/09. AddressableAssets/RemoteGroup/", "");
AudioClip = null;
}
}
#endif
[field: SerializeField] public float Volume { get; private set; } = 1f;
public override void ApplyEffect(EffectOrder order, GameObject target = null)
{
//todo. Sound 이펙트
}
}
AssetDatabase.GetAssetPath를 이용해 파일 주소를 찾을 수 있었다.
그 중에 어드레서블에서 필요가 없는 부분을 Replace로 제거해주었다.
AssetDatabase에서 GetAssetPath의 비용이 얼마나 되는지는 잘 모르겠다.
하지만 UnityEditor에서만 실행되는 부분이라 비용 부담을 덜고 사용했다.
결과
BaseEffectData를 상속 받는 SO 구조가 그대로 유지된다!
다형성이 유지되어 확장성이 보존된다.
SO가 에셋 자체를 직접 참조하지 않고 Path만 저장되므로 EffectDatabase가 로드되더라도 실제 용량이 큰 에셋은 메모리에 로드되지 않는다.
메모리 최적화라는 목적을 달성한 것이다.
또한, 기획자는 여전히 인스펙터에서 에셋을 드래그 앤 드롭으로 연결하는 직관적인 방식을 유지할 수 있다.
직관적으로 데이터 연결 상태를 확인할 수 있으니 에디터 단계에서 데이터 유효성(Null 참조 등)을 검증할 수 있다!!
오브젝트 풀 매니저
각종 에셋들, 프리팹들, 사운드들 등등....
오브젝트 풀 매니저를 구현해야했다.
이펙트 풀만 이펙트 매니저에서 구현하려고 했었는데 여러 요청을 받아서 기왕 이렇게 된 것 한번에 구현하려고 총대 맸다.
public interface IInitializable
{
void Initialize();
}
public class ObjectPoolManager : Singleton<ObjectPoolManager>
{
//Resources 폴더 내의 경로 + 프리팹 이름을 키로 사용
private Dictionary<string, IObjectPool<GameObject>> pools = new Dictionary<string, IObjectPool<GameObject>>();
/// <summary>
/// Pool을 등록하는 메서드
/// 만약에 Pool에 이미 등록된 path라면 아무 동작도 하지 않는다.
/// </summary>
/// <param name="fullPath">Resources 폴더 내부의 Path + prefab name까지 포함한다. </param>
/// <param name="defaultCapacity"></param>
/// <param name="maxSize"></param>
public void RegisterPool(string fullPath, int defaultCapacity = 1, int maxSize = 100)
{
if (pools.ContainsKey(fullPath)) return;
var prefab = Resources.Load<GameObject>(fullPath);
if (prefab == null)
{
Debug.LogError($"프리팹 {fullPath}을 찾을 수 없다...");
return;
}
var pool = new ObjectPool<GameObject>(
() => Instantiate(prefab),
obj =>
{
obj.SetActive(true);
var initializable = obj.GetComponent<IInitializable>(); //사용 후 초기화가 필요한 컴포넌트가 있다면 초기화 메서드 호출
initializable?.Initialize();
},
obj => obj.SetActive(false),
obj => Destroy(obj),
false, defaultCapacity, maxSize
);
pools[fullPath] = pool;
}
/// <summary>
/// pool에 등록된 path가 없다면 자동으로 등록 후 오브젝트를 반환한다. (defaultCapacity = 1, maxSize = 100)
/// </summary>
/// <param name="fullPath">Resources 폴더 내부의 Path + prefab name까지 포함한다. </param>
/// <returns></returns>
public GameObject GetObject(string fullPath)
{
if (!pools.ContainsKey(fullPath))
{
RegisterPool(fullPath);
}
return pools[fullPath].Get();
}
public void ReleaseObject(string fullPath, GameObject obj)
{
if (pools.TryGetValue(fullPath, out var pool))
pool.Release(obj);
}
public void ClearAllPools()
{
foreach (var pool in pools.Values)
{
pool.Clear();
}
pools.Clear();
}
}
지난 번에 오브젝트 풀을 구현할 때 직접 구현하는 것과 성능 문제에서 큰 차이는 없을 것처럼 보였다.
Unity API 오브젝트풀이 가독성 면에서는 좋은 것 같아서 사용하기로 했다.
오브젝트 풀은 오브젝트의 fullPath를 키로 각 경로에 맞는 오브젝트 풀이 딕셔너리 value 값으로 관리된다.
fullPath가 헷갈릴 수도 있을 것 같아서 Summary로 설명해주었다.
(지금보니까 Resources 기준인데, 우리는 어드레서블 쓰니까 바꿔줘야 한다.)
직접 Register를 해주는 방법과, GetObject를 했을 때 Dictionary에 존재하지 않으면 따로 Register를 해주는 방법으로 구현했다.
싱글튼의 게으른 초기화처럼 이것도 게으른 등록(Lazy Register)라고 불리는 것 같다.
또한 오늘 오전에(오늘 한 게 많아서 그런가 오늘이 아니라 어제 같다...) 오브젝트 풀에 오브젝트가 너무 많아지면?? 이라는 추가 질문을 받은 적이 있다.
그 당시에는 설계에서부터 제약을 둘 수 있도록 기획자와 상의할 것 같다.고 답변했었는데, 생각해보니까 필요가 없어질 때 다시 풀을 클리어하면 되는 거 아닌가..?
그래서 혹시나 싶지만 ClearAllPools라는 메서드를 구현했다.
지금 보니까 public void ClearPoolByKey(string fullPath) 같은 메서드도 구현해두면 좋을 것 같다.
오브젝트들의 초기화 방법: 조금 더 고민이 필요하다.
지난번 오브젝트 풀을 활용했을 때 애를 먹었던 점은 바로 초기화다.
Initialize가 가능한 객체인지 아닌지를 파악하기 위해 IInitializable이라는 인터페이스를 생성하고 이를 찾아 Initialize한 뒤 Get할 수 있도록 했다.
근데 이게 최선은 아닌 것 같은 느낌이다.
Get 할때마다 GetComponent를 호출하는 것이 비용적으로 저렴하진 않아서 오브젝트 풀을 사용하는 의미가 없어질 것 같기도 하다.
아예 초기화가 가능한 경우는 자체적으로 OnDisable이나 OnEnable 등에서 진행하도록 하는 것이 나으려나?
아니면 풀로 관리되면서 초기화가 필요한 객체 자체에게 초기화를 도와주는 스크립트를 도입하는 것도 방법 일 것 같다.
하지만 지금은 부하가 생길지 안생길지도 모르는 가능성 때문에 최적화를 하는 것 보다는,
추후에 프로파일링을 통해 문제로 발견이 된다면 그때 가서 이번에 생각난 아이디어들을 구현해보면 좋을 것 같다. (시간이 없다ㅜㅜ)
느낀점
오늘 면접! 굉장히 뿌듯했다.
잘한 것 같아서 취업... 할 수 있을지도?? 라는 생각이 들어서 서울로 상경하는 것은 확정이 되었다.
또한 SO를 데이터를 관리하는 방식이 성능 면에서나 팀 협업 효율 면에서나, 두 마리의 토끼를 잡은 것 같아서 정말 뿌듯하다!
이런 순간들이 개발하면서 정말 즐겁다고 느끼는 부분인 것 같다.
당분간 또 이 즐거움과 뿌듯함을 동력으로 팀 프로젝트에 임할 수 있을 것 같다.
저번에는 Enemy 한 종류만 오브젝트 풀로 관리했었는데 이번에는 ObjectPool 자체를 딕셔너리로 관리하는 방식으로 구현했다.
잘 사용되었으면 좋겠다...
내일 학습 할 것은 무엇인지
ObjectPoolManager에서 Resources를 기준으로 불러오도록 구현되어있다.
우리는 어드레서블을 쓰기로 했기 때문에 이를 기준으로 변경을 해줘야할 것 같다.
오늘 구현한 것들을 실제로 사용이 되는지, 테스트를 하면 좋을 것 같다.
오전 내로 마무리 하고 싶다.
이 모든 작업들의 결론은 EffectManager를 완성시키는 것이다.
EffectManager를 완성하면... 보스 특수 스킬이 있다.
보스 특수 스킬 구현을 시작한지 어언... 일주일쯤 되는 것 같은데
'부트캠프 > 본캠프' 카테고리의 다른 글
| [내일배움캠프_2025SEP26] UniTask 도입 (0) | 2025.09.26 |
|---|---|
| [내일배움캠프_2025SEP25] 코루틴 예외 처리와 자원 정리 (1) | 2025.09.25 |
| [내일배움캠프_2025SEP23] 모의면접 2차 스터디, EffectManager 시작 (0) | 2025.09.23 |
| [내일배움캠프_2025SEP22] 이펙트(카메라), 애니메이션 (0) | 2025.09.22 |
| [내일배움캠프_2025SEP21] 오늘 뭐했지..? (0) | 2025.09.21 |