[Day12] JobAndCombat_Challenge

베리투스·2025년 8월 20일

TIL: Today I Learned

목록 보기
20/93
post-thumbnail

기본 직업 시스템을 확장하여 Player와 Monster가 서로 상호작용하는 실제 전투 시뮬레이션을 구현했다. 헤더 파일 간에 서로를 참조할 때 발생하는 순환 참조 문제를 '전방 선언'이라는 기법으로 해결하는 중요한 경험을 했다. 💪 또한, while문을 이용한 전투 루프를 설계하여 턴 기반 전투의 기본 로직을 완성했다. 직업마다 각기 다른 공격 방식으로 몬스터에게 피해를 입히는 다형성의 실제적인 활용법을 체득했다. ⚔️


📌 목표

  • 클래스 간의 상호작용 구현하기
  • 전방 선언으로 순환 참조 문제 해결하기
  • while문을 이용한 전투 루프 로직 설계하기
  • Getter/Setter를 이용한 캡슐화 심화 학습
  • 여러 파일로 분리된 프로젝트 관리하기

💻 코드

main.cpp

// main.cpp: 게임의 전체적인 흐름과 전투 루프를 관리합니다.
#include <iostream>
#include <limits> // cin.ignore()를 위해 필요
#include "player.h"
#include "monster.h"
// ... (warrior.h, magician.h 등 모든 직업 헤더 포함) ...

using namespace std;

int main() {
    Player* player = nullptr;
    // ... (이전과 동일하게 닉네임 입력 및 직업 선택) ...

    // 'new' 키워드로 Monster 객체를 동적으로 생성합니다.
    cout << "\n야생의 슬라임이 나타났다!\n" << endl;
    Monster* slime = new Monster("슬라임", 50, 10, 5);

    cout << "\n엔터를 눌러 전투를 시작하세요...";
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    cin.get();

    // --- 전투 루프 ---
    // 플레이어와 몬스터 둘 다 살아있는 동안 계속 반복됩니다.
    while (player->getHP() > 0 && slime->getHP() > 0) {
        // 플레이어의 턴
        cout << "\n--- 플레이어의 턴 ---" << endl;
        player->attack(slime); // 플레이어가 몬스터를 공격!
        if (slime->getHP() <= 0) break; // 몬스터가 죽으면 루프 탈출

        // 몬스터의 턴
        cout << "\n--- 몬스터의 턴 ---" << endl;
        slime->attack(player); // 몬스터가 플레이어를 공격!
        if (player->getHP() <= 0) break; // 플레이어가 죽으면 루프 탈출
    }

    // ... (전투 결과 출력 및 delete로 메모리 해제) ...
    return 0;
}

player.h

// player.h: Player 클래스에 Monster와의 상호작용 기능을 추가합니다.
#pragma once
#include <string>
using namespace std;

// 전방 선언 (Forward Declaration)
// #include "monster.h" 대신 사용합니다.
// "Monster라는 클래스가 어딘가에 있을 거야"라고 컴파일러에게 미리 알려주기만 합니다.
// 이렇게 하면 Monster의 구체적인 내용은 몰라도 Monster* 타입은 사용할 수 있게 되어,
// monster.h와의 순환 참조 문제를 해결할 수 있습니다.
class Monster; 

class Player {
public:
    Player(string nickname);
    // 이제 attack 함수는 Monster 객체의 포인터를 인자로 받습니다.
    virtual void attack(Monster* monster) = 0;

    // 외부에서 플레이어의 정보를 안전하게 가져가거나(getter) 설정(setter)할 함수들
    int getDefence();
    int getHP();
    bool setHP(int HP); // HP를 설정하고 생존 여부를 bool 값으로 반환합니다.
};

monster.h

// monster.h: Monster 클래스의 설계도입니다.
#pragma once
#include <string>
using namespace std;

class Player; // Player 클래스도 똑같이 전방 선언해줍니다.

class Monster {
public:
    Monster(string name, int hp, int power, int defence);
    // Monster의 attack 함수는 Player 객체의 포인터를 인자로 받습니다.
    void attack(Player* player);

    int getDefence();
    int getHP();
    bool setHP(int HP);
};

archer.cpp

// archer.cpp: Archer 클래스의 다중 공격 로직을 구현합니다.
#include "archer.h"
#include "monster.h" // Monster의 구체적인 멤버 함수(getName, getDefence 등)를 사용해야 하므로 .cpp 파일에서는 반드시 #include 합니다.
#include <iostream>

void Archer::attack(Monster* monster) {
    cout << "\n" << job_name << " '" << nickname << "' (이)가 " << monster->getName() << "에게 화살을 쏩니다!" << endl;
    
    // 몬스터의 방어력을 고려하여 총 데미지를 계산합니다.
    int total_damage = this->power - monster->getDefence();
    if (total_damage < 1) total_damage = 1;

    // 궁수는 3회 공격하므로, 1회당 데미지를 계산합니다.
    int damage_per_hit = total_damage / 3;
    if (damage_per_hit == 0) damage_per_hit = 1;

    for (int i = 0; i < 3; i++) {
        if (monster->getHP() <= 0) break; // 몬스터가 중간에 죽으면 공격 중단

        cout << damage_per_hit << "의 피해를 입혔습니다! (타격 " << i + 1 << ")" << endl;
        bool is_alive = monster->setHP(monster->getHP() - damage_per_hit); // 몬스터의 HP를 깎습니다.
        if (!is_alive) {
            cout << monster->getName() << "을(를) 물리쳤습니다!" << endl;
            break;
        }
    }
}

⚠️ 실수

  • playermonster를 공격하는데, monster->getDefence() 함수가 없다는 컴파일 오류를 만났다. 다른 클래스의 정보를 가져오려면 반드시 해당 함수(getter)를 만들어줘야 한다는 캡슐화의 기본을 다시 한번 깨달았다.
  • player.h#include "monster.h"를, monster.h#include "player.h"를 넣었더니 서로를 무한정 불러오는 순환 참조 오류가 발생했다. 헤더 파일에서는 포인터만 쓸 거라면, #include 대신 '전방 선언'을 써야 한다는 것을 배웠다. 💡
  • 예전에 쓰던 attack() 함수 선언을 지우지 않았더니, 자식 클래스들이 더는 그 함수를 구현하지 않는다는 이유로 LNK2001 링커 오류가 발생했다. 안 쓰는 코드는 과감히 정리해야 한다는 교훈을 얻었다. 🧹

✅ 핵심 요약

개념설명비고
클래스 간 상호작용한 객체가 다른 객체의 멤버 함수를 호출하는 등 서로 협력하여 프로그램을 구성하는 것.player->attack(monster)
전방 선언#include 없이 클래스의 이름만 미리 알려주는 기술. 헤더 파일의 순환 참조를 막을 때 사용한다.class Monster;
Getter/Setter클래스의 멤버 변수를 안전하게 읽고 쓰는 데 사용하는 함수. 캡슐화를 지키는 좋은 방법이다.getHP(), setHP()
게임 루프게임의 핵심 로직이 계속 반복되는 while문. 게임의 상태를 계속 갱신한다.while(player->getHP() > 0 && ...)
동적 바인딩실행 시점에 실제 객체의 타입에 따라 호출될 함수가 결정되는 것. 다형성의 핵심 원리이다.player->attack()Warrior의 공격, Archer의 공격으로 다르게 동작했다.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글