카테고리 없음

2025.06.12 10주차 목요일

mochoa 2025. 6. 12. 21:47

주요 구현 내용 요약

1. 플레이어 상태 시스템 일원화

2. 적 충돌/데미지 처리

3. 카메라 따라가기

4.양손 장비 스왑(Swap) 로직 구현

5. 인벤토리 UI에서 장착 상태(체크 마크) 동기화


구현 코드 구조 요약

PlayerStatus.cs

public class PlayerStatus : MonoBehaviour
{
    [SerializeField] private StatusData health, food, mana;

    // 자동 회복/감소
    void Update()
    {
        food.Add(food.PassiveValue * Time.deltaTime);
        mana.Add(mana.PassiveValue * Time.deltaTime);
    }

    // 자원 소모/획득/적 처치 보상
    public bool UseFood(float amount) { ... }
    public bool UseMana(float amount) { ... }
    public void GainFood(float amount) { ... }
    public void OnEnemyKilled(float reward) { GainFood(reward); }
    // 적 충돌 시 데미지 코루틴 등...
}

 

StatusData.cs

[Serializable]
public class StatusData
{
    [SerializeField] private float curValue, maxValue, passiveValue;
    public float CurValue { get; set; }
    public float MaxValue => maxValue;
    public float PassiveValue => passiveValue;

    public void Add(float v) => CurValue += v;
    public void Subtract(float v) => CurValue -= v;

    // 저장/불러오기용 함수도 추가!
    public StatusSaveData ToSaveData() { ... }
    public void FromSaveData(StatusSaveData data) { ... }
}

 

CameraFollow.cs

public class CameraFollow : MonoBehaviour
{
    public Transform target;
    public Vector3 offset = new Vector3(0, 3, -10); // y값 조절로 플레이어 위치 아래 배치
    public float smoothSpeed = 7f;
    void LateUpdate()
    {
        if (target == null) return;
        Vector3 desired = target.position + offset;
        Vector3 smooth = Vector3.Lerp(transform.position, desired, smoothSpeed * Time.deltaTime);
        transform.position = smooth;
    }
}

 

 

고민했던 부분 & 해결법

  • 자원이 많아질수록 저장/불러오기 복잡해질까?
    • StatusData/SaveData 구조화로 깔끔하게 해결할수있음.
  • 카메라 중앙이 아니라 아래쪽에 플레이어 배치하는 법?
    • offset.y 조절로 직관적으로 위치 제어
  • PassiveValue 활용(양수=자동 회복, 음수=자동 소모)
  • 주석/region 적극 활용: 협업, 문서화, 가독성 모두 UP

 


배운 점 & 느낀점

  • 상태/자원/저장 구조를 일관성 있게 잡으니
    확장(스탯 추가), 유지보수, 협업 모두 편해진다!
  • 카메라 연출도 작은 offset/y값 조정만으로 원하는 느낌을 손쉽게 낼 수 있다.
  • 클린코드(주석/region), 팀원/외주 협업 문서화의 중요성 재확인!

 


4.양손 장비 스왑(Swap) 로직 구현

  • 기존에는 EquipRight(무기), EquipLeft(방패) 메서드에서
    이미 장착 중인 아이템과 동일한 아이템을 다른 손에 장착하려 할 때
    단순히 기존 손의 장비를 해제(=None) 시켰음.
  • 이렇게 하면, 양손에 같은 아이템을 번갈아 끼우려 할 때 한쪽이 비어버리는(장비가 None이 되는) 문제가 발생함.
  • 이 문제를 해결하기 위해,
    • 장착하려는 아이템이 반대 손에 이미 장착 중이면, 두 손의 아이템을 swap(교환)하는 로직을 추가함.
    • 즉, 오른손에 칼, 왼손에 방패 상태에서 왼손에 칼을 장착하면
      → 오른손엔 방패, 왼손엔 칼로 서로 바뀌도록 구현.
    •  
// 오른손 장착 메서드 예시
public void EquipRight(EquipmentItem item)
{
    if (equippedWeapon == item) return;

    // 반대 손(왼손)에 이미 같은 아이템이 있으면 swap
    if (equippedShield == item)
    {
        var temp = equippedWeapon;
        equippedWeapon = item;
        equippedShield = temp;

        // 비주얼 처리
        characterVisual.EquipWeaponRight(item.equippedSprite);
        if (temp != null)
            characterVisual.EquipShieldLeft(temp.equippedSprite);
        else
            characterVisual.UnequipShieldLeft();

        FindObjectOfType<InventoryUI>()?.SyncEquippedUI();
        ApplyTotalStats();
        return;
    }

    // ... 이하 기존 로직
}

EquipLeft도 같은 원리로 swap 구현!


배운 점

  • 양손 장비가 존재하는 경우, 장비를 단순 해제하는 것이 아니라 swap을 구현해야 UX가 자연스러움.

5. 인벤토리 UI에서 장착 상태(체크 마크) 동기화

  • 슬롯 오브젝트에서 **MarkAsEquipped()**를 호출해 장착 체크 마크/버튼 비활성화,
    **ResetVisual()**로 해제(체크 해제+버튼 활성화)를 구현함.
  • **SyncEquippedUI()**를 통해 현재 장착 상태와 슬롯 UI를 동기화.
    • 장착 상태(EquipManager의 equippedWeapon, equippedShield)와 슬롯의 아이템이 같으면 체크
    • 아니면 해제
public void SyncEquippedUI()
{
    foreach (var slot in slots)
    {
        if (equipManager.equippedWeapon == slot.Item || equipManager.equippedShield == slot.Item)
            slot.MarkAsEquipped();
        else
            slot.ResetVisual();
    }
}

장비를 장착/해제/스왑할 때마다 SyncEquippedUI()를 호출하여
UI가 항상 실제 장착 상태와 일치하게 만듦.


 

배운 점

    • Start(), OnEnable(), Awake() 등 Unity 라이프사이클 순서를 정확히 이해하고, 슬롯/동기화 타이밍을 제어하는 게 중요함.
    • UI와 실제 데이터가 항상 동기화되도록, 장착/해제 시 SyncEquippedUI를 반드시 호출하는 습관을 들여야 한다.

 

Unity MonoBehaviour 라이프사이클 주요 순서

1. 오브젝트가 생성되어 Hierarchy에 등장할 때

      • Awake()
        • 컴포넌트가 인스턴스화되면 제일 먼저 실행
        • 다른 모든 컴포넌트/스크립트가 아직 Awake 중일 수 있음
        • DontDestroyOnLoad 등 초기 세팅에 많이 사용
      • OnEnable()
        • 오브젝트가 활성화(Enable) 상태가 될 때 호출
        • 비활성화 → 활성화 될 때마다 실행됨
        • (비활성화된 오브젝트는 Enable해도 Awake/Start는 다시 안 탄다!)

2. 씬이 처음 실행되거나, 오브젝트가 활성화된 직후

      • Start()
        • 모든 Awake/OnEnable이 끝난 뒤,
        • 프레임이 시작되기 직전에 한 번만 호출
        • "초기화가 다 끝난 후에 무언가를 하고 싶을 때" 사용

3. 게임 루프가 돌기 시작하면…

      • Update() (매 프레임마다)
      • LateUpdate() (Update 후)
      • FixedUpdate() (물리 루프마다)
      • …(이후 OnDisable, OnDestroy 등)

실제 호출 순서 예시

      1. 씬에 오브젝트가 활성화된 상태로 등장 →
        • Awake()
        • OnEnable()
        • Start()
      2. 오브젝트가 비활성화됐다가 다시 활성화되면 →
        • OnEnable() (Awake, Start는 한 번만!)
      3. 씬 전환 등으로 오브젝트가 삭제될 때 →
        • OnDisable()
        • OnDestroy()

정리 표

 

오브젝트 활성화 시 호출 순서
최초 생성(활성화 O) Awake → OnEnable → Start
비활성화 후 다시 활성화 OnEnable
비활성화 OnDisable
파괴 OnDestroy

 


느낀점

이번 양손 장비 시스템을 구현하면서,
처음엔 "그냥 장착 해제하고 새로 착용"하는 방식만 생각했다가
실제 사용해보니 UX가 굉장히 불편해지는 걸 직접 체감했다.
그래서 “swap” 방식을 도입해보니 사용감이 훨씬 자연스러워졌고,
실제로 게임에서도 자주 쓰는 “드래그&스왑” 패턴의 필요성을 깨달았다.

또한 UI 체크 표시와 실제 데이터 동기화를 꼼꼼하게 신경써야
사용자 입장에서 “내가 장비를 바꾼 게 바로바로 보인다”는
즉각적인 피드백이 가능하다는 점을 다시 느꼈다.

 

UI 오브젝트(특히 InventoryUI)가 SetActive(false) 상태로 시작하면
Start()가 늦게(=실제 켜질 때) 호출된다는 Unity 라이프사이클 특성을 명확히 알게 됐다.
덕분에 OnEnable/Start 타이밍 꼬임,
그리고 “왜 내 슬롯이 안 만들어지지?” 하는 문제를 직접 로그로 확인하며
“초기화 타이밍과 UI 활성화 상태”를 정말 신경써야 함을 깨달았다.