본문 바로가기
게임개발/Qt

[QT] 애니팡 - 이미지 배치하기

by 거북이 코딩 2024. 8. 16.

들어가며

 안녕하세요. 오늘부터 방학 동안 공부한 C++을 활용하여 간단한 게임을 만드는 프로젝트를 시작했습니다. GUI를 이용해 프로젝트해 보는 것이 처음이라 기대됩니다. 미숙하겠지만 열심히 해보겠습니다.

게임 개발 환경 설정

GUI 프레임워크 Qt를 사용합니다.

VS 에서 Qt tools 설치, Qt 버전 설정

새 프로젝트 → 위젯 애플리케이션

기본 틀

#include <QtWidgets/QApplication>
// 기본 헤더
#include <QtWidgets/QGraphicsScene>
// Scene 헤더
#include <QtWidgets/QGraphicsRectItem>
// RectItem 헤더
#include <QtWidgets/QGraphicsView>
// View 헤더

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene; // scene 생성
    
    QGraphicsRectItem rect(0, 0, 200, 100); 
    // 0,0 기준으로 가로 200 세로 100 크기 사각형 생성
    
    scene.addItem(&rect); // scene에 사각형 추가

    QGraphicsView view(&scene); // 보이게 하는 효과
    view.showFullScreen(); // 전체화면

    return a.exec();
}

Qt에서 자주 사용하는 기본적인 헤더와 함수들입니다.

이미지 배치하기

가운데 정렬된 12 * 12 크기 가상의 바둑판에 서로 다른 이미지 14개를 무작위로 배치하는 것을 목표로 합니다. 12 * 12 크기의 바둑판에 무작위로 배치할 때 첫 화면에서 같은 그림 3개가 이어지지 않을 확률이 14개부터 70% 이상이기 때문에 이미지의 종류는 14개로 정했습니다.

Consts.h
#pragma once

#include <string>

namespace Consts
{
	const std::string paths[]{ // 이미지 파일 절대 경로
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\1.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\2.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\3.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\4.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\5.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\6.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\7.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\8.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\9.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\10.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\11.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\12.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\13.png)",
		R"(C:\Users\4rest\Desktop\Shin\QtGame\threeMatchImage\PNG\ico\14.png)",
	};
	const int BOARD_LENGTH = 12;
	const int BOARD_ITEM_SIZE = 60;
}

14개 이미지의 절대경로를 설정할 때 다음과 같은 방법으로 이스케이프 시퀀스를 피할 수 있습니다.

R"(  )"
// 괄호 안의 이스케이프 시퀀스는 무시된다.

보드의 크기와 이미지 한 개의 크기를 상수로 설정합니다.

Board.h
#pragma once

#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsPixmapItem>
#include <QtWidgets/QGraphicsRectItem>

#include <vector>
#include <random>

class Board
{
private:
	QGraphicsScene* _scene;
	QGraphicsRectItem _root; // 보드판 묶음
	std::vector<std::vector<QGraphicsPixmapItem*>> _items; // item 배열
	std::random_device _device; // 난수 생성
	std::mt19937 _gen;

public:
	Board(QGraphicsScene* scene);
	~Board();
	void addItem(int row, int column);
	void removeItem(int row, int column);
};

/*
	items
	1 2 3 -> _items[0], row0
	4 5 6 -> _items[1], row1
	7 8 9 -> _items[2], row2

	_items[0][0] -> 1
	_items[0][1] -> 2
	_items[0][2] -> 3
*/

 

 

2중 벡터를 사용해서 2차원 보드판을 구현합니다.

Board.cpp
#include <random>

#include "Board.h"
#include "Consts.h"

Board::Board(QGraphicsScene* scene)
	: _scene(scene) // scene 설정
	, _gen(_device()) // 난수 생성
{
	_scene->addItem(&_root); // scene에 보드판 묶음 추가

	_root.setX(_scene->sceneRect().width() / 2 // 보드판 위치 설정
		- Consts::BOARD_ITEM_SIZE * Consts::BOARD_LENGTH / 2);
	_root.setY(_scene->sceneRect().height() / 2
		- Consts::BOARD_ITEM_SIZE * Consts::BOARD_LENGTH / 2);

	for (int row = 0; row < Consts::BOARD_LENGTH; ++row) // vector에 item 넣기
	{
		std::vector<QGraphicsPixmapItem*> rowItems(Consts::BOARD_LENGTH);
		_items.push_back(rowItems); // 행 삽입

		for (int column = 0; column < Consts::BOARD_LENGTH; ++column)
		{
			addItem(row, column); // 각 열에 item 추가
		}
	}
}

Board::~Board()
{
	for (int row = 0; row < _items.size(); ++row)
	{
		for (int column = 0; column < _items[row].size(); ++column)
		{
			removeItem(row, column);
		}
	}
}

void Board::addItem(int row, int column)
{

	std::uniform_int_distribution<int> dis(0, 13); // 난수를 0 ~ 13 까지 균등하게 생성

	QPixmap pixmap(Consts::paths[dis(_gen)].c_str()); // item 선택
	QPixmap scaled(pixmap.scaled(Consts::BOARD_ITEM_SIZE, Consts::BOARD_ITEM_SIZE)); // 크기 조정
	QGraphicsPixmapItem* item = new QGraphicsPixmapItem(scaled, &_root); // 동적 할당, _root를 부모로 설정

	item->setPos(column * Consts::BOARD_ITEM_SIZE, row * Consts::BOARD_ITEM_SIZE); // 부모위치를 기준으로 item 위치 설정
	_items[row][column] = item; // 배열에 item 삽입
}

void Board::removeItem(int row, int column)
{
	auto* item = _items[row][column];

	if (item == nullptr) // 존재하지 않는다면 종료
		return;

	_items[row][column] = nullptr; // 벡터 원소 해제
	item->setParentItem(nullptr); // 부모 정보 해제
	_scene->removeItem(item); // scene에서 해제
	delete item; // 동적할당 해제
}
main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QScreen>

#include "Board.h"

int main(int argc, char* argv[])
{
	QApplication a(argc, argv);
	QScreen* screen = a.primaryScreen(); // 스크린 정보 받아옴
	QRect geometry = screen->geometry();
	QGraphicsScene scene;
	scene.setSceneRect(geometry);

	Board board(&scene); // 보드 생성

	QGraphicsView view(&scene);
	view.showFullScreen();

	return a.exec();
}

 

프로젝트를 실행하면 다음과 같은 화면이 나타납니다.

 

마치며

 프레임워크를 적극적으로 활용하기보단 cpp를 이용해서 게임을 개발하는 흐름을 배우기 위한 프로젝트입니다. 그래서 Qt에 관한 정보는 저도 그냥 찾아보면서 하고 있습니다. 그래서 설명이 부족한 부분이 많으니 감안하고 봐주세요. ㅎㅎ.

 

 공부할 때 어려운 것과는 별개로 즉각적으로 이미지가 변하는 걸 보면서 하는 코딩은 참 재밌는 것 같습니다. 마지막 이미지를 보면 아시겠지만 3개가 연속으로 나온 경우는 핑크색 도넛 한 가지뿐입니다(아마도)! 이미지의 가짓수가 적절했던 거 같아서 기분이 좋네요.