728x90

1. 상태 패턴 정의

  • 객체의 내부 상태에 따라 스스로 행동을 변경할 수 있게 허가하는 패턴으로, 이렇게 하면 객체는 마치 자신의 클래스를 바꾸는 것처럼 보입니다.

 

2. 유한 상태 기계 ( FSM : Finite State Machine )

상태 기계 플로우 차트

  • 가질 수 있는 '상태'가 한정된다
  • 합 번에 '한 가지' 상태만 될 수 있다.
  • '입력'이나 '이벤트'가 기계에 전달된다.
  • 각 상태에는 입력에 따라 다음 상태로 바뀌는 '전이'가 있다.
  • 엄격하게 제한된 구조를 강제함으로써 복잡하게 얽힌 코드를 정리할 수 있게 한다.
  • 미리 정해놓은 여러 상태와 현재 상태 하나, 하드 코딩되어 있는 전이만 존재

 

3. 상태 인터페이스 구현

// 상태 인터페이스
class HeroineState
{
public:
	virtual ~HeroineState() {}
	virtual HeroineState* HandleInput( Heroine& InHeroine, INPUT InInput ) {}
	
	// 상태 입장.
	virtual void Enter( Heroine& InHeroine ) {}

	// 상태 퇴장
	virtual void Exit( Heroine& InHeroine ) {}
};

 

4. 상태별 클래스

// 상태별 클래스
class DuckingState : public HeroineState
{
public:
	virtual HeroineState* HandleInput( Heroine& InHeroine, INPUT InInput ) override
	{
		if ( InInput == INPUT::RELEASE_DOWN )
			return new StandingState();
	}

	virtual void Enter( Heroine& InHeroine ) override
	{
		// 상태 입장 시 실행될 함수들
		InHeroine.SetGraphics( STATE_IMAGE::IMAGE_DUNCKING );
	}

	virtual void Exit( Heroine& InHeroine ) override
	{
		// 상태 퇴장 시 실행될 함수들
	}
};

class StandingState : public HeroineState
{
public:
	virtual HeroineState* HandleInput( Heroine& InHeroine, INPUT InInput ) override
	{
		if ( InInput == INPUT::PRESS_DOWN )
			return new DuckingState();

		// 현재 상태 유지
		return nullptr;
	}

	virtual void Enter( Heroine& InHeroine ) override
	{
		// 상태 입장 시 실행될 함수들
		InHeroine.SetGraphics( STATE_IMAGE::IMAGE_STAND );
	}

	virtual void Exit( Heroine& InHeroine ) override
	{
		// 상태 퇴장 시 실행될 함수들
	}
};

 

5. 동작 상태 위임

class Heroine
{
private:
	HeroineState* State;

public:
	virtual void HandleInput( INPUT InInput )
	{
		HeroineState* state = State->HandleInput( *this, InInput );
		if ( state != nullptr )
		{
        	// 기존 상태 퇴장 함수 호출
			State->Exit( *this );

			delete State;
			State = state;

			// 새로운 상태의 입장 함수 호출
			State->Enter( *this );
		}		
	}

	void SetGraphics( STATE_IMAGE InStateImage );
};

 

6. 병행 상태 기계

// 병행 상태 기계
class Heroine
{
private:
	// 캐릭터 상태
	HeroineState* State;

	// 장비 상태
	HeroineState* Equipment;

public:
	virtual void HandleInput( INPUT InInput )
	{
		// 나누어진 상태 모두에게 전달
		HeroineState* state = State->HandleInput( *this, InInput );
		HeroineState* equipment = Equipment->HandleInput( *this, InInput );
		//....
	}
};
  • 유한 상태 기계 방식을 이용한 경우, 두 종류의 상태를 위해서는 N*M개의 상태가 필요하다.
  • 상태 기계 둘로 나누어 사용할 경우 N+M개의 상태만 있으면된다.

 

7. 계층형 상태 기계

// 상위 상태
class OnGroundState : public HeroineState
{
public:
	virtual HeroineState* HandleInput( Heroine& InHeroine, INPUT InInput ) override
	{
		if ( InInput == INPUT::RELEASE_DOWN )
		{
			return new StandingState();
		}
		else if ( InInput == INPUT::PRESS_DOWN )
		{
			//..
		}
	}
};

// 하위 상태
class DuckingState : public OnGroundState
{
public:
	virtual HeroineState* HandleInput( Heroine& InHeroine, INPUT InInput ) override
	{
		if ( InInput == INPUT::RELEASE_DOWN )
		{
			return new StandingState();
		}
		else
		{
			// 따로 입력을 처리하지 않고, 상위 상태로 보낸다.
			OnGroundState::HandleInput( InHeroine, InInput );
		}
	}
};
  • '땅 위에 있는' , '바다 안에 있는' 상태 등 특정 상태를 상위 클래스로 정의해 처리하는 것을 계층형 상태 기계라고 한다.
  • 어떤 상태는 상위 상태를 가질 수 있고, 그 경우 그 상태 자신은 하위 상태가 된다.
  • 이벤트가 들어올 때 하위 상태에서 처리하지 않으면, 상위 상태로 넘긴다.

 

반응형
728x90

1. 싱글턴 패턴 정의

  • 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공하는 패턴.

 

2. 싱글턴 구현

class FileSystem
{
public:
    static FileSystem& GetInstance()
    {
        static FileSystem* instance = new FileSystem();
        return *instance;
    }

private:
    // 외부에서 생성자 호출을 허용하지 않기 위해
    FileSystem() {}
};
  • 오직 한 개의 클래스 인스턴스만 갖도록 보장한다.
  • 전역 접근점 제공
  • 사용할 때 초기화되므로, 사용하지 않는다면 인스턴스를 생성하지 않는다. ( 런타임 초기화 )

 

3. 싱글턴  상속을 이용한 예시

class FileSystem
{
public:
    static FileSystem& GetInstance();

    virtual ~FileSystem();
    virtual char* ReadFile( char* InPath ) = 0;
    virtual void WriteFile( char* InPath, char* InContents ) = 0;

protected:
    FileSystem() {}
};

// Android FileSystem
class AndroidFileSystem : public FileSystem
{
    virtual char* ReadFile( char* InPath ) override;

    virtual void WriteFile( char* InPath, char* InContents) override;
};

// iOS FileSystem
class iOSFileSystem : public FileSystem
{
    virtual char* ReadFile( char* InPath ) override;

    virtual void WriteFile( char* InPath, char* InContents) override;
};

FileSystem& FileSystem::GetInstance()
{
#if PLATFORM == PLATFORM_ANDROID
    static FileSystem* instance = new AndroidFileSystem();
#elif PLATFORM == PLATFORM_iOS
    static FileSystem* instance = new iOSFileSystem();
#endif
    return *instance;
}
  • #if 전처리기 지시문을 이용해 컴파일러가 시스템에 맞는 파일 시스템 객체를 만들게 할 수 있다.

 

4. 싱글턴 문제점

  • 전역 변수가 접근하는 곳을 모두 확인해야 하므로, 코드를 이해하기 어렵게 한다.
  • 인스턴스에 대한 접근 통제가 없으므로, 커플링을 조장한다.
  • 다른 스레드가 전역 변수에 어떤 작업을 하는지 모를 때가 있어 데드락이 생기기 쉽다.
반응형
728x90

1. 프로토타입 패턴 ( Prototype Pattern ) 정의

  • 원형이 되는 인스턴스를 사용하여 생성할 객체의 종류를 명시하고, 이렇게 만든 견본을 복사해서 새로운 객체를 생성하는 패턴.

 

2. JSON을 이용한 게임 데이터 모델링

{
    "이름": "고블린 보병",
    "기본체력": 20,
    "최대체력": 30,
    "내성": ["추위", "독"],
    "약점": ["불", "빛"]
}

{
    "이름": "고블린 마법사",
    "기본체력": 20,
    "최대체력": 30,
    "내성": ["추위", "독"],
    "약점": ["불", "빛"],
    "마법": ["화염구", "번개 화살"]
}

{
    "이름": "고블린 궁수",
    "기본체력": 20,
    "최대체력": 30,
    "내성": ["추위", "독"],
    "약점": ["불", "빛"],
    "공격방법": ["단궁"]
}

 

  • 기본체력, 최대체력, 내성, 약점 등 데이터가 중복되고 있다.

 

3. JSON을 이용한 게임 데이터 모델링 - 프로토타입

{
    "이름": "고블린 보병",
    "기본체력": 20,
    "최대체력": 30,
    "내성": ["추위", "독"],
    "약점": ["불", "빛"]
}

{
    "이름": "고블린 마법사",
    "프로토타입": "고블린 보병",
    "마법": ["화염구", "번개 화살"]
}

{
    "이름": "고블린 궁수",
    "프로토타입": "고블린 보병",
    "공격방법": ["단궁"]
}
  • 원형이 되는 "고블린 보병" 인스턴스를 이용한 프로토타입 패턴 예시
  • 기본체력, 최대체력, 내성, 약점 등 중복되는 데이터를 중복해서 사용하지 않도록 수정되었다
반응형
728x90

1. 관찰자 패턴 ( Observer Pattern ) 정의

  • 객체 사이에 일 대 다의 의존 관계를 정의해두어, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지받고 자동으로 업데이트될 수 있게 만드는 패턴

 

2. 관찰자 인터페이스 정의

// 옵저버 클래스
class Observer
{
public:
    virtual ~Observer() {}

    virtual void OnNotify( const Entity& InEntity, Event InEvent ) = 0;
};

 

3. 관찰자 인터페이스 구현

// 업적 시스템에 옵저버 인터페이스 구현 추가
class Achievements : public Observer
{
public:
    virtual void OnNotify( const Entity& InEntity, Event InEvent ) override;
};

void Achievements::OnNotify( const Entity& InEntity, Event InEvent )
{
    switch ( InEvent )
    {
    case EVENT_LEVELUP:
        if ( InEntity.IsLevelUP() )
            // 레벨업 업적 이벤트 처리
        break;
    case EVENT_KILL_MONSTER:
        if ( InEntity.IsKillMonster())
             // 몬스터킬 업적 이벤트 처리
        break;
    }
}
  • 관찰자 인터페이스를 각각 시스템에 맞춰 정의하여 사용.

 

4. 관찰 대상 정의

// 관찰 대상자
class Subject
{
private:
    std::list< Observer* > observers;

protected:
    void Notify( const Entity& InEntity, Event InEvent );

public:
    // 관찰 대상을 추가한다.
    void AddObserver( Observer* InObserver );

    // 관찰 대상을 제거한다.
    void RemoveObserver( Observer* InObserver );
};

void Subject::Notify( const Entity& InEntity, Event InEvent )
{
    for ( auto& iter : observers)
		iter->OnNotify( InEntity, InEvent );
}
  • 관찰 대상 리스트에 추가 / 삭제하여 관찰 당하는 객체 OnNotity()를 호출한다.
  • 관찰 대상은 관찰자와 상호작용하지만, 서로 커플링되어 있지 않다.

 

5. 사라진 리스너 문제 ( Lapsed Listener Problem ) ★

  • UI를 열 때 관찰자를 등록한 뒤, UI를 닫을 때 관찰자를 제거하지 않으면 발생하는 문제
  • UI를 닫은 상태에서는 UI를 갱신할 필요가 없지만 관찰자를 제거하지 않아 알림이 올 때마다 UI가 갱신된다. ( CPU 연산 낭비 )
  • UI를 매번 새로 생성한 뒤, 관찰자를 등론한 경우 관찰 대상자에 메모리가 참조되고 있어 GC가 돌지 않는 문제가 발생한다.
  • ex ) 아이템 획득, 사용 등 빈번하게 발생하는 이벤트에서 가방을 갱신하도록 관찰자를 등록하고, 제거하지 않은 경우
반응형

+ Recent posts