이론/디자인패턴

업데이트 메서드

tsyang 2022. 2. 20. 11:57

2022.01.31 - [이론/디자인패턴] - 게임 루프

 

게임 루프

게임 루프 게임 루프는 게임 시간 진행을 유저 입력, 프로세서 속도(!)와 디커플링 한다. 유저 입력은 알겠는데 프로세서 속도와 디커플링 한다는게 무슨 말일까? 굉장히 간단한 게임 루프를 보

tsyang.tistory.com

 

업데이트는 어떻게


게임 루프에 루프는 간략하게 다음과 같다.

while(true)
{
	processInput();
	update();
	render();
}

update() 안에서는 게임의 개체들을 시뮬레이션한다. 그런데 어떻게?

 

update() 안에 직접 각각의 개체들을 움직이는 코드를 작성해야 할까? 만약에 개체의 종류가 수백 가지라면? 코드가 점점 유지 보수하기 어려워질 것이다. 

 

당연히 이런 경우 해결책은 간단하다. 모든 객체가 자신의 동작을 캡슐화하면 된다. 이를 위해 추상 메서드인 update()를 정의해 추상 계층을 더한다. 게임 루프는 객체를 관리하는 동적 컬렉션이 있고 매 update마다 컬렉션을 순회하며 개체들을 update 한다.

 

class Entity
{
public:
	Entity() : x_(0), y_(0){}
	virtual ~Entity() {}
	virtual void update(double deltaTime);

	//추가로 x_, y_에 대한 getter/setter

private:
	double x_;
	double y_;
};
class World
{
public:
	void update(double deltaTime)
	{
		for(int i=0;i<entities_.size();++i)
			entities_[i]->update(deltaTime);
	}

private:
	vector<Entity*> entities_;
};

 

모든 개체는 Entity 클래스를 상속받는다. 그러나 과한 상속은 유지보수를 힘들게 한다. 그래서 요즈음에는 클래스 상속보다는 객체 조합을 선호하는 편이다. 게임 산업에서는 컴포넌트 패턴을 쓴다. 이렇게 되면 update 함수는 개체 클래스가 아니라 객체의 컴포넌트에 있게 된다.

 

이런 업데이트 메서드는 다음의 상황에 적합하다. 

  • 동시에 동작해야 하는 객체나 시스템이 게임에 많다.
  • 각 객체의 동작은 다른 객체와 거의 독립적이다.
  • 객체는 시간의 흐름에 따라 시뮬레이션되어야 한다.

체스나 바둑 같은 경우는 이런 루프와 업데이트 메서드 패턴이 적합하지 않다.

 

 


 

디자인 결정


업데이트 메서드를 어느 클래스에?

개체 클래스

이런 방식은 단순하지만 개체 종류가 많고 새로운 동작을 만들 때마다 개체 클래스를 상속받아야 하기에 유지보수가 어려워진다. 또한 언젠가 단일 상속 구조의 한계에 부딪히면 답이 없다. 따라서 요즘 업계는 이 방식을 서서히 멀리 하고 있다고 한다.

 

컴포넌트 클래스

컴포넌트 패턴을 사용하는 경우 컴포넌트에서 자기 자신을 업데이트해주면 된다. 이렇게 되면 각 컴포넌트들이 서로를 모르는 상태에서 알아서 돌아간다.

 

위임 클래스

위와 비슷하게, 개체의 동작 일부를 다른 객체에 위임하는 패턴이다. 이 경우 GoF의 상태 패턴의 '상태'가 update를 하거나, 타입 객체 패턴의 '타입 객체'가 update를 한다. 이 경우 개체 클래스에도 update()가 있긴 하지만 단순히 포워딩만 해준다.

void Entity::update(double deltaTime)
{
	//다른 객체에 포워딩.
	state_->update(deltaTime);
}

 

 

휴면 객체 처리?

비활성 객체가 포함된 컬렉션 하나만

이 경우 모든 컬렉션을 순회하며 객체가 활성 상태인 경우에만 업데이트한다. 이런 방식은 비활성 객체의 활성여부를 체크해야 하고, 데이터 지역성도 잃게 돼 결국 느리다.

 

활성 객체만 모여있는 클래스를 하나 더

이 경우 메모리를 추가로 사용해야 하며, 중복 데이터가 많아진다. 그러나 메모리보다 속도가 중요하다면 받아들일만하다. 컬렉션 두 개의 동기화를 유지해야 하므로 구현이 조금 복잡해진다. 비활성 객체가 많을수록 컬렉션을 따로 두는 게 좋다.

 

 


 

주의사항

 


객체의 업데이트 순서가 중요할 수 있다.

결국 컬렉션을 순차적으로 도는 것이므로, 객체끼리 순서에 의존성을 가지고 있다. 이 경우 이중 버퍼 패턴 같은 게 필요하다.

 

업데이트 도중 객체 목록 바꾸는 건 조심해야 한다.

당연한 얘기지만 컬렉션을 순회하는 도중 컬렉션의 객체를 삭제하게 되면 문제가 발생한다. 따라서 순회 도중 객체를 삭제하고자 하자면 객체에 '삭제됨'을 표시한 뒤 업데이트가 종료된 후에 객체를 컬렉션에서 제거할 수 있다.

 

 


참고 : 로버트 나이스트롬, 게임 프로그래밍 패턴, 한빛미디어, [179-194p]

 

 

'이론 > 디자인패턴' 카테고리의 다른 글

객체 풀  (1) 2022.03.04
데이터 지역성  (0) 2022.02.28
타입 객체  (0) 2022.02.10
게임 루프  (1) 2022.01.31
이중 버퍼  (0) 2022.01.26