728x90

1. 하위 클래스 샌드박스 패턴 정의

  • 상위 클래스가 제공하는 기능들을 통해서 하위 클래스에서 행동을 정의하는 패턴
  • 제공 기능은 protected로 만들어져 하위 클래스용이라는 걸 분명히 하여 사용한다.

 

2. 상위 클래스 구현

class Superpower
{
public:
    virtual ~Superpower() {}

protected:
    virtual void Activate() = 0;

    void Move( double InX, double InY, double InZ )
    {
        //..
    }

    void PlaySound( SoundId InSoundId, double InVolume )
    {
        //..
    }
};
  • Activate() 는 샌드박스 메서드다. 순수 가상 함수로 만들었기 때문에 하위 클래스가 반드시 오버라이드해야 한다.
  • Move(), PlaySound() 함수는 제공 기능이며, 하위 클래스에서 Activate() 메서드를 구현할 때 호출한다.

 

3. 하위 클래스 구현

class SkyLaunch : public Superpower
{
protected:
    // 순수 가상함수 오버라이드
    virtual void Activate() override
    {
        // 상위 클래스에 정의된 제공 기능
        PlaySound( SOUND_SPROING, 1.f );

        //...
        //...
        //...
 
        // 상위 클래스에서 정의된 제공 기능
        Move( 0, 0, 0 );
    }
};
  • Activate() 는 샌드박스 메서드이므로 사용처에 맞게 자유롭게 구현할 수 있다.

 

4. 객체를 통해 메서드 제공

class FSoundPlayer
{
public:
    void PlayerSound( SoundId InSoundId, double InVolume ) {}
    void StopSound( SoundId InSoundId ) {}
    void SetVolume( SoundId, double InVolume ) {}
};

class Superpower
{
private:
    FSoundPlayer SoundPlayer;

public:
    virtual ~Superpower() {}

protected:
    virtual void Activate() = 0;

	// 제공 기능을 별도 객체를 통해 제공
    FSoundPlayer& GetSoundPlayer() { return SoundPlayer; }

    void Move( double InX, double InY, double InZ )
    {
        //..
    }
};


class SkyLaunch : public Superpower
{
protected:
    // 순수 가상함수 오버라이드
    virtual void Activate() override
    {
        // 상위 클래스에 정의된 제공 기능
		GetSoundPlayer().PlayerSound( SOUND_SPROING, 1.f );

        //...
        //...
        //...
 
        // 상위 클래스에서 정의된 제공 기능
        Move( 0, 0, 0 );
    }
};
  • 제공 기능 일부를 별도 객체를 통해 제공한다.
  • 상위 클래스의 메서드 개수를 줄일 수 있는 장점이 있다.
  • SuperPower 클래스 사운드 관련된 모든 의존을 SoundPlayer 클래스 하나에 캡슐화할 수 있어 커플링을 낮출 수 있다.
반응형
728x90

1. 바이트 코드 패턴 정의

  • 가상 머신 명령어를 인코딩한 데이터로 행동을 표현할 수 있는 유연함을 제공하는 패턴

 

2. 명령어 집합

enum Instruction
{
    INST_SET_HEALTH = 0x00,
    ISNT_SET_WISDOM = 0x01,
    INST_SET_AGILITY = 0x02,
    INST_PLAY_SOUND = 0x03,
    INST_SPAWN_PARTICLES = 0x04,
    INST_LITERAL = 0x05,
    INST_GET_HEALTH = 0x06,
    INST_GET_WISDOM = 0x07,
    INST_GET_AGILITY = 0x08,
    INST_ADD = 0x09,
};
  • 명령어는 열거형으로 표현할 수 있다.
  • 명령어들을 데이터로 인코딩하기 위해서는 열거형 값( Byte )을 배열에 저장하면 된다.
  • 데이터로 인코딩 된 명령어들은 열거형 값들의 배열, 즉 바이트들의 목록이다 보니 '바이트코드'라고 불린다.

 

3. 명령어를 해석하고 실행시키기 위한 API메서드

switch ( instruction )
    {
        case INST_SET_HEALTH:
        SetHealth( 0, 100 );
        break;
        
        case ISNT_SET_WISDOM:
        SetWisdom( 0, 100 );
        break;
        
        case INST_SET_AGILITY:
        SetAgility( 0, 100 );
        break;

        case INST_PLAY_SOUND:
        PlaySound( SoundData::SOUND_BANG );
        break;

        case INST_SPAWN_PARTICLES:
        SpawnParticles( Particle::PARTICLE_FLAME );
        break;
    }
  • 위와 같이 가상 머신( 인터프리터 )은 코드와 데이터를 연결한다.

 

4. VM에서 래핑 한 코드

class VM
{
public:
    void Interpret( char bytecode[], int size )
    {
        for ( int index = 0; index < size; ++index )
        {
            char instruction = bytecode[ index ];
            switch (instruction)
            {
                case INST_SET_HEALTH:
                // ..
                break;
            }
        }
    }
};
  • 바이트코드를 읽어 명령어를 실행한다.

 

5. 스택 머신을 이용한 명령어 실행 순서 제어

class VM
{
private:
    static const int MAX_STACK = 128;
    int stackSize;
    int stack[ MAX_STACK ];

private:
    void Push( int InValue )
    {
        // +스택 오버플로 검사
        stack[ stackSize++ ] = InValue;
    }

    int Pop()
    {
        // +스택 비어 있지 않은지 검사
        return stack[ --stackSize ];
    }

public:
    VM() : stackSize(0) {}
    void Interpret( char bytecode[], int size )
    {
        for ( int index = 0; index < size; ++index )
        {
            char instruction = bytecode[ index ];
            switch ( instruction )
            { 
               case INST_SET_HEALTH:
                {
                    int amount = Pop();
                    int wizard = Pop();
                    SetHealth( wizard, amount );
                }
                break;
                
                case INST_LITERAL:
                {
                // 바이트코드에서 다음 바이트 값을 읽는다.
                int value = bytecode[ ++index ];
                Push( value );
                }
                break;

                case INST_GET_HEALTH:
                {
                    int wizard = Pop();
                    Push( GetHealth( wizard ) );
                }
                break;

                case INST_ADD:
                {
                    int b = Pop();
                    int a = Pop();
                    Push( a + b );
                }
                break;
                
                //case ISNT_SET_WISDOM:
                //...     
                //case INST_SET_AGILITY:
                //...
                //case INST_PLAY_SOUND:
                //...
                //...
             }
        }
    }
};

명령어 및 스택 정보

  • 스택에서 값을 얻어오기 위해 리터럴 명령어를 추가한다.
  • 리터럴 값 이후에 나오는 바이트를 값으로 읽어 스택에 넣어 사용한다.
  • Get, Set 명령어를 통해 다양한 행동을 할 수 있도록 조합하여 사용한다.
반응형
728x90

1. 업데이트 메서드 패턴 정의

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

 

2. 업데이트 메서드 패턴 구현

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

// 해골
class Skeleton : public Entity
{
	virtual void Update() override 
	{
		// 갱신 내용1
	}
};

// 석상
class Statue : public Entity
{
	virtual void Update() override 
	{
		// 갱신 내용2
	}
};

class World
{
private:
	int NumEntities;
	Entity* Entities[ MAX_ENTITIES ];

public:
	World() : NumEntities ( 0 ) {}

	void GameLoop();
};

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

		// 각 개체를 업데이트 한다.
		for ( int index = 0; index < NumEntities; ++index )
			Entities[ index ]->Update();

		Render( 0 );
	}
}
  • 게임은 개체 컬렉션을 관리하며, 구현된 소스에서는 게임 월드가 개체 컬렉션을 관리하도록 했다.
  • Entity를 상속받아 Update를 인스턴스에 맞게 재정의하여 사용한다.
  • 매 프레임마다 개체들을 업데이트하는 것으로 업데이트 메서드 패턴은 단순하다.

 

반응형
728x90

1. 게임 루프 패턴 정의

  • 게임 시간 진행을 유저 입력, 프로세서 속도와 디커플링 하는 패턴.

 

2. 게임 루프 구현

	while ( true )
	{
		double start = GetCurrentTime();
		ProcessInput();
		Update();
		Render();

		// 프레임 레이드를 유지하기 위해, 다음 프레임까지 남은 시간을 기다린다.
		Sleep( start + MS_PER_FRAME - GetCurrentTime() );
	}
  • 게임이 60FPS로 돌린다면 한 프레임에 16ms가 주어진다.
  • Sleep() 함수로 한 프레임이 빨리 끝나더라도, 게임이 너무 빨라지지 않는다.
  • 하지만 한 프레임당 16ms가 넘는 경우에는 느려지는 것을 방지하지 못한다.

 

3. 업데이트 간격 따라잡기

고정 시간 간격으로 업데이트

	double previous = GetCurrentTime();
	double lag = 0.0; 	// 실제 시간이 얼마나 지났는지를 위한 변수

	while ( true )
	{
		double current = GetCurrentTime();
		double elapsed = current - previous;
		previous = current;
		lag += elapsed;

		ProcessInput();

		// 고정 시간 간격 방식으로 루프를 돌면서 실제 시간을 따라잡을 때까지 게임을 업데이트한다.
		while ( lag >= MS_PER_UPDATE )
		{
			Update();
			lag -= MS_PER_UPDATE;
		}
		
		// 업데이트와 렌더링 시점이 다른 경우 보간을 통해 게임 객체 랜더링을 진행한다.
		Render( lag / MS_PER_UPDATE );
	}
  • 이전 루프 이후로 실제 시간이 얼마나 지났는지를 확인한 후,
  • 게임의 '현재'가 실제 시간의 '현재'를 따라잡을 때까지 고정 시간 간격만큼 게임 시간을 여러 번 시뮬레이션한다.

 

반응형

+ Recent posts