반응형

 

 

 

 

 

 

 

제작 기간

- 총 9일

 

 

 

게임 클래스 구성

 

 

 

GameManager

- 클래스 멤버는 Player, Monster, DataManager를 가지고 있다.

- Main 함수에서 GameManager의 객체를 생성한 뒤, 게임을 시작하게된다.

- 각 화면은 정적 클래스(Draw)에 화면을 그리는 함수를 정의해서 GameManager에서 호출하여 그린다.

 

 

 

DataManager

- Weapon, Player, Monster의 데이터 문자열, Monster 배열, Weapon 배열 벡터를 멤버로 가지고있다.

- 기본 데이터들(Weapon, Player, Monster)의 데이터를 가지고 있다.

- 저장하기, 불러오기 기능으로 데이터들을 파일로 입출력하는 기능을 가지고 있다.

 

 

 

Unit

- 추상 클래스

- Player와 Monster에 상속한다.

- 저장이 될 데이터 값들을 멤버로 두고 있다. (이름, 체력, 레벨, 경험치 등)

- 데미지를 주고받는 기능, 정보 보기 기능 등을 추상 함수로 가지고 있다.

 

 

 

Player

- Unit을 상속 받는다. (Unit의 추상 함수를 재정의해서 사용)

- Weapon을 포인터 멤버로 가지고 있다.

- 레벨 업, 스킬 기능을 가지고있다.

 

 

 

Monster

- Unit을 상속 받는다. (Unit의 추상 함수를 재정의해서 사용)

 

 

 

Weapon

- 추상 클래스

- 각 무기 클래스에 상속한다.

- Player와 DataManager에서 업캐스팅해서 사용한다.

- 무기 이름, 무기 데미지, 무기 값을 멤버로 가지고있다.

- 무기의 스킬, 무기 종류 확인 등을 추상 함수로 가지고 있다.

 

 

 

각 무기 클래스

- Weapon을 상속 받는다. (Weapon의 추상 함수를 재정의해서 사용)

 

 

 

Draw

- 게임 화면을 그리기 위한 함수를 정의해놓았다.

- 콘솔 좌표 이동, 게임 문자열 중앙 정렬로 그리는 등의 함수가 정의되어 있다.

 

 

 

저장되어있는 데이터 구조

- 기본 유저, 몬스터의 데이터, 유저의 플레이 데이터, 무기 데이터

 

 

 

 

 

반응형

'Game > Personal development' 카테고리의 다른 글

[WIN API] 서커스 찰리 게임  (0) 2022.02.05
[WIN API] 카드 맞추기 게임  (0) 2022.01.30
산성비 게임 제작 (C++)  (2) 2022.01.23
뱀 게임 제작 (C++)  (0) 2022.01.22
오목 게임 제작 (C++)  (0) 2022.01.21
반응형

 

 

 

 

 

 

 

제작 기간

- 총 2일

 

 

 

게임 클래스 구성

 

 

 

 

GameManager

- 클래스 멤버는 Snake를 가지고 있고, 나머지는 점수, 장애물 좌표 배열, 음식 좌표 배열을 멤버로 가지고 있다.

- 장애물, 음식의 좌표 배열은 게임을 할 때마다 새로 만들지 않고 풀링을 하여 객체를 재사용했다.

- 각 화면은 정적 클래스(Draw)를 정의하여 GameManager에서 호출하여 그린다.

 

 

 

Snake

- Snake의 몸체 좌표(List), 꼬리 좌표, 방향 정보, 이동 속도(시간)를 멤버로 가지고있다.

- Snake에서 키 입력을 받아서 방향을 바꾼다.

- 음식을 먹어 몸체가 길어질때마다 이동 기준 시간을 줄여 Snake의 이동 속도를 높인다.

- 방향 정보(enum)을 정의해서 반대방향으로 이동하지 못하게 예외처리를 한다.

 

 

 

Draw

- 게임 화면을 그리기 위한 함수를 정의해 놓았다.

- 콘솔 좌표 이동, 벽과 장애물, Snake등을 그리는 함수가 정의되어 있다.

 

 

 

 

 

반응형

'Game > Personal development' 카테고리의 다른 글

[WIN API] 서커스 찰리 게임  (0) 2022.02.05
[WIN API] 카드 맞추기 게임  (0) 2022.01.30
산성비 게임 제작 (C++)  (2) 2022.01.23
RPG 게임 제작 (C++)  (0) 2022.01.22
오목 게임 제작 (C++)  (0) 2022.01.21
반응형

 

 

 

 

 

 

제작 기간

- 오목 : 2일

- 데이터 저장, 파일 입출력 적용에 따른 기존 코드 수정 : 2일

- 총 4일

 

 

 

 

게임 클래스 구성

 

 

 

 

GameManager

- 클래스 멤버는 Player를 가지고있고, 나머지는 바둑판의 가로와 세로, 되돌리기 수, 턴 수, 돌과 커서 종류, 플레이모드 멤버 변수를 가지고 있다.

- Main함수에서 GameManager의 객체를 생성한 뒤, 게임을 시작하게된다.

- 각 화면은 정적 클래스(MapDraw)에 화면을 그리는 함수를 구성해서 GameManager에서 호출하여 그린다.

- 게임이 중간에 게임을 그만두거나, 한판 이상 마무리된 게임에 대해 정보를 txt파일로 저장하고 불러올 수 있다.

 

 

 

Player

- 구조체 멤버 Stone을 가지고 있고, 나머지는 커서 위치, 무르기 수, 돌을 둔 기록(리스트), 리플레이 기록(리스트)를 멤버로 가지고 있다.

- Player에서 키 입력을 받아서 커서를 움직인다.

- 각 리스트 멤버는 게임 중간에 그만두거나, 한판 이상 마무리 되었을때 데이터를 저장하기 위한 멤버이다.

 

 

 

Stone

- 돌이 두어진 좌표를 멤버로 가지고 있다.

 

 

 

MapDraw

- 화면에 게임을 그리기 위한 함수들을 정의해 놓았다.

- 콘솔 좌표를 이동시키기, 바둑판 그리기, 바둑알 그리기, 커서 그리기 등의 기등이 정의되어있다.

 

 

 

Replayer

- 플레이어 이름, 돌 모양 정보, 돌을 둔 좌표 구조체(Stone)리스트를 멤버로 가지고 있다.

- GameManager에 멤버로 있지 않고, 게임 내에서 다시보기 기능을 실행할 때 지역변수로 선언된다.

 

 

 

저장되어있는 데이터 구조

- 플레이중 데이터와 게임이 끝난 데이터 2가지를 저장한다.

 

 

 

 

 

반응형

'Game > Personal development' 카테고리의 다른 글

[WIN API] 서커스 찰리 게임  (0) 2022.02.05
[WIN API] 카드 맞추기 게임  (0) 2022.01.30
산성비 게임 제작 (C++)  (2) 2022.01.23
RPG 게임 제작 (C++)  (0) 2022.01.22
뱀 게임 제작 (C++)  (0) 2022.01.22
반응형

 

※ 연산자 오버로딩 기본 개념 참고

 

C++ 연산자 오버로딩

※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것. C++ friend friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다. ※ 그렇기 때문에, 클래스의 friend 선언은 객체

srdeveloper.tistory.com


 

 

대입 연산자

- 정의하지 않으면 디폴트 대입 연산자가 삽입된다.

- 복사 생성자와 마찬가지로 멤버 대 멤버의 복사가 일어난다.

- 디폴트 대입 연산자는 얕은 복사이기 때문에 깊은 복사가 필요하다면 직접 정의해야한다.

#include <iostream>
using namespace std;

class SomeClass
{
private:
    int n1;
    
public:
    SomeClass(int n1 = 0) : n1(n1) { }
    void ShowData()
    {
        cout << n1 << endl;
    }
    SomeClass& operator=(const SomeClass& ref)
    {
        cout << "SomeClass operator=()" << endl;
        this->n1 = ref.n1;
        return *this;
    }
};

void main()
{
    SomeClass sc1(10);
    SomeClass sc2;
    sc2 = sc1;
    sc2.ShowData();
    
    //결과
    //SomeClass operator=()
    //10
}
  • sc2 선언시 바로 대입 연산자를 사용하면 대입 연산자가 호출 되는것이 아닌 복사 생성자가 호출이 된다.

 

 

 

디폴트 대입 연산자의 문제점

- 복사 생성자에서 생기는 문제와 유사하다. (얕은 복사의 문제)

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;

class Name
{
private:
    char* name;
    
public:
    Name(const char* name) 
    {
        int len = strlen(name) + 1;
        this->name = new char[len];
        strcpy(this->name, name);
    }
    void ShowData()
    {
        cout << name << endl;
    }
    Name& operator=(const Name& ref)
    {
        delete[] name;
        int len = strlen(ref.name) + 1;
        this->name = new char[len];
        strcpy(this->name, ref.name);
        return *this;
    }
    ~Name()
    {
        delete[] name;
    }
};

void main()
{
    Name n1("Lee");
    Name n2("Yoon");
    n2 = n1;
    n2.ShowData();
    
    //결과 : Lee
}
  • 대입 연산자를 통한 얕은 복사에서 일어나는 메모리 누수를 막기위해 name 멤버 변수에 할당된 기존 데이터를 할당 해제하고 새롭게 할당하여 깊은 복사를 진행한다.

 

 

 

상속 구조에서의 대입 연산자

- 자식 클래스에서 대입 연산자를 오버로딩 하는 경우, 부모 클래스의 대입 연산자를 명시해 주지 않으면 부모 클래스의 대입 연산자는 호출이 되지 않는다.

- 따라서, 디폴트 대입 연산자의 경우 부모 클래스의 대입 연산자까지 호출되는 것을 확인 할 수 있다.

#include <iostream>
using namespace std;

class Parent
{
private:
    int n1, n2;
    
public:
    Parent(int n1 = 0, int n2 = 0) : n1(n1), n2(n2) { }
    void ShowData()
    {
        cout << n1 << " / " << n2 << endl;
    }
    Parent& operator=(const Parent& ref)
    {
        cout << "Parent operator=()" << endl;
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

class Child : public Parent
{
private:
    int n1, n2;
    
public:
    Child(int n1, int n2, int n3, int n4) : Parent(n1, n2), n1(n3), n2(n4) { }
    void ShowData()
    {
        Parent::ShowData();
        cout << n1 << " / " << n2 << endl;
    }
    Child& operator=(const Child& ref)
    {
        cout << "Child operator=()" << endl;
        //부모 클래스 대입 연산자 호출
        Parent::operator=(ref);
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

void main()
{
    Child c1(1, 2, 3, 4);
    Child c2(0, 0, 0, 0);
    c2 = c1;
    c2.ShowData();
    
    //결과
    //Child operator=()
    //Parent operator=()
    //1 / 2
    //3 / 4
}
  • Child 클래스에서 대입 연산자 오버로딩에 Parent::operator(ref)를 주석처리하고 실행해보면 부모 클래스의 멤버는 대입 연산자가 작용하지 않은것을 확인할 수 있다.

 

 

 

인덱스 연산자

- C, C++의 배열은 '경계검사를 하지 않는다'는 단점을 가지고 있다.

- 즉 배열의 범위에 벗어난 인덱스 값을 넣어도 실행하는데 무리없이 진행이 되기 때문에, 안정성을 고려해서 배열 클래스를 만든다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void main()
{
    IntArray ia(3);
    for(int i = 0; i <= 3; i++)
    {
        ia[i] = i + 1;
    }
    
    //결과 : Arr index out of range
}
  • 결과와 같이 배열에 대한 잘못된 접근을 차단함으로써 안정성을 보장받을 수 있다.

 

 

 

※ 배열 클래스의 복사에 대해서..

- 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 저장소의 복사는 잘못된 일로 간주된다.

- 따라서, 복사 생성자와 대입 연산자는 private 멤버로 지정하여 복사를 원칙적으로 막는것이 좋은 선택이 되기도 한다.

//IntArray 클래스의 private에 복사 생성자와 대입 연산자를 멤버로 둔다.
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }

 

 

 

 

const를 통한 배열 안정성 보장하기

- 어떠한 함수를 정의할 때, 매개변수를 const로 지정하면 해당 함수 내에서는 값의 변경이 불가능하므로 안정성을 보장받을 수 있지만, 위와 같은 코드에서는 인덱스 연산자에서 const 선언이 되어있지 않기 때문에 const로 지정된 매개변수로 받으면 컴파일 에러가 난다.

//전역함수
void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;	//컴파일 에러 발생
    }
}

 

- 해당 컴파일 에러를 해결하려면 const함수를 오버로딩하면 해결이 된다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;
    
    IntArray(const IntArray& ref) { }
    IntArray& operator=(const IntArray& ref) { }

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    //배열 안에 값을 넣을 때 호출됨.
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    //const 매개변수로 선언된 배열 안의 값을 찾을 때 호출된
    int operator[](int index) const
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;
    }
}

void main()
{
    IntArray ia(3);
    for(int i = 0; i < 3; i++)
    {
        ia[i] = i + 1;
    }
    ShowData(ia);
    
    //결과
    //1
    //2
    //3
}
  • 기존의 인덱스 연산자에서 const를 붙이는게 아닌 오버로딩을 한 이유는, 배열 멤버에 대한 저장이 불가능해지기 때문이다.
  • 따라서 const 선언을 이용해 인덱스 연산자를 오버로딩하여 해결한다.

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

namespace

- 특정 영역에 이름을 붙여주기 위한 문법적 요소

- namespace가 다르면 같은 이름의 선언이 허용된다.

- 여러 외부 라이브러리 사용 중 발생하는 문제를 해결하기 위해 만들어졌다.

- namespace는 다른 namespace 안에 삽입될 수 있다.

- namespace의 별칭 지정이 가능하다.

 

namespace 예제

#include <iostream>

namespace A
{
    void Func()
    {
        std::cout << "Namespace A : Func()" << std::endl;
    }
}

namespace B
{
    void Func()
    {
        std::cout << "Namespace B : Func()" << std::endl;
    }
}

void main()
{
    A::Func();
    B::Func();
    
    //결과
    //Namespace A : Func()
    //Namespace B : Func()
}
  • 위의 예제에서 ::을 범위지정 연산자라고 한다.

 

namespace 중첩 예제

#include <iostream>

namespace A
{
    int num = 0;
    
    namespace B
    {
        int num = 1;
    }
    
    namespace C
    {
        int num = 2;
    }
}

void main()
{
    std::cout << A::num << std::endl;
    std::cout << A::B::num << std::endl;
    std::cout << A::C::num << std::endl;
    
    //결과
    //0
    //1
    //2
}

 

 

namespace 별칭 지정 예제

#include <iostream>

namespace A
{
    namespace B
    {
        int num = 1;
    }
}

void main()
{
    namespace AB = A::B;
    std::cout << AB::num << std::endl;
    
    //결과
    //1
}

 

 

 

 

using

- namespace에 선언된 모든것에 대해 namespace 지정의 생략을 명령한다.

- 편리함은 증가하지만 무분별하게 사용하면 충돌이 발생할 확률이 상대적으로 높아진다.

 

using 예제

#include <iostream>
using namespace A;
using namespace C;

namespace A
{
    int num = 0;
    
    namespace B
    {
        int num = 1;
    }
	
    namespace C
    {
        int num = 2;
    }
}

void main()
{
    std::cout << num << std::endl;		//모호함으로 인해 에러 발생
    std::cout << B::num << std::endl;	//namespace A 생략 가능
    std::cout << C::num << std::endl;	//namespace A 생략 가능
}

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 연산자 오버로딩 (대입, 인덱스 연산자)  (0) 2021.12.26
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17

+ Recent posts