면접 자기 소개 작성

 

더보기

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

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 영역은 답변시 완전이진트리 키워드가 나와야한다.

https://github.com/lostwaltz/PossibleDefense

 

GitHub - lostwaltz/PossibleDefense

Contribute to lostwaltz/PossibleDefense development by creating an account on GitHub.

github.com

 

일주일간 4명의 팀원과 진행한 모바일 3D 타워 디펜스 게임에 프로토타입을 개발하였다.

결과물은 만족 스럽게 나왔으나 코드 작성시에 하드코딩이 좀 많았던것 같아 회고를 해보려고 한다. 

 

해당 파트에서 나는 Stage에 관련된 파트를 담당했다.

 

Stage에서 필요한 기능들은

-Stage에 사용될 Tile 관리

-Tile에 설치될 타워 관리 (설치 및 판매)

-해당 Stage에서 사용될 타워 업그레이드

-Stage에서 사용되는 골드 재화

-로비 -> 게임 -> 로비 로 이동시의 Data 유지와 Scene 연결

요약하면 이정도가 될것같다. 

 

해당 기능들을 크게 키워드로 해서 Class로 기능을 분리해서 작업해야 가독성이 오르는데

이번에는 하드코딩도 많고 팀원끼리 소통을 그렇게 많이 했는데도 변수명을 정하지 않았던점이 문제였던것 같다.

GameScene에서 사용된 오브젝트들과 컴포넌트들인데, 전혀 가독성이 없고 코드가 일관성이 없어 보기 힘들다.

1번 문제점 :

UI매니저가 없기에 우선 UI를 만들어두자 하고 작업을 진행하였고 Refactoring 하는 과정에서

UI매니저를 작성하여 UI매니저에 집어넣고 꺼내쓰는 식으로 사용 했어야 됬는데 결국에는 UI매니저를 사용하기 위해 UIBase 인터페이스 자체도 작성하지 않아 결국에는 동적생성을 하지 못해 이런식으로 사용하게 되었다.

 

1번 문제의 대처점 : 첫 작업시에 프레임워크 작업을 진행하고 , 해당 매니저들을 미리 작성해놓아야한다.

그러기에 팀원과 회의를 정말 많이 해야된다. 

 

2번 문제점 :

3번과 연관되는 문제점이긴한데 SpawnManager가 있지만 이 Manager는 EnemySpanwManager이다.

이름이 직관적이지 않아 직접 열어봐야만 알수있고 BulletObjectPool은 하이어라키에있고

Tile ObjectPool은 왜 Stageamanager의 안에 있는지 의문이다. ( ObjectPoolLegacy가 Tile ObjectPool)

즉, Class 및 오브젝트의 이름이 직관성 없기때문에 발생했던 문제다.

 

2번 문제의 대처점 : 해당 오브젝트 명 컴포넌트 명을 정확하게 구체적으로 작성해서 사용 할것 

 

3번 문제점

위에서 말한 큼직큼직한 기능들을 키워드 삼아서 Class별로 분리해서 컴포넌트처럼 붙여 사용하거나 따로 오브젝트화 시켰어야했는데 StageManager에 관련없는 기능을 다 넣어서 사용 한것이 문제가 되었다.

 

정말 하드코딩하고 Inspector에 다 때려 밖은 상태 절대 이렇게 작업하면 안된다.

 

StageManager의 Inspector에서 보면 밑에 컴포넌트로 부착한것들을 전부 등록하여 사용하고있다.

물론 내부에서도 해당 컴포넌트들이 비면 null체크를 해서 컴포넌트를 호출해서 사용하고 있지만

이런 Inspector 구조는 가독성이 떨어지기에이런식으로 Inspctor에 정리없이 사용하면 안될것 같다. 

또한 Scene 전환시에도 Missing이 일어날 확률이 높다.

 

3번 문제의 대처점 : Inspector에서는 최소한으로 등록하여 사용하고 내부적으로 동적생성하게 코드를 구현해야 되며 UI매니저를 사용하여 UI를 원하는 시점에서 꺼내 쓸수 있게 구현 해야된다.

 

 

p.s 코드 구조를 잡지 않고 자료구조를 막 사용한 나쁜 예시

이 코드는 제출하기 직전에 급하게 만든 코드라 최적화를 생각하지 못했다.

타일을 선택할떄마다 타일 선택 정보를 초기화 해주는 코드인데 맨처음 작성시 자료구조를 List를 쓴것부터가 문제의 시작이였고 해당 문제를 해결하려면 TowerTiled을 딕셔너리로 변경해서 사용할수있게 구조를 바꿧어야 했다. 

 

 

그래도 해당 프로젝트에서는 오전 / 오후 / 저녁으로 자기의 코드리뷰와 프로젝트 진척도를 회의하는 시간을 계속 가져왔고

작업량이 없어도 무조건 코드리뷰를 하게 하였다. 그러면서 서로 문제점이 있는부분을 서로 보완해주고 서로에 대한 정보공유를하면서 정말 실력이 많이 늘었다. 

 

다음 프로젝트에서도 팀원과의 소통 및 회의/회고를 적극 권장해야겠다.

위에 조건처럼 [어떤 동작을해도 같은 목적을 취함 + 동작이 다름] 이라는 조건하에 전략패턴커맨드패턴의 디자인패턴을 자주 사용할 것이다. 하지만 UI의 버튼이나 규모가 작은 경우에는 전략패턴을 구조로 설계하기에 리소스 낭비적인 부분이 있다. 

 

이런 경우에는 <매핑 테이블 활용>을 하여 각 기능을 정의한뒤 인자값을 다르게 설정하는 것이다. 

 

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
using Unity.Burst.Intrinsics;

public class UpgradesController : MonoBehaviour 
{
    [SerializeField] private SlimeTowerStatUpgradeData[] _upgradeDatas;
    [SerializeField] private Button[] buttons;

    private Dictionary<Button, TowerGrade> upgradeButtonMappings;

    private void Awake()
    {
        upgradeButtonMappings = new Dictionary<Button, TowerGrade>
        {
            { buttons[0], TowerGrade.Common},
            { buttons[1], TowerGrade.Normal},
            { buttons[2], TowerGrade.Epic},            
        };

        foreach(KeyValuePair<Button, TowerGrade> pair in upgradeButtonMappings)
        {
            pair.Key.onClick.AddListener(() => UpgradeTowerByGrade(pair.Value));
        }
    }

    //버튼 누르면 해당 등급에 따라 강화 
    public void UpgradeTowerByGrade(TowerGrade grade)
    {
        foreach (var data in _upgradeDatas)
        {
            if (data.Grade == grade)
            {
                data.OnUpgrade();
                break;
            }
        }
    }
}

위 예제 코드는 버튼이 3개가 있고 , 해당 버튼들은 해당 등급의 유닛들을 강화해주는 기능이있다.

목적은 강화기능이기에 등급이라는 인자값을 받아서 적용시켜주어야한다. 

 

초기화시 사용할 데이터를 Inspector를 통해서 초기화한뒤(스크립트 코드로 적용시켜도 됨)

버튼의 onClick.AddListener로 람다식을 이벤트로 초기화 하여 사용하는 방식을 취한다.

 


이런식으로 테이블매핑을 한뒤 각 기능에 맞춰 이벤트를 적용시켜주면 전략패턴을 사용하지않고

간단히 사용 할 수 있다.

파일 경로명을 인자값으로 받아서 CSV데이터를 읽어 파싱한 후  List<Vector3>로 반환한다.

반환값이 T가 아닌 고정되어있어 범용성이 떨어지는 메서드이다. 나중에 Refactoring이 필요해보인다. 

 

Stage Way Point는 Vector3를 저장하는 CSV 외부데이터이다.

 

  public static List<Vector3> LoadStageWayPointFromCSV(string fileName)
  {
      // Resources 폴더에서 파일 로드
      TextAsset csvFile = Resources.Load<TextAsset>(fileName);

      if (csvFile == null)
      {
          Debug.LogError($"File {fileName} not found in Resources folder.");
          return null;
      }

      // 데이터를 한 줄씩 분리
      string[] lines = csvFile.text.Split('\n');
      List<Vector3> wayPointList = new List<Vector3>();

      //첫번쨰 줄은 헤더이기에 제외 
      for (int i = 1; i < lines.Length; i++)
      {

          string line = lines[i].Trim(); // 공백 제거
          if (string.IsNullOrEmpty(line)) continue; // 빈 줄 무시

          string[] values = line.Split(',');

          if (int.TryParse(values[0], out int x) &&
              int.TryParse(values[1], out int y) &&
              int.TryParse(values[2], out int z))
          {
              wayPointList.Add(new Vector3Int(x, y, z));
          }
      }

      return wayPointList;
  }

비록 간단한 내용이지만, 생각밖으로 놓치고 갈 수도 있을것 같아 정리를 하였다.

 

0.생명주기 함수(Update,Start)들을 사용하지 않으면 무조건 지워라

이 함수들은 [리플렉션]이란 것때문에 호출만 하고 있어도 동작을 하여 처리비용을 계속 잡아먹기 때문이다. 

 

p.s 리플렉션

리플렉션은 컴파일 시에 알 수 없었던 타입이나 멤버들을 찾아내고 사용할 수 있게 해주는 메커니즘이다.

리플렉션 정보는 해당 블로그 글을 참고하자.

https://tsyang.tistory.com/56

 

C# - 리플렉션 (Reflection)

리플렉션 리플렉션은 컴파일 시에 알 수 없었던 타입이나 멤버들을 찾아내고 사용할 수 있게 해주는 메커니즘이다. 그러나 다음의 주요한 단점이 존재한다. 리플렉션을 사용하면 컴파일 시에

tsyang.tistory.com

 

1. 빌드시에 Debug.Log()를 지우고 사용하기

빌드시에도 코드영역에 Debug.Log()가 남아있는경우 여전히 처리비용이 들며 , 해킹의 여지도 있게된다.

왜냐하면 개발시에 디버깅용으로 넣은 Debug.Log()로 데이터를 파악하기떄문이다.

 

2.LinQ함수 남용 금지

Linq 패키지로 구현이 가능한건 for과if문으로 구현이 가능하기 때문이며 , Linq는 생각밖으로 처리비용이 높기때문이다.

(Linq와 for문 루프 돌린 성능 비교 그래프 참조 필요함)

 

3.코루틴 사용시 yield return new WaitForSeconds(); 사용 지양하기

사실 코루틴은 반복하게 하는 메서드이지만 new라는 키워드로 인해서 결국 반복적으로 객체를 생성하고

코루틴이 사용이 완료되면 해당 데이터는 가비지가 되어 결국 최적화에 영향을 끼친다.

WaitForSeconds updateTime = new WaitForSeconds(1.0f);

이런식으로 class내에 멤버변수로 선언하고 사용하는것이 올바르다. 

 

4.오브젝트 풀링시 대량의 오브젝트 생성시 자식오브젝트로 생성하지 마라

하이어라키에 오브젝트 풀링을 정리하겠다고 부모오브젝트를 생성하고 그 밑에 만드는데

결국 하이어라키는 계층구조(tree)이기떄문에 나중에 위치가 이동되거나 할때 문제가 발생하기 때문이다. 

이번 개인 프로젝트 진행중에 Player,Enemy,Item의 데이터를 ScriptableObject 로 관리 해보았다.

또한 저장/불러오기 기능을 ScriptableObject 로 구현해보기 위해서였다.

 

하지만 Player,Enemy의 Status 데이터들은 계속 변동 되는 값이였기에 나중에 게임을 다시 시작하거나 하면 데이터가 유지는 되는데 아이템을 장착하지 않았는데 공격력이 적용 되어 있거나 그랬다.

 

Item처럼 불변되지 않는값에 ScriptableObject 를 사용하여 데이터를 호출하는 목적으로만 사용하는것이 좋은것 같다,. 

[문제점]
Enemy Idle Class에서 Enemy 의 변수인 Data(EnemyData 스크립타블오브젝트)에 Collider변수를 저장한뒤
Enemy Chase Class에서 SO를 호출하니 null이 반환되는 건

 

[첫번쨰 대처]

1. Collider -> GameObject로 타입으로 수정한뒤  Collider의 정보가 필요할때 TryGetComponet를 이용해서 호출하였지만 되지 않았음 

 

해당 대처를 한 이유 : 

ScriptableObject에서 Collider와 같은 Unity 컴포넌트 타입은 일반적인 값이나 데이터처럼 직접 저장하거나 유지할 수 없습니다.  ScriptableObject는 주로 데이터만 저장하기 위한 용도로 설계목적이기 때문

 

디버깅중 찾은 원인은 Enemy가 하나만 있는경우 상관이 없지만
Enemy가 여러명이 되면서 해당 Data에 접근해서 계속 Data를 바꾸게 로직을 작성함

 

즉, ScriptableObject는 참조형식(Class) 이기에 A Class에서 사용중이다가 B Class값을 변경하게되면 A Class에서 사용하던 데이터가 사라지기 때문이였다. 

Class => 참조형식이다.

[해결방법]

Enemy Data(스크립타블오브젝트)에서는 Data 호출만하게 하고

Enemy의 Class로직을 담당하는곳에 따로 필요한 변수(Target)를 선언하여 사용하기로 함 

 

MVC 패턴이란 Model - View - Controller의 약자이며 , Unity 에서 개임 개발시 주로  UI에 사용된다.

 

Model : 데이터를 의미하며 , DB를 만들어 데이터를 보관하는 목적이다.

데이터는 엑셀을 이용해서 외부에서 파싱해서 사용 할 수 있다.

데이터들은 ID 라는 int 정수값을 이용해서 사용 하는것이 매우 유리하다.

 

View : 유저들에게 눈으로 보여지는 것을 의미하며 유니티로는 Canvas의 UI를 의미하며,

UI 스크립트에는 로직이 아닌 오로지 출력이 필요하다. 출력이 필요한 데이터는 컨트롤러를 통해서 전달 받아야한다.

 

Controller : Model과 View의 중간 다리 역할이라고 보면된다. 즉, Manager와 역할이 비슷하며

해당 영역에 로직을 작성하여 Model과 View를 Update를 해주는 역할이다. 

+ Recent posts