모든 게임 오브젝트를 단일 목록에 저장하는 것이 허용 가능한 디자인입니까?


24

내가 만든 모든 게임에 대해 모든 게임 오브젝트 (글 머리 기호, 자동차, 플레이어)를 단일 배열 목록에 배치하고,이를 반복하여 그리기 및 업데이트합니다. 각 엔티티의 업데이트 코드는 해당 클래스 내에 저장됩니다.

궁금한 점이 있습니다. 이것이 올바른 방법인지, 더 빠르고 효율적인 방법입니까?


4
이것이 .Net이라고 가정하면 "배열 목록"이라고 말한 것을 알 수 있습니다. 대신 List <T>로 업그레이드해야합니다. List <T>가 유형에 강하다는 점을 제외하면 거의 동일합니다. 따라서 요소에 액세스 할 때마다 캐스트를 입력 할 필요가 없습니다. 문서는 다음과 같습니다. msdn.microsoft.com/en-us/library/6sh2ey19.aspx
John McDonald

답변:


26

올바른 길은 없습니다. 당신이 묘사하는 것은 효과가 있으며 아마도 성능 문제를 일으키는 것이 아니라 디자인에 관심이 있기 때문에 묻는 것처럼 보이기 때문에 중요하지 않을 정도로 빠릅니다. 그래서는 있는지, 올바른 방법.

예를 들어 각 객체 유형에 대해 동종 목록을 유지하거나 이종 목록의 모든 항목을 그룹화하여 캐시에있는 코드의 일관성을 향상 시키거나 병렬 업데이트를 허용함으로써 확실히 다르게 할 수 있습니다. 이렇게하면 게임의 범위와 규모를 알지 못하면 성능이 크게 향상 될 수 있습니다.

또한 단일 책임 원칙을 더 잘 준수하기 위해 엔티티의 책임을 추가로 고려할 수 있습니다. 즉, 엔티티가 현재 업데이트 및 렌더링되는 것처럼 들립니다. 이를 통해 개별 인터페이스를 서로 분리하여 개별 인터페이스의 유지 관리 성과 유연성을 높일 수 있습니다. 그러나 다시 말하지만, 추가 작업의 이점을 볼 수 있는지 여부는 내가 당신의 게임에 대해 알고있는 것을 아는 것은 어렵습니다 (기본적으로 아무것도 아닙니다).

나는 그것에 대해 너무 많은 스트레스를받지 않는 것이 좋으며, 성능이나 유지 보수성 문제를 발견 한 경우 개선을위한 잠재적 인 영역이 될 수 있도록 머리 뒤에 두십시오.


16

다른 사람들이 말했듯이, 충분히 빠르면 충분히 빠릅니다. 더 좋은 방법이 있는지 궁금하다면 자신에게 물어보아야 할 질문은

모든 객체를 반복하면서 하위 세트에서만 작동하는 것을 알 수 있습니까?

그럴 경우 관련 참조 만 보유하는 여러 목록을 원할 수도 있습니다. 예를 들어, 모든 게임 오브젝트를 반복하여 렌더링하지만 오브젝트의 서브 세트 만 렌더링해야하는 경우 렌더링해야하는 오브젝트에 대해서만 별도의 렌더링 가능 목록을 유지하는 것이 좋습니다.

이렇게하면 목록의 모든 개체가 처리 될 것임을 이미 알고 있으므로 반복되는 개체 수를 줄이고 불필요한 검사를 건너 뜁니다.

성능이 많이 향상되는지 확인하려면 프로파일 링해야합니다.


나는 이것에 동의하며, 일반적으로 각 프레임마다 업데이트 해야하는 객체에 대한 내 목록의 하위 집합이 있으며 렌더링하는 객체에 대해서도 동일한 작업을 고려하고 있습니다. 그러나 이러한 속성이 즉시 변경 될 수있는 경우 모든 목록을 관리하는 것이 복잡 할 수 있습니다. 및 오브젝트가 렌더링 가능에서 렌더링 불가능 또는 갱신 가능으로 업데이트 할 수없는 경우, 이제는 한 번에 여러 목록에서 오브젝트를 추가하거나 제거합니다.
Nic Foster

8

그것은 거의 전적으로 특정 게임 요구 에 달려 있습니다. 간단한 게임에서는 완벽하게 작동하지만 더 복잡한 시스템에서는 실패합니다. 게임에서 작동한다면 걱정하지 말고 필요할 때까지 오버 엔지니어링을 시도하십시오.

일부 상황에서 단순한 접근 방식이 실패하는 이유는 가능성이 무한하며 전적으로 게임 자체에 의존하기 때문에 단일 게시물로 요약하기가 어렵습니다. 예를 들어 책임을 공유하는 객체를 별도의 목록으로 그룹화하려는 다른 대답이 있습니다.

게임 디자인에 따라 가장 일반적인 변경 중 하나입니다. 나는 다른 (더 복잡한) 몇 가지 예를 설명 할 수는 있지만, 여전히 많은 다른 이유와 해결책이 있다는 것을 기억하십시오.

우선, 게임 객체를 업데이트하고 렌더링하는 것은 일부 게임에서 요구 사항이 다를 수 있으므로 별도로 처리해야 함을 지적 합니다. 게임 오브젝트 업데이트 및 렌더링과 관련된 몇 가지 가능한 문제 :

게임 오브젝트 업데이트

다음 은 읽을 것을 권장하는 Game Engine Architecture에서 발췌 한 것입니다 .

개체 간 종속성이있는 경우 위에서 설명한 단계별 업데이트 기술을 약간 조정해야합니다. 개체 간 종속성으로 인해 업데이트 순서를 제어하는 ​​규칙이 충돌 할 수 있기 때문입니다.

즉, 일부 게임에서는 개체가 서로 의존하고 특정 순서의 업데이트가 필요할 수 있습니다. 이 시나리오에서는 오브젝트와 상호 종속성을 저장하기 위해 목록보다 복잡한 구조를 고안해야 할 수도 있습니다.

여기에 이미지 설명을 입력하십시오

게임 오브젝트 렌더링

일부 게임에서 장면과 오브젝트는 부모-자식 노드의 계층 구조를 생성하며 부모와 관련하여 올바른 순서로 변환해야합니다. 이러한 경우 단일 목록 대신 장면 그래프 (트리 구조) 와 같은보다 복잡한 구조가 필요할 수 있습니다 .

여기에 이미지 설명을 입력하십시오

다른 이유는 예를 들어 공간 분할 데이터 구조를 사용하여 뷰 프러스 텀 컬링의 효율성을 향상시키는 방식으로 객체를 구성하는 것일 수 있습니다.


4

대답은 "예, 괜찮습니다"입니다. 성능을 협상 할 수없는 큰 철 시뮬레이션에서 작업 할 때 하나의 큰 지방 (BIG 및 FAT) 전역 배열에서 모든 것을 보는 것에 놀랐습니다. 나중에 나는 OO 시스템에서 일했고 둘을 비교해야했습니다. 매우 추악했지만 디버그컴파일 된 단일 스레드 평범한 오래된 C의 "모든 것을 지배하는 하나의 배열"버전 은 C ++에서 "O2"로 컴파일 된 "꽤"멀티 스레드 OO 시스템보다 몇 배 빠르다 . 나는 그것이 큰 지방 배열 버전에서 (공간과 시간 모두에서) 우수한 캐시 위치와 관련이 있다고 생각합니다.

성능을 원한다면 큰 뚱뚱한 글로벌 C 어레이가 경험의 상한이 될 것입니다.


2

기본적으로 원하는 것은 목록을 필요에 따라 구성하는 것입니다. 지형 개체를 한 번에 다시 그리면 해당 개체의 목록이 유용 할 수 있습니다. 그러나 수만 가지가 필요하지만 지형 이 아닌 수많은 10,000 가지가 필요 합니다. 그렇지 않으면 모든 것의 목록을 숙고하고 "if"를 사용하여 지형 객체를 꺼내는 것이 충분히 빠릅니다.

내가 가진 다른 목록의 수에 관계없이 항상 하나의 마스터 목록이 필요했습니다. 일반적으로 개별 항목에 무작위로 액세스 할 수 있도록 일종의 맵 또는 키 테이블 또는 사전으로 만듭니다. 그러나 간단한 목록은 훨씬 간단합니다. 게임 오브젝트에 무작위로 액세스하지 않으면 맵이 필요하지 않습니다. 그리고 올바른 항목을 찾기 위해 수천 개의 항목을 통해 가끔 순차 검색하는 데 시간이 오래 걸리지 않습니다. 그래서 나는 당신이 무엇을하든 마스터 목록을 고수 할 것이라고 말하고 싶습니다. 커지면지도 사용을 고려하십시오. (키 대신 인덱스를 사용할 수는 있지만 배열을 고수하고 맵보다 훨씬 더 좋은 것을 가질 수 있습니다. 나는 항상 인덱스 번호 사이에 큰 간격이 생겨서 포기해야했습니다.)

배열에 관한 한마디 : 그것들은 믿을 수 없을 정도로 빠릅니다. 그러나 약 10 만개의 요소가 생겨나면 가비지 콜렉터에 적합합니다. 공간을 한 번 할당하고 나중에 만질 수 없다면 괜찮습니다. 그러나 지속적으로 어레이를 확장하는 경우 지속적으로 많은 양의 메모리를 할당하고 해제하며 한 번에 몇 초 동안 게임이 중단되고 메모리 오류로 인해 실패 할 수도 있습니다.

더 빠르고 효율적입니까? 확실한. 무작위 접근을위한지도. 정말 큰 목록의 경우 임의 액세스가 필요 하지 않은 경우 연결 목록이 배열보다 우선 합니다. 필요에 따라 다양한 방법으로 오브젝트를 구성하기위한 다중 맵 / 목록. 업데이트를 멀티 스레딩 할 수 있습니다.

그러나 그것은 모두 많은 일입니다. 그 단일 배열은 빠르고 간단합니다. 필요할 때만 다른 목록과 사물을 추가 할 수 있으며 필요하지 않을 수도 있습니다. 게임이 커지면 무엇이 잘못 될 수 있는지 알아 두십시오. 실제 버전보다 10 배 많은 객체가 포함 된 테스트 버전을 사용하여 문제가 발생할시기를 알 수 있습니다. (게임 커질 때만 이것을하십시오 .) 멀티 스레딩을 사용하면 큰 가격을 지불 할 수 있으며, 머무는 데 따른 비용이 높으며, 다시 나가기가 어렵습니다.)

다시 말해, 당신이하고있는 일을 계속하십시오. 당신의 가장 큰 한계는 아마도 당신 자신의 시간이며, 당신은 그것을 최대한 활용하고 있습니다.


중간에 항목을 자주 추가하거나 제거하지 않으면 링크 된 목록이 어떤 크기의 배열보다 성능이 뛰어나다 고 생각하지 않습니다.
Zan Lynx

@ 잔 린스 : 그리고 심지어. 내가 생각하는 새로운 런타임 최적화 도움말 어레이를 많게 - 그들은 그것을 필요로하지 않는 것이. 그러나 큰 배열에서 발생할 수있는 것처럼 많은 양의 메모리 청크를 반복적으로 빠르게 할당하고 해제하면 가비지 수집기를 매듭으로 묶을 수 있습니다. (총 메모리 양도 여기에 영향을 미칩니다. 문제는 블록 크기, 사용 가능 및 할당 빈도 및 사용 가능한 메모리의 기능입니다.) 프로그램이 중단되거나 충돌하면 갑자기 오류가 발생합니다. 일반적으로 사용 가능한 메모리가 많이 있습니다. GC는 그것을 사용할 수 없습니다. 연결된 목록은이 문제를 피합니다.
RalphChapin 2019 년

반면, 링크 된 목록은 노드가 메모리에서 연속적이지 않기 때문에 반복시 캐시 누락 가능성이 상당히 높습니다. 거의 잘려서 마르지 않습니다. 재 할당 문제를 해결하지 않고 (가능한 경우 자주 할당) 재 할당 문제를 완화 할 수 있으며 노드를 풀로 할당하여 링크 된 목록에서 캐시 일관성 문제를 완화 할 수 있습니다 (여전히 참조 비용이 발생하더라도) 비교적 사소합니다).
Josh

예, 그러나 배열에서도 객체에 대한 포인터를 저장하지 않습니까? (파티클 시스템이나 다른 제품을위한 단기 배열이 아닌 이상) 그렇지 않은 경우 여전히 많은 캐시 누락이 발생합니다.
Todd Lehman

1

나는 간단한 게임을 위해 다소 비슷한 방법을 사용했다. 다음 조건에 해당하는 경우 모든 것을 한 배열에 저장하는 것이 매우 유용합니다.

  1. 게임 개체 수가 적거나 알려진 최대 수
  2. 성능보다 단순성을 선호합니다 (예 : 트리와 같이 더욱 최적화 된 데이터 구조는 문제가되지 않습니다)

어쨌든이 솔루션을 gameObject_ptr구조체 가 포함 된 고정 크기 배열로 구현했습니다 . 이 구조체에는 게임 오브젝트 자체에 대한 포인터와 오브젝트가 "사용 중"인지 여부를 나타내는 부울 값이 포함되어 있습니다.

부울의 목적은 작성 및 삭제 조작 속도를 높이는 것입니다. 시뮬레이션에서 게임 오브젝트를 제거 할 때 게임 오브젝트를 파괴하는 대신 단순히 "alive"플래그를로 설정합니다 false.

객체에서 계산을 수행 할 때 코드는 배열을 반복하여 "alive"로 표시된 객체에서 계산을 수행하고 "alive"로 표시된 객체 만 렌더링합니다.

또 다른 간단한 최적화는 객체 유형별로 배열을 구성하는 것입니다. 예를 들어, 모든 글 머리 기호 는 배열 의 마지막 n 개 셀에 있을 수 있습니다 . 그런 다음 인덱스 값 또는 배열 요소에 대한 포인터를 가진 int를 저장하면 특정 유형을 저렴한 비용으로 참조 할 수 있습니다.

말할 것도없이이 솔루션은 많은 양의 데이터가있는 게임에는 적합하지 않습니다. 또한 사용하지 않는 개체가 메모리를 차지하지 못하도록하는 메모리 제한이있는 게임에는 적합하지 않습니다. 결론은 한 시점에 가질 게임 오브젝트 수에 대한 알려진 상한이 있으면 정적 배열에 저장하는 데 아무런 문제가 없다는 것입니다.

이것은 나의 첫 번째 게시물입니다! 나는 그것이 당신의 질문에 대답하기를 바랍니다!


첫번째 포스트! 예!
Tili

0

드물지만 허용됩니다. "빠른"및 "보다 효율적인"과 같은 용어는 상대적입니다. 일반적으로 게임 개체를 별도의 범주 (총알 목록, 적 목록 등)로 구성하여 프로그래밍 작업을보다 체계적이고 효율적으로 수행 할 수 있지만 컴퓨터가 모든 개체를 더 빨리 반복하는 것과는 다릅니다. .


2
대부분의 XNA 게임과 관련하여 충분히 사실이지만 객체를 구성 하면 실제로 객체를 더 빠르게 반복 할 수 있습니다 . 동일한 유형의 객체는 CPU의 명령 및 데이터 캐시에 충돌 할 가능성이 높습니다. 캐시 미스는 특히 콘솔에서 비싸다. 예를 들어 Xbox 360에서 L2 캐시 미스의 비용은 ~ 600 사이클입니다. 그것은 테이블에서 많은 성능을 남기고 있습니다. 이것은 관리 코드가 네이티브 코드보다 유리한 곳입니다. 시간에 가깝게 할당 된 객체는 메모리에서도 가깝고 가비지 수집 (컴팩 션)으로 상황이 개선됩니다.
만성 게임 프로그래머

0

단일 배열과 객체를 말합니다.

그래서 (코드를 보지 않고) 모두 'uberGameObject'와 관련이 있다고 생각합니다.

두 가지 문제가있을 수 있습니다.

1) 당신은 '신'객체를 가지고있을 수 있습니다-실제로 물건이나 물건으로 분류되지 않은 톤의 물건을 만듭니다.

2)이 목록을 반복하고 유형을 캐스팅합니까? 또는 각각 개봉합니다. 성능이 저하됩니다.

다른 유형 (글 머리 기호, 나쁜 사람 등)을 강력한 형식의 목록으로 나누는 것을 고려하십시오.

List<BadGuyObj> BadGuys = new List<BadGuyObj>();
List<BulletsObj> Bullets = new List<BulletObj>();

 //Game 
OnUpdate(gameticks gt)
{  foreach (BulletObj thisbullet in Bullets) {thisbullet.Update();}  // much quicker.

업데이트 루프 (예 :)에서 호출 될 모든 메소드가있는 공통 기본 클래스 또는 인터페이스가있는 경우 유형 캐스팅을 수행 할 필요가 없습니다 update().
bummzack 2018 년

0

각 객체 유형에 대한 일반 단일 목록과 별도의 목록간에 타협점을 제안 할 수 있습니다.

여러 목록에서 하나의 객체를 참조하십시오. 이 목록은 기본 동작으로 정의됩니다. 예를 들어, MarineSoldier객체에서 참조 할 것 PhysicalObjList, GraphicalObjList, UserControlObjList, HumanObjList. 물리학을 처리 PhysicalObjList할 때 독극물로 인한 프로세스 손상이 발생 HumanObjList하면 병사가 사망 한 경우이를 제거 HumanObjList하지만 계속 유지해야 PhysicalObjList합니다. 이 메커니즘을 작동 시키려면 자동 삽입 / 제거 개체를 생성 / 삭제할 때 구성해야합니다.

접근 방식의 장점 :

  • 형식화 된 개체 구성 ( AllObjectsList업데이트시 캐스팅 없음 )
  • 목록은 객체 클래스가 아닌 논리를 기반으로합니다.
  • 결합 된 능력을 가진 물체에는 아무런 문제가 없습니다 ( MegaAirHumanUnderwaterObject유형에 대한 새로운리스트를 만드는 대신 해당리스트에 삽입 될 것입니다)

추신 : 자체 업데이트의 경우 여러 객체가 상호 작용하고 각 객체가 다른 객체에 의존 할 때 문제가 발생합니다. 이 문제를 해결하려면 자체 업데이트가 아닌 외부에서 객체에 영향을 주어야합니다.

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