게임개발/Qt

[QT] 애니팡 - 점수 UI 만들기 (완성)

거북이 코딩 2024. 8. 28. 18:36

점수 UI 만들기

Values.h
#pragma once

#include <vector>
#include <functional>

template<typename T>
class ObservableValue
{
	using Observer = std::function<void(const T&)>;
private:
	T _value;
	std::vector<Observer> _observers;

public:
	const T& get() const
	{
		return _value;
	}

	void set(const T& value)
	{
		_value = value;
		
		for (auto& observer : _observers)
		{
			observer(_value);
		}
	}

	void observe(Observer observer)
	{
		_observers.push_back(observer);
		observer(_value);
	}
};

struct Values
{
	ObservableValue<int> score;
};

 ObservableValue 클래스는 value와 observer 들을 관리합니다. get()과 set()으로 점수를 관리하고, observe함수로 옵저버를 모아놓은 벡터에 옵저버를 추가하고 값을 업데이트합니다. Values 구조체는 ObservableValue 클래스가 <int>로 특수화된 객체를 가지고 있습니다.

Score.h
#pragma once

#include <QtWidgets/QGraphicsTextItem>
#include <QtWidgets/QGraphicsScene>
#include "Value.h"

class Score : public QGraphicsSimpleTextItem
{
public:
	Score(QGraphicsScene* scene, Values* values);
};
Score.cpp
#include "Score.h"
#include "Consts.h"

void observe(Score* score, const int& value)
{
	score->setText(("Score : " + std::to_string(value)).c_str());
}

Score::Score(QGraphicsScene* scene, Values* values)
{
	scene->addItem(this);

	setX(scene->sceneRect().width() / 2
		- Consts::BOARD_ITEM_SIZE * Consts::BOARD_LENGTH / 2);
	setY(scene->sceneRect().height() / 2
		+ Consts::BOARD_ITEM_SIZE * Consts::BOARD_LENGTH / 2);
	setScale(2.5);

	values->score.observe([this](const int& value)
		{
			observe(this, value);
		});
}

 Score 클래스는 QGraphicsSimpleTextItem를 상속하여 점수를 표시하는 역할을 합니다. 생성자의 인자로 scene과 Values 포인터를 받습니다. scene을 통해 점수판을 추가하고, values를 통해 ObservableValue 클래스의 observe 함수에 람다 객체를 전달합니다. 람다 객체로 감싸서 observe의 오버로딩 함수를 호출하여 Score를 업데이트합니다.

main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QScreen>

#include "Board.h"
#include "Score.h"
#include "Value.h"

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

	QGraphicsScene scene;
	scene.setSceneRect(geometry);

	Values values; // 점수 저장

	Board board(&scene, &values); // 보드 생성
	Score score(&scene, &values); // 점수 생성

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

	return a.exec();
}

 main에서 점수를 담고 있는 values와 점수를 표시해 주는 score를 만든다. 보드와 스코어에 같은 scene과 value를 전달한다.

Board.cpp
Board::Board(QGraphicsScene* scene, Values* values)
	: _scene(scene) // scene 설정
	, _values(values) // 점수 저장
	, _gen(_device()) // 난수 생성
	, _moveCount(0)
{
	_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);

	_values->score.set(0); // 점수 초기화

	// ... 중략
}

bool Board::refresh()
{
	MatchSet matched = matchedItems(); // 3개 이상 연속된 아이템 모음
	if (matched.size() < 3) // 3개 이하라면 종료
		return false;

	for (const auto& pair : matched) // 3개 이상이라면 아이템 제거
	{
		removeItem(pair.first, pair.second);

		_values->score.set(_values->score.get() + 10); // 점수 증가
	}
	
	// ... 중략
}

 보드의 생성자에서 점수를 초기화한다. 그리고 refresh() 함수에서 아이템을 한 개 지울 때마다 점수를 10점씩 추가한다.

스코어 객체가 만들어지는 과정은 이렇다.

 점수가 증가하는 로직을 정리하면 다음과 같다.

마치며

 제가 처음으로 만들어본 게임이 완성되었습니다. 비록 간단한 게임이지만 어떻게든 하나 만들었다는 게 참 뿌듯합니다. 게임을 만드는 과정에서 정리하면서 만들다 보니까 머릿속에 더 잘 정리되는 느낌입니다. 이제 점수 UI 가 적용된 영상을 보시겠습니다.

 

 블록들도 좀 더 구분이 잘되는 색상들로 바꾸었습니다. 개선할만한 점은 어떠한 경우에도 3 match를 만들 수 없을 때, 몇 점에 도달하면 게임이 끝난다. 등을 만들어 볼 수 있을 거 같습니다. 감사합니다.