AssetManager를 디자인하는 방법?


26

게임의 그래픽, 사운드 등에 대한 참조를 보유 할 AssestManager를 설계하는 가장 좋은 방법은 무엇입니까?

이러한 자산을 키 / 값 맵 쌍에 저장해야합니까? 즉, "백그라운드"자산을 요청하면 맵에서 관련 비트 맵을 반환합니까? 더 좋은 방법이 있습니까?

특히 Android / Java 게임을 작성하고 있지만 답변이 일반적 일 수 있습니다.

답변:


16

게임의 범위에 따라 다릅니다. 큰 게임에서는 자산 관리자가 필수적이며 작은 게임에서는 그렇지 않습니다.

큰 제목의 경우 다음과 같은 문제를 관리해야합니다.

  • 공유 자산-여러 모델에서 벽돌 텍스처를 사용하고 있습니까?
  • 자산 수명-15 분 전에로드 한 자산이 더 이상 필요하지 않습니까? 무언가가 끝나는 시점을 알 수 있도록 자산 계산 참조
  • DirectX 9에서 특정 자산 유형이로드되고 그래픽 장치가 '잃어버린'경우 (다른 것들 중에서 Ctrl + Alt + Del을 누르면 발생)-게임에서 자산을 다시 만들어야합니다
  • 자산이 필요하기 전에 자산을로드합니다.
  • 대량로드 자산-종종로드 시간을 개선하기 위해 많은 자산을 단일 파일로 묶습니다. 디스크를 탐색하는 데 많은 시간이 소요됩니다

작은 제목의 경우 이러한 문제는 문제가되지 않으며 XNA와 같은 프레임 워크에는 자산 관리자가 있습니다.

자산 관리자가 필요하다고 생각하면 실제로 모든 크기에 맞는 솔루션은 없지만 키를 파일 이름의 해시 *로 사용하는 해시 맵을 찾았습니다 (낮은 구분 기호는 모두 '고정'). 내가 한 프로젝트에서 잘 작동합니다.

일반적으로 앱에서 파일 이름을 하드 코딩하지 않는 것이 좋습니다. 일반적으로 XML과 같은 다른 데이터 형식으로 파일 이름을 'ID'로 나타내는 것이 좋습니다.

  • 재미있는 참고로, 일반적으로 프로젝트 당 하나의 해시 충돌이 발생합니다.

자산을 관리해야한다고해서 너무 많은 방법, 성능 저하 및 흐린 메모리 의미가있는 대문자로 된 중요한 명사 인 AssetManager가 필요하지 않습니다. 비교를 위해 프로젝트 관리 가 많으면 (보통 양호) 프로젝트 관리자 가 많으면 (보통 나쁨) 어떻게되는지 생각해보십시오 .

2
@Joe Wreschnig-자산 관리자를 사용하지 않고 icStatic이 언급 한 5 가지 요구 사항을 어떻게 해결 하시겠습니까?
antinome

8

(토픽을 고려하지 않기 때문에 "자산 관리자를 사용하지 마십시오"-논의를 피하려고합니다.)

키 / 값 맵은 매우 유용한 접근 방식입니다.

서로 다른 리소스 유형에 대한 팩토리를 등록 할 수있는 하나의 ResourceManager 구현이 있습니다.

"getResource"메소드는 템플리트를 사용하여 원하는 자원 유형에 대한 올바른 팩토리를 찾고 특정 ResourceHandle을 리턴합니다 (템플릿을 사용하여 SpecificResourceHandle을 리턴 함).

리소스는 ResourceManager (ResourceHandle 내부)에 의해 계산되고 더 이상 필요하지 않을 때 해제됩니다.

우리가 작성한 첫 번째 애드온은 "reload (XYZ)"방법으로 코드를 변경하거나 게임을 다시로드하지 않고도 실행중인 엔진 외부에서 리소스를 변경할 수 있습니다. (예술가가 콘솔에서 작업 할 때 필수적입니다.)

대부분의 경우 ResourceManager 인스턴스에만 있지만 가끔 레벨이나 맵에 대해서만 새 인스턴스를 만듭니다. 이런 식으로 levelResourceManager에서 "종료"를 호출하고 누출이 없는지 확인할 수 있습니다.

(간략) 예

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource

6

전담 관리자 수업은 결코 올바른 엔지니어링 도구가 아닙니다. 애셋이 한 번만 필요한 경우 (배경 또는 맵과 같이) 한 번만 요청하고 애셋이 끝나면 정상적으로 죽게해야합니다. 특정 종류의 객체를 캐시 해야하는 경우 먼저 캐시를 확인하고 무언가를로드하고 캐시에 넣은 다음 반환하는 팩토리를 사용해야합니다. 공장은 정적 변수에 액세스하는 정적 함수 일 수 있습니다 자체 유형이 아닙니다.

Steve Yegge (많은 사람들 중에서도)는 싱글 톤 패턴을 통해 쓸모없는 관리자 클래스가 어떻게 끝났는지에 대한 좋은 이야기를 썼습니다. http://sites.google.com/site/steveyegge2/singleton-considered-stupid


2
그럼. 당연하지. 그러나 Android (또는 다른 게임)와 같은 경우 게임을 시작하기 전에 메모리에 많은 그래픽 / 사운드를로드해야합니다. 로드 화면에서이를 수행하기 위해 말하는 내용 (공장)을 어떻게 사용할 수 있습니까? 로드 화면에서 공장의 모든 물체를 때리면 캐시됩니다.
Bryan Denny

Android 세부 정보에 익숙하지 않지만 "게임을 시작하기 전에"가 무슨 의미인지 잘 모르겠습니다. 프로그램을 시작할 때가 아니라 필요할 때 (또는 필요할 때) 리소스를로드하는 것이 실제로 불가능합니까? 예를 들어 안드로이드의 빈약 한 RAM에 비해 더 많은 텍스처를 가질 수는 없습니다.

@Joe는 "로드 화면"에 대한 다른 질문을 살펴보십시오. gamedev.stackexchange.com/questions/1171 / ... 빈 캐시를 비우면 디스크로 이동하는 데 시간이 오래 걸리고 첫 번째 호출에서 일부 FPS 성능이 저하 될 수 있습니다 . 미리 공격 할 대상을 이미 알고 있다면로드하는 동안 미리 캐싱하여 미리 캐싱 할 수 있습니까?
Bryan Denny

다시 안드로이드와 대화 할 수는 없지만 디스크로가는 스레드는 CPU를 전혀 사용하지 않기 때문에 일반적으로 디스크로가는 것은 FPS 적중없이 수행 할 수있는 작업입니다. 당신은 팝업을 얻지 않을 정도로 미리 예산을 책정해야합니다. 필요한 것을 미리 알고 있기 때문에 모든 것을 사전 캐시하려는 경우 자산을 전혀 관리 할 필요가 없기 때문에 AssetManager가 필요하지 않습니다. 이미 모든 것이 준비되어 있습니다.

1
@Joe, 공장도 "전담 관리자"가 아닙니까?
MSN

2

저는 항상 훌륭한 자산 관리자가 여러 가지 운영 모드를 가져야한다고 생각했습니다. 이러한 모드는 공통 인터페이스를 준수하는 별도의 소스 모듈 일 가능성이 높습니다. 두 가지 기본 작동 모드는 다음과 같습니다.

  • 생산 모드-모든 자산이 로컬이며 모든 메타 데이터가 제거되었습니다.
  • 개발 모드-의견은 추가 메타 데이터와 함께 데이터베이스 (예 : MySQL 등)에 저장됩니다. 데이터베이스는 로컬 데이터베이스가 공유 데이터베이스를 캐싱하는 2 계층 시스템입니다. 콘텐츠 제작자는 공유 데이터베이스를 편집하고 업데이트 할 수 있으며 개발자 / QA 시스템에 자동으로 업데이트됩니다. 플레이스 홀더 컨텐츠를 작성할 수도 있어야합니다. 모든 것이 데이터베이스에 있기 때문에 데이터베이스에 대한 쿼리를 작성하고 생산 상태를 분석하기 위해 생성 된 보고서를 작성할 수 있습니다.

공유 데이터베이스의 모든 의견을 파악하고 프로덕션 데이터 세트를 생성 할 수있는 도구가 필요합니다.

몇 년 동안 개발자로서, 나는 소수의 회사에서만 일한 적이 있기 때문에 이런 것을 본 적이 없습니다.

최신 정보

네, 일부 부정적인 투표입니다. 이 디자인을 확장하겠습니다.

첫째, 팩토리 클래스가 필요하지 않습니다.

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

유형을 알고 있으므로 다음과 같이하십시오.

TextureHandle tex = new TextureHandle ("test.otx");

그러나 위에서 말하려는 것은 어쨌든 명시 적 파일 이름을 사용하지 않을 것이고, 텍스처가 사용되는 모델에 의해로드 될 텍스처가 지정되므로 실제로 사람이 읽을 수있는 이름이 필요하지 않다는 것입니다. 32 비트 정수 값일 수 있으며 CPU가 처리하기가 훨씬 쉽습니다. 따라서 TextureHandle의 생성자에는 다음이 있습니다.

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream은 resource_id 매개 변수를 사용하여 데이터의 위치를 ​​찾습니다. 이를 수행하는 방법은 실행중인 환경에 따라 다릅니다.

개발 중 : 스트림은 데이터베이스에서 ID를 조회하여 (예를 들어 SQL 사용) 파일 이름을 얻은 다음 파일을 엽니 다. 파일이 로컬로 캐시되거나 로컬 파일이 존재하지 않거나 서버에있는 경우 서버에서 파일을 가져올 수 있습니다 오래된.

릴리스에서 : 스트림은 키 / 값 테이블에서 ID를 조회하여 큰 팩 파일 (Doom의 WAD 파일)로 오프셋 / 크기를 가져옵니다.


실제 VCS를 사용하는 대신 기본 키를 사용하여 모든 것을 SQL 테이블에 구두로 묶는 것을 제안했기 때문에 투표를했습니다. 또한 문자열 이름 조기 최적화보다는 불투명 ID를 사용하는 것이 좋습니다. 나는 번역 키 이외의 모든 자산에 대해 두 개의 큰 프로젝트에서 문자열을 사용했는데, 그 중 수십만 개의 매우 긴 문자열 키가 있었고 콘솔에만 이식했습니다. 그것들은 일반적으로 정규화되어 문자열 비교보다는 포인터 비교를 사용할 수 있지만 문자열 비교는 종종 실제 비교가 아닌 메모리 페치 비용에 의해 지배됩니다.

@Joe : SQL을 예로 들어서 개발 환경에서만 VCS를 사용할 수 있습니다. 저장된 객체에 추가 정보를 추가하고 SQL 함수를 사용하여 데이터베이스에서 정보를 쿼리 할 수 ​​있기 때문에 SQL 데이터베이스 만 제안했습니다. 조기 최적화와 같은 불투명 한 ID에 관해서는-일부 사람들은 내가 추측하는 방식으로 그것을 볼 수 있지만, 나중에 개발 단계에서 그것을 보여주기보다는 그것을 시작하는 것이 더 쉽다고 생각합니다. ID 또는 문자열을 사용하면 개발에 많은 영향을 줄 것이라고 생각하지 않습니다.
Skizz

2

내가 자산을 위해하고 싶은 것은 일괄 관리자 를 설정하는 것 입니다. Doom 엔진에서 영감을 얻은 덩어리는 자산을 포함하고 덩어리 이름, 길이, 유형 (비트 맵, 사운드, 쉐이더 등) 및 콘텐츠 유형 (파일, 다른 덩어리, 내부)을 선언 하는 덩어리 파일에 저장되는 데이터 조각입니다 . 덩어리 파일 자체). 시작할 때 이러한 덩어리는 이진 트리에 입력되지만 아직로드되지는 않습니다. 각 맵 (또한 덩어리 임)에는 맵이 작동해야하는 덩어리의 이름 인 종속성 목록이 있습니다. 이러한 덩어리는 이미로드되지 않은 경우 맵이로드 될 때로드됩니다. 또한, 동시에 인접한지도가 아니라 어떤 이유로 엔진이 공전 할 때지도의 인접한지도 덩어리가로드됩니다. 이를 통해 맵을 매끄럽게 만들 수 있으며 로딩 화면이 없습니다.

내 방법은 오픈 월드 맵에 완벽하지만 레벨 기반 게임은이 방법으로 얻는 매끄러움의 이점을 얻지 못합니다. 이것이 도움이되기를 바랍니다!

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