ฉันกำลังเขียนโค้ดขั้นสุดท้ายสำหรับหลักสูตรการเขียนโปรแกรมวิดีโอเกมและฉันต้องการทราบวิธีสร้างไฟล์บันทึกสำหรับเกมของฉันเพื่อให้ผู้ใช้สามารถเล่นได้แล้วกลับมาใหม่ในภายหลัง ความคิดใด ๆ ที่ทำสิ่งนี้ทุกสิ่งที่ฉันทำมาก่อนเป็นโปรแกรมที่ทำงานครั้งเดียว
ฉันกำลังเขียนโค้ดขั้นสุดท้ายสำหรับหลักสูตรการเขียนโปรแกรมวิดีโอเกมและฉันต้องการทราบวิธีสร้างไฟล์บันทึกสำหรับเกมของฉันเพื่อให้ผู้ใช้สามารถเล่นได้แล้วกลับมาใหม่ในภายหลัง ความคิดใด ๆ ที่ทำสิ่งนี้ทุกสิ่งที่ฉันทำมาก่อนเป็นโปรแกรมที่ทำงานครั้งเดียว
คำตอบ:
คุณต้องใช้การจัดลำดับเพื่อบันทึกตัวแปรในหน่วยความจำลงในฮาร์ดไดรฟ์ของคุณ มีหลายประเภทของการทำให้เป็นอันดับใน. NET XML เป็นรูปแบบทั่วไปแม้ว่าจะมี binary และ JSON serializers ใช้ได้ ฉันไม่ได้เป็นโปรแกรมเมอร์ C ++ มากนัก แต่การค้นหาอย่างรวดเร็วได้เปิดตัวอย่างในการทำให้เป็นอนุกรมใน C ++:
มีห้องสมุดที่เสนอฟังก์ชันการทำให้เป็นอันดับ บางคนกล่าวถึงในคำตอบอื่น ๆ
ตัวแปรที่คุณจะสนใจนั้นอาจจะเกี่ยวข้องกับสถานะของเกม ตัวอย่างเช่นคุณอาจต้องการทราบข้อมูลประเภทนี้
คุณไม่สนใจว่าจะใช้พื้นผิวใด ๆ (เว้นแต่ผู้เล่นของคุณสามารถเปลี่ยนรูปลักษณ์ของพวกเขานั่นเป็นกรณีพิเศษ) เพราะโดยปกติแล้วพวกเขาจะเหมือนกัน คุณต้องมุ่งเน้นไปที่การบันทึกข้อมูลเกมมิ่งที่สำคัญ
เมื่อคุณเริ่มเกมของคุณคุณจะเริ่มเกมตามปกติสำหรับเกม "ใหม่" (สิ่งนี้จะโหลดพื้นผิวแบบจำลองของคุณ ฯลฯ ) แต่ในเวลาที่เหมาะสมคุณจะโหลดค่าจากไฟล์บันทึกของคุณกลับไปเป็นวัตถุสถานะเกมแทน "ค่าเริ่มต้น" ใหม่ สถานะเกม จากนั้นคุณอนุญาตให้ผู้เล่นกลับมาเล่นต่อ
ฉันทำให้มันง่ายขึ้นมากที่นี่ แต่คุณควรได้รับความคิดทั่วไป หากคุณมีคำถามเพิ่มเติมถามคำถามใหม่ที่นี่และเราสามารถช่วยคุณได้
โดยทั่วไปนี่เป็นเฉพาะเกมของคุณ ฉันแน่ใจว่าคุณได้เรียนรู้เกี่ยวกับการเขียนและอ่านจากไฟล์ในชั้นเรียนของคุณแล้ว แนวคิดพื้นฐานคือ:
สิ่งที่คุณเขียนขึ้นอยู่กับคุณขึ้นอยู่กับเกมของคุณ วิธีหนึ่งในการเขียนคือการเขียนตัวแปรที่คุณต้องการตามลำดับเฉพาะเป็นสตรีมไบต์ จากนั้นเมื่อโหลดให้อ่านในโปรแกรมของคุณในลำดับเดียวกัน
ตัวอย่างเช่น (ในรหัสหลอกอย่างรวดเร็ว):
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;
}
}
อาจมีหลายวิธีในการทำสิ่งนี้ แต่สิ่งที่ง่ายที่สุดที่ฉันพบเสมอและใช้ทั้งส่วนตัวและมืออาชีพคือการสร้างโครงสร้างที่มีค่าทั้งหมดที่ฉันต้องการบันทึก
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
}
ฉันเพียงแค่เขียน / fread ข้อมูลไปยังและจากไฟล์โดยใช้ค่า File IO พื้นฐาน InventoryCount คือจำนวนของโครงสร้างรายการที่บันทึกไว้หลังจากโครงสร้าง SaveGameData หลักในไฟล์ดังนั้นฉันจึงรู้ว่ามีกี่รายการที่ต้องอ่านหลังจากดึงข้อมูลนั้น กุญแจสำคัญคือเมื่อฉันต้องการบันทึกสิ่งใหม่เว้นแต่ว่าจะมีรายการของรายการหรือสิ่งที่ชอบสิ่งที่ฉันต้องทำคือเพิ่มมูลค่าให้กับโครงสร้างบางที่ หากรายการของรายการนั้นฉันจะต้องเพิ่มการอ่านผ่านเหมือนฉันได้โดยนัยแล้วสำหรับวัตถุรายการเคาน์เตอร์ในส่วนหัวหลักและจากนั้นรายการ
สิ่งนี้มีข้อเสียของการสร้างไฟล์บันทึกรุ่นต่าง ๆ ที่เข้ากันไม่ได้กับการจัดการแบบพิเศษ (แม้ว่าจะเป็นเพียงค่าเริ่มต้นสำหรับแต่ละรายการในโครงสร้างหลัก) แต่โดยรวมแล้วสิ่งนี้ทำให้ระบบขยายได้ง่ายเพียงแค่เพิ่มค่าข้อมูลใหม่และใส่ค่าลงไปเมื่อต้องการ
อีกไม่กี่วิธีในการทำสิ่งนี้และสิ่งนี้อาจนำไปสู่ C มากกว่า C ++ แต่มันทำให้งานเสร็จ!
ก่อนอื่นคุณต้องตัดสินใจว่าจะต้องบันทึกข้อมูลใด ตัวอย่างเช่นอาจเป็นที่ตั้งของตัวละครคะแนนของเขาและจำนวนเหรียญ แน่นอนว่าเกมของคุณจะมีความซับซ้อนมากขึ้นดังนั้นคุณจะต้องบันทึกข้อมูลเพิ่มเติมเช่นหมายเลขระดับและรายชื่อศัตรู
ถัดไปเขียนรหัสเพื่อบันทึกสิ่งนี้ลงในไฟล์ (ใช้กระแสข้อมูล) รูปแบบที่ค่อนข้างง่ายที่คุณสามารถใช้ได้มีดังนี้:
x y score coins
ดังนั้นไฟล์จะมีลักษณะดังนี้:
14 96 4200 100
ซึ่งหมายความว่าเขาอยู่ในตำแหน่ง (14, 96) ด้วยคะแนน 4200 และ 100 เหรียญ
คุณต้องเขียนโค้ดเพื่อโหลดไฟล์นี้ (ใช้ ifstream)
การบันทึกศัตรูสามารถทำได้โดยรวมถึงตำแหน่งของพวกเขาในไฟล์ เราสามารถใช้รูปแบบนี้:
number_of_enemies x1 y1 x2 y2 ...
ก่อนอื่นให้number_of_enemies
อ่านจากนั้นอ่านแต่ละตำแหน่งด้วยการวนซ้ำอย่างง่าย
การเพิ่ม / ข้อเสนอแนะหนึ่งรายการจะเพิ่มระดับการเข้ารหัสในการทำให้เป็นอนุกรมของคุณเพื่อให้ผู้ใช้ไม่สามารถแก้ไขค่าของข้อความเป็น "9999999999999999999" เหตุผลหนึ่งที่ดีในการทำเช่นนี้ก็เพื่อป้องกันไม่ให้จำนวนเต็มล้น (ตัวอย่าง)
ฉันคิดว่าทางออกที่ดีที่สุดของคุณคือ boost :: serialization เพราะง่ายต่อการเผยแพร่ในลำดับชั้นของคุณ คุณจะต้องเรียกใช้ฟังก์ชันบันทึก / โหลดบนสุดของวัตถุและคลาสทั้งหมดของคุณจะถูกสร้างขึ้นอย่างง่ายดาย
ดู: http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html
เพื่อความสมบูรณ์ฉันต้องการพูดถึงไลบรารี่อนุกรม c ++ ที่ฉันใช้เป็นการส่วนตัวและยังไม่ได้กล่าวถึง: ซีเรียล
มันใช้งานง่ายและมีไวยากรณ์ที่ดีและสะอาดสำหรับการทำให้เป็นอนุกรม นอกจากนี้ยังมีรูปแบบหลายรูปแบบที่คุณสามารถบันทึกลงใน (XML, Json, Binary (รวมถึงรุ่นพกพาที่มีความเคารพ)) สนับสนุนการสืบทอดและเป็นส่วนหัวเท่านั้น
เกมของคุณจะประนีประนอมโครงสร้างข้อมูล (หวังว่า?) ที่คุณต้องการแปลงเป็นไบต์ (เป็นอันดับ) เพื่อให้คุณสามารถเก็บไว้ ในอนาคตคุณสามารถโหลดไบต์เหล่านั้นกลับมาและแปลงกลับเป็นโครงสร้างเดิมของคุณ (ดีซีเรียลไลเซชัน) ใน C ++ มันไม่ยุ่งยากนักเนื่องจากการสะท้อนมี จำกัด มาก แต่ถึงกระนั้นห้องสมุดบางแห่งสามารถช่วยคุณได้ที่นี่ ฉันเขียนบทความเกี่ยวกับเรื่องนี้: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ โดยทั่วไปฉันขอแนะนำให้คุณดู ไลบรารีซีเรียลหากคุณสามารถกำหนดเป้าหมายคอมไพเลอร์ C ++ 11 ได้ ไม่จำเป็นต้องสร้างไฟล์ระดับกลางเช่นเดียวกับ protobuf ดังนั้นคุณจะประหยัดเวลาหากคุณต้องการผลลัพธ์ที่รวดเร็ว คุณอาจเลือกระหว่างไบนารีและ JSON เพื่อให้สามารถช่วยแก้ไขข้อบกพร่องได้ที่นี่
ยิ่งไปกว่านั้นหากความปลอดภัยเป็นเรื่องที่น่ากังวลคุณอาจต้องการเข้ารหัส / ถอดรหัสข้อมูลที่คุณจัดเก็บโดยเฉพาะถ้าคุณใช้รูปแบบที่มนุษย์สามารถอ่านได้เช่น JSON อัลกอริทึมเช่น AES มีประโยชน์ที่นี่
คุณต้องใช้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") ;
การกระทำอื่น ๆ ที่เป็นไปได้ในไฟล์ของคุณ: ผนวก , ไบนารี , trunc , ฯลฯ คุณจะใช้ไวยากรณ์เดียวกันข้างต้นแทนเราใส่ std :: ios: :( ธง) ตัวอย่างเช่น:
ios::out
สำหรับการดำเนินการส่งออกios::in
สำหรับการดำเนินการอินพุตios::binary
สำหรับการดำเนินการแบบไบนารี (raw byte) IO แทนการใช้อักขระios::app
สำหรับการเริ่มเขียนที่ส่วนท้ายของไฟล์ios::trunc
สำหรับถ้าไฟล์มีอยู่แล้วแทนที่ลบเนื้อหาเก่าและแทนที่ด้วยใหม่ios::ate
- วางตัวชี้ไฟล์ "ที่ท้าย" สำหรับอินพุต / เอาต์พุตEx:
#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;
}
goto
ของคุณจะได้รับการประชาทัณฑ์ในสถานที่สาธารณะ อย่าใช้goto
ของ