ใน XNA ฉันจะโหลดบางส่วนของแผนที่โลกขนาดใหญ่แบบไดนามิกได้อย่างไร


17

ฉันต้องการพัฒนาแผนที่โลกขนาดใหญ่ ขนาดอย่างน้อย 8000 × 6000 พิกเซล ฉันแบ่งมันเป็นรูปแบบ PNG ขนาด 800 × 600 พิกเซลที่ 10 × 10 เพื่อหลีกเลี่ยงการโหลดทุกอย่างลงในหน่วยความจำภาพควรจะโหลดและไม่โหลดขึ้นอยู่กับตำแหน่งของผู้เล่นในตาราง

ตัวอย่างเช่นนี่คือผู้เล่นที่ตำแหน่ง(3,3):

ผู้เล่นที่ (3,3)

เมื่อเขาเลื่อนไปทางขวา(4,3)ภาพสามภาพทางด้านซ้ายสุดจะถูกจัดสรรคืนขณะที่ภาพทั้งสามทางด้านขวาถูกจัดสรร:

ผู้เล่นย้ายไปที่ (4,3)

อาจมีขีด จำกัด ในแต่ละเซลล์ของกริดที่เรียกการโหลดและการขนถ่าย การโหลดอาจเกิดขึ้นในเธรดแยกต่างหาก

ฉันจะออกแบบระบบดังกล่าวได้อย่างไร


1
คำแนะนำด่วน: คุณมีสองคำถามที่นี่ "ฉันจะโหลดไทล์แบบไดนามิกในเกมได้อย่างไร" และ "ฉันจะทำเกลียวบน XNA สำหรับ XBox 360 ได้อย่างไร" ลบกลุ่มเฉพาะระบบปฏิบัติการจากโพสต์นี้ - รหัส tileloading จะเหมือนกันใน 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;
}

ปัญหาสุดท้ายคือวิธีการตัดสินใจว่าจะโหลดและยกเลิกการโหลดเมื่อใด นี่อาจเป็นส่วนที่ง่ายที่สุด สิ่งนี้สามารถทำได้ในวิธีการอัปเดต () เมื่อตำแหน่งหน้าจอของผู้เล่นถูกกำหนดแล้ว:

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);
}

แน่นอนว่าคุณจะต้องวาดแบบเรียงต่อกัน แต่ด้วยไทล์ไวด์ไทล์ไฮไฮต์และไทล์เรียง [] สิ่งนี้น่าจะเป็นเรื่องเล็กน้อย


ขอขอบคุณ. พูดอย่างมาก ฉันอยากจะแนะนำถ้าคุณรู้: คุณช่วยอธิบายได้ว่าการโหลดไทล์จะทำได้อย่างไรในเธรด ฉันจินตนาการว่าการโหลดไทล์ 3 800x600 ครั้งจะหยุดเกมเล็กน้อย
pek

กราฟิกการ์ดจำเป็นต้องมีส่วนร่วมเมื่อสร้างวัตถุ Texture2D ดังนั้นเท่าที่ฉันรู้ว่ามันจะทำให้เกิดการข้ามแม้ว่าการโหลดเสร็จในเธรดที่สอง
ฌอนเจมส์

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;
}

อย่างที่คุณเห็นมีหลายอย่างเกิดขึ้น คุณต้องการกล้องที่จะสร้างเมทริกซ์ของคุณคุณต้องแปลงตำแหน่งพิกเซลปัจจุบันของคุณเป็นดัชนีย่อยคุณพบคะแนนต่ำสุด / สูงสุดของคุณ (ฉันเพิ่มเล็กน้อยถึง MAX ของฉันดังนั้นมันจึงวาดหน้าจอที่มองเห็นได้เล็กน้อย ) แล้วคุณสามารถวาดได้

ฉันขอแนะนำให้ดูชุดการสอน เขาครอบคลุมถึงปัญหาในปัจจุบันของคุณวิธีการสร้างตัวแก้ไขไทล์ทำให้ผู้เล่นอยู่ในขอบเขตแอนิเมชั่นสไปรต์การโต้ตอบของ AI และอื่น ๆ

ในบันทึกด้านข้างTiledLibมีสิ่งนี้ในตัวคุณสามารถศึกษารหัสได้เช่นกัน


แม้ว่าจะมีประโยชน์มาก แต่ครอบคลุมเฉพาะการเรนเดอร์แผนที่ย่อยเท่านั้น แล้วการโหลดและการโหลดไทล์ที่เกี่ยวข้องจะเป็นอย่างไร ฉันไม่สามารถโหลดไทล์ได้ 100 800x600 ในหน่วยความจำ ฉันจะดูวิดีโอการสอน ขอบคุณ
pek

อ่า .. ฉันเข้าใจคำถามของคุณผิด คุณกำลังใช้ tilemap แบบดั้งเดิมหรือคุณมีภาพขนาดใหญ่สำหรับระดับที่คุณแตกเป็นชิ้น ๆ

แค่คิด .... คุณสามารถเก็บชื่อได้ไพ่ของคุณในอาเรย์และใช้เทคนิคเดียวกันเพื่อรู้ว่าจะโหลดและวาดอย่างไร โปรดจำไว้ว่าให้ลองใช้วัตถุใด ๆ อีกครั้งหรือคุณอาจสร้างขยะจำนวนมาก

ที่สอง: แผนที่ 8000x6000 แบ่งออกเป็น 10x10 แผ่น 800x600 px สำหรับการใช้ซ้ำฉันมีผู้จัดการเนื้อหาที่รับผิดชอบเรื่องนั้นอยู่แล้ว
pek

0

ฉันทำงานเกี่ยวกับสิ่งที่คล้ายกันมากสำหรับโครงการปัจจุบันของฉัน นี่เป็นวิธีที่ฉันทำอย่างรวดเร็วโดยมีข้อสังเกตด้านข้างเกี่ยวกับวิธีทำให้ตัวเองง่ายขึ้นเล็กน้อย

สำหรับฉันปัญหาแรกคือการแตกโลกเป็นชิ้นเล็ก ๆ ที่เหมาะสมในการโหลดและขนถ่ายได้ทันที เมื่อคุณใช้แผนที่แบบเรียงต่อกันขั้นตอนนั้นจะง่ายขึ้นอย่างเห็นได้ชัด แทนที่จะพิจารณาตำแหน่งของวัตถุ 3 มิติแต่ละชิ้นในระดับคุณได้แบ่งระดับของคุณเป็นอย่างดีแล้ว สิ่งนี้ช่วยให้คุณแบ่งโลกออกเป็นชิ้นส่วน X โดย Y และโหลดสิ่งเหล่านั้น

คุณจะต้องการที่จะทำโดยอัตโนมัติมากกว่าด้วยมือ เนื่องจากคุณใช้ XNA คุณมีตัวเลือกในการใช้ Content Pipeline กับผู้ส่งออกที่กำหนดเองสำหรับเนื้อหาระดับของคุณ หากคุณไม่ทราบวิธีการเรียกใช้กระบวนการส่งออกโดยไม่ต้องทำการคอมไพล์ซ้ำอีกครั้งฉันขอแนะนำอย่างตรงไปตรงมา แม้ว่า 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.