팀스파르타 내일배움캠프

📚 TIL - 2025년 5월 1일 (목) / 본격 구현 단계 진입

creator2041 2025. 5. 1. 21:30

 

오늘 배운 것 & 작업한 내용

오늘은 유니티 프로젝트에서 캐릭터의 공격 및 피격 처리, 원거리 무기 시스템, 코루틴 기반 적 생성, 투사체 처리, 스탯 관리, 그리고 애니메이션 연동까지 본격적으로 구현하며 전반적인 실전 로직에 대한 이해를 다졌습니다. 각 스크립트별로 구조와 작동 원리를 하나하나 뜯어보며 동작 과정을 익혔습니다.


주요 학습 및 분석

1. AnimationHandler.cs

  • Animator에 파라미터를 전달하여 이동(isMove)과 피격(isDamage) 상태를 제어하는 스크립트.
  • Animator.StringToHash()로 문자열을 해시화해 성능 최적화.
  • Move(Vector2)는 magnitude를 기준으로 움직임 여부 판단, Damage(), InvincibilityEnd()는 상태 변화 제어.

느낀 점: 애니메이션 파라미터의 정확한 대소문자 일치가 중요하며, 상태에 따라 애니메이션을 나누는 기본 구조를 이해함. 특히 SetBool()을 통해 이동 상태를 실시간으로 제어하면서, 시각적 피드백이 얼마나 중요한지 깨달았다. isDamage처럼 특정 상태에서만 애니메이션을 변경하고 이후 다시 해제하는 로직은 앞으로 자주 쓰이게 될 것 같다.


2. BaseController.cs

  • 모든 캐릭터(Player/Enemy)의 공통 조상 클래스. 핵심 입력 처리, 이동 로직, 회전, 공격 쿨타임 관리 등 공통 기능을 포괄함.
  • movementDirection, lookDirection, weaponHandler, statHandler 등 주요 컴포넌트를 보유.
  • FixedUpdate()에서는 리지드바디를 통한 물리 이동 처리. Update()에서는 공격 쿨타임 및 회전처리 담당.
  • Rotate()에서는 캐릭터의 시선 방향을 기준으로 좌우 반전을 계산하고 무기의 flipY 처리까지 함께 구현됨.

느낀 점: 단일 클래스가 모든 기능을 갖고 있으면 유지보수가 어렵기 때문에, 공통 로직은 여기서 관리하고 각 역할별 클래스(Player/Enemy 등)에서 세부 구현을 나누는 구조가 효율적임을 체감했다. 특히 회전과 무기 flip 처리까지 시각적 요소와 연계된 부분이 흥미로웠다.


3. PlayerController.cs / EnemyController.cs

  • Player는 키보드 입력(GetAxisRaw)과 마우스 위치(ScreenToWorldPoint)로 방향 설정.
  • Enemy는 target을 기준으로 거리 계산 → 범위 내 진입 시 추적 또는 공격 수행.
  • EnemyController는 레이캐스트를 통해 앞에 벽이 있는지, 진짜 적인지까지 판별하고 공격 여부 결정.

느낀 점: Player는 입력 기반이지만, Enemy는 AI 기반으로 동작하므로 로직이 완전히 다르다. Raycast를 활용한 조건부 공격 판단은 앞으로 여러 종류의 AI에 적용할 수 있는 구조로 느껴졌다. 특히 layerMaskTarget == (layerMaskTarget | (1 << hit.collider.gameObject.layer)) 같은 비트 연산 기반의 충돌 판별이 처음엔 어려웠지만 핵심적인 부분이었다.


4. ResourceController.cs

  • 체력(Health) 관리 및 피격 처리 기능. 일정 시간(healthChangeDelay) 무적 시간 구현.
  • 피격시 Damage() 호출 → 애니메이션 변경 → 시간이 지나면 InvincibilityEnd()로 해제.
  • 체력이 0 이하로 떨어지면 Death() 호출, 향후 이 안에서 캐릭터 비활성화 및 사망 처리 구현 예정.

느낀 점: 단순히 숫자를 깎는 것이 아니라, 실제 피격 시 무적 시간 동안 피격 애니메이션 지속 여부를 조절하고, 여러 공격이 겹쳐도 처리되지 않게 하는 흐름이 핵심이다. 앞으로 쉴드나 버프 처리 같은 것도 이런 방식으로 구현할 수 있을 것 같아 확장성도 느껴졌다.

 

주요 학습 및 분석 (계속)

5. WeaponHandler.cs / RangeWeaponHandler.cs

  • WeaponHandler는 기본 무기 구조를 정의. 공격 쿨타임(delay), 무기 크기(weaponSize), 넉백(knockbackPower) 등 설정 포함.
  • RangeWeaponHandler는 WeaponHandler를 상속받아 원거리 공격용 투사체 생성 로직을 구현.
  • numberOfProjectilesPerShot, spread, multipleProjectilesAngle을 기반으로 총알을 퍼뜨리는 방식 구현.
  • CreateProjectile()에서 발사 각도를 조정하고 실제 발사 위치인 projectileSpawnPosition에서 투사체 생성.

느낀 점: 원거리 무기를 세밀하게 커스터마이징할 수 있는 구조가 매우 유연하고 강력하다고 느껴졌다. 특히 총알 개수, 크기, 퍼짐 각도, 속도, 생존 시간 등을 한꺼번에 제어할 수 있어 다양한 무기 설계가 가능해졌다. 다만, Start()에서 ProjectileManager.Instance를 찾지 못하면 null 에러가 날 수 있어 싱글톤 구조에 대한 이해도 함께 필요했다.


6. ProjectileManager.cs / ProjectileController.cs

  • ProjectileManager는 총알을 생성하고 이펙트를 관리하는 중앙 컨트롤러 역할.
  • ProjectileController는 개별 총알에 붙어 있으며, 이동, 충돌, 삭제, 이펙트 생성 등의 로직을 담당.
  • Init()에서 색상, 크기, 방향 등을 설정하며, 총알이 살아있는 시간(duration)을 넘어가면 자동 파괴됨.
  • 충돌 시 layerMask로 명확히 충돌 대상 구분 후, 피격 이펙트 생성 후 총알 삭제.

느낀 점: 투사체는 생각보다 처리할 항목이 많았고, OnTriggerEnter2D()의 조건문들이 제일 핵심이었다. Rigidbody2D로 움직이게 하면서도, 충돌을 감지해 파괴 타이밍과 이펙트를 분리한 구조는 앞으로 폭발물이나 다양한 투사체에도 응용 가능할 것으로 보인다.


7. EnemyManager.cs

  • 적을 스폰하는 클래스. Coroutine을 이용하여 웨이브 방식으로 적 생성.
  • List<Rect>로 정의된 범위 중 랜덤 위치에 적 프리팹 생성.
  • OnDrawGizmosSelected()를 통해 스폰 구역을 에디터에서 시각적으로 확인 가능.

느낀 점: 단순히 Instantiate()로 적을 생성하는 것이 아니라, 비동기 생성, 구역별 랜덤화, 에디터 디버깅 시각화까지 겸비한 코드 구조에 감탄했다. 특히 StartWave()를 누를 때마다 스폰 루틴이 동작하는 구조는 향후 웨이브 게임의 기본 베이스가 될 수 있다.


8. StatHandler.cs

  • 캐릭터의 체력과 속도를 SerializeField + Range + Property 조합으로 관리.
  • 외부에서는 statHandler.Speed나 Health로 접근할 수 있고, 내부적으로는 Mathf.Clamp로 범위 보장.

느낀 점: 인스펙터로 값을 조정하면서도, 스크립트에서 안전하게 값을 유지할 수 있다는 점이 매우 유용했다. 실수로 체력을 음수로 만들거나 속도를 0 이하로 설정할 수 없게 하는 안전장치가 깔끔한 코드 설계를 돕는다고 느꼈다.


트러블 슈팅 & 느낀 점

  1. isMove / IsMove 파라미터 혼동
    • 파라미터 등록은 isMove인데 코드에선 IsMove로 잘못 사용해서 애니메이션이 작동 안 됨.
    • Animator.StringToHash("IsMove") → Animator.StringToHash("isMove")로 수정하여 해결.
    • 대소문자 구분 철저히 하자. Animator 파라미터는 코드 기준이 아님, 에디터 기준임.
  2. 프리팹 등록 후 저장 안됨 문제
    • 플레이 모드 중에 프리팹 등록하고 저장 안 하고 종료해서 변경사항이 반영 안 됨.
    • ✅ 변경은 반드시 에디터 정지 상태에서 저장 필요.
  3. 화살(Projectile) 오류 발생
    • RangeWeaponHandler에서 projectile을 발사했지만 에러 발생.
    • 확인 결과 projectileSpawnPosition이 미지정되어 NullReference 발생.
    • ✅ 프리팹 내부에도 반드시 SpawnPosition의 트랜스폼 지정 필요.
  4. 공격이 안 되는 문제
    • Attack() 함수가 실행되는데 화살이 안 나감.
    • 원인은 lookDirection의 magnitude가 낮아서 방향을 Vector2.zero로 처리해 무효화된 것.
    • ✅ 방향 벡터의 기준 설정과 유효성 체크가 필수.

오늘은 단순한 기능 구현을 넘어서 전체 시스템을 유기적으로 연결하고, Unity에서 실무적인 버그를 많이 경험하며 실력을 크게 늘릴 수 있는 날이었다. 코루틴, 공격 로직, 투사체 처리, 애니메이션, 피격 반응 등 게임 캐릭터의 기본 골격을 구성하는 핵심 요소를 직접 다뤄본 덕분에, 코드를 보는 눈이 훨씬 넓어졌다.

다만, 아직 변수 설계와 클래스 분리 설계는 명확히 손에 익지 않아서, 기능이 많아질수록 구조가 헷갈리는 점이 있었다. 따라서 내일은 이 구조를 도식화하고, 기능 단위로 정리하는 문서 작성도 병행할 예정이다.