Skip to content

[Feature] 전투 시스템 기반 구현 (엔티티, 웨이브, 자동 전투, 스킬 트리) #18

Open
leesiuuuu wants to merge 15 commits intodevelopfrom
feature/battle-system
Open

[Feature] 전투 시스템 기반 구현 (엔티티, 웨이브, 자동 전투, 스킬 트리) #18
leesiuuuu wants to merge 15 commits intodevelopfrom
feature/battle-system

Conversation

@leesiuuuu
Copy link
Copy Markdown
Contributor

내용

  • 전투 엔티티 기반 클래스 계층 구현 (Entity → Player/Enemy)
  • 웨이브 기반 자동 전투 시스템 구현 (BattleManager, WaveController)
  • 공격 컴포넌트 타겟 지정 수정 (AttackCollider, EntityType 기반 아군 피해 방지)
  • 재화 시스템 ScriptableObject 구현 (Gold, XP, SP, Mithril, UpgradeScroll)
  • 스킬 트리 데이터 구조 및 전략 패턴 구현 (검사/궁수/법사 트리 대응)
  • 전투 테스트 씬 및 ScriptableObject 에셋 추가

변경 사항

전투 시스템 (Assets/Scripts/Battle/)

  • Entity.cs — HP, 공격력, TakeDamage(), Death() 기반 클래스
  • Player/Player.cs, Player/Warrior.cs — 플레이어 계열
  • Enemy/Enemy.cs, Enemy/EnemyAI.cs, Enemy/TestEnemy.cs — 적 계열 및 AI
  • BattleManager.cs — 전투 흐름 전체 관리 (던전 진입/종료, 웨이브 순서 제어)
  • WaveController.cs — 웨이브 단위 적 스폰 및 클리어 감지
  • AutoAttackController.cs — 자동 공격 루프
  • AttackCollider.cs — 2D Trigger 기반 히트 판정, 타겟 지정 수정

데이터 구조 (Assets/Scripts/Battle/Data/)

  • DungeonData.cs, WaveData.cs, EnemyRewardData.cs — 던전/웨이브/보상 데이터 클래스
  • ScriptableObject 에셋: TestDungeonData, TestWaveData, TestRewardData

Core 시스템 (Assets/Scripts/Core/)

  • EntityStatData.cs — 스탯 ScriptableObject
  • EntityStatModifier.cs — 스탯 수정자
  • DamageCalculator.cs — 데미지 계산 로직
  • SkillData.cs — 스킬 데이터 기반 클래스

스킬 트리 (Assets/Scripts/Core/SkillTree/)

  • SkillTreeData.cs, SkillNodeData.cs, SkillTreeComponent.cs
  • 전략 패턴으로 트리 타입 분리: BranchingTreeStrategy (검사), LinearTreeStrategy (법사), RowSelectTreeStrategy (궁수)

재화 시스템 (Assets/Scripts/Core/Goods/)

  • SO_Goods.cs (추상), SO_Gold, SO_XP, SO_SP, SO_Mithril, SO_UpgradeScroll

씬 및 프리팹

  • Assets/Scenes/BattleTestScene.unity — 전투 테스트용 씬
  • Assets/Prefabs/Enemy/Enemy.prefab — 적 프리팹

테스트

  • BattleTestScene에서 플레이 모드 진입 후 웨이브 자동 진행 확인
  • 플레이어가 Enemy만, Enemy가 Player만 타격하는지 EntityType 검증
  • 웨이브 클리어 시 다음 웨이브로 정상 전환 확인
  • 적 처치 시 보상(Gold/XP) 정상 지급 확인

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive battle loop and skill tree system for a 2D idle RPG. It includes core entity management, automated combat logic via state machines, and a flexible skill tree architecture using the Strategy pattern to support different class-specific progression rules. Key feedback focuses on preventing potential runtime crashes during enemy spawning, optimizing performance by caching component references instead of repeated lookups in loops, and correcting a logic issue where applying stat modifiers unintentionally fully heals the entity.

/// 웨이브 데이터에 따라 적을 스폰한다.
/// 스폰된 Enemy 인스턴스 목록을 반환한다.
/// </summary>
public List<Enemy> SpawnWave(WaveData waveData, Transform[] spawnPoints)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

spawnPoints 배열이 비어있거나 null일 경우, spawnPoints.Length가 0이 되어 spawnIndex % spawnPoints.Length 연산(31라인)에서 DivideByZeroException이 발생하여 크래시가 일어날 수 있습니다. 스폰 포인트를 사용하기 전에 배열이 유효한지 반드시 확인하는 방어 코드를 추가해야 합니다. 예를 들어, 메서드 시작 부분에 if (spawnPoints == null || spawnPoints.Length == 0) { ... } 와 같은 가드 구문을 추가할 수 있습니다.

{
// Enemy 프리팹에 AttackCollider가 있으면 Attack()으로 애니메이션+충돌 처리.
// AttackCollider가 없는 경우 DamageCalculator로 직접 피해 적용.
if (HasAttackCollider())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

AttackLoopwhile 루프 내에서 HasAttackCollider()가 매번 호출되며, 내부적으로 GetComponentInChildren를 실행합니다. 이는 매 공격마다 불필요한 컴포넌트 검색을 유발하여 성능을 저하시킬 수 있습니다. Awake()에서 _hasAttackCollider와 같은 bool 변수에 결과를 캐시하고, 루프에서는 이 변수를 확인하는 방식으로 최적화해야 합니다.

Comment on lines +70 to +71
MaxHp = statData.Hp + modifier.BonusHp;
Hp = MaxHp;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

ApplyStatModifier가 호출될 때마다 HpMaxHp로 재설정되어 체력이 완전히 회복됩니다. 이는 스킬 트리에서 패시브 스킬을 해금할 때마다 플레이어의 체력이 가득 차는 의도치 않은 동작을 유발할 수 있습니다. 체력을 완전히 회복시키는 대신, 기존 체력 비율을 유지하는 방식으로 수정해야 합니다.

        float hpPercentage = MaxHp > 0 ? (float)Hp / MaxHp : 1f;
        MaxHp = statData.Hp + modifier.BonusHp;
        Hp = Mathf.RoundToInt(MaxHp * hpPercentage);

Comment on lines +142 to +143
foreach (Enemy e in _currentWaveEnemies)
e.GetComponent<EnemyAI>()?.Initialize(_player);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

foreach 루프 내에서 GetComponent<EnemyAI>()를 반복적으로 호출하면 스폰되는 적의 수가 많아질 경우 성능에 부담을 줄 수 있습니다. Enemy 스크립트의 Awake()에서 EnemyAI 참조를 캐싱해두고, 여기서는 캐시된 프로퍼티를 사용하도록 개선하는 것이 좋습니다. 이 방식은 152, 160 라인의 루프에도 동일하게 적용할 수 있습니다.

{
// Enemy 프리팹에 AttackCollider가 있으면 Attack()으로 애니메이션+충돌 처리.
// AttackCollider가 없는 경우 DamageCalculator로 직접 피해 적용.
if (HasAttackCollider())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetComponentInChildren은 비용이 매우 큰 연산이므로 AttackCollider를 캐싱하거나 bool값을 캐싱해서 사용하는 것을 권장합니다

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 런타임에 AttackCollider를 추가/삭제 하는 방식을 사용하고 계신가요?

@leesiuuuu
Copy link
Copy Markdown
Contributor Author

@gemini-code-assist

@gemini-code-assist
Copy link
Copy Markdown

안녕하세요! 전투 시스템 구현에 대한 코드 리뷰를 요청해주셔서 감사합니다. 구현하신 전투 시스템의 구조와 데이터 기반 설계는 매우 체계적입니다. 특히 전략 패턴을 활용한 스킬 트리 구현과 ScriptableObject를 통한 데이터 관리는 유지보수 측면에서 훌륭한 접근 방식입니다.

전체적인 코드 흐름을 검토한 결과, 몇 가지 개선 제안을 드립니다.

1. WaveController.cs - SpawnWave 메서드

spawnPoints 배열이 비어있을 경우 spawnIndex % spawnPoints.Length에서 DivideByZeroException이 발생할 수 있습니다. 스폰 포인트가 할당되지 않았을 때의 예외 처리를 추가하는 것이 좋습니다.

  • 제안:
if (spawnPoints == null || spawnPoints.Length == 0)
{
    Debug.LogError("[WaveController] 스폰 포인트가 설정되지 않았습니다.");
    return null;
}

2. EnemyAI.cs - hasAttackCollider 프로퍼티

hasAttackCollider 프로퍼티의 set 접근자에서 자기 자신을 참조하는 무한 재귀 호출이 발생하고 있습니다. childCollider를 직접 확인하도록 수정해야 합니다.

  • 제안:
private bool HasAttackCollider => childCollider != null;

3. BattleManager.cs - 상태 전환

EnterWaveStart에서 TransitionTo(BattleState.Fighting)을 호출하고 있는데, 이는 상태 머신 흐름상 의도된 것이겠지만, Fighting 상태로 진입하기 전에 적들이 완전히 스폰되었는지 확인하는 로직이 필요할 수 있습니다. 현재는 즉시 전환되므로 문제가 없으나, 향후 스폰 애니메이션 등을 추가할 경우 고려가 필요합니다.

전반적으로 매우 견고하게 작성된 기능입니다. 위 제안 사항들을 검토하시고 적용하시면 더욱 안정적인 시스템이 될 것입니다.

@leesiuuuu
Copy link
Copy Markdown
Contributor Author

@gemini-code-assist

@gemini-code-assist
Copy link
Copy Markdown

안녕하세요! 추가적인 질문이나 도움이 필요하신가요? 구현하신 전투 시스템에 대해 궁금한 점이 있으시거나, 코드 리뷰에서 제안드린 내용 중 추가 설명이 필요하시면 언제든 말씀해주세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants