XNA में, मैं गतिशील रूप से एक बड़े 2D दुनिया के नक्शे के कुछ हिस्सों को कैसे लोड करूं?


17

मैं एक विशाल विश्व मानचित्र विकसित करना चाहता हूं; आकार में कम से कम 8000 × 6000 पिक्सेल। मैंने इसे 800 × 600-पिक्सेल पीएनजी छवियों के 10 × 10 ग्रिड में तोड़ दिया है। मेमोरी में सब कुछ लोड करने से बचने के लिए, चित्रों को ग्रिड में खिलाड़ी की स्थिति के आधार पर लोड और अनलोड किया जाना चाहिए।

उदाहरण के लिए, यहां खिलाड़ी स्थिति में है (3,3):

प्लेयर (3,3) पर

जब वह दाईं ओर जाता है (4,3), तो बाईं ओर की तीन छवियों को हटा दिया जाता है, जबकि दाईं ओर की तीन छवियों को आवंटित किया जाता है:

प्लेयर (4,3) में चला जाता है

लोडिंग और अनलोडिंग को चालू करने वाले ग्रिड के प्रत्येक सेल के अंदर संभवतः एक सीमा होनी चाहिए। लोडिंग संभवतः एक अलग थ्रेड में होनी चाहिए।

मैं ऐसी प्रणाली कैसे डिजाइन कर सकता हूं?


1
त्वरित सुझाव: आपको यहां दो प्रश्न मिले हैं, "मैं एक खेल में गतिशील रूप से टाइल कैसे लोड करता हूं" और "मैं एक्सबॉक्स 360 के लिए एक्सएनए पर थ्रेडिंग कैसे करूं"। इस पोस्ट से OS- विशिष्ट सेगमेंट को हटा दें - Tileloading कोड XB360, Linux, और Nintendo Wii पर समान होगा - और XBox360 पर थ्रेडिंग के लिए एक नया पोस्ट करें।
जोरबाहुत

अपनी वास्तविक समस्या के बारे में एक प्रश्न के लिए, क्या यह एक चिकनी-स्क्रॉलिंग मानचित्र है, या व्यक्तिगत गैर-स्क्रॉलिंग स्क्रीन की एक श्रृंखला है?
ज़ोरबहुत

अगर मुझे "चिकनी-स्क्रॉलिंग मैप" समझ में आया, हां, यह है। प्रश्न को अलग करने के लिए, मैंने इसके बारे में सोचा, लेकिन थोड़े परिणाम के रूप में प्रश्न पूछने में संकोच किया। लेकिन, मैं इसे अभी करूंगा।
जेक

जवाबों:


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 टाइल लोड करने से खेल थोड़ा रुक जाएगा।
जेक

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 में थोड़ा जोड़ता हूं इसलिए यह दृश्य स्क्रीन से थोड़ा आगे निकलता है ), और फिर आप इसे आकर्षित कर सकते हैं।

मैं वास्तव में ट्यूटोरियल श्रृंखला देखने का सुझाव देता हूं। वह आपकी वर्तमान समस्या को कवर करता है, कैसे एक टाइल संपादक बनाने के लिए, खिलाड़ी को सीमा के भीतर रखते हुए, एनीमेशन, एआई इंटरैक्शन, आदि ...

एक साइड नोट पर TiledLib में यह बनाया गया है। आप उनके कोड का भी अध्ययन कर सकते हैं।


जबकि यह बहुत सहायक है, यह केवल टाइल के नक्शे के प्रतिपादन को कवर करता है। प्रासंगिक टाइलों को लोड करने और उतारने के बारे में क्या? मैं स्मृति में 100 800x600 टाइल लोड नहीं कर सकता। मैं वीडियो ट्यूटोरियल पर एक नज़र डालूंगा; धन्यवाद।
जेक

आह्ह .. मैंने आपका सवाल गलत समझा। तो क्या आप एक पारंपरिक तिलमप का उपयोग कर रहे हैं, या क्या आपके पास अपने स्तर के लिए एक बड़ी छवि है जिसे आपने टुकड़ों में तोड़ दिया है?

बस एक विचार .... आप अपनी टाइलों के नाम एक सरणी में संग्रहीत कर सकते हैं , और लोड करने और आकर्षित करने के लिए उसी तकनीक का उपयोग कर सकते हैं। ध्यान रखें कि किसी भी वस्तु को फिर से उपयोग करने की कोशिश करें या आप बहुत सारा कचरा पैदा कर सकते हैं

दूसरा: 800x600 px के 10x10 टाइल्स में मैप 8000x6000 टूट गया है। पुन: उपयोग के लिए, मेरे पास पहले से ही एक सामग्री प्रबंधक है जो इसके लिए जिम्मेदार है।
जेक

0

मैं अपने मौजूदा प्रोजेक्ट के लिए कुछ इसी तरह काम कर रहा हूं। यह एक त्वरित रन डाउन है कि मैं यह कैसे कर रहा हूं, कुछ साइड नोट्स के साथ चीजों को अपने आप पर थोड़ा आसान कैसे बनाया जाए।

मेरे लिए, पहली समस्या दुनिया को छोटे-छोटे टुकड़ों में तोड़ना था जो उड़ने पर लोड करने और उतारने के लिए उपयुक्त होगी। चूंकि आप टाइल आधारित मानचित्र का उपयोग कर रहे हैं, इसलिए यह कदम काफी आसान हो जाता है। स्तर में प्रत्येक 3 डी ऑब्जेक्ट के पदों पर विचार करने के बजाय, आपके पास पहले से ही अपने स्तर को अच्छी तरह से टाइलों में विभाजित किया गया है। यह आपको बस Y टाइल के टुकड़े द्वारा दुनिया को एक्स में तोड़ देता है, और उन्हें लोड करता है।

आप हाथ से नहीं, बल्कि स्वचालित रूप से ऐसा करना चाहते हैं। चूंकि आप XNA का उपयोग कर रहे हैं, आपके पास अपने स्तर की सामग्री के लिए कस्टम निर्यातक के साथ सामग्री पाइपलाइन का उपयोग करने का एक विकल्प है। जब तक आप recompiling के बिना निर्यात प्रक्रिया को चलाने का कोई तरीका नहीं जानते, मैं ईमानदारी से इसके खिलाफ सिफारिश करूंगा। हालांकि C # लगभग उतना धीमा नहीं है जितना C ++ के संकलन के लिए है, फिर भी आप विज़ुअल स्टूडियो को लोड करना नहीं चाहते हैं और हर बार जब आप नक्शे में मामूली बदलाव करते हैं, तो अपने गेम को फिर से जोड़ सकते हैं।

यहां एक और महत्वपूर्ण बात यह सुनिश्चित करना है कि आप अपने स्तर के चंक्स वाली फाइलों के लिए एक अच्छे नामकरण सम्मेलन का उपयोग करें। आप यह जानना चाहते हैं कि आप chunk C को लोड या अनलोड करना चाहते हैं, और फिर रन टाइम में ऐसा करने के लिए आपको जो फाइल लोड करना है, उसे जेनरेट करें। अंत में, किसी भी छोटी चीजों के बारे में सोचें जो आपको सड़क पर आने में मदद कर सकती हैं। यह बदलने में सक्षम होना बहुत अच्छा है कि एक चंक कितना बड़ा है, फिर से निर्यात करता है, और फिर प्रदर्शन पर उस के प्रभाव को तुरंत देखें।

रन समय में, यह अभी भी बहुत सीधा है। आपको अतुल्यकालिक रूप से लोड और अनलोड करने के लिए किसी तरह की आवश्यकता होगी, लेकिन यह इस बात पर बहुत निर्भर करता है कि आपका गेम या इंजन कैसे काम करता है। आपकी दूसरी छवि बिल्कुल सही है - आपको यह निर्धारित करने की आवश्यकता है कि क्या चंक को लोड या अनलोड किया जाना चाहिए, और यह सुनिश्चित करने के लिए उचित अनुरोध करें कि यदि यह पहले से ही नहीं है। आपके द्वारा एक समय में कितने चक्कों को लोड किए जाने के आधार पर, आप ऐसा तभी कर सकते हैं जब खिलाड़ी एक बाउंड से दूसरे छोर तक एक सीमा पार करता है। सब के बाद, किसी भी तरह से, आप यह सुनिश्चित करना चाहते हैं कि पर्याप्त लोड हो गया है कि सबसे खराब (उचित) लोड समय में भी, खिलाड़ी को देखने से पहले ही चंक को लोड किया जाता है। आप शायद इस संख्या के साथ खेलना चाहते हैं जब तक आपको प्रदर्शन और स्मृति खपत के बीच एक अच्छा संतुलन नहीं मिलता।

जहां तक ​​वास्तविक आर्किटेक्चर की बात है, आप वास्तव में लोडिंग / अनलोड किए जाने के निर्धारण की प्रक्रिया से डेटा को मेमोरी से लोड और अनलोड करने की प्रक्रिया को अमूर्त करना चाहते हैं। आपके पहले पुनरावृत्ति के लिए, मैं लोडिंग / अनलोडिंग के प्रदर्शन के बारे में भी चिंता नहीं करूंगा और बस सबसे सरल चीज प्राप्त कर सकता हूं जो संभवतः काम कर सकती है, और यह सुनिश्चित कर सकती है कि आप उचित समय पर उचित अनुरोधों को उत्पन्न कर रहे हैं। उसके बाद, आप अनुकूलन को देख सकते हैं कि आप कचरे को कम करने के लिए कैसे लोड करते हैं।

मैं इंजन का उपयोग करने के कारण बहुत सी अतिरिक्त जटिलता में भाग गया, लेकिन यह बहुत विशिष्ट कार्यान्वयन है। यदि आपने मेरे बारे में कोई प्रश्न किया है, तो कृपया टिप्पणी करें और मैं वह करूँगा जो मैं कर सकता हूँ।


1
आप Visual Studio के बाहर सामग्री पाइपलाइन को चलाने के लिए msbuild का उपयोग कर सकते हैं। दूसरा WinForms नमूना आपने कवर किया है: creators.xna.com/en-US/sample/winforms_series2
एंड्रयू रसेल

@ और !!!!! बहुत बहुत धन्यवाद!!! मैं उस कारण के लिए सामग्री पाइपलाइन को पूरी तरह से छोड़ने वाला था!
pek
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.