C# 질문

더보기

 

21 인터페이스와 추상클래스의 차이는 무엇인가요? 인터페이스는 행동에 대한 추상화가 필요할때 사용됩니다.
예상이 되지 않는 행동일때 많이 사용되며
A has B관계로 고양이 =/=걷다 이지만 , 고양이는 걷는 행동을 할 수 있습니다.

추상클래스는 구체적인 추상화가 필요할때 사용됩니다.
필드,메서드 를 부모,자식 사이에서 공통적으로 사용할때 많이 사용됩니다.
A is B관계로 , 고양이 = 동물 , 강아지 = 동물
22 가비지 컬렉터란 무엇인가요? GC란 Heap 영역에서 참조가 끊겨 사용하지 않는 데이터들을 수집하여
처리해주는 기능입니다.
23 가비지 컬렉터의 장점과 단점에 대해 설명해주세요. GC의 장점은 프로그래머가 신경을 덜 써도 되기에 실수 발생률을 줄여줍니다.
메모리의 할당 해지는 런타임시 매우 중요합니다.
즉, 프로그래머가 놓친 부분을 GC가 자동으로 다 처리해주는 것이 장점입니다.

단점으로는 GC는 처리비용이 높기에 결국 너무 의존해서는 안됩니다.
GC는 프로그래머가 원하는 호출시점을 알 수 없기에 런타임 되고 있는
프로그램이 어느순간 렉을 먹거나 하는 경우입니다.
24 가비지 컬렉터의 세대 개념에 대해 설명해주세요. GC의 세대 개념은 Java는 .NET에서 사용되는 개념입니다.
GC는 일반적으로 <세가지 세대>로 메모리를 나눕니다

0세대는 객체가 생성되면 할당됩니다.
1세대는 GC가 동작한뒤 사용되고 있는 0세대 객체들이 1세대로 승격합니다.
똑같은 동작을 반복하게 되고 1세대에서 살아남은 객체들은 2세대로 승격하게
됩니다.

즉, 2세대는 가장 큰 메모리 공간을 차지하게되며 계속 쌓이게 되면 오버헤드
문제점을 일으킬수도 있습니다.

크기가 큰 객체는 LOH(라지 오브젝트 heap)에 할당되에
일반적인 세대 기반 수집과는 다른방식으로 처리됩니다.
25 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요? 박싱,언박싱은 결국 형변환의 일종이기에 박싱을 한 필드(변수)가 언박싱 될때
다른 자료형으로 변환시 잘못된 데이터가 들어가는 경우를 주의해야 합니다.
null값이 들어가게 될수도 있다.
이러한 단점을 방지하기 위해 제네릭을 사용 할 수 있습니다.
26 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요? 오브젝트를 Inisiate/Destroy를 반복하게 될경우 오브젝트의 생성/파괴시
처리비용이 높으며 , 파괴는 즉 GC의 수집량을 늘리는 상황이 됩니다.

즉 오브젝트 풀링을 사용하여 런타임시에 사용할 오브젝트를 미리 할당해놓고
사용하게되면 필요없는 생성/파괴가 일어나지 않기에 메모리 관리에 도움이
됩니다./
27 제네릭이란 무엇인가요? 제네릭이란 일반화로 , 클래스를 자료형에 상관없이 사용할때,
즉,다양한 데이터 타입으로 재사용 할수 있는 기술이며
자주 사용되는 Class를 변수처럼 사용하기 위한 용도로 보면됩니다.

28 람다식(Lambda Expression)이 무엇인지 설명해주세요. 람다식은 이름없는 함수로도 불리며,
원하는 호출시점에서 편하게 생성하여 사용 할 수 있는 기능이다.
단점으로는 이름이 없기에 다른 곳에서 호출하지 못하여 재사용성이 떨어지는
단점이 있다.
29 LINQ란 무엇인가요? LINQ를 설명하기전에 쿼리에 대해 알아야합니다.
쿼리는 DB에 정보를 요청하는 행위 ,즉 데이터를 필터링,정렬,조회 등등할때
사용되는 단어입니다.

LINQ는 .NET 프레임워크에 내장된 기능이며
사용하면 좀더 SQL, 컬렉션, XML, 엔티티 프레임워크 등의 데이터에
접근하여 쉽게 관리 할 수 있습니다.

하지만 LINQ의 단점으로는 간단한 데이터 조회시에는 괜찮지만
데이터가 복잡해지고 양이 많아지게 되면 퍼포먼스나 코드 가독성 면으로
떨어질수 있습니다.
30 리플렉션(Reflection)이 뭔지, 사용을 해봤다면 어떤 이유에서 사용했는지 설명해주세요. 리플렉션은 .NET에서 런타임에 메타데이터를 사용하여 클래스,메서드,
프로퍼티 등에 접근하고 조작할 수 있는 기능을 말합니다.

보통 코드를 빌드 과정에서 컴파일시에 해당 프로그램에 사용되는 자료형이
선언이 되는데, 해당 동작은 그 과정을 위반하고 런타임 중에 동적으로 생성하는것이다.

즉, 이러한 동작이 처리비용이 높고 , 컴파일 타임의 안정성을 저해하기 때문에
사용에 매우 신중을 가해야 된다고 생각합니다.

 

유니티 질문

더보기

 

1 Unity 생명주기(Unity Life Cycle)에 대해서 설명해주세요. Monobehaviour를 상속한 Class의
함수 호출 시점을 나타낸 것이다. 즉, 이 생명주기 함수들로
Class의 초기화,생성,업데이트,파괴를 할 수 있는것입니다.
2 MonoBehaviour 클래스의 주요 메서드와 그 기능에 대해 설명해주세요. 주요 메서드로 Awake와 Start,Update가 있습니다.
물론 이 밖에도 주요 메서드들은 더 있지만 이 3가지를
주로 사용하였기에 설명해보겠습니다.

2-1 MonoBehaviour 클래스에서 Start와 Awake의 차이점은 무엇이며,
이를 적절히 사용하는 방법에 대해 설명해주세요
Awkae와 Start의 주요 차이점은 호출 시점입니다.
Awake는 오브젝트가 활성화되자마자 호출됩니다.
Start는 오브젝트가 활성화 된 이후 첫 번쨰 프레임 업데이터 전에
호출됩니다. 즉, Awake보다 뒷 시점에 호출이 됩니다.

Awake는 스크립트간의 참조, 즉 초기화를 하기위해
주로 사용합니다.

Start는 Awake에서 초기화가 되고 난 후의 동작을 할때
주로 사용 하였습니다.
3 Update, FixedUpdate, LateUpdate의 차이점에 대해 설명해주세요. Update는 매 프레임마다 호출되는 메서드입니다.
FixedUpdate는 물리연산에 처리되는 메서드로 프레임률에 상관없이
일정한 간격으로 호출 됩니다.
LateUpdate는 프레임 종료직전에 한번 호출하게 되는 메서드입니다.
4 Time.deltaTime이란 무엇이며, 사용하는 이유에 대해 설명해주세요. deltaTime이란 프레임과 프레임 사이의 시간을 측정한것입니다.
이 델타타임이 필요한 이유는 기기의 성능에 따라 게임 플레이가
다른것을 보여주면 안되기 때문입니다.

간단히 말하면 핑이 튄다, 핑이 높다라는 표현이며 이 델타타임을 이용하여
기기성능에 상관없이 유저들에게 똑같은 게임 플레이를 제공 할 수 있습니다.
5 코루틴의 동작원리와 사용해본 예시를 함께 설명해주세요. 코루틴은 비동기라고 생각하지만 아닙니다. 메인 쓰레드에서 동작하며
비동기적으로 일시적으로 중단/재개 할수있는 기능입니다.

코루틴을 시작하게되면 메인 로직 동작중에 코루틴 시점이 오면
코루틴 함수가 동작하고 종료되면 다시 원복하여 메인 로직을
동작하게 하는 원리입니다.

사용해본 예시로 , Update 처럼 매 프레임을 체크할 필요는 없
6 Invoke와 코루틴의 차이에 대해 설명해주세요. Invoke는 인자값을 사용 할 수 없지만 코루틴은 인자값을 사용 할수 있습니다.
즉 , 간단한 시간 지연 함수 호출시에는 Invoke가 좋지만 , 로직 및 구조가
복잡해지고 확장성을 챙겨야 되는경우에는 코루틴이 적합하다고 생각합니다.
7 코루틴과 멀티쓰레딩은 어떤 차이가 있는지 설명해주세요. 코루틴은 메인스레드에서 동작하며 비동기적으로 동작하는거고
멀티쓰레딩은 스레드를 여러개 생성하여 비동기로 동작합니다.
즉, 여러 로직(렌더링,서버)를 병렬로 작업하여 효율성을 증가합니다.
8 유니티 최적화 기법은 어떤 것들이 있나요? 여러장의 스프라이트를 하나의 파일로 관리하는 스프라이트 아틀라스 및
거리에 따른 렌더링 최적화 기법인 LOD 기법 , 카메라의 영역에 있지 않으면
렌더링하지 않는 오클루전 컬링 기법 등을 알고 있습니다.
8-1 최적화를 해본 적이 있나요? 없다면 어떤 최적화가 있는지 설명해주세요. 2D 슈팅게임을 만들며 , 플레이어 캐릭터 및 적,투사체의 Sprite리소스를 Sprite
아틀라스로 만들어 사용했던 적이 있습니다. 해당 기능을 사용하여
기존에 Sprite를 사용했던것보다 SpriteAtala를 사용하여 드로우콜을 최적화한
경험이 있습니다.
8-2 최적화에서 가장 중요한 부분은 무엇인가요? 해당 최적화에서 중요한 부분은 DrawCall를 최소한으로
호출하는것입니다. 렌더링이 아주 큰 처리비용을 가지고 있기에
최소한의 DrawCall을 사용해야됩니다.
8-3 최적화를 위해서 적용해본 텍스쳐 포맷이 있나요? 텍스쳐 포맷은 따로 사용해본적은 없습니다. 하지만 필요로 해야되는 기능이기에
꼭 익혀서 자신의 것으로 만들어놓겠습니다.
9 드로우콜에 대해서 설명하고, 최적화하는 방식에 대해 알고 있는 것이 있으면 설명하세요. 드로우 콜은 CPU가 현재 프레임에 무엇을 그릴지 결정하고
GPU에 그리게하는 것입니다.

제가 아는 최적화 방식은 LOD(Level of Detail) 기법이 있습니다.
GPU는 하나의 폴리곤을 그리기 위해 가까이 있든 멀리있든 해당 메쉬의 크기에 맞춰
그리게 됩니다. 하지만 이렇게되면 쿼드오버드로우 현상이 발생하게 되고 멀리 조그만한
점을 그리기 위해 4개의 픽셀을 사용하게되는 비효율적인 상황이 나옵니다.

즉 멀리있는 폴리곤은 그리지 않는것이 LOD 기법의 특징입니다.
10 Find 함수 사용을 자제해야 하는 이유에 대해 설명해주세요. FInd 함수는씬에 존재하는 모든 오브젝트를 탐색하기에
시간 복잡도 및 처리 비용이 매우 높기 때문입니다. 대처법으로는 하이어라키에
자식 오브젝트로 부모오브젝트가 필요한것을 배치하거나 컴포넌트를 사용하는
방법이 있습니다.

현재 가지고 있는 재화에 따라 스탯 레벨업이 적용된다.

 

PlayerInfoModel.cs

    public void HpLevelUp(int amount)
    {
    	//플레이어가 가지고 있는 골드가 업그레이드 코스트보다 높으면 true
        if (GameManager.Instance.player.UserData.Gold >= Utils.UpgradeCost(Status.Hp))
        {
        	//플레이어 골드 - 업그레이드 코스트 비용 적용
            GameManager.Instance.player.UserData.Gold = 
                Mathf.Max(0, GameManager.Instance.player.UserData.Gold - BigInteger.ToInt32(Utils.UpgradeCost(Status.Hp)));
            //플레이어 레벨업 => 스텟 증가량 및 스테이터스 타입을 전달하여 스탯 증가
            GameManager.Instance.player.LevelUp(amount, Status.Hp);
            OnHpUpgrade?.Invoke(); //View에 데이터를 전달하여 출력을 갱신함
        }
    }

 

Utils.cs에 있는 static Class 

 /// <summary>
 /// 스텟 업그레이드시 적용되는 코스트 증가율 수식
 /// </summary>
 /// <param name="baseCost">초기 비용. 레벨 1에서의 기본 비용입니다.</param>
 /// <param name="level">현재 레벨</param>
 /// <param name="growRate">코스트 증가율 (예: 1.5~2.0 사이).</param>
 /// <param name="constantIncrease">레벨마다 고정적으로 추가되는 비용 (선택 사항).</param>
 /// <returns></returns>
 public static BigInteger UpgradeCost(Status statType)
 {
     int baseCost = 0;
     float growRate = 0;
     int playerStatLevel = 0;
     int constIncrease = 0;

     switch (statType)
     {
         case Status.Hp:
         	 /*필요한 데이터를 StatUpgradeDB에서 호출해서 사용 */
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).MaxHealthBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).MaxHealthGrowRate;
             /*플레이어의 스텟 레벨을 호출 */
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.MaxHealthLevel;
             constIncrease = 20;
             break;
         case Status.Atk:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).AtkBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).AtkGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.AtkLevel;
             constIncrease = 20;
             break;
         case Status.Def:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).DefBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).DefGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.DefLevel;
             constIncrease = 20;
             break;
         case Status.ReduceDmg:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).ReduceDamageBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).ReduceDamageGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.ReduceDamageLevel;
             constIncrease = 20;
             break;
         case Status.CritChance:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalRateBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalRateGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.CriticalRateLevel;
             constIncrease = 20;
             break;
         case Status.CritDmg:
             baseCost = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalDamageBaseCost;
             growRate = DataManager.Instance.StatUpgradeDB.GetByKey(100).CriticalDamageGrowRate;
             playerStatLevel = GameManager.Instance.player.StatHandler.CurrentStat.CriticalDamageLevel;
             constIncrease = 20;
             break;
     }
     
     //코스트가 증가하는 기본적인 적용 수식 중 하나 
     int Cost = (int)(baseCost * Mathf.Pow(playerStatLevel, growRate) + (playerStatLevel * constIncrease));

     return new BigInteger(Cost);

 }

 

Cost가 레벨에 증가함에 따라 서서히 증가하는 함수가 필요 했기에 검색해보니

방치형 게임에서 주로 사용되는 수식이 하나 존재하여 사용하였다. 

 

빠른 성장 속도의 게임  GrowRate ▲ BaseCost ▽
느린 성장 속도의 게임 GrowRate ▽ ConstantIncrease ▲
성장 속도의 커브를 조정 
(제곱하는 자리에 해당 함수를 대입하면됨)
로그함수(y = logx) 적용 -> 증가폭이 점점 줄어듬 지수함수(y=x^2) -> 증가폭이 점점 상승함 (현재 적용중인 함수)

 

 

현재 UI 구조에 문제가 있었다.

MVC 구조를 이용하는 UI 객체들이 [UI + 이름]클래스 에서 MVC구조에 맞게 초기화를 하여 작업을 하고있다.

하지만 이 초기화 할때 각 클래스별로 "하드코딩"으로 작업이 진행되고 있었다.

이렇게 되면 문제가 발생하게되는데: 해당 구조를 모르는 작업자가 해당 작업 진행시 구조를 모르기에 작업 효율이 떨어지고 가독성이 떨어지는 문제가 발생하였다. 

 

그래서 두가지 방법이 있다.

Controller 부분에서 초기화 작업을 진행 ( Controller 파트는 이미 UIController 라는 상위 클래스로 상속받고 있는상태)

하지만 Controller에는 Monobehaviour가 상속 되어있지 않고 이미 작업을 해온게 있어 다 갈아 엎기는 문제가 있는 상태였다.

 

두번쨰 방법으로 현재 초기화 하는 클래스의 상위 클래스를 만들어 상속을 시켜주는 방식을 사용하는것이다. 

 

using UnityEngine;

//Model : 해당 UI에 관련된 데이터(모델)
//View : 데이터를 유저의 눈에 보여주는 출력물(View)
//Controller : Model과 View를 중재하는 관리자

[System.Serializable]
public abstract class UIBase<M,V,C> : MonoBehaviour
    where M : UIModel, new()
    where V : IUIBase
    where C : UIController, new()
{ 
    protected M model;
    [SerializeField] protected V[] views;
    protected C controller;

    public virtual void Start()
    {
        //Model(Data) 초기화
        //model = new M();

        //컨트롤러  초기화 및 View 등록
        controller = new C();
        for (int i = 0; i < views.Length; i++)
        {
            controller.Initialize(views[i], model);
        }

        //UI매니저에 UI 등록
        UIManager.Instance.RegisterController(typeof(C).ToString(), controller);    
    }
}

 

해당 코드가 UI MVC구조를 초기화 해주는 코드이고 ,Modeldms 각각 필요한 데이터가 다르기도 하고 호출 시점도 조금씩 다르기  Model 영역은 자식클래스에서 초기화를 담당하고 있다. 

 

public class PlayerInfo : UIBase<PlayerInfoModel,PlayerInfoView, PlayerInfoController>
{
    public override void Start()
    {
        model = new PlayerInfoModel();
        base.Start();

        gameObject.SetActive(false);
    }
}

사용 예시로 , 플레이어 정보창 UI 초기화를 담당하는 클래스이다. 

우리 프로젝트에서 적용해야 될점
1.가챠 연출씬 상승 시켜야됨 -> 다른 가챠겜 연출 참고할것

 

Soul키우기 피드백

1.배경 툰쉐이더 적용 시켜볼것 ->3D Object에 툰 쉐이더
2.GameSceneTrigger에 각종 매니저들이 누락될 가능성이 높다.
SceneManager.sceneLoaded 이벤트 함수를 사용하거나 중계자(Init을 관리하는 해당 함수들)를 사용해서 만들어놓을것
ㄴ 즉, 게임이 재시작되거나 , 다음 Stage로 넘어갈때 메서드에 모아놓은뒤 메서드만 호출하면 다 동작하게끔 만들어 놓을것
3.외부 데이터를 Index(순서) 및 Key값을 사용하여 참조 할 수 있게 설계
ㄴData를 지역변수로 들고있는건 상관없다,
ㄴ 서버가 없어서 초기 런타임시 Json으로 데이터를 파싱해서 가지고 있는게 맞음
ㄴ 서버가 생기면 구조가 바뀔수 있다고 인지는 하고 있어야한다 -> 면접관련
ㄴ 서버가 있다면 : Dict key값 , 주소를 써서 서버에 호출하여 데이터를 받아와야된다. ,
ㄴ DB 컨테이너로 통째로 호출해서 사용하지 말것
4.Skill 쿨타임 에서 List가 아닌 Queue (선입선출) 자료구조 추천
5.ShowUI나 ObjectPool에서 string Key값이 아닌 제네릭을 사용할것을 추천
ㄴ string으로 사용시에 아무래도 문자열 누락 가능성인 높기에 제네릭으로 예방할것
6.MVC 패턴의 UIInventory에서 Model과 Controller의 역할을 Controller가 전부 책임을 맡아도 된다고 함
ㄴ 인벤토리 뿐만 아닌 모든 UI 구조가 현재 UI + 이름 에서 MVC 세 Class를 초기화 하는데 이렇게 하게 되면 문제가 있음
문제 : 해당 구조를 모르는 작업자가 해당 작업 진행시 구조를 모르기에 누락될 가능성이 있음
대처 1 : Controller에서 Model과 View를 초기화 하는 방법 ->하지만 이방법은 현재 Controller가 Monobehaviour가 없기에 사용 불가
대처 2 : UI + 이름 Class에 부모클래스를 만들어 상속 시켜 사용하게 할것 ,
대처 2의 방법으로 리팩토링 진행 예정
7.ScottGaland라고 BigIntegeer 외부라이브러리를 사용하였는데 ReadMe에 참조를 해주고 작성할것
8.로딩씬에서 코루틴 내부 안에 Async를 사용해서 , 코루틴 안에 비동기(Async) 메서드가??(코루틴은 비동기가 아니다!!)
 구조가 이상함 ->로딩씬 리팩토링 진행 예정

 

다른 팀 발표 피드백


1.구조적인 복잡도가 올라가도 괜찮다 -> 너무 신경쓰지 마라
2.역참조를 최대한 지양해라
3.이중 반복문(N^2) 사용해도 된다. 하지만 시돗횟수가 3000회 이상 올라가게 된 경우, 다른 알고리즘을 사용해보아야한다.
4.UI Code에서 데이터 가져오는 행위 => MVC 위반
4-1.OpenUI 실행하기전에 UI를 Update하고 Poen해야된다.
4-2.Data가 필요하면 인자값으로 넣는식으로 사용
4-3.Data가 필요하면 넣는 식으로 사용하는것이 좋음
5.Code는 엄청 세분화 되지 않아도 된다.
5-1.이유 : 면접관이 보기가 힘듬 + 개발 속도가 떨어짐 -> 스크립트의 크기가 커지기전에 나누는 행위 -> 좋지 않다.
5-2. 스크립트가 커지면 나누는것을 지향
6.장착 관련 : ItemManager에서 장착까지 관리하여도 좋다., 따로 Equip 매니저를 안ㅁ들어도 된다는 뜻
7.Find 쓰지마라
8.Skill 같은 경우 범위나 이펙트를 동적 생성해야함 , 결국 일반공격도 결국 Skill로 활용 가능
현재 무기에 데미지가 적용되어 있으면 결국 하드코딩이 되게 된다.
9.폴더링 신경써라
10.데이터 매니저를 제네릭을 사용해서 너무 일반화 하였다 -> 데이터들은 각각 고유의 값과 형식ㅇ ㅣ많기에
일반화(제네릭)을 하지않는것이 좋다. -> DB에 각각 사용 할수있게 Class내에서 변환해서 사용하는 방식이 좋을것같다.
11.리소스 용량이 큰 경우 , 아틀라스를 참고하여 사용 할것

면접 자기 소개 작성

 

더보기

안녕하십니까, 소통을 중시하는 클라이언트 프로그래머 [송명성]입니다. 
제 열정과 노력으로 이 분야에서 역량을 키우고 있습니다.

2020년부터 2년간 공장 라인설비 회사에서 엔지니어로 근무하며 공장 라인 출장 업무를 주로 담당했습니다. 
이를 통해 문제 해결 능력과 관계자와의 원활한 소통 능력을 키울 수 있었습니다. 
이 경험은 이후 게임 개발 프로젝트에서도 팀원들과 협업하고 소통하는 데 큰 도움이 될 것입니다.

2022년부터는 본격적으로 프로그래머가 되기 위한 여정을 시작했습니다. 부산 아텐츠 게임아카데미에서 C/C++, 자료구조, 
컴퓨터 그래픽스를 배우며 기초를 다졌고, Unity를 활용해 2D 슈팅 게임을 모작하며 실제 개발 경험을 쌓았습니다. 
이를 통해 기초적인 게임 개발 과정과 Unity 엔진의 활용법을 익혔습니다.

2024년 8월에는 대전 인디게임잼에 프로그래머로 참가하여 **<마지막 빵>**이라는 스토리 어드벤처 게임을 개발했습니다. 
여기서 전체기획과 프로그래머로써 Scene 간 데이터 전달 파트를 맡으며, 제한된 시간 안에 팀원들과 협업하며 결과물을 만들어내는 
경험을 했습니다. 
하지만 당시 제 실력과 협업 능력의 부족함을 느껴, 이를 보완하고자 내일배움캠프 유니티 6기에 지원하게 되었습니다.

캠프에서는 매주 진행되는 미니 프로젝트의 리더를 맡으며 개발 실력뿐만 아니라 팀원들과의 소통, 리소스 분배, 일정 관리 능력을 키웠습니다. 
현재는 최종 프로젝트로 2.5D 방치형 게임을 개발하고 있으며, 이 게임은 itch.io에 배포할 예정입니다. 
이 과정에서 Unity 엔진을 심도 있게 다뤄보고, 팀원들과의 협업을 통해 하나의 완성된 게임을 만들어가는 기쁨을 느끼고 있습니다.

이러한 경험들을 통해 저는 단순히 개발 역량뿐만 아니라, 협업과 커뮤니케이션 능력을 갖춘 개발자로 성장하고 있습니다. 감사합니다.
앞으로도 제가 가진 열정과 팀워크 능력을 바탕으로 더 많은 도전을 통해 성장해 나가고 싶습니다. 감사합니다.

자기소개 나열식은 좋지 않다. -> 서사 얘기는 좋지 않다.
자기 소개 :: 듣는사람 입장에서 이사람이 나를 어떡해 기억할 수있지??

팀원과의 프로젝트에서 소통 및 갈등,트러블슈팅 등을 스토리텔링 서사를 풀어야된다. 

 


면집 질문 준비

C#의 event란 무엇인가요? Delegate 기반으로 구현된 대리자이며 ,
객체 간 비동기 통신을 가능하게 해줍니다.
event에 메서드를 첨삭하는것은 내부/외부에서도 가능하지만
event를 실행시키는 것은 내부에서만 가능합니다.
event는 Observer패턴과 동작방식이 유사합니다.

장점으로는 발행자와 구독자 간의 느슨한 결합으로
모듈화 및 유연성을 장점으로 가지고 있습니다.
Unity에서 사용하는 델리게이트 혹은 이벤트에는 어떤 것이 있나요? Unity의 Button 컴포넌트, InputSystem의 키 맵핑시
동작방식을 event로 선언할 수 있으며 , 여러 콜백 메서드가
있습니다.
참조 형식과 값 형식에 대해 설명해주세요. 값 형식의 Stack에서 생성되는 데이터들을 의미합니다.
흔히 사용되는 int,float,struct,,enum이 여기에 속합니다.

참조 형식은 Heap영역에 생성되는 데이터들을 의미하며
Heap영역의 데이터들은 Stack영역에 있는 데이터들을
참조를 하여 사용하게 됩니다.

그러기 때문에 참조 형식의 데이터는 변경이 되어도
다른 값형식의 변수에서 변경된 값을 가져오게 됩니다.
메모리에서 스택과 힙의 차이점에 대해 설명해주세요. 스택은 함수내에서 선언시나 변수 할당시 메모리가 생성이되며
함수가 종료될때 스택 영역의 데이터는 삭제됩니다.

힙은 new키워드나 Unity 에서 Instaniate를 통해 생성이 가능하고
참조를 끊을수는 있지만 heap에 저장된 메모리는 GC를 통해서만
삭제가 가능합니다.
1번과 2번 질문의 답안을 기반으로 struct와 class의 차이점에 대해 설명해주세요. 구조체는 Stack 영역에 저장되며 , 매개변수 없는 생성자를
작성할 수 없습니다. (C# 10 이상 예외)

클래스는 Heap 영역에 저장되며 매개변수 없는 생성자를
명시적으로 작성해서 사용합니다.
얕은 복사와 깊은 복사의 차이점은 무엇인가요? 얕은 복사는 데이터의 주소를 복사해 가는것이기에
외부에서 데이터가 변경 되는경우 원본 데이터가 변경이 됩니다.

깊은 복사는 데이터 그 자체를 복사하는것이기에
데이터가 변경되어도 원본 데이터는 유지를 하고 있습니다.
박싱과 언박싱이 일어나는 과정을 메모리 관점에서 설명해주세요. 우선 C#에서 박싱 언박싱을 하기위해서는
참조형식의 변수인 object가 필요합니다.
object 변수는 모든 데이터의 부모이기에 형변환이
가능하기 때문입니다.

박싱할 변수를 object 변수에 대입 하게되면
Stack 영역에 참조할 object 변수가 생성되고
Heap 영역에 object 에 박싱한
자료의 데이터가 저장되어있습니다.

언박싱은 heap영역에 존재하는
object 변수의 데이터를 Stack으로 복사하여
언박싱 할 변수에 생성하게 됩니다. 이
때 언박싱한 데이터는 독립적인 데이터가 됩니다.
클래스를 다른 클래스로 상속하기 위한 방법은 무엇인가요? 상속할 클래스 명 뒤에 세미콜론 과 부모클래스명 을 입력합니다.
클래스 상속에서 다이아몬드 문제(diamond problem)가 발생하는 이유와 이를 해결하는 방법에 대해 설명해주세요. C#에서는 Class간의 다중상속을 지원하지 않습니다.
C++ 에서는 Class간의 다중상속을 지원하기에 다이아몬드 문제가
발생 할수 있습니다.

다이아몬드 문제가 발생하는 이유는 두개의 자식클래스에서
동일한 부모 클래스를 상속 받은뒤 , 다른 클래스가 두개의 자식클래스를
둘다 상속 하고 있는경우 발생하게 됩니다,

부모의 있는 메서드나 필드에 접근하려고 할때
두 자식 클래스 호출이 애매모호 해짐

유니티<C#>에서는 해당 문제를 대처하기위해 다중 상속을 interface에만
지원을 해놓았습니다. 인터페이스는 구현체를 포함하지 않으므로
다이아몬드 문제와 같은 모호성 문제가 발생하지 않습니다.

C++ 에서는 가상 상속을 이용하여 다이아몬드 문제를 방지가 가능합니다.
인터페이스란 무엇인가요? 인터페이스란 ,일종의 행동의 약속이라고 볼수있습니다.
행동 즉, 메서드를 인터페이스에 정의하면 인터페이스를 상속한
클래스들은 반드시 해당 메서드를 재정의 해야됩니다.

 

Heap 영역은 답변시 완전이진트리 키워드가 나와야한다.

오늘은 2024-12-23(월) 중간발표를 할 자료를 만들기로 하였고 

팀원들에게 각자 자기가 작업하였던 파트를 문서화 하라고 지시한 뒤

오전에는 영상작업 진행을 하였다.

 

그러나 영상 녹화를 하면서 생각밖으로 치명적인 버그가 있었는데

치명적인 버그로 3스테이지 보스의 스킬로 사용하는 투사체가 오브젝트 풀로 관리를하는데 Destroy()메서드로 파괴되는 로직이 있어서, 해당 작업을 진행한 팀원에게 버그 수정을 요청하였다.

 

PPT 및 영상 자료를 제작한 뒤 내일은 모의면접 준비 및 코드 리팩토링 + 폴더링의 시간을 가질것 같다.

영상 자료의 일부

 

 

MVP 이전 중간점검(튜터님 지도)

1.MVP 이전 폴더링 및 코드 가독성 올릴것

2.Boss 등장시 연출 추가할것

3.Player가 죽을경우 , 가식성을 올릴것 

 

MVP 이후 진행 예정 (약 2주)

1.Player의 인벤토리, Soul(Skill) 시스템을 Player와 연동하여 UserData로 저장/불러오기 사용하게 할것

2.Boss Try 기능 추가

3.Stage 추가(숲 맵)

4.Player 무기 교체 이미지 추가

5.Player 계정 레벨 및 계정 경험치 구현

6.추가 컨텐츠 기획하기 -> 보스 러쉬 및 요일던전 등등


 Boss 등장시 연출 컷씬 추가 

임시 테스트로 적용 한상태 , 나중에 MVP 이후 교체 예정

 

UI이기에 코드는 MVP 패턴에 UI매니저를 추가하였고 , All In 1 Sprite 에셋을 사용하여 쉽게 이펙트를 적용 시켰다


 

 Unity Mask 적용

 

마스크로 쓸것을 부모 객체로 ,마스크를 적용시킬 이미지(Target)을 자식객체로 선언한뒤

부모 객체는 Mask 컴포넌트를 적용시키면 정상적으로 동작하게된다. 

원 프레임에 들어가게되는 버튼

1.도전과제 MVC 리팩토링체크

ㄴ 도전과제 알람에 내용이 제대로 출력되지 않아서 다시 한번더 리팩토링 하였음

ㄴ 인스펙터에 컴포넌트가 미싱되어있는 상황이여 수정하였음

2.Player HP 스텟 UI와 연결

ㄴLobby 화면에 있는 체력UI를 플레이어 객체와 연결하여 사용

적에게 공격을 받아 체력이 감소
MVC 패턴으로 설계한뒤 UI매니저에 등록하여 사용 중

 

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using ScottGarland;

public class UIPlayerHPDisplayView : MonoBehaviour, IUIBase
{
    [SerializeField] private RectTransform HPFrontImg;
    [SerializeField] private TextMeshProUGUI HpText;

    public void HpRatioChange(BigInteger curHp , BigInteger maxHP)
    {
        int curHpNum = BigInteger.ToInt32(curHp);
        int maxHpNum = BigInteger.ToInt32(maxHP);
        float result = curHpNum / (float)maxHpNum;

        HPFrontImg.localScale = new Vector3(result, 1, 1);

        string curHealthString = Utils.FormatBigInteger(curHp);
        string maxHealthString = Utils.FormatBigInteger(maxHP);

        HpText.text = $"{curHealthString} / {maxHealthString}";
    }

    public void HideUI()
    {
        gameObject.SetActive(false);
    }

    public void Initialize()
    {
        
    }

    public void ShowUI()
    {
        gameObject.SetActive(true);
    }

    public void UpdateUI()
    {

    }
}

UIPlayerHPDisplayView.cs에 BigInterger를 적용하여 큰 숫자를 더욱 쉽게 출력 할 수 있게 구현

 

3.Player가 죽었을때 밀려나는 버그 해결

ㄴ Player가 죽으면 , Player의 동작이 비활성화 되어(enabled = false) 적의 공격을 맞고 멀리 날라가는 버그가 발생하였다. 

    [ContextMenu("PlayerDie")]
    public void Die()
    {
        if (!baseHpSystem.IsDead)
        {
            baseHpSystem.IsDead = true;
            Debug.Log("Player Die!!! ");
            string animName = PlayerAnimationController.DeathAnimationName;
            PlayerAnimationController.spineAnimationState.SetAnimation(0, animName, false);

            rb.velocity = Vector3.zero; //캐릭터 이동되지않게 속도를 0으로 수정
            rb.isKinematic = true;
            GameManager.Instance.GameOver();
            enabled = false;
        }
    }

    public void Respwan()
    {
        //ToDoCode : 플레이어가 죽을경우 재세팅하는 함수
        statHandler.CurrentStat.health = statHandler.CurrentStat.maxHealth;
        transform.position = Vector3.up;
        rb.isKinematic = false;
        enabled = true;
        baseHpSystem.IsDead = false;
        UIManager.Instance.ShowUI("PlayerHPDisplay");
    }

Die 메서드에서 죽으면 리지드바디의 키네마틱 옵션을 true로바꿔 물리충돌 연산을 하지 않게 막은뒤

Respwan 메서드에서 다시 부화할때 리지드바디의 키네마틱을 false로 바꿔 처리 하였다.

 

4.현재 이슈 : Stage 맵 중복 생성

스테이지가 진행될때마다 맵이 복사가 되고있다.

+ Recent posts