XNA에서 큰 2D 세계지도의 일부를 동적으로로드하려면 어떻게합니까?


17

거대한 세계지도를 개발하고 싶습니다. 최소 8000 × 6000 픽셀 크기. 나는 그것을 800 × 600 픽셀 PNG 이미지의 10 × 10 그리드로 나누었습니다. 모든 것을 메모리에로드하지 않으려면 격자에서 플레이어의 위치에 따라 이미지를로드 및 언로드해야합니다.

예를 들어 다음은 위치에있는 플레이어입니다 (3,3).

(3,3)의 선수

오른쪽으로 이동 (4,3)하면 맨 왼쪽에있는 세 개의 이미지가 할당 해제되고 오른쪽에있는 세 개의 이미지가 할당됩니다.

플레이어가 (4,3)으로 이동

그리드의 각 셀 내에로드 및 언로드를 트리거하는 임계 값이 있어야합니다. 로딩은 아마도 별도의 스레드에서 발생해야합니다.

그러한 시스템을 어떻게 설계합니까?


1
빠른 제안 : 여기에는 "게임에서 타일을 동적으로로드하는 방법"과 "XBox 360 용 XNA에서 스레딩을 수행하는 방법"이라는 두 가지 질문이 있습니다. 이 포스트에서 OS 특정 세그먼트를 제거하십시오-타일 로딩 코드는 XB360, Linux 및 Nintendo Wii에서 동일하며 XBox360에서 스레딩을위한 새로운 포스트를 만듭니다.
ZorbaTHut

실제 문제에 대한 질문으로, 이것은 부드러운 스크롤 맵입니까, 아니면 일련의 개별 비 스크롤 화면입니까?
ZorbaTHut

"매끄러운 스크롤 맵"을 이해했다면 그렇습니다. 질문을 분리하는 것에 관해서는 그것에 대해 생각했지만 결과적으로 질문을 주저했습니다. 하지만 지금 할게요
pek

답변:


10

여기서 해결해야 할 몇 가지 문제가 있습니다. 첫 번째는 타일을로드 및 언로드하는 방법입니다. 기본적으로 ContentManager를 사용하면 특정 컨텐츠를 언로드 할 수 없습니다. 그러나 이것의 커스텀 구현은 :

public class ExclusiveContentManager : ContentManager
{
    public ExclusiveContentManager(IServiceProvider serviceProvider,
        string RootDirectory) : base(serviceProvider, RootDirectory)
    {
    }

    public T LoadContentExclusive<T>(string AssetName)
    {
        return ReadAsset<T>(AssetName, null);
    }

    public void Unload(IDisposable ContentItem)
    {
        ContentItem.Dispose();
    }
}

두 번째 문제는로드 및 언로드 할 타일을 결정하는 방법입니다. 다음은이 문제를 해결합니다.

public class Tile
{
    public int X;
    public int Y;
    public Texture2D Texture;
}

int playerTileX;
int playerTileY;
int width;
int height;

ExclusiveContentManager contentEx;
Tile[,] tiles;

void updateLoadedTiles()
{
    List<Tile> loaded = new List<Tile>();

    // Determine which tiles need to be loaded
    for (int x = playerTileX - 1; x <= playerTileX + 1; x++)
        for (int y = playerTileY - 1; y <= playerTileY; y++)
        {
            if (x < 0 || x > width - 1) continue;
            if (y < 0 || y > height - 1) continue;

            loaded.Add(tiles[x, y]);
        }

    // Load and unload as necessary
    for (int x = 0; x < width; x++)
        for (int y = 0; y < height; y++)
        {
            if (loaded.Contains(tiles[x, y]))
            {
                if (tiles[x, y].Texture == null)
                    loadTile(x, y);
            }
            else
            {
                if (tiles[x, y].Texture != null)
                    contentEx.Unload(tiles[x, y].Texture);
            }
        }
}

void loadTile(int x, int y)
{
    Texture2D tex = contentEx.LoadEx<Texture2D>("tile_" + x + "_" + y);
    tiles[x, y].Texture = tex;
}

마지막 문제는 언제로드 및 언로드할지 결정하는 방법입니다. 아마도 가장 쉬운 부분 일 것입니다. 플레이어의 화면 위치가 결정되면 간단히 Update () 메서드에서 수행 할 수 있습니다.

int playerScreenX;
int playerScreenY;
int tileWidth;
int tileHeight;

protected override void Update(GameTime gameTime)
{
    // Code to update player position, etc...

    // Load/unload
    int newPlayerTileY = (int)((float)playerScreenX / (float)tileWidth);
    int newPlayerTileX = (int)((float)playerScreenY / (float)tileHeight);

    if (newPlayerTileX != playerTileX || newPlayerTileY != playerTileY)
        updateLoadedTiles();

    base.Update(gameTime);
}

물론 타일을 그릴 필요가 있지만 tileWidth, tileHeight 및 Tiles [] 배열이 주어지면 사소한 것입니다.


감사합니다. 아주 잘 말했다. 나는 당신이 알고 있다면 제안을하고 싶습니다 : 타일로드가 스레드에서 어떻게 수행되는지 설명 할 수 있습니까? 한 번에 3 개의 800x600 타일을로드하면 게임이 약간 일시 중지 될 것이라고 생각합니다.
pek

Texture2D 객체를 만들 때 그래픽 카드가 필요하므로, 두 번째 스레드에서로드가 완료된 경우에도 여전히 건너 뛸 수 있습니다.
Sean James

0

당신이하고 싶은 것은 꽤 일반적입니다. 이 기술 및 기타 일반적인 기술에 대한 유용한 자습서를 보려면 이 타일 엔진 시리즈를 확인하십시오 .

이전에 이와 같은 작업을 수행하지 않은 경우 시리즈를 시청하는 것이 좋습니다. 그러나 원하는 경우 마지막 자습서 코드를 얻을 수 있습니다. 나중에 수행하는 경우 그리기 방법을 확인하십시오.

간단히 말해서 플레이어 주위의 최소 및 최대 X / Y 지점을 찾아야합니다. 일단 당신은 그것을 각각 반복하고 그 타일을 그립니다.

public override void Draw(GameTime gameTime)
{
    Point min = currentMap.WorldPointToTileIndex(camera.Position);
    Point max = currentMap.WorldPointToTileIndex( camera.Position +
        new Vector2(
            ScreenManager.Viewport.Width + currentMap.TileWidth,
            ScreenManager.Viewport.Height + currentMap.TileHeight));


    min.X = (int)Math.Max(min.X, 0);
    min.Y = (int)Math.Max(min.Y, 0);
    max.X = (int)Math.Min(max.X, currentMap.Width);
    max.Y = (int)Math.Min(max.Y, currentMap.Height + 100);

    ScreenManager.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend
                                    , SpriteSortMode.Immediate
                                    , SaveStateMode.None
                                    , camera.TransformMatrix);
    for (int x = min.X; x < max.X; x++)
    {
        for (int y = min.Y; y < max.Y; y++)
        {

            Tile tile = Tiles[x, y];
            if (tile == null)
                continue;

            Rectangle currentPos = new Rectangle(x * currentMap.TileWidth, y * currentMap.TileHeight, currentMap.TileWidth, currentMap.TileHeight);
            ScreenManager.SpriteBatch.Draw(tile.Texture, currentPos, tile.Source, Color.White);
        }
    }

    ScreenManager.SpriteBatch.End();
    base.Draw(gameTime);
}

public Point WorldPointToTileIndex(Vector2 worldPoint)
{
    worldPoint.X = MathHelper.Clamp(worldPoint.X, 0, Width * TileWidth);
    worldPoint.Y = MathHelper.Clamp(worldPoint.Y, 0, Height * TileHeight);


    Point p = new Point();

    // simple conversion to tile indices
    p.X = (int)Math.Floor(worldPoint.X / TileWidth);
    p.Y = (int)Math.Floor(worldPoint.Y / TileWidth);

    return p;
}

보시다시피 많은 일이 진행되고 있습니다. 매트릭스를 만들 카메라가 필요합니다. 현재 픽셀 위치를 타일 인덱스로 변환해야합니다. 최소 / 최대 포인트를 찾으십시오. )를 그릴 수 있습니다.

튜토리얼 시리즈를 보는 것이 좋습니다. 그는 현재 문제, 타일 편집기를 만드는 방법, 플레이어를 경계 내에서 유지, 스프라이트 애니메이션, AI 상호 작용 등을 다루고 있습니다.

참고로 TiledLib 에는이 기능이 내장되어 있습니다. 코드도 연구 할 수 있습니다.


이것은 매우 유용하지만 타일 맵의 렌더링에만 적용됩니다. 관련 타일로드 및 언로드는 어떻습니까? 메모리에 800x600 타일 100 개를로드 할 수 없습니다. 비디오 자습서를 살펴 보겠습니다. 감사.
pek

아 .. 나는 당신의 질문을 오해했습니다. 전통적인 타일 맵을 사용하고 있습니까, 아니면 조각으로 나눈 레벨에 대한 큰 이미지가 있습니까?

그냥 생각하면 .... 타일 의 이름 을 배열에 저장 하고 동일한 기술을 사용하여로드하고 그릴 내용을 알 수 있습니다. 객체를 재사용하려고 시도하거나 많은 쓰레기를 만들 수 있음을 명심하십시오

두 번째 :지도는 8000x6000이며 800x600 픽셀의 10x10 타일로 나뉩니다. 재사용과 관련하여 이미 책임이있는 콘텐츠 관리자가 있습니다.
pek

0

나는 현재 프로젝트와 매우 비슷한 것을 연구하고 있습니다. 이것은 내가하는 일에 대한 간단한 요약이며, 일을 좀 더 쉽게하는 방법에 대한 몇 가지 참고 사항이 있습니다.

저에게있어 첫 번째 문제는 세상을로드 및 언로드에 적합한 작은 덩어리로 나누는 것이 었습니다. 타일 ​​기반 맵을 사용하고 있으므로 해당 단계가 훨씬 쉬워집니다. 레벨에서 각 3D 객체의 위치를 ​​고려하지 않고 이미 레벨을 타일로 멋지게 분할했습니다. 이를 통해 Y 타일 청크로 세상을 X로 간단히 나누고로드 할 수 있습니다.

수동이 아닌 자동으로 수행하려고합니다. XNA를 사용하고 있으므로 레벨 컨텐츠에 대한 사용자 정의 내보내기와 함께 컨텐츠 파이프 라인을 사용할 수 있습니다. 재 컴파일하지 않고 내보내기 프로세스를 실행하는 방법을 모른다면 정직하게 권장합니다. C #은 C ++처럼 컴파일 속도가 느리지 않지만 맵을 약간 변경할 때마다 Visual Studio를로드하고 게임을 다시 컴파일 할 필요는 없습니다.

여기서 또 다른 중요한 점은 레벨 청크가 포함 된 파일에 대해 좋은 명명 규칙을 사용하는 것입니다. 청크 C를로드 또는 언로드하고 런타임에이를 수행하기 위해로드해야하는 파일 이름을 생성한다는 것을 알고 자합니다. 마지막으로, 길을 내리는 데 도움이 될만한 작은 것들을 생각해보십시오. 청크의 크기를 변경하고 다시 내보내고 즉시 성능에 미치는 영향을 볼 수 있다는 것이 정말 좋습니다.

런타임에는 여전히 간단합니다. 청크를 비동기식으로로드 및 언로드하는 방법이 필요하지만 게임이나 엔진의 작동 방식에 따라 크게 달라집니다. 두 번째 이미지는 정확합니다.로드 또는 언로드 할 청크를 결정하고 해당 요청이 아직없는 경우 적절한 요청을해야합니다. 한 번에로드 한 청크 수에 따라 플레이어가 한 청크에서 다음 청크로 경계를 넘을 때마다이 작업을 수행 할 수 있습니다. 어쨌든 최악의 (합리적인)로드 시간에도 청크가로드되기 전에 충분한 양이로드되었는지 확인하려고합니다. 성능과 메모리 소비간에 균형이 잘 맞을 때까지이 숫자를 많이 사용하고 싶을 것입니다.

실제 아키텍처가 진행되는 한,로드 / 언로드해야 할 사항을 결정하는 프로세스에서 메모리에서 데이터를 실제로로드 및 언로드하는 프로세스를 추상화하려고합니다. 첫 번째 반복에서는로드 / 언로드 성능에 대해 걱정하지 않고 가능한 가장 간단한 것을 가져 와서 적절한 시간에 적절한 요청을 생성하는지 확인하십시오. 그런 다음 가비지를 최소화하기 위해로드 방법을 최적화 할 수 있습니다.

사용중인 엔진으로 인해 많은 추가 복잡성이 발생했지만 구현에 따라 다릅니다. 내가 한 일에 대해 궁금한 점이 있으면 의견을 말하고 도와 드리겠습니다.


1
msbuild를 사용하여 Visual Studio 외부에서 콘텐츠 파이프 라인을 실행할 수 있습니다. 두 번째 WinForms 샘플은 다음과 같습니다. creators.xna.com/en-US/sample/winforms_series2
Andrew Russell

@Andrew !!!!! 정말 고맙습니다!!! 바로 그 이유로 컨텐츠 파이프 라인을 완전히 포기하려고했습니다!
pek
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.