공부/SFML

Draw 우선순위 적용, GameObject 지연 추가 및 삭제, 축 저장 및 해당 축 입력 확인, 축 입력 기능, 축 값 점진적 증가, 마우스 입력, 람다식 sorting 활용, 함수객체 sorting 활용

월러비 2025. 7. 3. 18:31

짧은 설명

  • 협업 중 의사소통은 중요하다
  • 프레임 워크에서 사용 안하는 파일 제거
    • 프로젝트 이름 변경
    • 프로젝트 파일명이 쓰여지는 부분 수정
    • 바이너리 폴더(bin) 수정
    • .vs, gitignore 같은 폴더 또는 파일은 협업할때 삭제를 해줘야한다.
      • 다른사람의 설정과 맞지 않을 수 있기 때문이다.

프레임워크 수정

SortingLayers sortingLayer = SortingLayers::Default;
int sortingOrder = 0; //이것을 기준으로 작은 숫자가 먼저 그려질 것이다.
std::list<GameObject*> sortedObjects(gameObjects);

//함수 객체 사용
//DrawOrderComparer -> 임시객체를 만들어서 매개변수로 전달하는 것이다.
sortedObjects.sort(DrawOrderComparer());

/*
//GameObject*인 이유 ; 위의 리스트 데이터형이 GameObject*니까
sortedObjects.sort(
	[](const GameObject* a, const GameObject* b)
	{
		if (a->sortingLayer != b->sortingLayer)
		{
			return a->sortingLayer < b->sortingLayer;
		}

		return a->sortingOrder < b->sortingOrder;
	}
);
*/

//헤더파일
struct DrawOrderComparer
{
	//함수객체 사용
	bool operator()(const GameObject* a, const GameObject* b)
	{
		return a->sortingOrder < b->sortingOrder;
	}
};
  • 그리기 순서 적용
  • 리스트를 레이어, Order 2개를 기준으로 그리려는 것이다.
  • 정렬 → 그리기 순서로 진행된다.
  • 매 프레임마다 정렬하기 때문에 업데이트 되어서 정렬이 이루어지고 그리기 순서가 바뀌어진다.
void Scene::Draw(sf::RenderWindow& window)
{
	~~

	for (auto obj : sortedObjects)
	{
		//활성화 된 오브젝트만 드로우 진행
		if (obj->GetActive())
		{
			obj->Draw(window);
		}
	}

	// 추가할 오브젝트 검사 후 배열에 추가
	for (GameObject* go : objectsToAdd)
	{
		//중복 검사 후 없으면 추가
		if (std::find(gameObjects.begin(), gameObjects.end(), go) == gameObjects.end())
		{
			gameObjects.push_back(go);
		}
	}
	objectsToAdd.clear(); //이걸 안하면 다음 프레임에 또 담고를 반복한다.

	// 삭제도 같은 작용
	for (GameObject* go : objectsToRemove)
	{
		gameObjects.remove(go);
	}
	objectsToRemove.clear();

}

GameObject* Scene::AddGameObject(GameObject* go)
{
	objectsToAdd.push_back(go);
	return go;
}

void Scene::RemoveGameObject(GameObject* go)
{
	go->SetActive(false); //업데이트 되거나 드로우 못하게 비활성화

	objectsToRemove.remove(go);
}
  • GameObject 지연 추가, 삭제
    • 계속 추가를 하게되면 추가중 추가를 하게되니 프로그램이 멈추게 된다.
      • update 또는 draw 도중 gameobject를 수정하면 iterator 무효, 중복 처리, 크래시 등의 문제가 발생한다.
  • Addlist, Removelist 생성
    • Addlist : 루프 도중 추가 요청한 오브젝트 임시 저장
    • Removelist : 루프 도중 삭제 요청한 오브젝트 임시 저장
  • draw를 마지막에 해야하는 이유 : 그리기가 끝난 후에 안전하게 수정해야하기 때문이다.
  • 주의점
    • 넣은 다음 한번 비워줘야한다. → 리스트.clear()
    • 중복 추가 방지 → std::find(추가 리스트 검사)
    • 바로 삭제 금지 → go→SetActive(false) 비활성화로 드로우와 업데이트를 못하게 막아야한다.
//키와 키에 따른 값이다.
static std::unordered_map<Axis, AxisInfo> axisInfoMap;

//축 가져오기
static float GetAxisRaw(Axis axis); //해당 축의 -1 0 1 반환
static float GetAxis(Axis axis); //정해진 시간만큼의 value가 반환되는 함수
  • 축 저장 및 해당 축 입력 확인

축 입력 기능

// 가로축 키 할당
AxisInfo infoH;
infoH.axis = Axis::Horizontal;
infoH.positivies.push_back(sf::Keyboard::Right);
infoH.positivies.push_back(sf::Keyboard::D);
infoH.negativies.push_back(sf::Keyboard::Left);
infoH.negativies.push_back(sf::Keyboard::A);
axisInfoMap.insert({ Axis::Horizontal, infoH });

// 세로축 키 할당
AxisInfo infoV;
infoV.axis = Axis::Vertical;
infoV.positivies.push_back(sf::Keyboard::Down);
infoV.positivies.push_back(sf::Keyboard::S);
infoV.negativies.push_back(sf::Keyboard::Up);
infoV.negativies.push_back(sf::Keyboard::W);
axisInfoMap.insert({ Axis::Vertical, infoV });
  • 상하좌우 + 대각선 → 8방향 (탑뷰)
  • 축을 입력하고 → float 값 반환
float InputMgr::GetAxisRaw(Axis axis)
{
	// held 키에서 뒤쪽에 있는 값을 받아서 값을 넘긴다.
	//키를 매개변수로 넘겨서 iterator 데이터형으로 반환
	auto findIt = axisInfoMap.find(axis);
	if (findIt == axisInfoMap.end()) //키가 없는경우
	{
		return 0.f;
	}

	const AxisInfo axisInfo = findIt->second; //축값 저장
	//이걸 뒤에서부터 순회해야한다.
	auto it = heldKeys.rbegin(); //rbegin() : 역방향 시작 iterator(위치) 반환 함수
	while (it != heldKeys.rend()) //역방향 순회
	{
		sf::Keyboard::Key code = *it; //지금 순회하고있는 iterator의 값
		if (Contains(axisInfo.positivies, code)) //키보드의 키가 들어있는지 확인
		{
			return 1.f;
		}
		if (Contains(axisInfo.negativies, code)) //키보드의 키가 들어있는지 확인
		{
			return -1.f;
		}
		++it;
	}
	return 0.0f;
}
  • 우키 : 1.f , 좌키 : -1.f
  • 상키 : -1.f , 하키 : 1.f
sf::Vector2f dir;
dir.x = InputMgr::GetAxisRaw(Axis::Horizontal);
dir.y = InputMgr::GetAxisRaw(Axis::Vertical);

sf::Vector2f pos = testGo->GetPosition();
pos += dir * 100.f * dt;
testGo->SetPosition(pos);
  • 시간의 흐름에 따라 값을 증가시켜주면 점진적으로 증가하는 값을 만들 수 있다.
    • 함수 2개 : -1 0 1 즞각 변환 함수 , 값이 들어오면 해당 값까지 점진적으로 증가하는 함수
  • 히트박스(버튼 누르는 방식)은 2개 동시 입력이 불안정하다.
    • 2개 들어오면 2개의 방법중 하나를 쓴다.
      1. 중립축을 하나 더 만들어서 처리한다.
      2. 나중에 입력된 값으로 처리한다. (지금은 이거로 처리)
        1. 아니면, 먼저 들어온 값으로 처리한다.
  • 마우스 입력 기능
case sf::Event::MouseButtonPressed:
	if (mouseButtonCondition[ev.mouseButton.button] == 0)
	{
		mouseButtonCondition[ev.mouseButton.button] = 1;
	}

	else if (mouseButtonCondition[ev.mouseButton.button] == 1)
	{
		mouseButtonCondition[ev.mouseButton.button] = 2;
	}
	break;

case sf::Event::MouseButtonReleased:
	mouseButtonCondition[ev.mouseButton.button] = 3;
	break;
	
bool InputMgr::Contains(const std::vector<int>& list, sf::Mouse::Button button, int condition)
{
	return list[button] == condition;
}

if (InputMgr::GetMouseButtonDown(sf::Mouse::Left))
{
	std::cout << InputMgr::GetMousePosition().x << ", " << InputMgr::GetMousePosition().y << std::endl;
}

프레임워크 수정 할 부분

  • 업데이트 → draw 순서로 진행된다.
  • gameObjects에 담겨진 순서대로 그려지고 있다.
    1. 숫자로 오름차순으로 정렬해서 작은 숫자 순서대로 그린다.
    2. 레이어로 그룹을 나눈다.
      1. 레이어 설정에 따라 그리기 순서가 달라진다.
      2. 같은 레이어는 숫자 순서에 따라서 그려지는 순서가 달라진다.
  • 숫자 순서, 레이어 순서 2개의 멤버가 필요하다.
  • Scene Draw 함수 수정
    • 게임 오브젝트 별로 레이어, 그리기 순서에 따라 정렬해서 그 순서로 그려야한다.
      • 그리기 순회를 돌면서 정렬시킨다.
      • 비효율적이니 나중에 선택적으로 고쳐라
sortedObjects.sort();
  • 이렇게 하면 원하는대로 동작 안함
    • 이유 : 포인터 형으로 정렬되기에 예외가 나던가, 주소값을 기준으로 정렬된다.
  • 해결법 : 정렬 기준을 매개변수로 넘길 수 있다. - 3가지 방법
    1. 게임 오브젝트의 함수 기준으로 넘긴다.
    1. 람다식으로 정렬기준을 만드는 경우 -게임 오브젝트 포인터형이 넘어올때 어떤게 더 크다 작다를 비교할 수 이따.
  • bool 형을 반환하는 변수 2개를 넘기면 된다.
  • a > b로 하면 정수형 기준으로 정렬된다.
bool (a, b)

return a > b -> 멤버의 sortingOrder에 접근
그거에 맞게 정렬이 된다.

//GameObject*인 이유 ; 위의 리스트 데이터형이 GameObject*니까
sortedObjects.sort(
	[](const GameObject* a, const GameObject* b)
	{
		if (a->sortingLayer != b->sortingLayer)
		{
			return a->sortingLayer < b->sortingLayer;
		}

		return a->sortingOrder < b->sortingOrder;
	}
);
  • sort 함수로 멤버 변수를 받는 변수를 bool 형으로 받을 수 있도록 반환받는다.
    • bool 형이 반환되게 된다.
    • 둘 중 누가 더 큰지 확인하고, 더 작은 수를 먼저 그리기 위해 사용한다.
    • 편하긴한데 조금 어렵다.
  1. 함수 객체 사용
// 헤더 파일
struct DrawOrderComparer
{
	bool operator()(const GameObject* a, const GameObject* b)
	{
		return a->sortingOrder < b->sortingOrder;
	}
};

//함수 객체 사용
//DrawOrderComparer -> 임시객체를 만들어서 매개변수로 전달하는 것이다.
sortedObjects.sort(DrawOrderComparer());
  • 값을 비교해서 true false를 반환한다.
  • 솔트는 최적화가 필요하니 각자 공부해서 최적화를 적용시켜야한다.

AddGameObject 수정

수정 부분

  • push_back의 지연이 필요하다.
  • Framework에서 씬매니저의 업데이트를 호출할때 저장과 삭제를 담아놨다가, 프레임이 끝났을 때 넣거나 뺴야한다.
  • 삭제 리스트, 추가 리스트를 만들어서 정리해야한다.

그리기 순서 정리

  • 리스트 복사
    • 정렬
    • 게임오브젝트 추가
    • 원하는대로 그리기

비주얼 스튜디오 깃허브 레포지토리 생성

  1. 보기
  2. git 변경 내용
  3. 레포지토리 만들기 가능

rbegin

  • 역방향 위치 반환 함수다.
  • ++를 하면 역방향으로 증가하게 된다.
  • begin이 나오면 정지하게 된다.