이번에 작성할 코드는 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;
}
함수 분리 뿐 아니라 변수명 개선 주석을 통한 가독성 높히기 등이 있지만,
함수를 분리하여 유지보수를 높인거에 만족한다.
사실 함수를 더 세밀하게 분리할 수 있고, 구조체가 아닌 클래스를 이용하여
객체지향적 언어에 어울리게 수정할 수 있다.
#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년뒤에 봤을때도 나쁘지 않은 코드가 되어 있길 바라는 마음이다.
끄읕!