언어/디자인 패턴

[디자인 패턴]#9_업데이트 패턴, Update Pattern

Hardii2 2022. 9. 4. 13:06

[디자인 패턴] #9_업데이트 패턴, Update Pattern

게임 디자인 패턴 중 "순서 패턴"에 대해 알아보겠습니다.

"게임 프로그래밍 패턴"의 10 항목, "업데이트 메서드"에 해당하는 내용입니다.

 

 


 

업데이트 패턴

 

1. 개념 

* 개념

: 객체 컬렉션에 들어 있는 객체별로 한 프레임 단위의 작업을 진행하라고 "알려줘서" 전체를 시뮬레이션한다.

 

2. 왜?

// 메인 게임 루프

while(true)
{
    //...

    // 몬스터 객체의 이동 거리 업데이트
    if(bMonsterLeft == true)
    {
        x--;
        if(x == 0) bMosterLeft = false;
    }else
    {
        x++;
        if(x == 100) bMonsterLeft = true;
    }
    
    // 이동 거리 결과 업데이트
    Monster.setPositionX(x);
	
    // 유저 입력
    processInput();
    
    // 렌더링
    render();
}
* Monster의 이동 거리 업데이트를 예제로 살펴보겠습니다.

1. Monster 객체는 "한 프레임"에 한 걸음씩 이동합니다.
2. 이 와중에도, 게임 루프는 유저 입력에 끊임없이 반응하고, 루프 결과를 렌더링 합니다.
3. Skeleton, Zombie,... etc 다양한 객체들을 추가할수록 각자의 동작들을 제어할 코드들로 가득해집니다!!
4. 각 객체들이 자신들의 동작들을 스스로 캡슐화할 필요가 있어 보이죠?

* 이때, 업데이트 패턴을 활용하여 각 객체들의 동작을 캡슐화할 수 있습니다.

 

update() 메서드
* update() 메서드?

: 각 객체들은 "update()" 메서드를 정의해 추상 계층을 더하고, 게임 루프는 객체 컬렉션을 쭉 돌면서 "update()"만 호출합니다! 이때, "update()" 메서드는 "한 프레임"에 업데이트할 동작들을 진행합니다! 

 

정리
정리하자면, 게임 월드 즉 메인 게임 루프는 "객체 컬렉션"을 관리합니다. 각 객체는 "한 프레임" 단위의 동작을 시뮬레이션하기 위한 "update()" 추상 메서드를 정의하겠죠! 매 프레임마다 메인 루프는 컬렉션에 들어있는 모든 객체들을 돌면서 "update()" 추상 메서드만 호출하면 업데이트 동작을 완료하는 것입니다! 

 

언제?
1. 동시에 동작하는 객체나 시스템이 게임에 많을 때
2. 각 객체의 동작은 다른 객체와 독립적일 때
3. 시간의 흐름에 따라 객체를 시뮬레이션할 때

 

주의 사항

1. 다음 프레임 업데이트를 위해 현재 상태를 저장해야 합니다! 

* 왜?

현재 프레임에서 이동 중인 Monster의 위치를 다음 프레임에서 반전시켜야 한다면, 현재 Monster의 상태를 다음 프레임까지 저장하고 기억할 수 있어야겠죠! 이처럼, 다음 프레임에서도 객체들의 자연스러운 다음 움직임을 시뮬레이션하기 위해 현재 상태를 충분히 저장해야 합니다!

 

2. 모든 객체가 동시에 업데이트되는 것은 아닙니다! 

* 왜?

게임 루프를 돌면서 모든 객체를 "프레임" 단위로 업데이트합니다. 이때, 한 객체가 다른 객체와 상호작용이 필수적이라면, 서로 다른 객체에 접근하는 코드 작성이 업데이트 중에 발생할 수 있습니다. 따라서, 객체 컬렉션을 돌며 "update()"를 호출하는 순서 또한 중요합니다!

 

3. 업데이트 도중에 객체 목록을 변경하는 것은 조심해야 합니다! 

// 게임 루프
while(true)
{
    // Update
    size_t NumOfObjects = ObjectsCollection.size();
    for(int i=0; i < NumOfObjects; i++)
    {
        ObjectsCollection[i].update();
    }
    
    processInput();
    
    render();
}
* 왜?

업데이트 도중 죽은 객체를 객체 목록에서 삭제하면 문제가 발생할 수 있습니다. 왜냐하면, 의도치 않게 객체 목록이 컨테이너 내부에서 이동하며 특정 객체의 "update()" 호출을 생략하고 점프할 수 있기 때문입니다. 따라서, 객체 목록의 삭제 동작은 모든 객체의 "update()" 호출이 다 끝난 시점에 수행하는 것이 좋아 보입니다!

 

 

코드 예제

코드 예제의 상속 구조

class Entity
{
public:
	Entity() : x(0), y(0), {}
	virtual ~Entity();
	virtual void Update() = 0;

	double getX() const { return x; }
	double getY() const { return y; }

	void setX(double val) { x = val; }
	void setY(double val) { y = val; }

private:
	double x;
	double y;
};

class Monster : public Entity
{
public:
	Monster() : LeftMove(false) {}

	virtual void Update()
	{
		if (LeftMove == true)
		{
			setX(getX() - 1);
			if (getX() == 0) LeftMove = false;
		}
		else
		{
			setX(getX() + 1);
			if (getX() == 100) LeftMove = true;
		}
	}

private:
	bool LeftMove;
};

class World
{
public:
	World() : NumOfEntity(0) {}
	
	void GameLoop();

private:
	Entity* entities[MAX_ENTITIES];
	int NumOfEntity
};

void World::GameLoop()
{
	while (true)
	{
		processInput();

		for (int i = 0; i < NumOfEntity; i++)
		{
			entities[i]->Update(); 
		}

		redner();
	}
}
메인 게임 루프에서 각 객체들의 동작들을 하드 코딩하지 않고, "Update()" 추상 메서드를 통해 추상화 계층을 추가해주어 "캡슐화" 했습니다. 이것을 "Update 패턴"이라고 합니다!