카테고리 없음

간단한 전직 시스템 구현

dkuen 2026. 3. 13. 21:50

필수 기능

1.기본 클래스(설계도) 정의

- Player 클래스 선언
- 모든 직업의 부모가 되는 기본 클래스 Player를 만듭니다.
- 순수 가상 함수 attack() 포함
- virtual void attack() = 0; 을 선언하여, 이 클래스를 상속받는 자식들은 **무조건** 이 함수를 구현하도록 해야 합니다.
- 이 함수는 단순히 무기를 휘두르는 동작의 '이름'만 정해준 것입니다.


2.직업 클래스 생성 (상속과 재정의)

  • 4가지 직업 클래스 만들기
    • Warrior(전사), Magician(마법사), Thief(도적), Archer(궁수) 클래스를 생성합니다.
    • 각 클래스는 Player를 상속 받습니다.
  • 직업별 공격 기능 구현 (오버라이딩)
    • 각 직업에서 attack() 함수를 다시 정의하세요.
    • 전사: "검을 휘두릅니다!" 출력
    • 마법사: "마법 화살을 쏩니다!" 출력
    • 도적/궁수 등: 각 직업에 어울리는 공격 메시지를 출력하도록 만들어보세요!

3. 메인 로직 구현 (다형성 활용)

  • 부모 타입 포인터 선언
    • Player* player = nullptr; 처럼 부모 클래스의 포인터를 먼저 준비합니다.
  • 직업 선택 메뉴판 만들기
    • 유저에게 번호를 입력받습니다. (1.전사 / 2.마법사 / 3.도적 / 4.궁수)
  • 번호에 맞는 객체 할당 (동적 할당)
    • switch나 if문을 작성해서 선택한 번호에 맞게 직업이 선택되도록 합니다.
    • 예 ) 유저가 1번을 누르면 player = new Warrior();가 실행됩니다.
  • 다형성 공격 실행
    • 할당이 완료된 후 player->attack();을 호출합니다.
    • 확인 : 내가 선택한 직업에 맞는 공격 메시지가 제대로 나오나요?

 


구현

1.기본 클래스(설계도) 정의

- Player 클래스 선언
- 모든 직업의 부모가 되는 기본 클래스 Player를 만듭니다.
- 순수 가상 함수 attack() 포함
- virtual void attack() = 0; 을 선언하여, 이 클래스를 상속받는 자식들은 **무조건** 이 함수를 구현하도록 해야 합니다.
- 이 함수는 단순히 무기를 휘두르는 동작의 '이름'만 정해준 것입니다.

//player.h
#pragma once

class Player {
public:
	virtual void Attack() = 0;
	virtual ~Player() = default;
};

 

기본 골자가 되는 Player 클래스 선언입니다. 아직은 기능이 적지만, 기능이 추가 될 때마다 복잡하게 관리할 필요 없이

Player 클래스를 수정하고, 나머지 직업 클래스를 수정하면 됩니다.


2.직업 클래스 생성 (상속과 재정의)

  • 4가지 직업 클래스 만들기
    • Warrior(전사), Magician(마법사), Thief(도적), Archer(궁수) 클래스를 생성합니다.
    • 각 클래스는 Player를 상속 받습니다.
  • 직업별 공격 기능 구현 (오버라이딩)
    • 각 직업에서 attack() 함수를 다시 정의하세요.
    • 전사: "검을 휘두릅니다!" 출력
    • 마법사: "마법 화살을 쏩니다!" 출력
    • 도적/궁수 등: 각 직업에 어울리는 공격 메시지를 출력하도록 만들어보세요!

아래는 Player.h를 상속받는 클래스입니다.

//Warrior.h
#pragma once
#include "Player.h"

class Warrior : public Player {
public:
	Warrior();
	virtual void Attack();
};
//Magician.h
#pragma once
#include<iostream>
#include "Player.h"

class Magician : public Player {
public:
	Magician();
	virtual void Attack();
};
//Thief.h
#pragma once
#include "Player.h"

class Thief : public Player {
public:
	Thief();
	virtual void Attack();
};
//Archer.h
#pragma once
#include "Player.h"

class Archer : public Player {
public:
	Archer();
	virtual void Attack();
};

 

아래는 구현체 입니다. 예시로 Player 클래스와 Warrior만 첨부했습니다.

//Player.h
#include "Player.h"
//Warrior.cpp
#include<iostream>
#include "Warrior.h"

Warrior::Warrior() : Player() {
	std::cout << "전사로 전직했습니다." << std::endl;
}

void Warrior::Attack() {
	std::cout << "검을 휘두릅니다!!" << std::endl;
}

 

Player.cpp가 비어있는 것은 순수 가상 함수만 가지고 있기 때문입니다.


3. 메인 로직 구현 (다형성 활용)

  • 부모 타입 포인터 선언
    • Player* player = nullptr; 처럼 부모 클래스의 포인터를 먼저 준비합니다.
  • 직업 선택 메뉴판 만들기
    • 유저에게 번호를 입력받습니다. (1.전사 / 2.마법사 / 3.도적 / 4.궁수)
  • 번호에 맞는 객체 할당 (동적 할당)
    • switch나 if문을 작성해서 선택한 번호에 맞게 직업이 선택되도록 합니다.
    • 예 ) 유저가 1번을 누르면 player = new Warrior();가 실행됩니다.
  • 다형성 공격 실행
    • 할당이 완료된 후 player->attack();을 호출합니다.
    • 확인 : 내가 선택한 직업에 맞는 공격 메시지가 제대로 나오나요?

마지막으로 메인 함수입니다.

//Main.cpp
#include <iostream>

#include "Player.h"
#include "Warrior.h"
#include "Magician.h"
#include "Thief.h"
#include "Archer.h"

int main()
{
	Player* player = nullptr;
	int selectClass = 0;
	while (1) {
		std::cout << "번호를 입력하시오.\n" << "1. 전사 / 2. 마법사 / 3. 도적 / 4. 궁수" << std::endl;
		std::cin >> selectClass;
		if (selectClass > 0 && selectClass < 5) {
			break;
		}
		std::cout << "유효하지 않은 입력입니다." << std::endl;
	}

	switch (selectClass)
	{
	case 1:
		player = new Warrior();
		break;
	case 2:
		player = new Magician();
		break;
	case 3:
		player = new Thief();
		break;
	case 4:
		player = new Archer();
		break;
	default:
		break;
	}

	delete player;
}

 

 

Monster 클래스를 작성해주세요!

  • 기본 속성 및 생성자 구현
    • 이름(name), HP(10), 공격력(30), 방어력(10), 스피드(10)를 속성으로 가집니다.
    • 생성자에서 이름을 입력받고, 모든 능력치를 기본값으로 초기화하세요.
  • Monster의 공격 함수 (attack)
    • 매개변수로 Player*를 전달받아 플레이어를 공격합니다.
    • 데미지 공식: 몬스터 공격력 - 플레이어 방어력 (결과가 0 이하이면 1로 고정)
    • 플레이어의 setHP를 호출하여 체력을 갱신하고, 남은 HP를 화면에 보여주세요.
  • Getter / Setter 구현
    • 외부에서 몬스터의 이름, HP 등에 접근하고 수정할 수 있도록 모든 Get/Set 함수를 만드세요.

Monster 클래스 요구사항

class Monster {
public:
		// Monster 생성자
		// - 몬스터의 이름을 매개변수로 입력 받습니다.
		// - 모든 몬스터는 HP 10, 공격력 30, 방어력 10, 스피드 10의 능력치를 가집니다.
    Monster(string name);
    ~Monster();
    
    // 몬스터의 공격 함수
    // - 플레이어 객체 포인터를 매개변수로 입력 받습니다.
    // - 몬스터의 공격력-플레이어의 방어력을 데미지로 정의합니다.
    // - 만약 위에서 계산한 데미지가 0 이하라면, 데미지를 1로 정의합니다.
    // - 플레이어에게 얼마나 데미지를 입혔는지 출력합니다.
    // - 플레이어 객체의 getHP 함수를 실행하여 플레이어HP-데미지 계산 결과를
    // - 플레이어 객체의 setHP 매개변수로 전달합니다.
    // - 플레이어가 생존했을 경우, 플레이어의 남은 HP를 출력합니다.
    void attack(Player* player);
    
    // 몬스터의 속성값을 리턴하는 get 함수
    string getName();
    int getHP();
    int getPower();
    int getDefence();
    int getSpeed();
		
		// 몬스터의 속성값을 정의하는 set 함수
    void setName(string name);
    void setHP(int HP);
    void setPower(int power);
    void setDefence(int defence);
    void setSpeed(int speed);

protected:
    string name; // 몬스터의 이름
    int HP; // 몬스터의 HP
    int power; // 몬스터의 공격력
    int defence; // 몬스터의 방어력
    int speed; // 몬스터의 스피드
};

 

해당 클래스를 기본으로 Get에 const만 추가해서 구현합니다

 

//Monster.cpp
#include "Monster.h"
#include<iostream>

Monster::Monster(std::string name) :
	name(name), HP(10), power(10), defence(10), speed(10){
}
Monster::~Monster() {
	std::cout << name << " 소멸합니다..." << std::endl;
}

void Monster::attack(Player* player) {

}

//getter
std::string Monster::getName() const{
	return name;
}
int Monster::getHP() const {
	return HP;
}
int Monster::getPower() const {
	return power;
}
int Monster::getDefence() const {
	return defence;
}
int Monster::getSpeed() const {
	return speed;
}

//setter
void Monster::setName(std::string name) {
	this->name = name;
}
void Monster::setHP(int HP) {
	this->HP = HP;
}
void Monster::setPower(int power) {
	this->power = power;
}
void Monster::setDefence(int defence) {
	this->defence = defence;
}
void Monster::setSpeed(int speed) {
	this->speed = speed;
}

 

몬스터 공격 로직은 Player 클래스를 전부 고친 후 진행하겠습니다.

 

아래는 수정된 Player 클래스 입니다.

더보기

수정된 Player.h , Player.cpp

//player.h

#pragma once
#include<string>

class Monster;

class Player {
public:
	Player(std::string name);
    virtual ~Player() = default;
    virtual void Attack(Monster* monster) = 0;

    //getter
    std::string getName() const;
    int getHP() const;
    int getPower() const;
    int getDefence() const;
    int getSpeed() const;

    //setter
    void setName(std::string name);
    void setHP(int HP);
    void setPower(int power);
    void setDefence(int defence);
    void setSpeed(int speed);

protected:
	std::string name;
	int HP;
	int power;
	int defence;
	int speed;
};
//수정된 player.cpp
#include "Player.h"
#include <iostream>

Player::Player(std::string name) :
	name(name), HP(10), power(10), defence(10), speed(10) {
}

//getter
std::string Player::getName() const {
	return name;
}
int Player::getHP() const {
	return HP;
}
int Player::getPower() const {
	return power;
}
int Player::getDefence() const {
	return defence;
}
int Player::getSpeed() const {
	return speed;
}

//setter
void Player::setName(std::string name) {
	this->name = name;
}
void Player::setHP(int HP) {
	this->HP = HP;
}
void Player::setPower(int power) {
	this->power = power;
}
void Player::setDefence(int defence) {
	this->defence = defence;
}
void Player::setSpeed(int speed) {
	this->speed = speed;
}

몬스터 공격 로직

void Monster::attack(Player* player) {
	int damage = this->power - player->getDefence();

	if (damage <= 0) {
		damage = 1;
	}

	std::cout << "플레이어는 " << damage << "만큼 피해를 입었다!!" << std::endl;

	player->setHP(player->getHP() - damage);

	if (player->getHP() > 0) {
		std::cout << "플레이어의 남은 체력!!" << player->getHP() << std::endl;
	}
}

이어서 각 클래스 별로 클래스와 로직을 구현합니다.

Warrior.h / Warrior.cpp

전사의 컨셉은 superPower입니다. 일종의 버프같은 느낌이고, superPower 수치만큼 power가 높아집니다.

더보기
//Warrior.h
#pragma once
#include<string>
#include "Player.h"

class Warrior : public Player {
public:
    Warrior(std::string name);
    ~Warrior() override;
    void Attack(Monster* monster);

    int getsuperPower() const;

    void setsuperPower(int superPower);
protected:
    int superPower;
};
//Warrior.cpp
#include<iostream>
#include "Warrior.h"
#include "Monster.h"

Warrior::Warrior(std::string name)
	: Player(name), superPower(3) {
	HP = 20;
	power = 15;
	defence = 15;
	speed = 8;
	std::cout << "전사로 전직했습니다." << std::endl;
}

Warrior::~Warrior() {
	std::cout << "캐릭터가 소멸합니다...." << std::endl;
}

void Warrior::Attack(Monster* monster) {
	int damage;
	int super;

	std::cout << "검을 휘두릅니다!!" << std::endl;

	if (superPower > 0) {
		super = superPower;
		--superPower;
	}

	damage = power*super - monster->getDefence();

	if (damage <= 0) damage = 1;

	std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;

	monster->setHP(monster->getHP() - damage);

	if (monster->getHP() > 0) {
		std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
	}
}

int Warrior::getsuperPower() const {
	return superPower;
}

void Warrior::setsuperPower(int superPower) {
	this->superPower = superPower;
}

 

Magician.h / Magician.cpp

마법사의 컨셉은 cost입니다. cost가 있는 동안 공격을 cost만큼 진행합니다.

더보기
//Magician.h
#pragma once
#include<string>
#include "Player.h"

class Magician : public Player {
public:
    Magician(std::string name);
    ~Magician() override;
    void Attack(Monster* monster);

    int getCost() const;

    void setCost(int cost);
protected:
    int cost;
};
//Magician.cpp
#include<iostream>
#include "Magician.h"
#include "Monster.h"

Magician::Magician(std::string name)
	: Player(name), cost(5) {
	HP = 12;
	power = 12;
	speed = 12;
	std::cout << "마법사로 전직하였습니다." << std::endl;
}

Magician::~Magician() {
	std::cout << "캐릭터가 소멸합니다...." << std::endl;
}

void Magician::Attack(Monster* monster) {
	std::cout << "마법 미사일로 공격합니다." << std::endl;
	int damage = this->power - monster->getDefence();

	if (damage <= 0) damage = 1;

	for (int i = cost; i > 0; i--) {
		std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;
		monster->setHP(monster->getHP() - damage);
		if (monster->getHP() > 0) {
			std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
		}
	}
	--cost;
}

int Magician::getCost() const {
	return cost;
}

void Magician::setCost(int cost) {
	this->cost = cost;
}

Thief.h / Thief.cpp

도적의 컨셉은 스택입니다. accumulate를 사용해서 공격을 할 때마다 10중첩씩 획득하고 30중첩이 되면 30데미지를 넣습니다.

더보기
//Thief.h
#pragma once
#include<string>
#include "Player.h"

class Thief : public Player {
public:
    Thief(std::string name);
    ~Thief() override;
    void Attack(Monster* monster);

    int getAccumulate() const;

    void setAccumulate(int accumulate);
protected:
    int accumulate;
};
//Thief.cpp
#include<iostream>
#include "Thief.h"
#include "Monster.h"

Thief::Thief(std::string name)
	: Player(name), accumulate(0) {
	HP = 10;
	power = 15;
	defence = 5;
	speed = 15;
	std::cout << "도적으로 전직하였습니다." << std::endl;
}

Thief::~Thief() {
	std::cout << "캐릭터가 소멸합니다...." << std::endl;
}

void Thief::Attack(Monster* monster) {
	std::cout << "단검으로 공격합니다." << std::endl;
	int damage = this->power - monster->getDefence();

	if (damage <= 0) damage = 1;
	if (accumulate >= 30) {
		damage = accumulate;
	}
	else {
		accumulate += 10;
	}

	std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;
	monster->setHP(monster->getHP() - damage);

	if (monster->getHP() > 0) {
		std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
	}
}

int Thief::getAccumulate() const {
	return accumulate;
}

void Thief::setAccumulate(int accumulate) {
	this->accumulate = accumulate;
}

Archer.h / Archer.cpp

궁수의 컨셉은 크리티컬 확률입니다. critical을 통해서 높은 데미지를 입힙니다.

더보기
//Archer.h
#pragma once
#include<string>
#include "Player.h"

class Archer : public Player {
public:
    Archer(std::string name);
    ~Archer() override;
    void Attack(Monster* monster);

    int getCritical() const;

    void setCritical(int critical);
protected:
    int critical;
};
//Archer.cpp
#include<iostream>
#include<cstdlib>
#include<ctime>
#include "Archer.h"
#include "Monster.h"

Archer::Archer(std::string name) 
	: Player(name), critical(50) {
	HP = 15;
	power = 12;
	speed = 20;
	std::cout << "궁수로 전직하였습니다." << std::endl;
}

Archer::~Archer() {
	std::cout << "캐릭터가 소멸합니다...." << std::endl;
}

void Archer::Attack(Monster* monster) {
	std::cout << "화살로 공격합니다." << std::endl;
	int damage = this->power - monster->getDefence();
	
	if (damage <= 0) damage = 1;
	
	if ((rand()%100) +1 <= critical) {
		std::cout << "크리티컬!!!" << std::endl;
		damage *= 2;
	}

	std::cout << "몬스터는 " << damage << "만큼 피해를 입었다!!" << std::endl;

	monster->setHP(monster->getHP() - damage);

	if (monster->getHP() > 0) {
		std::cout << "몬스터의 남은 체력!!" << monster->getHP() << std::endl;
	}
}

int Archer::getCritical() const {
	return critical;
}

void Archer::setCritical(int critical) {
	this->critical = critical;
}

 

궁수의 크리티컬 확률은 랜덤 시드를 통한 rand()함수를 통해 만들었습니다.


Main.cpp

#include <iostream>

#include "Player.h"
#include "Warrior.h"
#include "Magician.h"
#include "Thief.h"
#include "Archer.h"
#include "Monster.h"

bool playerFirstAttack(Player* player, Monster* goblin);
bool monsterFirstAttack(Player* player, Monster* goblin);

int main()
{
	srand(time(nullptr));
	Player* player = nullptr;
	std::string playerName;
	int selectClass = 0;
	while (1) {
		std::cout << "닉네임을 설정하세요 : ";
		std::cin >> playerName;
		std::cout << "번호를 입력하시오.\n" << "1. 전사 / 2. 마법사 / 3. 도적 / 4. 궁수" << std::endl;
		std::cin >> selectClass;
		if (selectClass > 0 && selectClass < 5) {
			break;
		}
		std::cout << "유효하지 않은 입력입니다." << std::endl;
	}

	switch (selectClass)
	{
	case 1:
		player = new Warrior(playerName);
		break;
	case 2:
		player = new Magician(playerName);
		break;
	case 3:
		player = new Thief(playerName);
		break;
	case 4:
		player = new Archer(playerName);
		break;
	default:
		break;
	}

	Monster* goblin = new Monster("Goblin");
	std::cout << "전투 발생!!!!!!!!!!" << std::endl;
	while (true)
	{
		std::cout << "--------------------------" << std::endl;
		if (player->getSpeed() > goblin->getSpeed()) {
			if (playerFirstAttack(player, goblin)) {
				delete goblin;
				delete player;
				break;
			}
		}
		else if (player->getSpeed() == goblin->getSpeed()) {
			if (rand() % 2 == 1) {
				if (playerFirstAttack(player, goblin)) {
					delete goblin;
					delete player;
					break;
				}
			}
			else {
				if (monsterFirstAttack(player, goblin)) {
					delete player;
					delete goblin;
					break;
				}
			}
		}
		else if (monsterFirstAttack(player, goblin)) {
			delete player;
			delete goblin;
			break;
		}
	}
}

bool playerFirstAttack(Player* player, Monster* goblin) {
	std::cout << player->getName() << "의 선공!!" << std::endl;
	player->Attack(goblin);
	if (goblin->getHP() <= 0) {
		std::cout << player->getName() << "의 승리!!!" << std::endl;
		return 1;
	}
	goblin->attack(player);
	if (player->getHP() <= 0) {
		std::cout << goblin->getName() << "의 승리....." << std::endl;
		return 1;
	}
	return 0;
}

bool monsterFirstAttack(Player* player, Monster* goblin) {
	std::cout << goblin->getName() << "의 선공!!" << std::endl;
	goblin->attack(player);
	if (player->getHP() <= 0) {
		std::cout << goblin->getName() << "의 승리....." << std::endl;
		return 1;
	}
	player->Attack(goblin);
	if (goblin->getHP() <= 0) {
		std::cout << player->getName() << "의 승리!!!" << std::endl;
		return 1;
	}
	return 0;
}

 

간단한 로직입니다.