Framework 5

김성진·2024년 2월 14일

2월 14일 수업 정리

싱글톤 패턴 보장

이전에 간단히 만들어본 싱글톤 패턴은 해당 클래스의 Instance를 호출하면 전역으로 사용할 수는 있었지만, 메인함수 같은 외부에서 해당 클래스를 new를 통해 새로 생성하면 그대로 생성되는 문제가 있었다.
해결법은 간단히 생성자를 protected로 넣어두면 되지만, 싱글톤 클래스 자체에서 자식 클래스들에 접근할 수 없는 문제가 생긴다. 따라서 friend로 예외를 두도록 만든다.

template<typename T>
class ResourceMgr : public Singleton<ResourceMgr<T>>
{
	friend Singleton<ResourceMgr<T>>;

private:
	std::unordered_map<std::string, T*> resources;
	ResourceMgr() = default;
	virtual ~ResourceMgr()
	{
		UnloadAll();
	}
	static T Empty;
	

ㄴ싱글톤 패턴을 적용했던 리소스 매니저

class scene;

class SceneMgr : public Singleton<SceneMgr>
{
	friend class Singleton<SceneMgr>;

protected:
	SceneMgr() = default;
	virtual ~SceneMgr();

	std::vector<Scene*> scenes;

	SceneIds startScene = SceneIds::SceneGame;
	SceneIds currentScene = startScene;

마찬가지로 씬 매니저도 protected 에 넣어서 보안을 강화했다.

클래스를 만들때, 수업의 편의를 위해 public에 멤버를 넣어두는 일이 많았지만 이제 get과 set을 활용할 일이 많다.

FrameWork

이제 리소스, 씬 등이 아닌, 메인함수 자체를 대체하는 객체 FrameWork를 만들어보자.

#pragma once
#include "singleton.h"
//게임의 메인 형태를 함수로 만드는 클래스

// 1. 초기화 / 메인루프 / 정리 (릴리즈)
// 2. 시간 관련 기능
// 3. ...Mgr 초기화 ,정리 등등의 관리

class Framework : public Singleton<Framework>
{
	friend class Singleton<Framework>;

protected:
	Framework() = default;
	virtual ~Framework() = default;

	sf::RenderWindow window;
	sf::Vector2i windowSize;
	
	sf::Clock clock;
	float timeScale = 1.f;

	sf::Time realTime; //게임 시작부터 경과시간
	sf::Time time; //게임 시작부터 경과시간 (timeScale 적용된)
	
	sf::Time realDeltaTime;
	sf::Time deltaTime;

public:
	sf::RenderWindow& GetWindow() { return window; } // !!
	const sf::Vector2i& GetWindowSize() const { return windowSize; }
	float GetDT() const { return deltaTime.asSeconds(); };
	float GetRealDT() const { return realDeltaTime.asSeconds(); };
	
	float GetTime() const { return time.asSeconds(); };
	float GetRealTime() const { return realTime.asSeconds(); };

	float GetTimeScale() const { return timeScale; }
	void SetTimeScale(float scale) { timeScale = scale; }

	virtual void Init(int width, int height, const std::string& name = "Game");
	virtual void Do();
	virtual void Release();

};

#define FRAMEWORK (Singleton<Framework>::Instance())

구현부

#include "pch.h"
#include "Framework.h"

void Framework::Init(int width, int height, const std::string& name)
{
    srand(std::time(NULL));
   
    windowSize.x = width;
    windowSize.y = height;

    window.create(sf::VideoMode(windowSize.x, windowSize.y), name);

    SCENE_MGR.Init();
}

void Framework::Do()
{
    while (window.isOpen())
    {
        deltaTime = realDeltaTime = clock.restart();
        deltaTime *= timeScale;

        time += deltaTime;
        realTime += realDeltaTime;

        InputMgr::Clear();

        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();

            InputMgr::UpdateEvent(event);
        }

        InputMgr::UpdateEvent(event);


        //Update


        SCENE_MGR.Update(GetDT());

        window.clear();
        SCENE_MGR.Draw(window);
        window.display();
    }
}

void Framework::Release()
{
    SCENE_MGR.Release();
}

메인함수는 초기화 / 메인루프 / 정리 부분으로 나뉘어있었다.
Framework에서 Init, Do, Release 로 구현해서 메인 루프를 옮겨줬다.

매번 Instance를 호출하기 힘드니, 각 매니저에
#define SCENE_MGR (Singleton::Instance())
#define TEXTURE_MGR_TEXTURE (ResourceMgr<sf::Texture>().Instance());
#define TEXTURE_MGR_FONT (ResourceMgr<sf::Font>().Instance());
처럼 define을 통해 간단하게 호출할수 있도록 했다.
Framework 도 #define FRAMEWORK (Singleton::Instance())로 줄여주었다.


int main()
{
  FRAMEWORK.Init(1920, 1080, "Timber");
  FRAMEWORK.Do();
  FRAMEWORK.Release();

  return 0;
}

메인함수가 단 3줄로 끝난다.

TimeBar 게이지

남은 시간을 표현하는 timebar 게이지를 객체지향으로 다시 구현해보자.
RectangleShape 데이터형을 사용하면 사각형을 표시할 수 있다.

#pragma once
#include "GameObject.h"

class SceneGame;

class TimeBar : public GameObject
{
protected:
	sf::RectangleShape timeBar;
	sf::Vector2f timeBarSize = { 400.f, 80.f };
	sf::Vector2f timeBarCurrentSize = timeBarSize;

	float timeBarDuration = 3.f;
	float timeBarSpeed = -timeBarSize.x / timeBarDuration;

public:
	TimeBar(const std::string name);

	void Init() override;
	void setFillColor(sf::Color color);
	void setOrigin(Origins origin);
	void setPosition(sf::Vector2f position);
	float gettimeBarCurrentSize() const { return timeBarCurrentSize.x; };

	void setSpeed(float speed); 
	void setPlusTime();

	void Reset() override;
	void Update(float dt) override;
	void Draw(sf::RenderWindow& window) override;
};

gameobject를 상속받아서, 사각형 멤버변수 timeBar를 만든다.
맥스 사이즈와 현재 사이즈를 표현하는 vector2f형 변수를 만들어주고,
줄어드는 속도를 조절할 변수 timeBarDuration 과 timeBarSpeed 를 만들어준다.

이제 필요한 메소드를 가져와서 재구현하면, 초기화 역할의 Init, 색,오리진,포지션을 set 해줄 메소드들과 타임바의 x값을 출력해줄 gettimeBarCurrentSize 메소드, 속도와 시간 증가를 해줄 메소드와 기본적인 리셋 업데이트, 드로우까지 필요하다.

구현

#include "pch.h"
#include "TimeBar.h"

TimeBar::TimeBar(const std::string name) : GameObject(name)
{
	timeBar.setSize(timeBarCurrentSize);
}

void TimeBar::Init()
{
	timeBar.setFillColor(sf::Color::Red);
}

void TimeBar::setFillColor(sf::Color color)
{
	timeBar.setFillColor(color);
}

void TimeBar::setOrigin(Origins origin)
{
	Utils::SetOrigin(timeBar, origin);
}

void TimeBar::setPosition(sf::Vector2f position)
{
	timeBar.setPosition(position);
}

우선 오리진이 찍히기 전에 반드시 사이즈가 미리 셋팅되어 잇어야 한다. 사이즈는 생성자에 넣어두었다.


void TimeBar::setSpeed(float speed)
{
	timeBarDuration = speed;

	//매개변수가 큰 수일수록 느려짐
}

void TimeBar::setPlusTime()
{
	timeBarCurrentSize.x += 40.f;
	if (timeBarCurrentSize.x > 400.f)
	{
		timeBarCurrentSize.x = 400.f;
	}
}

void TimeBar::Reset()
{
	timeBarCurrentSize = timeBarSize;
}

호출시 timeBarCurrentSize.x 값을 더해주도록 했다.
Reset 하면 timeBarCurrentSize.x 값이 초기값으로 돌아가도록 만들었다.

void TimeBar::Update(float dt)
{
	timeBarCurrentSize.x += timeBarSpeed * FRAMEWORK.GetDT();
	timeBar.setSize(timeBarCurrentSize);
}

void TimeBar::Draw(sf::RenderWindow& window)
{
	window.draw(timeBar);
}

update 에서 매 dt를 받을 때마다 사이즈를 다시 설정해주면 타임바가 줄어드는 것처럼 표현된다.

SceneGame에서 적용

Status

해당 씬에서 시작, 정지, 재시작, 게임오버 상태를 표현하도록 한다.
우선 열거형 status를 만들고, 상호작용할 timebar 클래스를 전방선언하고 내부 변수에 넣어둔다.

class TimeBar;

class SceneGame : public Scene
{

public:
	enum class Status
	{
		Awake,
		Game,
		GameOver,
		Pause,
	};

void SceneGame::SetStatus(Status newStatus)

protected:

	UIScore* uiScore;
	TextGo* uiMsg;

	TimeBar* timebar;

	Status currStatus;

	float timer = 0.f;
	float duration = 3.f;


이제 구현부에서 SetStatus 를 만든다.


void SceneGame::SetStatus(Status newStatus)
{
	Status prevStatus = currStatus;
	currStatus = newStatus;

	switch (currStatus)
	{
	case Status::Awake:
		FRAMEWORK.SetTimeScale(0.f);
		uiMsg->SetActive(true);
		uiMsg->SetString("Press Enter To Start");
		break;
	case Status::Game:
		FRAMEWORK.SetTimeScale(1.f);
		uiMsg->SetActive(false);
		uiMsg->SetString("");

		break;
	case Status::GameOver:
		FRAMEWORK.SetTimeScale(0.f);
		uiMsg->SetActive(true);
		uiMsg->SetString("GAME OVER! Press Enter To Restart");
		break;
	case Status::Pause:
		FRAMEWORK.SetTimeScale(0.f);
		uiMsg->SetActive(true);
		uiMsg->SetString("Press Enter To RESUME");
		break;
	}
}

현재 Status에 따른 상태와 메시지, 메시지들의 활성화 상태, 그리고 timeScale을 조정해준다.

이후 Enter , Exit에 각각 추가해준다.

void SceneGame::Enter()
{
	Scene::Enter();
	SetStatus(Status::Awake);
}

void SceneGame::Exit()
{
	FRAMEWORK.SetTimeScale(1.f);
}

처음 씬이 켜질때 Enter가 되니 Awake 상태로, 그리고 정지된 상태여도 Exit시 timeScale을 1로 해두어서 다시 시간이 가도록 한다.

void SceneGame::Update(float dt)
{
	Scene::Update(dt);

	switch (currStatus)
	{
	case Status::Awake:
		if (InputMgr::GetKeyDown(sf::Keyboard::Enter))
		{
			SetStatus(Status::Game);
		}
		break;
	case Status::Game:
		if (InputMgr::GetKeyDown(sf::Keyboard::Escape))
		{
			SetStatus(Status::Pause);
		}
		break;
	case Status::GameOver:
		if (InputMgr::GetKeyDown(sf::Keyboard::Enter))
		{
			timebar->Reset();
			SetStatus(Status::Game);
		}
		break;
	case Status::Pause:
		if (InputMgr::GetKeyDown(sf::Keyboard::Enter))
		{
			SetStatus(Status::Game);
		}
		break;
	}
	if (InputMgr::GetKeyDown(sf::Keyboard::Space) && currStatus == Status::Game)
	{
		timebar->setPlusTime();
	}

	if (timebar->gettimeBarCurrentSize() < 0)
	{
		SetStatus(Status::GameOver);
	}
                             이제 업데이트 메소드에 적용시켜서 키 입력시에 해당 상태로 들어가도록 만들어준다.
                                         
                                또한 space 키를 눌러서 타임바를 증가시키고, 게임 오버시 엔터를 눌러 리셋해줄 수 있도록 했다. SceneGame에 timebar를 변수로 넣어놓지 않으면 접근할 방법이 없어서 findgo를 통해 찾거나 하는 번거로운 방법이 필요하다.
                                         
                                         
profile
듀얼리스트

0개의 댓글