C ++ 엔진 프로그래밍에서 싱글 톤을 올바르게 사용하려면 어떻게해야합니까?


16

싱글 톤이 나쁘다는 것을 알고 있습니다. 오래된 게임 엔진은 모든 데이터를 보유하는 것부터 실제 게임 루프까지 모든 것을 처리하는 싱글 톤 'Game'객체를 사용했습니다. 지금 나는 새로운 것을 만들고 있습니다.

문제는 SFML window.draw(sprite)에서 window가 인 곳 에서 무언가를 그리는 것입니다 sf::RenderWindow. 여기에 두 가지 옵션이 있습니다.

  1. 게임의 모든 엔티티가 검색하는 싱글 톤 게임 오브젝트를 만듭니다 (이전에 사용한 것).
  2. 이것을 엔티티의 생성자로 만드십시오 : Entity(x, y, window, view, ...etc)(이것은 말도 안되고 성가시다)

엔티티의 생성자를 x와 y로 유지하면서이를 수행하는 올바른 방법은 무엇입니까?

메인 게임 루프에서 내가 만드는 모든 것을 추적하고 시도 할 수 있으며 게임 루프에서 스프라이트를 수동으로 그립니다.하지만 너무 지저분 해 보이고 엔티티의 전체 드로우 기능을 완전히 제어하고 싶습니다.


1
창을 '렌더링'함수의 인수로 전달할 수 있습니다.
dari December

25
싱글 톤은 나쁘지 않습니다! 그들은 유용하고 때로는 필요할 수 있습니다 (물론 논쟁의 여지가 있습니다).
ExOfDe

3
싱글 톤을 평범한 전역으로 바꾸십시오. "필요에 따라"전 세계적으로 필요한 리소스를 생성 할 필요가 없으며 리소스를 전달할 필요도 없습니다. 엔티티의 경우 "레벨"클래스를 사용하여 모든 엔티티와 관련된 특정 항목을 보유 할 수 있습니다.
snake5

내 창에서 내 창과 다른 종속성을 선언 한 다음 다른 클래스에 포인터가 있습니다.
KaareZ

1
@JAB main ()의 수동 초기화로 쉽게 고정됩니다. 초기화 지연은 알 수없는 순간에 발생하므로 핵심 시스템에는 좋은 아이디어가 아닙니다.
snake5

답변:


3

스프라이트를 각 엔티티 내부에 렌더링하는 데 필요한 데이터 만 저장 한 다음 엔티티에서 검색하여 렌더링을 위해 창으로 전달하십시오. 엔티티 내부에 창을 보거나 데이터를 볼 필요가 없습니다.

레벨 클래스 (현재 사용중인 모든 엔티티 를 보유)를 보유하는 최상위 레벨 게임 또는 엔진 클래스 와 렌더러를 가질 수 있습니다. 클래스 (창, 뷰 및 렌더링을위한 기타 요소 포함)를 보유 할 수 있습니다.

따라서 최상위 클래스의 게임 업데이트 루프는 다음과 같습니다.

EntityList entities = mCurrentLevel.getEntities();
for(auto& i : entities){
  // Run game logic...
  i->update(...);
}
// Render all the entities
for(auto& i : entities){
  mRenderer->draw(i->getSprite());
}

3
싱글 톤에 대한 이상적인 것은 없습니다. 왜 구현 내부를 공개하지 않아도 되나요? 왜 Logger::getInstance().Log(...)대신에 쓰지 Log(...)않겠습니까? 한 번만 수동으로 할 수 있는지 묻는 메시지가 표시되면 왜 클래스를 무작위로 초기화합니까? 정적 전역을 참조하는 전역 함수는 작성 및 사용이 훨씬 간단합니다.
snake5

@ snake5 Stack Exchange에서 싱글 톤을 정당화하는 것은 Hitler를 동정하는 것과 같습니다.
Willy Goat

30

간단한 접근법은 Singleton<T>전 세계적으로 사용했던 것을 만드는 것입니다.T 대신 . 글로벌에도 문제가 있지만 사소한 제약 조건을 적용하기위한 추가 작업 및 상용구 코드를 나타내지는 않습니다. 이것은 기본적으로 엔티티 생성자를 만지지 않는 유일한 솔루션입니다.

더 어렵지만 더 나은 방법은 의존성을 필요한 곳에 전달하는 것 입니다. 예, 여기에는Window * 보이는 것처럼 여러 개체 (예 : 엔티티)를 . 디자인이 거칠어 보일 수 있습니다.

이것이 더 어려운 이유는 (더 많은 타이핑을 포함하여) 인터페이스를 리팩토링하기 때문에 리프 레벨 클래스가 적을수록 전달해야 할 것이 필요하기 때문입니다. 이것은 렌더러를 모든 것에 옮길 때 발생하는 많은 추악함을 없애고 의존성 및 커플 링의 양을 줄임으로써 코드의 일반적인 유지 관리 성을 향상시킵니다. . 종속성이 싱글 톤 또는 글로벌 일 때는 시스템이 어떻게 상호 연결되어 있는지 분명하지 않았습니다.

그러나 그것은 잠재적으로 전공입니다 사업입니다. 사실 후에 시스템에이를 수행하는 것은 매우 고통 스러울 수 있습니다. 싱글 톤을 사용하여 시스템을 그대로 두는 것이 훨씬 실용적 일 수 있습니다. 거기에 싱글 톤 또는 4 개).

기존 디자인으로이 작업을 수행하려는 경우 이러한 변경을 수행하기위한 일반적인 점검 목록이 없기 때문에 현재 구현에 대한 자세한 정보를 게시해야 할 수 있습니다. 또는 채팅으로 토론하십시오 .

당신이 게시 한 것에서, 나는 "싱글 톤 없음"방향의 큰 단계는 당신의 엔티티가 창이나보기에 액세스 할 필요를 피하는 것이라고 생각합니다. 그것들은 스스로 그릴 것을 제안하며, 외부 시스템 (창 및보기 참조가있는)에 의해 그려지기 위해 엔티티를 그릴 필요가 없습니다 . 엔티티가 허용 할 정보 만 포함하는 방법론을 채택 할 수 있습니다. . 엔티티는 그 위치와 그것이 사용해야 할 스프라이트 (또는 복제 인스턴스 자체를 피하기 위해 렌더러 자체에서 실제 스프라이트를 캐시하려는 경우 해당 스프라이트에 대한 일종의 참조)를 노출합니다. 렌더러는 단순히 특정 엔터티 목록을 그리도록 지시되는데,이 목록은 루프를 통해 데이터를 읽고 내부에서 보유한 창 객체를 사용 draw하여 엔터티를 조회 한 스프라이트 를 호출 합니다.


3
C ++에 익숙하지 않지만이 언어에 대한 편안한 의존성 주입 프레임 워크가 있습니까?
bgusach

1
나는 그들 중 어느 것도 "편안한"것으로 묘사하지 않을 것이고, 그것들이 일반적으로 특히 유용하다고 생각하지는 않지만, 다른 사람들은 그들과 다른 경험을 가지고 있기 때문에 그것들을 키우는 것이 좋습니다.

1
그가 설명하는 방법은 엔터티가 스스로를 끌어 들이지 않고 정보를 보유하도록 만들고 모든 엔터티를 그리는 단일 시스템 핸들은 오늘날 가장 인기있는 게임 엔진에서 많이 사용됩니다.
Patrick W. McMahon

1
"거친 것처럼 보인다는 사실은 무언가를 말해줘야합니다. 디자인이 거칠 수 있습니다."
Shadow503

이상적인 경우와 실용적인 답변을 제공하는 +1

6

sf :: RenderWindow에서 상속

SFML은 실제로 클래스에서 상속하도록 권장합니다.

class GameWindow: public sf::RenderWindow{};

여기에서 도면 도면 요소에 대한 구성원 도면 함수를 작성합니다.

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity);
};

이제 당신은 이것을 할 수 있습니다 :

GameWindow window;
Entity entity;

window.draw(entity);

엔터티가 sf :: Sprite에서 상속하도록하여 고유 한 스프라이트를 유지하려는 경우이 단계를 더 멀리 진행할 수도 있습니다.

class Entity: public sf::Sprite{};

지금 sf::RenderWindow바로 엔티티을 그릴 수 있고, 기관은 지금과 같은 기능을 가지고 setTexture()setColor(). 엔터티는 스프라이트의 위치를 ​​자체 위치로 사용할 수도 setPosition()있으므로 엔터티와 스프라이트를 이동하는 기능 을 사용할 수 있습니다 .


결국 , 당신이 방금 가지고 있다면 꽤 좋습니다 :

window.draw(game);

다음은 몇 가지 간단한 구현 예입니다.

class GameWindow: public sf::RenderWindow{
 sf::Sprite entitySprite; //assuming your Entities don't need unique sprites.
public:
 void draw(const Entity& entity){
  entitySprite.setPosition(entity.getPosition());
  sf::RenderWindow::draw(entitySprite);
 }
};

또는

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity){
  sf::RenderWindow::draw(entity.getSprite()); //assuming Entities hold their own sprite.
 }
};

3

당신은 게임 개발에 당신이 소프트웨어 개발의 모든 다른 종류의 그들을 피하기 같은 방법으로 싱글을 피하기 : 당신이 종속성을 전달합니다 .

그 방법으로 종속성을 베어 유형으로 직접 전달하도록 선택할 수 있습니다 (예 : int , Window*등)하거나 (같은 종류의 랩퍼 하나 이상의 사용자 정의에 전달할 선택할 수 있습니다 EntityInitializationOptions).

전자의 방법은 성가 시게 할 수 있지만, 후자는 하나의 객체로 모든 것을 전달하고 모든 엔터티 생성자를 변경하지 않고도 필드를 수정하고 옵션 유형을 특수화 할 수 있습니다. 나는 후자의 방법이 더 낫다고 생각합니다.


3

싱글 톤은 나쁘지 않습니다. 대신 그들은 학대하기 쉽습니다. 반면에 세계는 학대하기가 훨씬 쉬우 며 더 많은 문제가 있습니다.

싱글 톤을 글로벌로 교체하는 유일한 이유는 종교적인 싱글 톤 증오를 진정시키기 위해서입니다.

문제는 단일 글로벌 인스턴스 만 존재하는 클래스를 포함하고 어디에서나 액세스 할 수있는 클래스를 포함하는 디자인을 갖는 것입니다. 예를 들어 분할 화면을 구현하는 게임 또는 단일 로거가 항상 그렇게 좋은 아이디어가 아닌 경우 충분히 큰 엔터프라이즈 응용 프로그램에서 단일 인스턴스의 여러 인스턴스를 갖게되면 즉시 분리됩니다. .

결론적으로, 참조로 합리적으로 통과 할 수없는 단일 글로벌 인스턴스가있는 클래스가 있다면 singleton은 종종 차선책 솔루션 풀에서 더 나은 솔루션 중 하나입니다.


1
나는 종교적인 싱글 톤 증오이며 글로벌 솔루션도 고려하지 않습니다. : S
Dan Pantry

1

의존성을 주입하십시오. 이렇게하면 공장을 통해 다양한 유형의 종속성을 만들 수 있다는 이점이 있습니다. 불행히도, 싱글 톤을 사용하는 클래스에서 싱글 톤을 추출하는 것은 카펫을 가로 지르는 뒷다리로 고양이를 잡아 당기는 것과 같습니다. 그러나 그것들을 주입하면 구현을 즉시 바꿀 수 있습니다.

RenderSystem(IWindow* window);

이제 다양한 유형의 창을 주입 할 수 있습니다. 이를 통해 다양한 유형의 창으로 RenderSystem에 대한 테스트를 작성할 수 있으므로 RenderSystem이 어떻게 깨지거나 수행되는지 확인할 수 있습니다. "RenderSystem"내에서 싱글 톤을 직접 사용하는 경우 불가능하거나 더 어려워집니다.

이제 더 테스트 가능하고 모듈 식이며 특정 구현과 분리되어 있습니다. 구체적인 구현이 아니라 인터페이스에만 의존합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.