🛠️ 오늘 한 작업

  • 실전 모의 면접 2회차를 진행했습니다.
  • 이번에는 Unity와 C# 기초 관련 질문을 많이 받았습니다.
  • 인벤토리는 Tab, 장비창은 O 입력으로 열 수 있도록 연결했습니다.
  • 화면 드래그 작업으로 패널을 드래그해서 이동할 수 있게 만들었습니다.
  • 인벤토리에서 장비를 더블클릭해 장착하면 장비창이 자동으로 열리도록 수정했습니다.
  • 장비창 슬롯을 더블클릭했을 때 장착 해제가 가능하도록 작업했습니다.
  • 퀵슬롯 버튼을 눌렀을 때 소비 아이템이 바로 사용되도록 수정했습니다.
  • 기획 변경에 맞춰 퀵슬롯 아이템 수량이 0이 되어도 자동 삭제하지 않고, 직접 삭제해야 없어지도록 바꿨습니다.

 

📌 오늘 계획

오전에는 면접 준비와 UI 사용성 개선 작업을 목표로 잡았습니다.

  • 면접 준비하기
  • 장비창 더블클릭 시 장착 해제 처리하기
  • 패널 드래그와 닫기 버튼 위치 수정하기
  • 퀵슬롯 버튼을 눌렀을 때 소비 아이템이 바로 사용되도록 수정하기

오후에는 작업을 더 이어가려고 했지만, 면접도 있었고 중간에 확인해야 할 부분이 많아서 계획처럼 깔끔하게 진행되지는 않았습니다.

그래도 오늘은 인벤토리, 장비창, 퀵슬롯을 실제 플레이 흐름에서 조금 더 편하게 사용할 수 있도록 다듬는 작업을 많이 했습니다.

 

🎤 기술 모의 면접

오늘도 실전 모의 면접을 봤습니다.

이번에는 인성 면접보다는 Unity나 C# 기초 쪽 질문이 더 많이 나왔습니다. 기억나는 질문으로는 public, private 같은 접근 제한자 관련 질문, SRP에 대한 질문, 그리고 게임에서 시간을 다룰 때 어떤 시간대를 기준으로 해야 하는지에 대한 질문이 있었습니다.

시간 관련 질문은 꽤 인상 깊었습니다. 게임을 만들 때 한국 시간만 생각하면 안 되고, 다른 나라 유저가 있을 때는 시간대가 달라질 수 있습니다. 그래서 서버나 저장 데이터에서는 기준 시간을 통일하고, 실제 표시할 때는 지역 시간으로 보여주는 식으로 생각해야겠다고 느꼈습니다.

저는 마지막 면접자라서, 앞에서 면접을 본 팀원들에게 어떤 질문을 받았는지 조금 물어볼 수 있었습니다. 덕분에 어떤 방향으로 질문이 나올지 어느 정도 감을 잡고 들어갈 수 있었습니다.

이번에도 튜터님께서 제가 제일 잘 봤다고 말씀해주셔서 정말 다행이었습니다. 저번 면접도 긴장했는데, 이번에는 기술 질문이 섞여 있어서 더 걱정됐습니다. 그래도 준비한 내용과 기존에 공부했던 내용을 바탕으로 답변할 수 있어서 조금 안심했습니다.

 

🪟 인벤토리 / 장비창 열기

개발 쪽에서는 먼저 인벤토리와 장비창을 열고 닫는 흐름을 만들었습니다.

인벤토리는 Tab, 장비창은 O 입력으로 열리도록 연결했습니다. 기존에는 UI를 직접 켜두거나 테스트용으로 확인하는 느낌이었다면, 이제는 실제 플레이 중 입력으로 패널을 여는 흐름에 가까워졌습니다.

PlayerInputHandler.cs - 인벤토리 / 장비창 입력 연결

public void OnInventory(InputAction.CallbackContext context)
{
    if (!context.performed) return;
    GameManager.Instance.UIManager.OpenInventoryPanel();
}

public void OnEquip(InputAction.CallbackContext context)
{
    if (!context.performed) return;
    GameManager.Instance.UIManager.OpenEquipPanel();
}

UIManager도 추가해서 인벤토리, 장비창, 퀵슬롯 패널을 관리하도록 했습니다. 시작할 때 인벤토리와 장비창은 꺼두고, 퀵슬롯은 켜두는 방식으로 설정했습니다.

UIManager.cs - UI 패널 기본 상태

void Start()
{
    inventoryPanel.gameObject.SetActive(false);
    equipPanel.gameObject.SetActive(false);
    quickSlotPanel.gameObject.SetActive(true);
}

이런 작업은 기능 자체는 단순해 보이지만, 실제 게임처럼 조작하려면 꼭 필요한 부분입니다. 인벤토리와 장비창이 언제 열리고 닫히는지 명확해야 이후 다른 UI와도 충돌이 덜 날 것 같습니다.

 

🧲 패널 드래그 기능

패널을 드래그해서 움직일 수 있도록 PanelDrag도 추가했습니다.

기획서에 패널 자체를 드래그할 수 있는 내용이 있었기 때문에, 인벤토리나 장비창 같은 UI를 고정 위치에만 두는 것보다 사용자가 직접 옮길 수 있게 만드는 방향으로 작업했습니다.

PanelDrag.cs - 패널 드래그 시작

public void OnBeginDrag(PointerEventData eventData)
{
    if (targetPanel == null) return;

    targetPanel.SetAsLastSibling();

    RectTransformUtility.ScreenPointToLocalPointInRectangle(
        targetPanel.parent as RectTransform,
        eventData.position,
        eventData.pressEventCamera,
        out var localMousePos);

    offset = targetPanel.localPosition - (Vector3)localMousePos;
}

드래그를 시작할 때 SetAsLastSibling을 호출해서, 클릭한 패널이 다른 패널보다 위로 올라오도록 했습니다. 여러 패널이 겹칠 수 있기 때문에 이 부분도 필요했습니다.

그리고 패널을 드래그하다가 화면 밖으로 완전히 나가버리면 다시 잡기 어려울 수 있습니다. 그래서 닫기 버튼을 기준으로 화면 밖으로 나가지 않도록 제한하는 처리도 추가했습니다.

PanelDrag.cs - 닫기 버튼 기준 화면 밖 이동 방지

private void KeepCloseButtonOnScreen()
{
    Vector3[] corners = new Vector3[4];
    standardRect.GetWorldCorners(corners);

    float minX = 0;
    float maxX = Screen.width;
    float minY = 0;
    float maxY = Screen.height;

    Vector3 fix = Vector3.zero;

    if (corners[0].x < minX) fix.x = minX - corners[0].x;
    if (corners[2].x > maxX) fix.x = maxX - corners[2].x;
    if (corners[0].y < minY) fix.y = minY - corners[0].y;
    if (corners[2].y > maxY) fix.y = maxY - corners[2].y;

    targetPanel.position += fix;
}

처음에는 닫기 버튼의 위치 하나만 기준으로 잡았는데, 이후에는 GetWorldCorners를 사용해서 영역 기준으로 확인하도록 수정했습니다. 단순히 한 점만 보면 버튼의 일부가 화면 밖으로 나갈 수 있어서, 코너 기준으로 보는 쪽이 더 안정적이라고 판단했습니다.

 

⚔️ 장착 시 장비창 자동 열기

인벤토리에서 아이템을 더블클릭해 장착했을 때, 장비창이 자동으로 열리도록 수정했습니다.

아이템이 장착됐는데 장비창이 닫혀 있으면 사용자가 장착 결과를 바로 확인하기 어렵습니다. 그래서 장착이 성공했을 때 장비창을 보여주는 방식으로 처리했습니다.

ItemSlot.cs - 장착 성공 시 장비창 열기

if (eq.TryEquipInventory(inv, Index, equipSlotType))
{
    GameManager.Instance.UIManager.ShowEquipPanel();
}

UIManager에는 이미 장비창이 열려 있으면 그대로 두고, 닫혀 있을 때만 여는 ShowEquipPanel을 추가했습니다.

UIManager.cs - 장비창 표시

public void ShowEquipPanel()
{
    if (!equipPanel.gameObject.activeSelf)
    {
        equipPanel.gameObject.SetActive(true);
    }
}

이건 작은 사용성 개선이지만, 실제로 장비를 장착했을 때 결과를 바로 확인할 수 있어서 훨씬 자연스럽다고 느꼈습니다.

 

🖱️ 더블클릭 장착 해제

장비창에서도 더블클릭으로 장착 해제가 가능하도록 작업했습니다.

이전에는 인벤토리에서 장비를 장착하는 흐름에 더 집중했다면, 이제는 장착된 아이템을 다시 빼는 흐름도 UI에서 자연스럽게 처리해야 했습니다.

ItemSlot.cs - 장비 슬롯 더블클릭 해제

case SlotKind.Equipment:
    var itemDB = DataManager.Instance.ItemDB;
    if (itemDB == null) return;

    eq.UnEquipToIndex(inv, Index, equipSlotType);
    break;

장비 해제는 단순히 장비 슬롯에서 빼는 게 아니라, 인벤토리의 어느 위치로 돌아갈지도 같이 생각해야 합니다. 그래서 기존에 만든 UnEquipToIndex 흐름을 사용해 장비창 더블클릭과 연결했습니다.

 

⚡ 퀵슬롯 아이템 바로 사용

퀵슬롯 입력도 수정했습니다.

기존에는 선택된 퀵슬롯을 다시 눌렀을 때만 사용되는 흐름에 가까웠는데, 오늘은 퀵슬롯 버튼을 누르면 바로 선택 처리와 사용 처리가 이어지도록 수정했습니다.

QuickSlotController.cs - 퀵슬롯 입력 시 사용 호출

public void SelectSlotByNumber(int number)
{
    int index = Mathf.Clamp(number - 1, 0, 4);
    var slotType = (QuickSlotType)index;

    SelectedIndex = index;
    ApplySelection();
    EquipSelectedIfWeapon();

    if (index == SelectedIndex)
    {
        Use(slotType);
        return;
    }
}

사실 이 코드는 아직 더 정리가 필요해 보입니다. SelectedIndex를 먼저 바꾼 뒤 바로 비교하고 있어서, 조건 자체가 항상 true처럼 보일 수 있습니다. 다만 오늘 작업 의도는 분명했습니다. 퀵슬롯 키를 눌렀을 때 소비 아이템은 바로 사용되도록 만들고 싶었습니다.

마지막에는 기획자님 의견에 맞춰 퀵슬롯 수량이 0이 되어도 자동으로 삭제하지 않도록 수정했습니다. 즉, 아이템을 다 써도 퀵슬롯 칸 자체는 남아 있고, 사용자가 직접 삭제해야 없어지는 방식입니다.

QuickSlotController.cs - 수량 0이어도 자동 삭제하지 않기

if (inv.HasInventoryItem(itemId) <= 0) return;

var player = GameManager.Instance.ActivePlayer;
if (player == null) return;

player.AddHunger(c.HungerRecover);
player.AddThirst(c.ThirstRecover);
player.AddStamina(c.MaxStaminaBuffAdd);

if (!inv.TryRemoveByItemId(itemId, 1))
    return;

처음에는 수량이 0이 되면 퀵슬롯에서 자동으로 지우는 게 자연스럽다고 생각했는데, 기획 방향은 달랐습니다. 기획자님은 수량이 없어져도 바로 삭제하지 않고, 직접 삭제해야 없어지는 쪽을 원하셨습니다.

그래서 퀵슬롯이 0 상태로 남아 있게 수정했습니다. 이런 부분은 개발자가 보기에는 자동 정리가 편해 보여도, 실제 UX나 기획 의도에 따라 달라질 수 있다는 걸 다시 느꼈습니다.

 

🧩 장신구 장착 슬롯 처리 수정

장신구 장착 처리도 조금 수정했습니다.

장신구는 슬롯이 Accessory1, Accessory2 두 개라서, 단순히 아이템 타입을 장비 슬롯 타입으로 바로 변환하면 문제가 생길 수 있습니다.

그래서 첫 번째 장신구 슬롯이 비어 있으면 Accessory1에 넣고, 두 번째 슬롯이 비어 있으면 Accessory2에 넣도록 수정했습니다. 둘 다 차 있으면 우선 첫 번째 슬롯으로 교체되도록 흐름을 잡았습니다.

ItemSlot.cs - 장신구 슬롯 선택

if (type == ItemType.Accessory)
{
    if (eq.GetEquipped(EquipSlotType.Accessory1) == null)
    {
        equipSlotType = EquipSlotType.Accessory1;
    }
    else if (eq.GetEquipped(EquipSlotType.Accessory2) == null)
    {
        equipSlotType = EquipSlotType.Accessory2;
    }
    else
    {
        equipSlotType = EquipSlotType.Accessory1;
    }
}
else
{
    equipSlotType = (EquipSlotType)type;
}

이 부분도 장비창을 실제로 쓰기 시작하니까 보이는 문제였습니다. 장비 타입과 장비 슬롯이 항상 1:1로 매칭되는 건 아니라서, 이런 예외 처리를 더 신경 써야 했습니다.

 

📌 오늘의 회고

오늘은 기술 모의 면접과 UI 기능 개선을 같이 진행한 날이었습니다.

면접에서는 접근 제한자, SRP, 시간대 처리 같은 질문을 받았습니다. 처음 들으면 당황할 수 있는 질문도 있었지만, 앞에서 면접을 본 사람들에게 질문을 조금 물어보고 들어간 덕분에 어느 정도 마음의 준비를 할 수 있었습니다.

저번 면접에 이어 이번에도 튜터님께서 잘 봤다고 해주셔서 정말 다행이었습니다. 솔직히 기술 질문은 틀릴까 봐 더 긴장되는데, 그래도 공부한 내용과 프로젝트 경험을 엮어서 답변하려고 했던 게 좋게 보인 것 같습니다.

개발 쪽에서는 인벤토리와 장비창 열기, 패널 드래그, 더블클릭 장착/해제, 퀵슬롯 아이템 사용 같은 사용성 작업을 많이 했습니다. 기능을 만들수록 단순히 “동작한다”에서 끝나는 게 아니라, 사용자가 자연스럽게 쓸 수 있는지도 계속 봐야 한다는 생각이 듭니다.

특히 퀵슬롯 수량이 0이 됐을 때 자동 삭제할지 말지는 기획자님 의견에 따라 바뀐 부분이었습니다. 개발자 입장에서는 자동으로 정리되는 게 편해 보였지만, 기획 의도에 맞춰 직접 삭제하도록 남겨두는 게 맞았습니다.

오늘은 면접도 잘 끝났고, UI도 실제 플레이 흐름에 조금 더 가까워졌습니다. 아직 고칠 부분은 많지만, 그래도 이제 인벤토리와 장비창이 점점 “테스트용 기능”이 아니라 실제 게임 UI처럼 보이기 시작한 것 같습니다.