게임 오브젝트의 모든 인스턴스에 DrawableGameComponent를 사용하는 단점은 무엇입니까?


25

캐릭터 나 타일과 같이 DrawableGameComponents를 "레벨"과 같은 물건이나 관리자 대신 사용하기 위해 저장해야하는 곳을 여러 곳에서 읽었습니다 (이 사람이 여기에서 말하는 것처럼 ). 그러나 이것이 왜 그런지 이해하지 못합니다. 나는 이 포스트를 읽었고 그것은 나에게 많은 의미가 있었다. 그러나 이것들은 소수이다.

나는 보통 이런 것들에 너무 많은 관심을 기울이지 않을 것이지만,이 경우에 명백한 대다수가 왜 이것이 갈 길이 아니라고 믿는지 알고 싶습니다. 어쩌면 뭔가 빠졌을 수도 있습니다.


2 년 후 왜 내 답변 에서 "허용 된"표시를 제거했는지 궁금합니다 . 일반적으로 나는 정말로 신경 쓰지 않을 것입니다-그러나이 경우 VeraShackle의 상단에 거품에 대한 나의 대답에 대한 분노한 오해가 발생 합니다 :(
Andrew Russell

@AndrewRussell 나는 오래 전에 (일부 응답과 투표로 인해)이 스레드를 읽었으며 귀하의 답변이 올바른지 아닌지에 대한 의견을 제시 할 자격이 없다고 생각했습니다. 당신이 말했듯이, 나는 VeraShackle의 대답이 가장 많이 찬성해야한다고 생각하지 않으므로 답을 다시 올바른 것으로 표시하고 있습니다.
Kenji Kina

1
다음과 같이 대답을 요약 할 수 있습니다. " 사용 DrawableGameComponent하면 단일 메소드 서명 세트와 특정 보유 데이터 모델로 잠금됩니다. 일반적으로 처음에는 잘못된 선택이며 잠금은 코드의 미래를 크게 방해합니다. "하지만 여기서 무슨 일이 일어나고 있는지 말씀 드리겠습니다 ...
Andrew Russell

1
DrawableGameComponent핵심 XNA API의 일부입니다 (다시 : 대부분 역사적 이유로). 초보자 프로그래머는 "있고"작동하기 때문에 더 잘 알지 못하기 때문에 사용합니다. 그들은 내 대답이 그들이 " 잘못되었다 "고 말하고 -인간 심리학의 본질 때문에-그것을 거부하고 다른 "측면"을 고릅니다. VeraShackle의 논증이 아닌 대답은 무엇입니까? 내가 즉시 부르지 않았기 때문에 추진력을 얻었습니다 (댓글 참조). 나는 결국 내 반박이 충분하다고 느낀다.
Andrew Russell

1
누군가가 공감에 근거하여 내 대답의 정당성을 질문하는 데 관심이 있다면 : (GDSE는 앞서 언급 한 초보자들과 함께 밝히고 있습니다) VeraShackle의 답변이 최근 몇 년 동안 내 것보다 몇 가지 더 많은 공의를 얻었습니다. 전 XNA에서 네트워크 전체에 수천 개의 투표를합니다. 여러 플랫폼에서 XNA API를 구현했습니다. 저는 XNA를 사용하는 전문 게임 개발자입니다. 내 대답의 가계도는 완벽합니다.
Andrew Russell

답변:


22

"게임 구성 요소"는 XNA 1.0 디자인의 초기 단계였습니다. 그들은 WinForms에 대한 컨트롤처럼 작동해야했습니다. 아이디어는 GUI를 사용하여 게임에 드래그 앤 드롭하고 (비 시각적 컨트롤을 추가하는 비트와 같은 타이머 등) 속성을 설정할 수 있다는 것입니다.

카메라 또는 FPS 카운터와 같은 구성 요소를 가질 수 있습니까?

알 겠어? 불행히도이 아이디어는 실제 게임에는 적용되지 않습니다. 게임에 필요한 구성 요소는 실제로 재사용 할 수 없습니다. "플레이어"구성 요소가있을 수 있지만 다른 모든 게임의 "플레이어"구성 요소와는 매우 다릅니다.

이런 이유로 GUI가 XNA 1.0 이전에 풀려 났다고 생각합니다.

그러나 API는 여전히 사용 가능합니다. 다음은 사용하지 않아야하는 이유입니다.

기본적으로 게임 개발자로서 작성하고 읽고 디버그하고 유지 관리하는 것이 가장 쉬운 방법으로 이어집니다. 로드 코드에서 다음과 같은 작업을 수행하십시오.

player.DrawOrder = 100;
enemy.DrawOrder = 200;

단순히 이것을하는 것보다 훨씬 덜 명확합니다.

virtual void Draw(GameTime gameTime)
{
    player.Draw();
    enemy.Draw();
}

특히 실제 게임에서 다음과 같은 결과가 나타날 수 있습니다.

GraphicsDevice.Viewport = cameraOne.Viewport;
player.Draw(cameraOne, spriteBatch);
enemy.Draw(cameraOne, spriteBatch);

GraphicsDevice.Viewport = cameraTwo.Viewport;
player.Draw(cameraTwo, spriteBatch);
enemy.Draw(cameraTwo, spriteBatch);

(아마도 유사한 Services아키텍처를 사용하여 카메라와 spriteBatch를 공유 할 수 있습니다 . 그러나 이것은 같은 이유로 나쁜 생각입니다.)

이 아키텍처 또는 그 부족 은 훨씬 더 가볍고 유연합니다!

몇 줄만 바꾸면 그리기 순서를 쉽게 변경하거나 여러 뷰포트를 추가하거나 렌더링 대상 효과를 추가 할 수 있습니다. 다른 시스템에서 할 것은 나를 필요로 생각 하여 여러 파일에 걸쳐 확산 - - 모든 구성 요소가 무엇인지에 대한 일을, 어떤 순서로한다.

선언적 프로그래밍과 명령형 프로그래밍의 차이점과 비슷합니다. 게임 개발에는 명령형 프로그래밍이 훨씬 더 바람직합니다.


2
나는 그들이 게임 프로그래밍에 널리 사용되는 Component-based programming 이라는 인상을 받았다 .
BlueRaja-대니 Pflughoeft

3
XNA 게임 컴포넌트 클래스는 실제로 해당 패턴에 직접 적용 할 수 없습니다. 이 패턴은 여러 구성 요소로 객체를 구성하는 것입니다. XNA 클래스는 객체 당 하나의 구성 요소에 맞춰져 있습니다. 그들이 당신의 디자인에 완벽하게 맞다면, 반드시 그것들을 사용하십시오. 그러나 당신은 그들 주위에 디자인을 구축 해서는 안됩니다 ! 여기 내 답변 도 참조 하십시오 -내가 언급 한 곳을 찾으십시오 DrawableGameComponent.
Andrew Russell

1
GameComponent / Service 아키텍처가 그다지 유용하지 않으며 Office와 같은 웹 응용 프로그램 개념이나 게임 프레임 워크에 강제로 적용되는 것처럼 들립니다. 그러나 나는 player.DrawOrder = 100;그리기 순서에 명령 식보다 메소드 를 사용하는 것을 훨씬 선호합니다 . Flixel 게임을 마무리하고 있는데, 씬에 추가하는 순서대로 오브젝트를 그립니다. FlxGroup 시스템은이 중 일부를 완화하지만 일종의 Z- 인덱스 정렬 기능이 내장되어 있으면 좋을 것입니다.
michael.bartnett

@ michael.bartnett Flixel은 유지 모드 API이고 XNA (게임 구성 요소 시스템 제외)는 즉시 모드 API입니다. 유지 모드 API를 사용하거나 자신 만의 API를 구현 하는 경우 그리기 순서를 즉시 변경할 수있는 기능이 반드시 중요합니다. 실제로, 그리기 순서를 변경해야 할 필요가 있기 때문에 무언가를 유지 모드로 만들어야합니다 (윈도우 시스템의 예가 떠 오릅니다). 그러나 즉시 모드로 무언가를 작성할 있다면 (XNA와 같은) 플랫폼 API가 그렇게 구축되면 IMO를 사용해야합니다.
Andrew Russell


26

흠. 여기에 약간의 균형을 위해이 것에 도전해야합니다.

Andrew의 답변에는 5 건의 청구가있는 것 같습니다. 따라서 차례로 하나씩 처리해 보겠습니다.

1) 구성 요소가 오래되고 버려진 디자인입니다.

컴포넌트 디자인 패턴에 대한 아이디어는 XNA 이전에 존재했습니다. 본질적으로 단순히 자신 만의 업데이트 및 그리기 동작의 홈인 오버-아키 게임 오브젝트가 아닌 오브젝트를 만드는 결정입니다. 컴포넌트 컬렉션에있는 오브젝트의 경우, 게임은 적절한 시간에 이러한 메소드 (및 다른 두 가지)를 자동으로 호출합니다. 결정적으로 게임 오브젝트 자체는이 코드를 '알지'않아도됩니다. 다른 게임 (및 다른 게임 오브젝트)에서 게임 클래스를 재사용하려면이 오브젝트 분리가 여전히 기본적으로 좋은 아이디어입니다. AppHub에서 XNA4.0의 최신 (전문적으로 제작 된) 데모를 간략하게 살펴보면 Microsoft가 여전히이 문제에 뒤지지 않고 있다는 인상을 받았습니다.

2) Andrew는 재사용 가능한 게임 클래스를 2 개 이상 생각할 수 없습니다.

제가 할수 있어요. 실제로 나는 짐을 생각할 수 있습니다! 방금 현재 공유 라이브러리를 확인했으며 80 개가 넘는 재사용 가능한 구성 요소 및 서비스로 구성되어 있습니다. 맛을 내기 위해 다음을 할 수 있습니다.

  • 게임 상태 / 스크린 관리자
  • 파티클 이미 터
  • 드로어 블 프리미티브
  • AI 컨트롤러
  • 입력 컨트롤러
  • 애니메이션 컨트롤러
  • 빌보드
  • 물리 및 충돌 관리자
  • 카메라

특정 게임 아이디어는이 세트에서 5 ~ 10 개 이상의 항목이 필요하며, 한 줄의 코드로 완전히 새로운 게임에서 완벽하게 작동 할 수 있습니다.

3) 구성 요소의 DrawOrder 속성을 사용하는 것보다 명시적인 그리기 호출 순서에 따라 그리기 순서를 제어하는 ​​것이 좋습니다.

글쎄, 나는 기본적으로 Andrew와 이것에 동의합니다. 그러나 구성 요소의 DrawOrder 속성을 모두 기본값으로 유지하면 컬렉션에 추가하는 순서에 따라 그리기 순서를 명시 적으로 제어 할 수 있습니다. 이것은 수동 가시 호출의 명시 적 순서와 '가시성'측면에서 실제로 동일합니다.

4) 객체의 Draw 메소드로드를 직접 호출하는 것은 기존 프레임 워크 메소드에서 호출하는 것보다 '가벼운'것입니다.

왜? 또한 가장 사소한 프로젝트를 제외하고는 Update logic 및 Draw 코드가 성능 저하 측면에서 메소드 호출을 완전히 어둡게합니다.

5) 디버깅 할 때 개별 객체의 파일에있는 것보다 하나의 파일에 코드를 배치하는 것이 좋습니다.

글쎄, 첫째, Visual Studio에서 디버깅 할 때 디스크의 파일 측면에서 중단 점의 위치는 요즘 거의 보이지 않습니다 .IDE는 드라마없이 위치를 처리합니다. 그러나 Skybox 도면에 문제가있는 순간도 고려하십시오. 스카이 박스의 그리기 방법 것입니다 정말 제 (아마도 매우 긴)에 코드를 그리기 스카이 박스의 위치를보다 더 부담 마스터 게임 객체 그리기 방법을? 아니, 사실은 아니다-사실 나는 그것이 정반대라고 주장 할 것이다.

요약하자면 앤드류의 주장을 오랫동안 열심히 살펴보십시오. 나는 그들이 실제로 얽힌 게임 코드를 작성하는 실질적인 근거를 제공한다고 생각하지 않습니다. 분리 된 구성 요소 패턴 (상당히 사용)의 장점은 방대합니다.


2
나는 단지이 게시물을 알아 차렸고 나는 강력한 반박을 발표해야한다. 나는 재사용 가능한 수업에 문제가 없다! (그리기 및 업데이트 방법 등이있을 수 있습니다.) 나는 드로우 / 업데이트 순서 (# 3에서 동의하는)에 대한 명시 적 제어의 손실로 인해 게임 아키텍처를 제공하기 위해 ( ) 사용과 관련된 특정 문제를 겪고 있습니다. 그들의 인터페이스 (당신이 해결하지 않은). DrawableGameComponent
앤드류 러셀

1
요점 # 4는 성능을 의미하기 위해 "경량"이라는 단어를 사용하는데, 실제로 건축 적 유연성을 분명히 의미합니다. 남은 포인트 # 1, # 2, 특히 # 5는 대부분의 / 모든 코드를 메인 게임 클래스에 적용해야한다고 생각 하는 터무니없는 짚맨 을 세우는 것 같습니다 ! 아키텍처에 대한 좀 더 자세한 생각은 여기 에서 내 대답을 읽으십시오 .
앤드류 러셀

5
마지막으로, 내 대답에 대한 답변을 게시하고 내가 틀렸다고 말하면 내 대답에 대한 답장을 추가하여 답글을 쓰지 않고 이에 대한 알림을 볼 수 있습니다. 그것은 이개월 이상.
앤드류 러셀

나는 오해했을 수도 있지만 이것이 어떻게 질문에 대답합니까? 스프라이트에 GameComponent를 사용해야합니까?
Superbest


4

나는 Andrew와 약간 동의하지 않지만 모든 것을위한 시간과 장소가 있다고 생각합니다. Drawable(GameComponent)Sprite 나 Enemy만큼 단순한 것을 사용하지는 않을 것 입니다. 그러나, 나는 그것을 사용하는 것이 SpriteManager거나 EnemyManager.

Andrew가 그리기 및 업데이트 순서에 대해 말한 내용으로 돌아가서 Sprite.DrawOrder많은 스프라이트에 대해 매우 혼란 스러울 것이라고 생각 합니다. 또한, 비교적 간편한 위로 설정 DrawOrder하고 UpdateOrder있는 관리자를위한 작은 (또는 적당한) 양 (Drawable)GameComponents.

마이크로 소프트가 실제로 그렇게 딱딱하고 아무도 그것을 사용하지 않았다면 그들과 함께했을 것이라고 생각합니다. 그러나 역할이 옳다면 완벽하게 작동하기 때문은 아닙니다. 나는 나 자신을 사용 Game.Services하여 백그라운드에서 업데이트 된 것을 개발 합니다.


2

나는 이것에 대해서도 생각하고 있었고 그것은 나에게 일어났다 : 예를 들어, 링크에서 예제를 훔치기 위해, RTS 게임에서 모든 단위를 게임 구성 요소 인스턴스로 만들 수 있습니다. 괜찮아.

그러나 UnitManager 구성 요소를 만들 수도 있습니다. 그러면 구성 요소 목록을 유지하고 필요에 따라 구성 요소를 그리고 업데이트합니다. 처음에 유닛을 게임 구성 요소로 만들고 싶은 이유는 코드를 구획화하는 것이므로이 솔루션이 그 목표를 적절하게 달성 할 수 있습니까?


2

의견에, 나는 이전 답변의 요약 버전을 게시했습니다.

를 사용하면 DrawableGameComponent단일 메소드 서명 세트 및 특정 보유 데이터 모델에 고정됩니다. 이것들은 일반적으로 처음에는 잘못된 선택이며 잠금 기능은 코드의 진화를 크게 방해합니다.

여기에서는 현재 프로젝트에서 일부 코드를 게시하여 이것이 왜 그런지 설명하려고 시도합니다.


게임 세계의 상태를 유지하는 루트 객체에는 Update다음과 같은 방법이 있습니다.

void Update(UpdateContext updateContext, MultiInputState currentInput)

Update게임이 네트워크에서 실행 중인지 여부에 따라 여러 가지 진입 점이 있습니다. 예를 들어 게임 상태를 이전 시점으로 롤백 한 다음 다시 시뮬레이션 할 수 있습니다.

다음과 같은 추가 업데이트와 같은 방법도 있습니다.

void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)

게임 상태에는 업데이트 할 액터 목록이 있습니다. 그러나 이것은 단순한 Update호출 이상 입니다. 물리를 다루기 전후에 추가적인 패스가 있습니다. 액터의 연기 스폰 / 파괴뿐만 아니라 레벨 전환에도 멋진 기능이 있습니다.

게임 상태 이외에는 독립적으로 관리해야하는 UI 시스템이 있습니다. 그러나 UI의 일부 화면은 네트워크 컨텍스트에서 실행될 수 있어야합니다.


게임의 특성상 그리기에는 다음과 같은 방법으로 입력을받는 사용자 정의 정렬 및 컬링 시스템이 있습니다.

virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)

그리고 나서 무엇을 그릴 지 알아 내면 자동으로 다음을 호출합니다.

virtual void Draw(DrawContext drawContext, int tag)

tag매개 변수는 어떤 배우가 다른 배우에 저장할 수 있기 때문에 필요합니다 - 그리고 필요하므로 보류 배우 위와 아래 모두 그릴 수 있도록. 정렬 시스템은 이것이 올바른 순서로 이루어 지도록합니다.

그릴 메뉴 시스템도 있습니다. 그리고 HUD 주변, 게임 주변에 UI를 그리는 계층 적 시스템.


이제 이러한 요구 사항을 DrawableGameComponent제공 하는 내용과 비교하십시오 .

public class DrawableGameComponent
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
    public int UpdateOrder { get; set; }
    public int DrawOrder { get; set; }
}

DrawableGameComponent위에서 설명한 시스템을 사용하여 시스템을 얻는 합리적인 방법은 없습니다 . (그리고 그것들은 다소 복잡한 게임에 대한 특별한 요구 사항이 아닙니다).

또한 이러한 요구 사항은 프로젝트 시작시 단순히 시작되지 않았다는 점에 유의해야합니다. 그들은 수개월에 걸친 개발 작업을 통해 진화했습니다.


사람들이 일반적으로 DrawableGameComponent경로를 시작하면 해킹을 시작합니다.

  • 메소드를 추가하는 대신 자신의 기본 유형으로 추악한 다운 캐스팅을 수행합니다 (직접 사용하지 않는 이유는 무엇입니까?).

  • 정렬 및 호출 Draw/ 의 코드를 바꾸는 대신 Update주문 번호를 즉시 변경하기 위해 매우 느린 작업을 수행합니다.

  • 그리고 최악의 방법 : 메소드에 매개 변수를 추가하는 대신, 스택이 아닌 메모리 위치에서 "파라미터"를 전달하기 시작합니다 (이 용도로 Services널리 사용됩니다). 이것은 복잡하고 오류가 발생하기 쉽고 느리고 깨지기 쉽고 스레드 안전성이 거의 없으며 네트워킹을 추가하거나 외부 도구를 만드는 등의 경우 문제가 될 수 있습니다.

    또한 API 경계에 게시되는 대신 종속성이 "구성 요소"안에 숨겨지기 때문에 코드 재사용이 더 어려워 집니다.

  • 또는 그들은 이 모든 것이 얼마나 미친 지 거의 알고 있으며 DrawableGameComponent이러한 문제를 해결하기 위해 "관리자"를 시작 합니다. "관리자"경계에서 여전히 이러한 문제가 있습니다.

    이러한 "관리자"는 원하는 순서대로 일련의 메소드 호출로 쉽게 대체 할 수 있습니다. 다른 것은 너무 복잡합니다.

물론 일단 해킹을 사용하기 시작 하면 제공하는 것보다 더 많은 것이 필요하다는 것을 알게되면 해킹 을 취소 하기 위해 실제 작업 이 필요 DrawableGameComponent합니다. 당신은 " 잠겨 있습니다 ". 그리고 유혹은 종종 해킹에 계속 쌓이는 경향이 있습니다. 실제로 해킹에 빠지고 상대적으로 단순한 게임으로 만들 수있는 게임의 종류를 제한합니다.


많은 사람들이이 시점에 도달하고 내장 반응을 보이는 것 같습니다. 아마도 그들이 DrawableGameComponent"잘못된"것을 받아들이고 싶지 않기 때문일 것 입니다. 그래서 그들은 적어도 그것이 "작동"하게하는 것이 가능 하다는 사실을 고수합니다 .

물론 이 될 수있다 만든 "작업"에! 우리는 프로그래머입니다. 비정상적인 구속 조건에서 일을하는 것은 실질적으로 우리의 일입니다. 그러나이 구속은 필요하지 않거나 유용하거나 합리적이지 않습니다 ...


특히 불쾌감 을주는 것은 이 전체 문제를 회피 하는 것이 놀랍도록 사소한 것입니다. 여기, 나는 당신에게 코드를 줄 것이다 :

public class Actor
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
}

List<Actor> actors = new List<Actor>();

foreach(var actor in actors)
    actor.Update(gameTime);

foreach(var actor in actors)
    actor.Draw(gameTime);

(그것의 단순에 따라 정렬에 추가 DrawOrder하고 UpdateOrder. 한 가지 간단한 방법은 두 개의 목록 및 사용을 유지하는 것입니다 List<T>.Sort(). 그것은 또한 핸들에 간단하다 Load/ UnloadContent.)

그 코드가 실제로 무엇을 중요하지 않습니다 이다 . 중요한 것은 내가 사소하게 수정할 수 있다는 것 입니다.

에 다른 타이밍 시스템이 gameTime필요하거나 더 많은 매개 변수 (또는 UpdateContext약 15 가지 항목이 포함 된 전체 객체)가 필요하거나 드로잉을 위해 완전히 다른 작업 순서가 필요하거나 추가 방법이 필요하다는 것을 알 때 다른 업데이트 패스의 경우 계속 진행 하면 됩니다.

그리고 나는 그것을 즉시 할 수 있습니다. DrawableGameComponent코드를 고르는 것에 대해 난처 할 필요가 없습니다 . 또는 더 나쁜 것은 다음과 같은 요구가 발생할 때 더 어려워 질 수있는 방식으로 해킹하는 것입니다.


XNA 용 드래그 앤 드롭 구성 요소 스타일 미들웨어를 문자 그대로 작성하고 게시하는 경우 사용하기에 가장 적합한 시나리오 는 실제로 하나 뿐입니다 . 그게 원래의 목적이었습니다. 어느 쪽도 추가 하지 않겠습니다.DrawableGameComponent

마찬가지로,에 대한 맞춤 콘텐츠 유형을 만들 때만 사용하는 것이 좋습니다ServicesContentManager .


나는 당신의 요점에 동의하지만, DrawableGameComponent특정 구성 요소, 특히 메뉴 화면 (메뉴, 많은 버튼, 옵션 및 물건) 또는 FPS / 디버그 오버레이와 같은 물건 에서 상속되는 물건을 사용할 때 특히 잘못된 것을 보지 않습니다. 당신은 언급했다. 이 경우 보통 이상 구매 그리 당신이 (당신이하지 않는, 좋은 일을 수동으로 작성해야 적은 코드로 끝날 세밀한 제어가 필요하지 않습니다 필요 가 코드를 작성). 그러나 그것은 당신이 언급 한 드래그 앤 드롭 시나리오와 거의 같습니다.
Groo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.