2D 사이드 스크롤러를위한“최적”게임 루프


14

2D 사이드 스크롤러의 게임 루프에 대한 "최적의"(성능 측면에서) 레이아웃을 설명 할 수 있습니까? 이러한 맥락에서 "게임 루프"는 사용자 입력을 받고 게임 객체의 상태를 업데이트하며 게임 객체를 그립니다.

예를 들어, 상속 상속 계층이 깊은 GameObject 기본 클래스를 사용하면 유지 관리에 도움이 될 수 있습니다. 다음과 같은 작업을 수행 할 수 있습니다.

foreach(GameObject g in gameObjects) g.update();

그러나이 접근법은 성능 문제를 일으킬 수 있다고 생각합니다.

반면에 모든 게임 오브젝트의 데이터와 기능은 전역적일 수 있습니다. 유지 관리가 어려울 수 있지만 최적의 성능을 발휘하는 게임 루프에 더 가깝습니다.

이견있는 사람? 나는 거의 최적의 게임 루프 구조의 실제 응용 프로그램에 관심이 있습니다 ... 큰 성능을 유지하기 위해 유지 보수 두통이 발생하더라도.


답변:


45

예를 들어 깊은 상속 계층 구조를 가진 GameObject 기본 클래스를 유지 관리하는 것이 좋습니다 ...

사실, 딥 계층은 일반적으로 얕은 계층보다 유지 관리가 더 나쁘고 게임 개체의 현대적인 아키텍처 스타일은 얕고 집계 기반 방식 으로 경향이 있습니다.

그러나이 접근법은 성능 문제를 일으킬 수 있다고 생각합니다. 반면에 모든 게임 오브젝트의 데이터와 기능은 전역적일 수 있습니다. 유지 관리가 어려울 수 있지만 최적의 성능을 발휘하는 게임 루프에 더 가깝습니다.

당신이 보여준 루프는 잠재적으로 성능 문제가 있지만, GameObject클래스 에 인스턴스 데이터와 멤버 함수가 있기 때문에 후속 명령문에서 암시하는 것은 아닙니다 . 오히려 문제는 게임의 모든 객체를 정확히 같은 것으로 취급 하면 해당 객체를 지능적으로 그룹화하지 않을 가능성이 있다는 것입니다. 해당 목록 전체에 무작위로 흩어져있을 가능성이 있습니다. 잠재적으로, 그 객체에 대한 update 메소드에 대한 모든 호출은 (메소드가 글로벌 함수인지 아닌지, 그리고 객체가 인스턴스 데이터를 가지고 있는지 또는 어떤 테이블에 인덱싱중인 "글로벌 데이터"인지 여부) 마지막 루프 반복에서 업데이트 호출과 다릅니다.

관련 기능이있는 메모리를 메모리 안팎으로 페이징하고 명령 캐시를 더 자주 채우면 루프가 느려질 수 있으므로 시스템에 부담을 줄 수 있습니다. 여부를이 육안 (또는 프로파일 러)에 의한 관측 정확히에 따라 어떤 A A "게임 오브젝트,"그들 중 얼마나 많은 사람들이 평균적으로 존재하고, 그 밖의 무엇을하는 응용 프로그램에서 벌어지고 간주됩니다.

컴포넌트 지향 객체 시스템은 현재 대중화 추세이며 집계보다 상속이 선호 된다는 철학을 활용합니다 . 이러한 시스템은 잠재적으로 구성 요소의 "업데이트"논리를 분할 할 수있게합니다 ( "구성 요소"는 물리 시스템에 의해 처리되는 일부 객체의 물리적으로 시뮬레이션 된 부분을 나타내는 것과 같은 일부 기능 단위로 대략 정의 됨). ) 구성 요소 유형에 따라 구별되는 여러 스레드에서 가능하고 원하는 경우 성능이 향상 될 수 있습니다. 최소한 주어진 유형의 모든 구성 요소가 함께 업데이트 되어 CPU 캐시를 최적으로 사용 하도록 구성 요소를 구성 할 수 있습니다 . 이러한 컴포넌트 지향 시스템의 예는 이 스레드에서 설명합니다 .

이러한 시스템은 종종 분리되어 유지 보수에 유리합니다.

데이터 지향 디자인 은 관련 접근 방식입니다. 즉, 데이터에 효과적으로 데이터를 처리 할 수 ​​있도록 객체에 필요한 데이터를 최우선 순위로 설정하는 것입니다. 이는 일반적으로 동일한 목적 클러스터에 사용 된 데이터를 함께 유지하고 한 번에 운영하는 조직을 의미합니다. 기본적으로 OO 디자인과 호환되지 않으며이 질문에서 GDSE 의 주제에 대한 대화를 찾을 수 있습니다 .

실제로 게임 루프에 대한보다 최적의 접근 방식은 원본 대신

foreach(GameObject g in gameObjects) g.update();

더 좋아하는 것

ProcessUserInput();
UpdatePhysicsForAllObjects();
UpdateScriptsForAllObjects();
UpdateRenderDataForAllObjects();
RenderEverything();

이러한 세계에서 각각 GameObject자체에 대한 포인터 또는 참조 할 수도 있습니다 PhysicsData또는 Script또는 RenderData, 당신이 개별적으로 객체와 상호 작용해야 할 수도 있습니다 경우지만, 실제 PhysicsData, Scripts, RenderData, 등등은 모두 각각의 서브 시스템에 의해 소유 될 것이다 (물리 시뮬레이터, 스크립트 호스팅 환경, 렌더러) 및 위에 표시된대로 대량으로 처리됩니다.

이다 매우 중요한 이 방법은 마법의 총알하지 않고 (그것은 일반적으로 더 나은하지만 항상 성능 향상을 얻을하지 않습니다하는 디자인 깊은 상속 트리 이하). 개체가 거의 없거나 업데이트를 효과적으로 병렬화 할 수없는 개체가 많으면 기본적으로 성능 차이가없는 것을 알 수 있습니다.

불행히도, 가장 최적 인 매직 루프는 없습니다. 모든 게임은 다르며 다른 방식으로 성능 조정이 필요할 수 있습니다. 따라서 인터넷에서 임의의 사람의 조언을 맹목적으로 가기 전에 물건을 측정 (프로필)하는 것이 매우 중요합니다.


5
좋은 주님, 이것은 훌륭한 답변입니다.
Raveline

2

소규모 프로젝트에는 게임 오브젝트 스타일의 프로그래밍이 적합합니다.

예를 들어 ... Position (x 및 y의 경우), displayObject (이는 그림 요소 인 경우 이미지를 나타냄), 너비, 높이 등의 최소 속성으로 게임 오브젝트베이스를 생성하기 시작했다고 가정합니다.

나중에 애니메이션 상태가 될 서브 클래스를 추가해야한다면, 아마도 애니메이션 컨트롤 코드 (currentAnimationId,이 애니메이션의 프레임 수 등)를 GameObjectBase로 옮길 것입니다. 오브젝트에는 애니메이션이 있습니다.
나중에 정적 (강력한 애니메이션이없는) 강체를 추가하려면 GameObject Base의 업데이트 기능에서 '애니메이션 제어 코드'를 확인해야합니다 (애니메이션이 있는지 여부를 확인하더라도). .. 중요합니다!) 구성 요소 기반 구조를 따르는 경우에는 객체에 '애니메이션 제어 구성 요소'가 부착됩니다. 필요한 경우이를 부착합니다. 불필요한 검사를 피할 수 있습니다.

따라서 스타일 선택의 선택은 프로젝트의 크기에 따라 다릅니다 .GameObject 구조는 소규모 프로젝트에 대해서만 마스터하기가 쉽습니다.


@ 조쉬 페트리-정말 좋은 답변입니다!
Ayyappa

! : @Josh 페트리 - 유용한 링크와 정말 좋은 대답 (P SRY 다음 게시물이 코멘트를 배치하는 방법을 모르는)
Ayyappa

일반적으로 내 답변에서 "댓글 추가"링크를 클릭 할 수 있지만 현재 보유한 것보다 더 많은 평판이 필요합니다 ( gamedev.stackexchange.com/privileges/comment 참조 ). 그리고 감사합니다!
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.