(C++) 스마트팩토리 -26

내 이름 안찬찬·2023년 2월 27일
0
post-thumbnail

이번에 작성할 코드는 SFML을 이용하여 구글 인터넷이 끊겼을 때 나오는 공룡게임을 만들 것이다!

SFML 설명과 설치 방법
https://kali-live.tistory.com/33

blockdmask 님의 코드를 초안으로 두고 기능을 추가하여 프로그래밍 했습니다.
https://blockdmask.tistory.com/371


공룡게임

// SFML 라이브러리를 이용
// 시스템, 윈도우, 그래픽, 오디오 및 네트워크의 5개 모듈로 구성
#include <SFML/Graphics.hpp>
#include <iostream>
using namespace sf;

// 상수 지정 키워드를 사용하여 게임 창의 너비와 높이를 정의
const int width = 1000;
const int height = 300;

// 구조체, Position 정의
struct Position {
  int x, y;
};

// 충돌 판정
// Position과 Texture 객체를 참조자로 선언, 함수 호출 시 해당 객체를 직접 참조
bool isCollision(const Position& DinoPos, const Position& TreePos,
                 const Texture& DinoTexture, const Texture& TreeTexture) {
  // Dino와 Tree의 너비와 높이를 텍스처의 크기 구함.
  // DinoWidth, DinoHeight, TreeWidth,TreeHeight 변수에 저장
  const int DinoWidth = DinoTexture.getSize().x;
  const int DinoHeight = DinoTexture.getSize().y;
  const int TreeWidth = TreeTexture.getSize().x;
  const int TreeHeight = TreeTexture.getSize().y;

  // Dino와 Tree의 왼쪽, 오른쪽, 위, 아래 좌표
  // DinoLeft는 Dino의 왼쪽 좌표(DinoPos.x)
  // DinoRight는 Dino의 오른쪽 좌표(DinoPos.x + DinoWidth)
  // DinoTop은 Dino의 위쪽 좌표(DinoPos.y)
  // DinoBottom은 Dino의 아래쪽 좌표(DinoPos.y + DinoHeight)
  const int DinoLeft = DinoPos.x;
  const int DinoRight = DinoPos.x + DinoWidth;
  const int DinoTop = DinoPos.y;
  const int DinoBottom = DinoPos.y + DinoHeight;

  // 위와 같은 방식으로 변수에 저장
  const int TreeLeft = TreePos.x;
  const int TreeRight = TreePos.x + TreeWidth;
  const int TreeTop = TreePos.y;
  const int TreeBottom = TreePos.y + TreeHeight;

  // DinoRight가 TreeLeft보다 크고, DinoLeft가 TreeRight보다 작으며,
  // DinoBottom이 TreeTop보다 크고, DinoTop이 TreeBottom보다 작은 경우
  // true반환
  return (DinoRight > TreeLeft) && (DinoLeft < TreeRight) &&
         (DinoBottom > TreeTop) && (DinoTop < TreeBottom);
}

int main() {
  int score = 0;
  // SFML 윈도우을 열고 title을 설정
  RenderWindow window(VideoMode(width, height), "DinoSaur Game!!");

  // 프레임 설정 - 프레임 제한 속도 == 초당 60프레임
  window.setFramerateLimit(60);

  // 캐릭터 또는 배경 이미지를 읽어서 화면에 출력하는 Texture클래스
  Texture t1, t2, t3, t4, t5;                 // 이미지 로드
  t1.loadFromFile("Dino_images/dino1.png");   // 공
  t2.loadFromFile("Dino_images/dino2.png");   // 룡
  t3.loadFromFile("Dino_images/tree.png");    // 나무
  t4.loadFromFile("Dino_images/cloud1.png");  // 구름1
  t5.loadFromFile("Dino_images/cloud2.png");  // 구름2

  // Sprite 생성
  // Sprite - 이미지를 window에 그릴 수 있는 개체
  Sprite DinoArr[2], Tree(t3), Cloud1(t4), Cloud2(t5);
  DinoArr[0] = Sprite(t1);
  DinoArr[1] = Sprite(t2);

  // 공룡 바닥 설정
  // static - 정적 변수를 선언하기 위한 키워드 (프로그램 실행 시 메모리 공간 할당, 종료되기 전까지 유지) 
  // const - 상수 선언을 위한 키워드 (해당 변수는 값 변경 불가능)
  static const int DINO_BOTTOM = height - t1.getSize().y;
  Position DinoPos;
  DinoPos.x = 30;
  DinoPos.y = DINO_BOTTOM;

  // 나무 바닥 설정
  static const int TREE_BOTTOM = height - t3.getSize().y;
  Position TreePos;
  TreePos.x = width - 20;
  TreePos.y = TREE_BOTTOM;

  // 구름 위치 설정
  // cloud1과 cloud2가 이어지도록 위치 설정
  static const int CLOUD_TOP = height - t4.getSize().y * 3;
  Position CloudPos1;
  CloudPos1.x = 0;
  CloudPos1.y = CLOUD_TOP;

  Position CloudPos2;
  CloudPos2.x = t4.getSize().x;
  CloudPos2.y = CLOUD_TOP;

  // 캐릭터 애니메이션을 위한 변수
  int footShapeIndex = 0;  // 캐릭터 발 모양을 결정
  float currentFrame = 0.0f;    // 현재 프레임 수 저장
  float frameDelay = 0.4f;  // 한 프레임당 대기 시간
             // 실수형으로 선언되어 있지 않으면 소수점 이하의 값이 버려지게 됨
  const int totalFrames = 5;  // 발 모양을 변경할 총 프레임 수

  // 공룡 점프
  const int JumpPower = 7;  // 점프력
  bool isJumping = false;   // 점프중
  bool isBottom = true;     // 걷는중

  // 나무 속도
  const int TreeSpeed = 9;

  // 구름 속도
  const int CloudSpeed = 3;

  // SFML 메인 루프 - 윈도우가 닫힐때 까지 반복
  while (window.isOpen()) {
    Event e;
    while (window.pollEvent(e)) {
      if (e.type == Event::Closed) {
        window.close();
      }
    }
    // 공룡 점프시켜주기
    if (Keyboard::isKeyPressed(Keyboard::Space)) {  // Space 클릭
      if (isBottom && !isJumping) {  // 공룡 걷고있고, 점프 안하고 있을 때
        isJumping = true;
        isBottom = false;  // 상태 변경
      }
    }
    // 점프
    if (isJumping) {
      DinoPos.y -=
          JumpPower;  // 점프중엔 y위치에서 JumpPower를 뺌 (그럼 올라감)
    } else {
      DinoPos.y += JumpPower;  // 고점에서 점프파워를 더하여 아래로
    }
    // 공룡의 위치 한계점 지정
    if (DinoPos.y >= DINO_BOTTOM)  // 바닥에서 지하로 가지 않도록 설정
    {
      DinoPos.y = DINO_BOTTOM;
      isBottom = true;
    }
    if (DinoPos.y <= DINO_BOTTOM - 100)  // 점프해서 하늘에 부딪히지 않게 설정
    {
      isJumping = false;
    }

    //  공룡 아장아장
    currentFrame += frameDelay;  // 현재 시간에서 마지막 업데이트된 시간을 더한 값
    if (currentFrame > totalFrames) {  // 한 번의 애니메이션이 완료
      currentFrame -= totalFrames;  // 현재 프레임을 다시 0부터 시작
      ++footShapeIndex;             // 1씩 증가
      if (footShapeIndex >= 2) {  // 2 이상인 경우 다시 0으로 초기화
        footShapeIndex = 0;
      }
    }

    // 나무 이동
    if (TreePos.x <= 0) {
      TreePos.x = width;
      score += 1;
    } else {
      TreePos.x -= TreeSpeed;
    }

    // 구름을 이동하고 화면 밖으로 나가면 다시 처음으로 돌림
    if (CloudPos1.x <= -1000) {
      CloudPos1.x = width;
    } else {
      CloudPos1.x -= CloudSpeed;
    }

    if (CloudPos2.x <= -1000) {
      CloudPos2.x = width + CloudPos1.x;
    } else {
      CloudPos2.x -= CloudSpeed;
    }

    // 공룡 위치
    DinoArr[footShapeIndex].setPosition(DinoPos.x, DinoPos.y);

    // 나무 위치
    Tree.setPosition(TreePos.x, TreePos.y);

    // 구름 위치
    Cloud1.setPosition(CloudPos1.x, CloudPos1.y);
    Cloud2.setPosition(CloudPos2.x, CloudPos2.y);

    // 충돌 시 게임 오버 처리
    if (isCollision(DinoPos, TreePos, t1, t3)) {
      std::cout << "Game Over\n";
      std::cout << "SCORE: " << score;
      window.close();
    }

    // 윈도우
    window.clear(Color::Cyan);             // 배경
    window.draw(DinoArr[footShapeIndex]);  // 공룡
    window.draw(Tree);                     // 나무
    window.draw(Cloud1);                   // 구름1
    window.draw(Cloud2);                   // 구름2

    // 프레임을 스크린에 출력
    window.display();
  }
  return 0;
}

개선사항

  1. 함수 분리
함수 분리 뿐 아니라 변수명 개선 주석을 통한 가독성 높히기 등이 있지만,
함수를 분리하여 유지보수를 높인거에 만족한다.
사실 함수를 더 세밀하게 분리할 수 있고, 구조체가 아닌 클래스를 이용하여
객체지향적 언어에 어울리게 수정할 수 있다.

수정한 코드

#include <SFML/Graphics.hpp>
#include <iostream>

using namespace sf;

const int width = 1000;
const int height = 300;

struct Position {
  int x, y;
};

bool isCollision(const Position& DinoPos, const Position& TreePos,
                 const Texture& DinoTexture, const Texture& TreeTexture) {
  const int DinoWidth = DinoTexture.getSize().x;
  const int DinoHeight = DinoTexture.getSize().y;
  const int TreeWidth = TreeTexture.getSize().x;
  const int TreeHeight = TreeTexture.getSize().y;

  const int DinoLeft = DinoPos.x;
  const int DinoRight = DinoPos.x + DinoWidth;
  const int DinoTop = DinoPos.y;
  const int DinoBottom = DinoPos.y + DinoHeight;

  const int TreeLeft = TreePos.x;
  const int TreeRight = TreePos.x + TreeWidth;
  const int TreeTop = TreePos.y;
  const int TreeBottom = TreePos.y + TreeHeight;

  return (DinoRight > TreeLeft) && (DinoLeft < TreeRight) &&
         (DinoBottom > TreeTop) && (DinoTop < TreeBottom);
}

void handleEvent(RenderWindow& window, bool& isJumping, bool& isBottom) {
  Event e;
  while (window.pollEvent(e)) {
    if (e.type == Event::Closed) {
      window.close();
    }
  }

  if (Keyboard::isKeyPressed(Keyboard::Space)) {
    if (isBottom && !isJumping) {
      isJumping = true;
      isBottom = false;
    }
  }
}

void handleJump(Position& DinoPos, bool& isJumping, bool& isBottom,
                const int JumpPower, const int DINO_BOTTOM) {
  if (isJumping) {
    DinoPos.y -= JumpPower;
  } else {
    DinoPos.y += JumpPower;
  }
  if (DinoPos.y >= DINO_BOTTOM) {
    DinoPos.y = DINO_BOTTOM;
    isBottom = true;
  }
  if (DinoPos.y <= DINO_BOTTOM - 100) {
    DinoPos.y = DINO_BOTTOM - 100;
    isJumping = false;
  }
}

void handleFootStep(int& footShapeIndex, float& currentFrame,
                    const float frameDelay, const int totalFrames) {
  currentFrame += frameDelay;
  if (currentFrame > totalFrames) {
    currentFrame -= totalFrames;
    ++footShapeIndex;
    if (footShapeIndex >= 2) {
      footShapeIndex = 0;
    }
  }
}

void handleTreePosition(Position& TreePos, const int TreeSpeed, const int width,
                        int& score) {
  if (TreePos.x <= -TreePos.x) {
    TreePos.x = width;
    score += 1;
  } else {
    TreePos.x -= TreeSpeed;
  }
}

void handleCloudPosition(Position& CloudPos1, Position& CloudPos2,
                         const int CloudSpeed) {
  if (CloudPos1.x <= -1000) {
    CloudPos1.x = width;
  } else {
    CloudPos1.x -= CloudSpeed;
  }

  if (CloudPos2.x <= -1000) {
    CloudPos2.x = width + CloudPos1.x;
  } else {
    CloudPos2.x -= CloudSpeed;
  }
}
int main() {
  int score = 0;

  RenderWindow window(VideoMode(width, height), "DinoSaur Game!!");

  window.setFramerateLimit(60);

  Texture t1, t2, t3, t4, t5;                 // 이미지 로드
  t1.loadFromFile("Dino_images/dino1.png");   // 공
  t2.loadFromFile("Dino_images/dino2.png");   // 룡
  t3.loadFromFile("Dino_images/tree.png");    // 나무
  t4.loadFromFile("Dino_images/cloud1.png");  // 구름1
  t5.loadFromFile("Dino_images/cloud2.png");  // 구름2

  Sprite DinoArr[2], Tree(t3), Cloud1(t4), Cloud2(t5);
  DinoArr[0] = Sprite(t1);
  DinoArr[1] = Sprite(t2);

  static const int TREE_BOTTOM = height - t3.getSize().y;
  Position TreePos;
  TreePos.x = width - 20;
  TreePos.y = TREE_BOTTOM;

  static const int CLOUD_TOP = height - t4.getSize().y * 3;
  Position CloudPos1;
  CloudPos1.x = 0;
  CloudPos1.y = CLOUD_TOP;

  Position CloudPos2;
  CloudPos2.x = t4.getSize().x;
  CloudPos2.y = CLOUD_TOP;

  const int JumpPower = 7;
  const int DINO_BOTTOM = height - t1.getSize().y;
  bool isJumping = false;
  bool isBottom = true;

  Position DinoPos;
  DinoPos.x = 30;
  DinoPos.y = DINO_BOTTOM;

  int footShapeIndex = 0;
  float currentFrame = 0.0f;
  float frameDelay = 0.4f;

  const int totalFrames = 5;

  const int TreeSpeed = 9;
  const int CloudSpeed = 3;

  while (window.isOpen()) {
    handleEvent(window, isJumping, isBottom);

    handleJump(DinoPos, isJumping, isBottom, JumpPower, DINO_BOTTOM);

    handleFootStep(footShapeIndex, currentFrame, frameDelay, totalFrames);

    handleTreePosition(TreePos, TreeSpeed, width, score);

    handleCloudPosition(CloudPos1, CloudPos2, CloudSpeed);

    DinoArr[footShapeIndex].setPosition(DinoPos.x, DinoPos.y);

    Tree.setPosition(TreePos.x, TreePos.y);

    Cloud1.setPosition(CloudPos1.x, CloudPos1.y);
    Cloud2.setPosition(CloudPos2.x, CloudPos2.y);

    if (isCollision(DinoPos, TreePos, t1, t3)) {
      std::cout << "Game Over\n";
      std::cout << "SCORE: " << score;
      window.close();
    }

    window.clear(Color::Cyan);
    window.draw(DinoArr[footShapeIndex]);
    window.draw(Cloud1);
    window.draw(Cloud2);
    window.draw(Tree);

    window.display();
  }

  return 0;
}

주요기술

주요기술은 내가 작성한 이 코드로 발표를 하기 위해,

개선 전 코드에 상세히 적혀있으니 넘어가도록 하겠다!




마무리

처음 작성한 코드를 개선할 때 고작 함수로 조금 분리하는 것 뿐인데, 참 여러번 고치고 여러번 멘탈이 나갔었다.

머리로는 일부 함수가 다른 함수에 너무 의존적이라는 것도 알고 있고,
몇몇 함수는 적어도 클래스를 이용해 좀 더 객체지향언어에 어울리게 사용해야 한다는 것을 알고 있고... const키워드를 써서 변수를 상수 선언 해야한다는 것도 알고있고..이외에도 많겠지만!!!

지금은 내 한계가 이정도인가?싶어 분했다 ㅋ
어떻게든 해결하고싶어 글을 업로드하는데에 일주일 좀 덜 걸린 것 같다.

아무튼 수정한 코드가 이대로 끝이 난다는 소리가 아니란 말씀..!
이 글도 차근차근 수정할 예정이다.

기술도 자세히 서술해 놓고싶고..
근데 코드가 마음에 들지 않아서 당장엔 마음이 가질 않는다.

원래 비공개 상태로 놔두고, 코드 수정이 다 끝나고 나면 전체공개로 바꾸려 했는데, 지금부터 전체 공개를 해놔야 내가 계속 와서 수정을 할 것 같다.

마지막 코드 수정이 끝났을 때는
10년뒤에 봤을때도 나쁘지 않은 코드가 되어 있길 바라는 마음이다.

끄읕!

profile
스마트팩토리 개발자가 되기 위한 □□ !!

0개의 댓글