타일 ​​맵 데이터를 저장하는 좋은 방법은 무엇입니까?


13

나는 유니 친구들과 2D 플랫 포머를 개발 중입니다. 타일 ​​맵을 저장하기 위해 .txt 파일을 사용하는 XNA Platformer 스타터 키트를 기반으로했습니다. 이것은 간단하지만 레벨 디자인으로 충분한 제어와 유연성을 제공하지는 않습니다. 몇 가지 예 : 여러 계층의 콘텐츠에 대해 여러 파일이 필요하고 각 개체는 그리드에 고정되어 있으며 개체 회전, 제한된 수의 문자 등을 허용하지 않습니다. 따라서 레벨 데이터를 저장하는 방법에 대한 연구를하고 있습니다 그리고지도 파일.

이것은 타일 맵의 파일 시스템 스토리지에만 해당되며 게임이 실행되는 동안 게임에서 사용되는 데이터 구조는 아닙니다. 타일 ​​맵은 2D 배열에로드되므로이 질문은 배열을 채울 소스에 관한 것입니다.

DB에 대한 추론 : 필자의 관점에서 데이터베이스를 사용하여 타일 데이터를 저장하는 데이터의 중복성이 줄어 듭니다. 동일한 특성을 가진 동일한 x, y 위치의 타일을 레벨에서 레벨로 재사용 할 수 있습니다. 데이터베이스에서 특정 수준으로 사용되는 모든 타일을 검색하는 방법을 작성하는 것만 큼 간단합니다.

JSON / XML에 대한 추론 : 시각적으로 편집 가능한 파일 인 SVN을 통해 변경 내용을 훨씬 쉽게 추적 할 수 있습니다. 그러나 반복되는 내용이 있습니다.

다른 것에 비해 단점 (로드 시간, 액세스 시간, 메모리 등)이 있습니까? 그리고 업계에서 일반적으로 사용되는 것은 무엇입니까?

현재 파일은 다음과 같습니다.

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1-플레이어 시작 지점, X 레벨 종료. -빈 공간, #-플랫폼, G-보석


2
기존의 "형식"을 사용하고 있습니까? "텍스트 파일"이라는 말은 바이너리 데이터를 저장하지 않는다는 의미입니다. "충분한 제어 및 유연성"이라고 말하면 구체적 으로 어떤 문제가 발생합니까? 왜 XML과 SQLite를 던지는가? 답변이 결정됩니다. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad

더 읽기 쉽기 때문에 JSON을 선택합니다.
Den

3
사람들은 왜 이런 일에 SQLite를 사용하려고 생각합니까? 그것은 A의 관계형 데이터베이스 ; 사람들이 관계형 데이터베이스가 좋은 파일 형식을 만든다고 생각하는 이유는 무엇입니까?
Nicol Bolas

1
@ StephenTierney : 왜이 ​​질문이 갑자기 XML 대 SQLite에서 JSON 대 데이터베이스로 바뀌 었습니까? 제 질문은 특히 "타일 맵 데이터를 저장하는 좋은 방법은 무엇입니까?"가 아닌 이유입니다. 이 X 대 Y 질문은 임의적이고 의미가 없습니다.
Nicol Bolas

1
@ 킬로 탄 : 그러나 그것은 잘 작동하지 않습니다. SQL 명령을 통해서만 데이터베이스를 편집 할 수 있기 때문에 편집하기가 매우 어렵습니다. 테이블은 실제로 타일 맵을보고 어떤 일이 일어나고 있는지 이해하는 효과적인 방법이 아니기 때문에 읽기가 어렵습니다. 조회는 수행 할 수 있지만 조회 절차는 엄청나게 복잡합니다. 무언가를 얻는 것이 중요하지만 실제로 게임 개발 과정을 훨씬 더 어렵게 만들려면 짧은 길을 걷는 것이 가치가 없습니다.
Nicol Bolas

답변:


14

따라서 업데이트 된 질문을 읽으면 디스크의 "중복 데이터"에 대해 가장 우려하고로드 시간과 업계에서 사용하는 것에 대한 두 번째 우려가있는 것 같습니다.

우선, 중복 데이터가 걱정되는 이유는 무엇입니까? XML과 같은 부풀린 형식의 경우에도 압축을 구현하고 레벨의 크기를 줄이는 것이 매우 간단합니다. 레벨 데이터보다 텍스처 또는 사운드가있는 공간을 차지할 가능성이 높습니다.

둘째, 이진 형식은 구문 분석해야하는 텍스트 기반 형식보다 빠르게로드 될 가능성이 높습니다. 메모리에 드롭하고 데이터 구조를 가질 수 있다면 두 배가됩니다. 그러나 몇 가지 단점이 있습니다. 하나, 바이너리 파일은 디버깅하기가 불가능합니다 (특히 컨텐츠 제작자). diff하거나 버전을 지정할 수 없습니다. 그러나 크기는 더 작아지고 더 빠르게로드됩니다.

일부 엔진의 역할과 이상적인 상황은 두 가지로드 경로를 구현하는 것입니다. 개발에는 일종의 텍스트 기반 형식을 사용합니다. 견고한 라이브러리를 사용하는 한 사용하는 특정 형식은 중요하지 않습니다. 릴리스의 경우 바이너리로드 (작고 빠른로드, 디버그 엔티티 제거) 버전으로 전환합니다. 레벨을 편집하는 도구는 둘 다 뱉어냅니다. 하나의 파일 형식보다 훨씬 많은 작업이지만 두 가지 이점을 최대한 활용할 수 있습니다.

말한대로, 당신이 총을 약간 뛰어 넘고 있다고 생각합니다.

이러한 문제의 1 단계는 항상 실행중인 문제를 철저히 설명하는 것입니다. 필요한 데이터를 아는 경우 어떤 데이터를 저장해야하는지 알고 있습니다. 거기에서 테스트 가능한 최적화 질문입니다.

테스트 할 유스 케이스가있는 경우 중요하다고 생각되는 사항 (로드 시간, 메모리 사용량, 디스크 크기 등)을 측정하기 위해 몇 가지 다른 데이터 저장 /로드 방법을 테스트 할 수 있습니다. 그 중 하나라도 알지 못하면 더 구체적으로 말하기가 어렵습니다.


9
  • XML : 손으로 쉽게 편집 할 수 있지만 많은 데이터를로드하기 시작하면 속도가 느려집니다.
  • SQLite : 많은 양의 데이터를 한 번에 또는 심지어 작은 덩어리로 검색하려는 경우에 더 좋습니다. 그러나 게임의 다른 곳에서 이것을 사용하지 않는 한, 그것은지도에 과잉이라고 생각합니다 (아마도 너무 복잡합니다).

권장 사항은 사용자 정의 이진 파일 형식을 사용하는 것입니다. 내 게임에는을 SaveMap사용하여 각 필드를 저장하고 저장 하는 방법이 BinaryWriter있습니다. 또한 원하는 경우 압축을 선택하고 파일 크기를보다 세밀하게 제어 할 수 있습니다. 즉, 저장하십시오 short대신의 int당신이 그것으로 뭔가를 저장, 큰 루프에서 32767보다 클 수 없을거야 알고 있다면 short대신의 int많은 작은 파일 크기를 의미 할 수있다.

또한이 경로를 사용하면 파일의 첫 번째 변수는 버전 번호가 좋습니다.

예를 들어, 맵 클래스 (매우 단순화 됨)를 고려하십시오.

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

자, 당신은지도에 새로운 속성을 추가하고 싶었 가정 해 봅시다하는 말 ListPlatform객체. 조금 더 복잡하기 때문에 이것을 선택했습니다.

우선,를 증가시키고 다음 MapVersion을 추가하십시오 List.

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

그런 다음 저장 방법을 업데이트하십시오.

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

그런 다음 여기에서 실제로 이점을 볼 수 있습니다.로드 방법을 업데이트하십시오.

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

보시다시피이 LoadMaps방법은 최신 버전의지도뿐만 아니라 이전 버전도로드 할 수 있습니다! 이전 맵을로드 할 때 사용하는 기본값을 제어 할 수 있습니다.


9

단편

이 문제의 깔끔한 대안은 레벨을 타일 당 한 픽셀로 비트 맵에 저장하는 것입니다. RGBA를 사용하면 하나의 이미지에 4 가지 크기 (레이어, ID, 회전, 색조 등)를 쉽게 저장할 수 있습니다.

긴 이야기

이 질문은 몇 달 전 Notch가 Ludum Dare에 생방송을했을 때를 상기시켜 주었으며, 그가 무엇을했는지 모른다면 공유하고 싶습니다. 정말 재미 있다고 생각했습니다.

기본적으로 비트 맵을 사용하여 레벨을 저장했습니다. 비트 맵의 ​​각 픽셀은 세계에서 하나의 "타일"에 해당합니다 (그의 경우 레이 캐스트 게임 이었기 때문에 실제로 타일은 아니지만 충분히 가깝습니다). 그의 레벨 중 하나의 예 :

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

따라서 RGBA (채널당 8 비트)를 사용하는 경우 각 채널을 다른 레이어로 사용하고 각 타일에 대해 최대 256 가지 유형의 타일을 사용할 수 있습니다. 충분할까요? 또는 예를 들어 채널 중 하나에서 언급 한 것처럼 타일의 회전을 유지할 수 있습니다. 게다가,이 포맷으로 작업 할 레벨 에디터를 만드는 것도 매우 간단합니다.

무엇보다도 다른 Texture2D처럼 XNA에로드하고 루프에서 픽셀을 다시 읽을 수 있기 때문에 사용자 정의 컨텐츠 프로세서가 필요하지 않습니다.

IIRC 그는 적에게 신호를 보내기 위해지도에 순수한 빨간 점을 사용했으며, 해당 픽셀의 알파 채널 값에 따라 적의 유형을 결정합니다 (예 : 255의 알파 값은 박쥐 일 수 있지만 254는 좀비 또는 다른 것).

또 다른 아이디어는 게임 오브젝트를 그리드에 고정 된 오브젝트와 타일 사이에서 "정밀하게"이동할 수있는 오브젝트로 세분화하는 것입니다. 고정 타일을 비트 맵에, 동적 객체를 목록에 유지하십시오.

더 많은 정보를이 4 개의 채널로 멀티플렉싱하는 방법이있을 수도 있습니다. 누군가 이것에 대해 알고 있다면 알려주세요. :)


3

당신은 아마 거의 아무것도 도망 갈 수 있습니다. 불행히도 현대 컴퓨터는 너무 빠르기 때문에이 상대적으로 적은 양의 데이터는 읽을 수있는 처리량이 많은 부풀어지고 잘못 구성된 데이터 형식으로 저장되어 있어도 눈에 띄는 시차를 만들지 않습니다.

만약 당신이 "올바른"일을하고 싶다면 Drackir가 제안한 이진 형식을 만들지 만 어리석은 이유로 다른 선택을한다면 아마 돌아 오지 않을 것입니다 (적어도 현명한 것은 아닙니다).


1
예, 크기에 따라 다릅니다. 약 165,000 타일의지도가 있습니다. 각각 6 개의 레이어가 있습니다. 원래 XML로 사용했지만 82MB였으며 로드하는 데 영원히 걸렸 습니다 . 이제 바이너리 형식으로 저장하고 압축하기 전에 약 5MB이며 약 200ms에로드됩니다. :)
Richard Marskell-Drackir

2

이 질문을 참조하십시오 : 게임 데이터에 대한 '이진 XML'? 또한 JSON, YAML 또는 XML을 선택하지만 오버 헤드가 많이 있습니다. SQLite는 레벨 저장에 사용하지 않습니다. 관계형 데이터베이스는 관련된 많은 데이터를 저장할 것으로 예상되는 경우 (예 : 관계형 링크 / 외부 키가있는 경우) 많은 양의 다른 쿼리를 요청할 경우 타일 맵을 저장해도 이러한 종류의 작업을 수행하지 않는 것 같습니다. 불필요한 복잡성을 추가합니다.


2

이 질문의 근본적인 문제는 서로 관련이없는 두 가지 개념을 혼동한다는 것입니다.

  1. 파일 저장 패러다임
  2. 데이터의 메모리 내 표현

당신은 어떤 임의의 형식으로 일반 텍스트로 파일을 저장할 수 있습니다, JSON은 그 루아 스크립트, XML, 임의의 바이너리 형식 등 그리고 아무도는 것입니다 필요 데이터의 사용자 메모리 표현이 특정 형태에 걸릴 것이다.

파일 저장 패러다임을 메모리 내 표현으로 변환하는 것은 레벨 로딩 코드의 작업입니다. 예를 들어 "타일 데이터를 저장하기 위해 데이터베이스를 사용하는 중복 데이터가 줄어 듭니다."라고 말합니다. 중복성을 줄이려면 레벨 로딩 코드가 살펴 봐야 할 것입니다. 메모리 내 표현이 처리 할 수있는 것입니다.

파일 형식과 관련 이있는 것은 아닙니다 . 그 이유는 다음과 같습니다. 해당 파일은 어딘가에서 가져와야합니다.

직접 작성하거나 레벨 데이터를 작성하고 편집하는 도구를 사용하게됩니다. 직접 작성하는 경우 가장 중요한 것은 읽고 수정하기 쉬운 형식입니다. 당신은 대부분의 지출되기 때문에 데이터 중복, 당신의 형식도 고려할 필요가있는 것이 아닙니다 당신의 파일을 편집하는 시간을. 실제로 데이터 중복성을 제공하기 위해 제공하는 메커니즘을 수동으로 사용해야합니까? 레벨 로더가 처리하도록 시간을 더 잘 사용하지 않습니까?

도구를 만드는 도구가 있다면 실제 형식은 가독성을 위해서만 중요합니다. 이 파일들 에 무엇이든 넣을 수 있습니다 . 파일에서 중복 데이터를 생략하려면 그렇게 할 수있는 형식을 디자인하고 도구가 해당 기능을 올바르게 사용하도록하십시오. 귀하의 tilemap 형식이 때문에 지금까지 압축 기술은 당신이 원하는 RLE (실행 길이 인코딩) 압축, 중복 압축을 포함 할 수 귀하의 tilemap 형식입니다.

문제 해결됨.

RDB (관계형 데이터베이스)는 많은 데이터 필드가 포함 된 대규모 데이터 집합에 대해 복잡한 검색을 수행하는 문제를 해결하기 위해 존재합니다. 타일 ​​맵에서 수행하는 유일한 검색 유형은 "위치 X, Y에 타일 가져 오기"입니다. 모든 배열이이를 처리 할 수 ​​있습니다. 관계형 데이터베이스를 사용하면 저장 데이터가 될 것 tilemap 유지하기 위해 극단적 인 과잉, 아무것도 정말 가치가 없다.

메모리 내 표현에 약간의 압축을 구축하더라도 성능 및 메모리 풋 프린트 측면에서 RDB를 쉽게 이길 수 있습니다. 예, 실제로 해당 압축을 구현해야합니다. 그러나 메모리 내 데이터 크기가 RDB를 고려하는 데 관심이 있다면 실제로 특정 종류의 압축을 구현하고 싶을 것입니다. RDB보다 빠르며 동시에 메모리가 떨어집니다.


1
  • XML : 정말 사용하기 쉽지만, 구조가 심해지면 오버 헤드가 번거로워집니다.
  • SQLite : 더 큰 구조에 더 편리합니다.

정말 간단한 데이터의 경우 XML로드 및 구문 분석에 이미 익숙한 경우 XML 경로를 사용합니다.

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