मैं C ++ गेम के लिए एक सेव फाइल कैसे बनाऊं?


33

मैं एक वीडियो गेम प्रोग्रामिंग कोर्स के लिए अपने फाइनल को कोड कर रहा हूं, और मैं जानना चाहता हूं कि अपने गेम के लिए एक सेव फाइल कैसे बनाई जाए, ताकि एक यूजर खेल सके, और फिर बाद में वापस आ सके। किसी भी विचार यह कैसे किया जाता है, हर चीज जो मैंने पहले की है वह एकल रन प्रोग्राम है।



2
आप
Nick Shvelidze

1
@ शाल्लो जब आप ऐसा कर सकते हैं, तो ऐसा लगता है कि यह बहुत अधिक जटिलता को जोड़ देगा जो जरूरी नहीं है।
नैट

जवाबों:


38

आपको अपने हार्ड ड्राइव में मेमोरी में अपने चर को बचाने के लिए क्रमांकन का उपयोग करने की आवश्यकता है । .NET XML में कई प्रकार के क्रमांकन हैं, हालांकि बाइनरी और JSON धारावाहिक उपलब्ध हैं। मैं C ++ प्रोग्रामर के बहुत अधिक नहीं हूं, लेकिन एक त्वरित खोज ने C ++ में क्रमांकन पर एक उदाहरण दिया:

पुस्तकालय हैं, जो क्रमबद्ध कार्यशीलता प्रदान करते हैं। कुछ अन्य उत्तरों में उल्लिखित हैं।

जिन चरों में आपकी रुचि होगी, वे शायद गेम स्टेट से संबंधित हैं। उदाहरण के लिए, आप शायद इस प्रकार की जानकारी जानना चाहेंगे

  1. खिलाड़ी स्तर 3 खेल रहा था
  2. खिलाड़ी X पर था, Y विश्व निर्देशांक
  3. खिलाड़ी के बैकपैक में तीन आइटम हैं
    1. हथियार
    2. कवच
    3. भोजन

आप वास्तव में परवाह नहीं करते हैं कि क्या बनावट का उपयोग किया जा रहा है (जब तक आपका खिलाड़ी अपनी उपस्थिति नहीं बदल सकता है, यह एक विशेष मामला है), क्योंकि वे आमतौर पर एक ही होते हैं। आपको महत्वपूर्ण गेमस्टेट डेटा को सहेजने पर ध्यान देने की आवश्यकता है।

जब आप अपना खेल शुरू करते हैं, तो आप "नए" गेम के लिए सामान्य रूप से शुरू करते हैं (यह आपके बनावट, मॉडल, आदि को लोड करता है) लेकिन उचित समय पर आप अपनी सहेजें फ़ाइल से मानों को खेल की वस्तु में वापस लोड करते हैं जो "डिफ़ॉल्ट" नए की जगह लेता है खेल की स्थिति। फिर आप खिलाड़ी को फिर से खेलना शुरू करने की अनुमति देते हैं।

मैंने इसे यहां बहुत सरल किया है, लेकिन आपको सामान्य विचार प्राप्त करना चाहिए। यदि आपके पास अधिक विशिष्ट प्रश्न है, तो यहां एक नया प्रश्न पूछें और हम आपकी सहायता करने का प्रयास कर सकते हैं।


मैं समझता हूं कि मुझे क्या बचाने की आवश्यकता है, लेकिन मैं क्या जानना चाहता हूं कि सटीक तरीका क्या है, क्या आप इसे प्रोजेक्ट में .txt फ़ाइल, उन संशोधित चर, या किसी अन्य तरीके से सहेज सकते हैं
टकर मॉर्गन

हां, यदि आपका खेल सरल है, तो एक पाठ फ़ाइल पर्याप्त हो सकती है; आपको यह ध्यान रखने की आवश्यकता है कि कोई भी किसी पाठ फ़ाइल को संपादित कर सकता है और इस प्रकार अपने गेम को बचा सकता है ...
Nate

पाठ फ़ाइल केवल साधारण गेम के लिए ही नहीं बचती है। विरोधाभास ने उन सभी खेलों के लिए फाइलों को बचाने के लिए एक संरचित पाठ प्रारूप का उपयोग किया, जो उन्होंने एक ही इंजन का उपयोग करते हुए प्रमुख यूरोपा युनिवर्सलिस इंजन के रूप में बनाया था। विशेष रूप से देर से खेल, इन फ़ाइलों को भारी हो सकता है।
डैन नीली

1
@DanNeely एक उचित बिंदु, कोई कारण नहीं कि आप बहुत सारे जटिल डेटा को संग्रहीत करने के लिए एक पाठ प्रारूप का उपयोग नहीं कर सकते, लेकिन आम तौर पर बोलते समय, जब आपका डेटा उस जटिल होता है, तो दूसरे प्रारूप (बाइनरी, xml, आदि) के लाभ अधिक स्पष्ट हो जाते हैं।
नैट

1
@ नैटब्रेड सहमत। विरोधाभास खेल बहुत आधुनिक थे और परिदृश्य डेटा के लिए एक समान (समान?) प्रारूप का उपयोग करते थे। अपने अधिकांश डेटा को पाठ के रूप में संग्रहीत करने का मतलब है कि उन्हें सार्वजनिक उपयोग के लिए परिदृश्य संपादक टूल में निवेश करने की आवश्यकता नहीं थी।
डैन नीली

19

आमतौर पर यह आपके खेल के लिए विशिष्ट है। मुझे यकीन है कि आपने अपनी कक्षाओं में अब तक फाइलों से लिखना और पढ़ना सीखा है। मूल विचार है:

  1. गेम से बाहर निकलते समय, उन मानों को लिखें जिन्हें आप फ़ाइल में सहेजना चाहते हैं।
  2. गेम को लोड करते समय, यह देखने के लिए जांचें कि क्या कोई सेव फ़ाइल मौजूद है, यदि ऐसा होता है, तो अपने प्रोग्राम में रीड वैल्यूज़ लोड करें। यदि फ़ाइल मौजूद नहीं है, तो जारी रखें जैसे आप अभी करते हैं और मानों को उनके आरंभिक / डिफ़ॉल्ट मानों पर सेट करते हैं।

आप जो लिखते हैं वह आपके ऊपर है, यह आपके खेल पर निर्भर करता है। लेखन का एक तरीका यह है कि आप एक विशिष्ट क्रम में बाइट स्ट्रीम के रूप में इच्छित चर लिखना चाहते हैं। फिर लोड करते समय, उन्हें उसी क्रम में अपने कार्यक्रम में पढ़ें।

उदाहरण के लिए (त्वरित छद्म कोड में):

SaveGame(FileInput file) {
    file.writeInt(playerLevel);
    file.writeInt(playerHealth);
    file.writeInt(gameProgress);
}

LoadGame(FileInput file) {
    if(file.exists()) {
        playerLevel= file.readInt();
        playerHealth = file.readInt();
        gameProgress = file.readInt();
    } else {
        playerLevel = 1;
        playerHealth = 100;
        gameProgress = 0;
    }
}

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

1
हां, यह भविष्य के सबूत बचाने वाली फ़ाइलों को उत्पन्न नहीं करता है। यह उन डेटा के लिए भी काम नहीं करता है जिनमें स्ट्रिंग्स जैसे परिवर्तनशील बाइट आकार होते हैं। उत्तरार्द्ध पहले डेटा के आकार को लिखने के द्वारा ठीक करना आसान है, जिसे लिखा जाना है, फिर बाइट्स की सही संख्या को पढ़ने के लिए लोड करते समय।
MichaelHouse

6

ऐसा करने के लिए संभवतः बड़ी संख्या में तरीके हैं, लेकिन सबसे सरल जो मैंने हमेशा पाया और व्यक्तिगत रूप से और व्यावसायिक रूप से दोनों का उपयोग किया है, एक संरचना बनाना है जिसमें वे सभी मान शामिल हैं जिन्हें मैं सहेजना चाहता हूं।

struct SaveGameData
{
    int              characterLevel; // Any straight up values from the player
    int              inventoryCount; // Number of items the player has on them or stored or what not
    int[STAT_COUNT]  statistics;     // This is usually a constant size (I am tracking X number of stats)
    // etc
}

struct Item
{
    int itemTypeId;
    int Durability; // also used as a 'uses' count for potions and the like
    int strength;   // damage of a weapon, protection of armor, effectiveness of a potion
    // etc
}

मैं तो बस मूल फ़ाइल IO मानों का उपयोग करके किसी फ़ाइल से डेटा को फिर से लिखना / फ़्रेड करना। इन्वेंट्रीकाउंट आइटम संरचनाओं की संख्या है जो फ़ाइल में मुख्य SaveGameData संरचना के बाद सहेजी जाती हैं इसलिए मुझे पता है कि उस डेटा को लाने के बाद उनमें से कितने को पढ़ना है। यहां कुंजी यह है कि जब मैं कुछ नया सहेजना चाहता हूं, जब तक कि उसकी वस्तुओं या इस तरह की सूची नहीं है, तब तक मुझे केवल इतना करना है कि संरचना में कुछ मूल्य जोड़ दें। यदि इसकी मदों की सूची है तो मुझे एक रीड पास जोड़ना होगा जैसे मैंने पहले ही आइटम ऑब्जेक्ट्स के लिए निहित किया है, मुख्य हेडर में एक काउंटर और फिर प्रविष्टियां।

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

फिर, यह करने के लिए कुछ तरीके और यह C ++ की तुलना में C की ओर अधिक हो सकता है, लेकिन इसने काम पूरा कर लिया है!


1
यह भी ध्यान देने योग्य है कि यह प्लेटफ़ॉर्म-इंडिपेंडेंट नहीं है, C ++ स्ट्रिंग्स के लिए, या रेफ़रेंस या पॉइंटर्स के माध्यम से संदर्भित ऑब्जेक्ट्स के लिए या उपरोक्त किसी भी ऑब्जेक्ट्स के लिए काम नहीं करेगा!
काइलोटन

क्यों इस मंच स्वतंत्र नहीं है? इसने PC, PS * सिस्टम और 360 .. fwrite (pToDataBuffer, sizeof (datatype), countOfElements, pToFile) पर ठीक काम किया; उन सभी वस्तुओं के लिए काम करता है, जो मानती हैं कि आप उनके डेटा, और ऑब्जेक्ट के आकार का एक संकेतक प्राप्त कर सकते हैं और फिर उनमें से संख्या जो आप लिखना चाहते हैं .. और पढ़ सकते हैं कि मैच ..
जेम्स

यह है मंच स्वतंत्र, वहाँ बस कोई गारंटी नहीं कि एक मंच पर सहेजी गई फ़ाइलें एक दूसरे पर लोड किया जा सकता। जो उदाहरण के लिए गेम डेटा की बचत के लिए अप्रासंगिक है। पॉइंटर-टू-डेटा-एंड-साइज़-मैस्क़ी सामान स्पष्ट रूप से थोड़ा अजीब हो सकता है, लेकिन यह काम करता है।
लेफ्टनैताबाउट

3
वास्तव में इस बात की कोई गारंटी नहीं है कि यह आपके लिए हमेशा काम करता रहेगा - यदि आप एक नया संस्करण जारी करते हैं जो एक नए संकलक या यहां तक ​​कि नए संकलन विकल्पों के साथ बनाया जाता है जो कि स्ट्रक्चर पैडिंग को बदलता है? मैं अकेले इस कारण से कच्चे-स्ट्रक्चर राइटाइट () के उपयोग का दृढ़ता से विरोध करूंगा (संयोग से इस पर मैं अनुभव से बोल रहा हूं)।
शराबी

1
यह '32 बिट्स ऑफ डेटा 'के बारे में नहीं है। मूल पोस्टर बस पूछ रहा है "मैं अपने चर कैसे बचाऊं"। यदि आप चर को सीधे लिखते हैं, तो आप प्लेटफार्मों में जानकारी खो देते हैं। यदि आपको फ्राइटी से पहले प्रीप्रोसेस करना है, तो आपने उत्तर का सबसे महत्वपूर्ण हिस्सा छोड़ दिया है, अर्थात। डेटा को कैसे संसाधित किया जाए ताकि यह सही ढंग से सहेजा जाए, और इसमें केवल तुच्छ बिट शामिल है, अर्थात। एक डिस्क पर कुछ डालने के लिए फिगराइट कॉलिंग।
काइलोटन

3

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

इसके बाद, इसे एक फाइल में सेव करने के लिए कोड लिखें। एक अपेक्षाकृत सरल प्रारूप जिसका आप उपयोग कर सकते हैं, इस प्रकार है:

x y score coins

और इसलिए फ़ाइल इस तरह दिखाई देगी:

14 96 4200 100

इसका मतलब है कि वह 4200 और 100 के सिक्कों के साथ (14, 96) स्थान पर था।

इस फ़ाइल को लोड करने के लिए आपको कोड लिखना होगा (ifstream का उपयोग करें)।


दुश्मनों को बचाने के लिए फ़ाइल में उनकी स्थिति को शामिल करके किया जा सकता है। हम इस प्रारूप का उपयोग कर सकते हैं:

number_of_enemies x1 y1 x2 y2 ...

पहले number_of_enemiesपढ़ा जाता है और फिर प्रत्येक स्थिति को एक साधारण लूप के साथ पढ़ा जाता है।


1

इसके अलावा / सुझाव आपके क्रमांकन में एन्क्रिप्शन के स्तर को जोड़ना होगा ताकि उपयोगकर्ता "9999999999999999999" पर अपने मूल्यों को संपादित न कर सकें। ऐसा करने का एक अच्छा कारण पूर्णांक ओवरफ्लो (उदाहरण के लिए) को रोकना होगा।


0

मुझे लगता है कि आपका सबसे अच्छा दांव बढ़ावा है :: क्रमांकन क्योंकि आपके पदानुक्रम में प्रचार करना आसान है। आपको केवल शीर्ष ऑब्जेक्ट के सेव / लोड फ़ंक्शन को कॉल करने की आवश्यकता है और आपकी सभी कक्षाएं आसानी से बनाई गई हैं।

देखें: http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html


0

पूर्णता के लिए मैं एक c ++ क्रमांकन पुस्तकालय का उल्लेख करना चाहता हूं, जिसका मैं व्यक्तिगत रूप से उपयोग करता हूं और अभी तक इसका उल्लेख नहीं किया गया है: अनाज
यह उपयोग करने के लिए आसान है और इसमें सीरीज़िंग के लिए एक अच्छा, साफ वाक्यविन्यास है। यह कई प्रकार के प्रारूप भी प्रदान करता है जिन्हें आप (XML, Json, बाइनरी को शामिल कर सकते हैं) (संबंधित एंडियन के साथ एक पोर्टेबल संस्करण सहित)। यह वंशानुक्रम का समर्थन करता है और केवल हेडर है,


0

आपका गेम डेटा संरचनाओं (उम्मीद है?) से समझौता करेगा जिसे आपको बाइट्स में बदलना होगा (क्रमबद्ध) ताकि आप उन्हें स्टोर कर सकें। भविष्य में, आप उन बाइट्स को वापस लोड कर सकते हैं और उन्हें अपने मूल ढांचे (डिरिएरलाइजेशन) में बदल सकते हैं। C ++ में यह इतना मुश्किल नहीं है क्योंकि प्रतिबिंब बहुत सीमित है। लेकिन फिर भी, कुछ पुस्तकालय यहाँ आपकी सहायता कर सकते हैं। मैंने इसके बारे में एक लेख लिखा: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ मूल रूप से, मैं आपको एक नज़र रखने का सुझाव दूंगा यदि आप C ++ 11 संकलक को लक्षित कर सकते हैं तो अनाज पुस्तकालय। प्रोटोबॉफ़ जैसी इंटरमीडिएट फ़ाइलों को बनाने की आवश्यकता नहीं है, इसलिए यदि आप त्वरित परिणाम चाहते हैं तो आप कुछ समय वहां बचाएंगे। आप बाइनरी और JSON के बीच भी चयन कर सकते हैं, इसलिए यह यहां डिबगिंग में काफी मदद कर सकता है।

उसके ऊपर, यदि सुरक्षा एक चिंता का विषय है, तो आप उस डेटा को एन्क्रिप्ट / डिक्रिप्ट कर सकते हैं, जिसे आप स्टोर कर रहे हैं, खासकर यदि आप JSON जैसे मानव-पठनीय स्वरूपों का उपयोग कर रहे हैं। एईएस जैसे एल्गोरिदम यहां मददगार हैं।


-5

आपको fstreamइनपुट / आउटपुट फ़ाइलों के लिए उपयोग करने की आवश्यकता है । वाक्यविन्यास सरल EX है:

#include <fstream>
// ...
std::ofstream flux ; // to open a file in ouput mode
flux.open("myfile.whatever") ; 

या

#include <fstream>
// ...
std::ifstream flux ; // open a file in input mode
flux.open("myfile.whatever") ;

आपकी फ़ाइल पर अन्य क्रियाएं संभव हैं: परिशिष्ट , बाइनरी , ट्रंक , आदि। आप उसी वाक्यविन्यास का उपयोग करेंगे जैसे कि हम इसके बजाय std :: ios:: (ध्वज) लगाते हैं, उदाहरण के लिए:

  • ios::out आउटपुट ऑपरेशन के लिए
  • ios::in इनपुट ऑपरेशन के लिए
  • ios::binary चरित्र-आधारित के बजाय द्विआधारी (कच्ची बाइट) आईओ ऑपरेशन के लिए
  • ios::app फ़ाइल के अंत में लिखना शुरू करने के लिए
  • ios::trunc यदि फ़ाइल पहले से मौजूद है तो पुरानी सामग्री को हटा दें और नए को बदल दें
  • ios::ate - इनपुट / आउटपुट के लिए फ़ाइल पॉइंटर "अंत में" रखें

उदाहरण के लिए:

#include <fstream>
// ...
std::ifstream flux ;
flux.open("myfile.whatever" , ios::binary) ;

यहाँ एक और अधिक पूर्ण लेकिन सरल उदाहरण है।

#include <iostream>
#include <fstream>

using namespace std ;

int input ;
int New_Apple ;
int Apple_Instock ;
int Eat_Apple ;
int Apple ;

int  main()
{
  bool shouldQuit = false;
  New_Apple = 0 ;
  Apple_Instock = 0 ;
  Eat_Apple = 0 ;

  while( !shouldQuit )
  {
    cout << "------------------------------------- /n";
    cout << "1) add some apple " << endl ;
    cout << "2) check apple in stock " << endl ;
    cout << "3) eat some apple " << endl ;
    cout << "4) quit " << endl ;
    cout << "------------------------------------- /n";
    cin >> input ;

    switch (input)
    {
      case 1 :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << " how much apple do you want to add /n";
        cout << "------------------------------------ /n";      
        cin >> New_Apple ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ; 

        Apple = New_Apple + Apple_Instock ;

        ofstream apple_adder ;
        apple_adder.open("apple.apl") ;
        apple_adder << Apple ;
        apple_adder.close() ;

        cout << "------------------------------------ /n";
        cout << New_Apple << " Apple has been added ! /n";
        cout << "------------------------------------ /n";
        break;
      }

      case 2 :  
      {
        system("cls") ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ;

        cout << "------------------------------------ /n";
        cout << " there is " << Apple_Instock ;
        cout << "apple in stock /n" ;
        cout << "------------------------------------ /n";
        break;
      }

      case 3 :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << "How many apple do you want to eat /n" ;
        cout << "------------------------------------ /n";
        cin >> Eat_Apple ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ;

        Apple = Apple_Instock - Eat_Apple ; 

        ofstream apple_eater ;
        apple_eater.open("apple.apl") ;
        apple_eater << Apple ;
        apple_eater.close() ;

        cout << "----------------------------------- /n";
        cout << Eat_Apple ;
        cout << " Apple has been eated! /n";
        cout << "----------------------------------- /n";
        cout << Apple << " Apple left in stock /n";
        cout << "----------------------------------- /n";
        break;
      }

      case 4 :
      {
        shouldQuit = true;
        break;
      }

      default :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << " invalide choice ! /n";
        cout << "------------------------------------ /n"; 
        break;
      }
    }
  }
  return 0;
}

4
-1 यह बहुत बुरा जवाब है। आपको कोड को सही ढंग से प्रारूपित और प्रदर्शित करना चाहिए और समझाएं कि आप क्या कर रहे हैं, कोई भी कोड का एक हिस्सा नहीं समझना चाहता है।
Vaillancourt

टिप्पणी के लिए धन्यवाद katu आप सही हैं, मुझे अपने कोड को और अच्छी तरह से समझाना चाहिए, क्या आप मुझे बता सकते हैं कि मैं वेबसाइट से अपने स्रोत को कैसे प्रारूपित करूं क्योंकि मैं इस तरह की चीज के लिए नया
हूं

से इस साइट, या के लिए इस साइट? पदों को प्रारूपित करने में सहायता के लिए, आप प्रारूपण सहायता पृष्ठ पर जा सकते हैं । आपके द्वारा पोस्ट किए गए पाठ क्षेत्र के शीर्षलेख के बगल में एक विस्मयादिबोधक चिह्न है जो आपकी सहायता करने के लिए पोस्ट करता है।
Vaillancourt

जो पूछा गया है उसे दस्तावेज़ करने का प्रयास करें; आपको सब कुछ टिप्पणी करने की आवश्यकता नहीं है। और यह स्पष्ट नहीं करके कि आप क्या कर रहे थे, मेरी टिप्पणी में, मेरा मतलब था कि आमतौर पर आप उस रणनीति का परिचय देते हैं जो आप कम से कम पैराग्राफ के साथ सुझाते हैं। (उदाहरण के लिए "तकनीक में से एक एक स्ट्रीम ऑपरेटर के साथ एक द्विआधारी फ़ाइल प्रारूप का उपयोग करना है। आपको उसी क्रम में पढ़ने और लिखने के लिए सावधान रहना होगा, bla bla lba")।
Vaillancourt

2
और gotos का उपयोग करके , आप सार्वजनिक स्थान पर पाले जाएंगे। gotoएस का उपयोग न करें ।
Vaillancourt
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.