2 천만 개의 타일이있는지도는 게임의 메모리가 부족하게 만드는데 어떻게 피해야합니까?


11

초대형 맵을 추가로로드하는 동안 새 맵 타일 인스턴스가 생성되는 경우로드 방법에서 메모리 예외가 발생합니다. 최소한 서버 앱 (및 가능한 경우 클라이언트)에서 전체 맵을 처리하고 싶습니다. 이 문제를 어떻게 해결해야합니까?

UPD : 여기에 대한 질문은 사용할 메모리가 아직 남아있을 때 게임이 중단되는 방법입니다. 덩어리로 맵을 분할하는 것은 좋은 접근 방법이지만 내 경우에는 원하는 것이 아닙니다.

UPD2 : 처음에는 타일 클래스의 모든 새 인스턴스에 텍스처를 할당했으며 메모리가 많이 소모되었습니다 (로드 시간). 이제 공간이 약 4 배 줄어 듭니다. 모두 덕분에 이제는 덩어리로 나눌 생각없이 거대한지도를 실행할 수 있습니다.

UPD3 : 타일 ​​속성 배열이 더 빨리 작동하는지 또는 메모리를 덜 소비하는지 확인하기 위해 (각 타일의 객체 속성보다 개체 속성이라고 말한 경우) 코드를 다시 작성하면 시도하는 데 많은 시간이 걸렸을뿐만 아니라 성능 향상을 가져 오지 않았으며 게임을 디버그하기가 몹시 어려워졌습니다.


8
플레이어 주변 영역 만로드하십시오.
MichaelHouse

4
타일 ​​당 4 바이트의 2 천만 타일은 약 80MB에 불과하므로 타일이 작지 않은 것처럼 들립니다. 우리는 마술처럼 더 많은 메모리를 줄 수 없으므로 데이터를 작게 만드는 방법을 연구해야합니다. 이 타일에 무엇이 있는지 알려주세요.
Kylotan

로딩 방법은 무엇입니까? 우편 번호, 컨텐츠 파이프 라인을 사용하십니까? 텍스처를 메모리 나 메타 데이터에만로드합니까? 어떤 메타 데이터? XNA 컨텐트 유형은 일반적으로 관리되지 않는 DirectX 항목을 참조하므로 관리되는 개체는 매우 작지만 관리되지 않는 쪽에서는 DirectX 프로파일 러를 실행하지 않으면 볼 수없는 많은 항목이있을 수 있습니다. stackoverflow.com/questions/3050188/…
Oskar Duveborn

2
시스템에서 메모리 부족 예외가 발생하면 생각할 수 있더라도 사용 가능한 메모리가 충분하지 않습니다. 여분의 메모리를 사용하도록 제공 할 수있는 비밀 코드 줄은 없습니다. 각 타일의 내용과 할당 방법이 중요합니다.
Kylotan

3
여유 메모리가 있다고 생각하는 이유는 무엇입니까? 64 비트 OS 내에서 32 비트 프로세스를 실행 중입니까?
Dalin Seivewright

답변:


58

1.5Gb에 도달하면 앱이 다운됩니다.

이것은 각 타일의 크기가 ~ 80 바이트임을 의미하므로 타일을 올바르게 표현하지 않는 것이 좋습니다.

당신이 이해해야 할 것은 타일 의 게임 플레이 개념과 사용자가 보는 시각적 타일 사이에 분리가 필요하다는 것 입니다. 이 두 개념은 같은 것이 아닙니다 .

Terraria를 예로 들어 보겠습니다. 가장 작은 Terraria 세계는 4200x1200 타일 (5 백만 타일)을 차지합니다. 이제 세상 을 나타내는 데 얼마나 많은 메모리가 필요 합니까?

각 타일에는 전경 레이어, 배경 레이어 (배경 벽), 와이어가있는 "와이어 레이어"및 가구 항목이있는 "가구 레이어"가 있습니다. 각 타일은 얼마나 많은 메모리를 차지합니까? 다시, 우리는 시각적으로가 아니라 개념적으로 이야기하고 있습니다.

포 그라운드 타일은 서명되지 않은 쇼트에 쉽게 저장 될 수 있습니다. 전경 타일 유형은 65536 개를 넘지 않으므로 이보다 더 많은 메모리를 사용하는 데는 아무런 의미가 없습니다. 배경 타일의 256 개 미만의 유형의 배경 타일이 있기 때문에 배경 타일은 부호없는 바이트에 쉽게있을 수 있습니다. 와이어 레이어는 순전히 바이너리입니다. 타일에 와이어가 있거나없는 와이어입니다. 타일 ​​당 1 비트입니다. 그리고 가구 층은 얼마나 많은 다른 가구가 있는지에 따라 다시 부호없는 바이트가 될 수 있습니다.

타일 ​​당 총 메모리 크기 : 2 바이트 + 1 바이트 + 1 비트 + 1 바이트 : 4 바이트 + 1 비트 따라서 작은 Terraria 맵의 총 크기는 20790000 바이트 또는 ~ 20MB입니다. (참고 :이 계산은 Terraria 1.1을 기반으로합니다. 그 이후로 게임이 크게 확장되었지만 현대 Terraria조차도 타일 위치 당 8 바이트 또는 ~ 40MB까지 수용 할 수 있습니다.

이 표현을 C # 클래스의 배열로 저장 해서는 안됩니다 . 정수 또는 이와 유사한 배열이어야합니다. AC # 구조체도 작동합니다.

이제 지도의 일부 를 그릴 때가되자 (강조를 주목하십시오) Terraria는 이러한 개념적 타일을 실제 타일 로 변환해야합니다 . 각 타일은 실제로 전경 이미지, 배경 이미지, 선택적인 가구 이미지를 선택하고 와이어 이미지를 가져야합니다. XNA는 다양한 스프라이트 시트 등을 제공합니다.

당신이해야 할 일은 개념적지도의 보이는 부분을 실제 XNA 스프라이트 시트 타일로 변환하는 것입니다. 한 번에 전체 내용 을 변환하려고하면 안됩니다 . 저장하는 각 타일은 "I는 타일 유형 X입니다"라는 색인이어야합니다. 여기서 X는 정수입니다. 정수 인덱스를 사용하여 표시 할 스프라이트 를 가져옵니다 . 또한 XNA의 스프라이트 시트를 사용하여 개별 쿼드를 그리는 것보다 빠르게 수행 할 수 있습니다.

이제 타일의 보이는 영역을 다양한 청크로 분할해야하므로 카메라가 움직일 때마다 스프라이트 시트를 지속적으로 작성하지 않아도됩니다. 따라서 스프라이트 시트로 64x64 청크가있을 수 있습니다. 플레이어의 현재 카메라 위치에서 볼 수있는 64x64 청크 중 어느 것이 든 청크입니다. 다른 청크에는 스프라이트 시트도 없습니다. 청크가 화면에서 떨어지면 해당 시트를 버리게됩니다 (참고 : 실제로 삭제하지는 않습니다. 나중에 보관할 수있는 새 청크에 대해 보관하고 다시 지정하십시오).

최소한 서버 앱 (및 가능한 경우 클라이언트)에서 전체 맵을 처리하고 싶습니다.

귀하의 서버가 알거나 타일의 시각적 표현에 대해 신경 쓸 필요가 없습니다. 신경 써야 할 것은 개념적 표현입니다. 사용자는 여기에 타일을 추가하므로 해당 타일 색인을 변경합니다.


4
+1 우수 답변. 내 것 이상으로 투표해야합니다.
MichaelHouse

22

지형을 영역 또는 덩어리로 분할합니다. 그런 다음 플레이어가 볼 수있는 청크 만로드하고 그렇지 않은 청크를 언로드하십시오. 컨베이어 벨트와 같이 생각할 수 있습니다. 컨베이어 벨트는 한쪽 끝에 청크를 넣고 다른쪽에 플레이어가 움직일 때 덩어리를 언로드합니다. 항상 플레이어보다 앞서 있어야합니다.

인스 턴싱과 같은 트릭을 사용할 수도 있습니다. 한 유형의 모든 타일이 메모리에 인스턴스가 하나만 있고 필요한 모든 위치에서 여러 번 그려지는 경우.

여기에서 더 많은 아이디어를 찾을 수 있습니다.

어떻게했는지 : Terraria의 수백만 개의 타일

큰 타일 맵과 던전에서 영역을 '존재'


문제는 그러한 접근 방식이 클라이언트 앱에는 좋지만 서버에는 좋지 않다는 것입니다. 세계의 일부 타일이 사라지고 연쇄 반응이 시작되면 (그리고 그 일이 많이 발생하면) 전체지도가 메모리에 있어야하며 메모리에서 벗어날 때 문제가됩니다.
user1306322

6
각 타일에는 무엇이 포함되어 있습니까? 타일 ​​당 얼마나 많은 메모리가 사용됩니까? 각각 10 바이트라도 190MB에 불과합니다. 서버는 텍스처 나 필요없는 다른 정보를로드해서는 안됩니다. 2 천만 타일에 대한 시뮬레이션이 필요한 경우 연쇄 반응에 필요한 정보 만있는 시뮬레이션 세트를 형성하기 위해 해당 타일에서 최대한 많이 제거해야합니다.
MichaelHouse

5

Byte56의 대답은 좋으며 이것에 대한 의견으로 이것을 작성하기 시작했지만 너무 길어서 대답에 더 도움이 될 수있는 몇 가지 팁이 포함되어 있습니다.

이 접근 방식은 클라이언트와 마찬가지로 서버에도 적합합니다. 실제로 서버가 클라이언트보다 훨씬 더 많은 영역을 처리하도록 요청 받으면 적절할 것입니다. 서버는 클라이언트와로드해야하는 대상 (모든 연결된 클라이언트가 관심을 갖는 모든 리전을 고려함)에 대해 다른 아이디어를 가지고 있지만 작업 영역 세트를 동일하게 관리 할 수 ​​있습니다.

메모리에 이러한 거대한 맵을 넣을 수 있고 (가능하지 않을 수도 있음) 조차 원하지 않습니다 . 즉시로드 할 필요가없는 데이터를로드 할 때는 전혀 제로 포인트가 없으며 비효율적이며 느립니다. 메모리에 넣을 수 있더라도 합리적인 시간 내에 모든 것을 처리 할 수는 없습니다.

월드 업데이트가 모든 타일을 반복하고 처리하기 때문에 특정 지역을 언로드하는 것에 대한 반대 의견이 있습니까? 그것은 확실히 유효하지만 특정 부품 만로드하는 것을 배제하지는 않습니다. 대신 리전이로드되면 누락 된 업데이트를 적용하여 최신 상태로 유지하십시오. 처리 효율성도 훨씬 뛰어납니다 (소규모 메모리 영역에 많은 노력이 집중됨). 영역 경계를 가로 지르는 처리 효과가있는 경우 (예 : 용암 흐름 또는 다른 영역으로 전달되는 물 흐름), 두 영역을 함께 묶어 한 영역이로드 될 때 다른 영역도 바인딩합니다. 이상적으로는 이러한 상황이 최소화 될 것입니다. 그렇지 않으면 '모든 지역을 항상로드해야합니다'경우를 빨리 알 수 있습니다.


3

XNA 게임에서 사용한 효율적인 방법은 다음과 같습니다.

  • 각 타일 / 객체에 대한 이미지가 아닌 스프라이트 시트를 사용하고 해당 맵에 필요한 것을로드하십시오.
  • 지도가이 크기의 4 배이거나 방해가 될 수있는 경우 500 x 200 타일의 청크지도와 같이 작은 부분으로지도를 나눕니다.
  • 이 청크를 메모리에로드하고 오프 스크린 버퍼에서 맵의 보이는 부분 (플레이어가 움직이는 각 방향에 대해 보이는 타일과 1 개의 타일) 만 페인트합니다.
  • 뷰포트를 지우고 버퍼에서 픽셀을 복사하십시오 (이중 버퍼라고하며 운동을 부드럽게합니다).
  • char이 움직일 때, 맵의 보드를 추적하고 그 근처에 도달하면 다음 덩어리를로드하고 현재 덩어리를 추가합니다.
  • 플레이어가이 새로운 맵에 완전히 들어가면 마지막 맵을 언로드 할 수 있습니다.

너무 크지 않은 경우이 방법으로 단일 큰 맵을 사용하고 제시된 논리를 사용할 수도 있습니다.

물론 텍스처의 크기가 균형을 이루어야하며 해상도가 큰 비용을 지불해야합니다. 구성 설정이 설정되어있는 경우 낮은 해상도로 작업하고 필요한 경우 고해상도 팩을로드하십시오.

매번이 맵의 관련 부분 만 반복하고 필요한 부분 만 렌더링해야합니다. 이렇게하면 성능이 많이 향상됩니다.

서버는 게임을 렌더링 할 필요가 없기 때문에 거대한 맵을 더 빠르게 처리 할 수 ​​있습니다. 가장 느린 작업 중 하나는 더 큰 청크 나 전체 맵을 사용할 수 있지만 논리 만 처리 할 수 ​​있습니다. 플레이어 주변 영역의 플레이어 뷰포트의 2 배와 같이 플레이어 주변.

여기서 조언은 내가 게임 개발 전문가가 아니며 졸업식 최종 프로젝트 (최고의 점수를 얻음)를 위해 게임을 만들었으므로 모든 시점에서 옳지 않을 수도 있지만 http://www.gamasutra.com 및 XNA 제작자 사이트 creators.xna.com (현재 http://create.msdn.com ) 과 같은 웹 및 사이트를 조사하여 지식과 기술을 수집하고 나에게 잘 작동했습니다. .


2

어느 한 쪽

  • 더 많은 메모리를 사다
  • 데이터 구조를보다 간결하게 표현
  • 모든 데이터를 한 번에 메모리에 보관하지 마십시오.

win7 64bit에 8Gb 램이 있으며 1.5Gb에 도달하면 앱이 다운됩니다. 따라서 내가 빠진 것이 아니라면 더 많은 램을 구입할 필요가 없습니다.
user1306322

1
@ user1306322 : 2 천만 개의 타일이 있고 ~ 1.5GB의 메모리를 차지하는 경우, 타일은 타일 ​​당80 바이트입니다 . 이 물건들에 정확히 무엇을 저장하고 있습니까?
Nicol Bolas

내가 말했듯이 @ NicolBolas, 몇 가지 정수, 바이트 및 texture2d. 또한 모두 1.5GB를 사용하지는 않으며이 메모리 비용에 도달하면 앱이 다운됩니다.
user1306322

2
@ user1306322 : 그것은 여전히 ​​필요한 것보다 훨씬 많습니다. 얼마나 큽 texture2d니까? 각 타일에는 고유 한 타일이 있습니까, 아니면 텍스처를 공유합니까? 또한, 어디서 말했습니까? 당신은 확실히 당신의 질문에 넣지 않았습니다. 정보의 위치입니다. 구체적인 상황을 더 잘 설명하십시오.
Nicol Bolas

@ user1306322 : 솔직히, 나는 왜 내 대답이 다운 투표되었는지 알지 못합니다. 메모리 부족 상황에 대한 세 가지 치료법을 열거했습니다. 물론 모두 적용 해야 한다는 의미는 아닙니다 . 그러나 나는 여전히 그들이 옳다고 생각합니다. 가상 머신의 경우를 포함하기 위해 "구매"대신 "메모리 확장"을 작성했을 것입니다. 내 대답이 장황한 것이 아니라 간결했기 때문에 다운 투표를 받고 있습니까? 나는 그들이 더 이상의 설명없이 이해할 수있을 정도로 간단하다고 생각합니까?!
Thomas

1

여기서 문제는 여전히 사용 가능한 메모리가 남아있을 때 게임이 중단되는 방법입니다. 덩어리로 맵을 분할하는 것은 좋은 접근 방법이지만 내 경우에는 원하는 것이 아닙니다.

업데이트에 대한 응답으로 배열을 초과 Int32.MaxValue하여 크기를 할당 할 수 없습니다 . 덩어리로 나눠야합니다. 배열과 같은 파사드를 노출시키는 래퍼 클래스로 캡슐화 할 수도 있습니다.

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