परिचय
इस परियोजना का लक्ष्य अंतिम डीओएम (
स्टीम से संस्करण ) के साथ जारी संसाधनों का उपयोग करके डीओएम इंजन का एक क्लोन बनाना है।
इसे एक ट्यूटोरियल के रूप में प्रस्तुत किया जाएगा - मैं कोड में अधिकतम प्रदर्शन प्राप्त नहीं करना चाहता हूं, लेकिन बस एक कार्यशील संस्करण बना सकता हूं, और बाद में मैं इसे सुधारना और अनुकूलन करना शुरू कर दूंगा।
मुझे गेम या गेम इंजन बनाने का कोई अनुभव नहीं है, और लेख लिखने में बहुत कम अनुभव है, इसलिए आप अपने खुद के बदलावों का सुझाव दे सकते हैं या कोड को पूरी तरह से फिर से लिख सकते हैं।
यहां संसाधनों और लिंक की एक सूची दी गई है।
बुक गेम इंजन ब्लैक बुक: डीओएम फेबियन संगलर । DOOM इंटर्नल पर सबसे अच्छी पुस्तकों में से एक।
कयामत विकीDOOM स्रोत कोडस्रोत कोड चॉकलेट कयामतआवश्यकताओं
- विजुअल स्टूडियो: कोई भी आईडीई करेगा; मैं विजुअल स्टूडियो 2017 में काम करूंगा।
- SDL2: पुस्तकालय।
- DOOM: अल्टीमेट DOOM के स्टीम वर्जन की एक कॉपी, हमें केवल उसमें से WAD फाइल चाहिए।
ऐच्छिक
- Slade3: हमारे काम का परीक्षण करने के लिए एक अच्छा उपकरण।
विचारों
मुझे नहीं पता, मैं इस परियोजना को पूरा कर सकता हूं, लेकिन मैं इसके लिए पूरी कोशिश करूंगा।
विंडोज मेरा टारगेट प्लेटफॉर्म होगा, लेकिन जब से मैं एसडीएल का उपयोग करता हूं, यह सिर्फ किसी अन्य प्लेटफॉर्म के तहत इंजन का काम करेगा।
इस बीच, Visual Studio स्थापित करें!
इस परियोजना का नाम हस्तनिर्मित डूम से डू इट इट योरसेल्फ डूम टू एसएलडी (DIY डूम) रखा गया, ताकि इसे अन्य प्रोजेक्ट्स के साथ भ्रमित न किया जाए, जिसे "हस्तनिर्मित" कहा जाता है। ट्यूटोरियल में कुछ स्क्रीनशॉट हैं जहां इसे अभी भी हस्तनिर्मित डूम कहा जाता है।
WAD फाइलें
कोडिंग शुरू करने से पहले, लक्ष्य निर्धारित करें और सोचें कि हम क्या हासिल करना चाहते हैं।
पहले, आइए देखें कि क्या हम DOOM संसाधन फ़ाइलों को पढ़ सकते हैं। सभी DOOM संसाधन WAD फ़ाइल में हैं।
WAD फाइल क्या है?
"मेरा सारा डेटा कहां है"? ("मेरा सारा डेटा कहां है?") वे WAD में हैं! WAD एक फ़ाइल में स्थित सभी DOOM संसाधनों (और DOOM- आधारित गेम) का एक संग्रह है।
डूम डेवलपर्स खेल संशोधनों के निर्माण को आसान बनाने के लिए इस प्रारूप के साथ आए थे।
WAD फाइल एनाटॉमी
WAD फ़ाइल में तीन मुख्य भाग होते हैं: शीर्ष लेख (हेडर), "टुकड़े" (गांठ), और निर्देशिका (निर्देशिका)।
- हैडर - WAD फाइल और डायरेक्टरी ऑफसेट के बारे में बुनियादी जानकारी है।
- गांठ - यहाँ संग्रहीत खेल संसाधन, नक्शे, स्प्राइट, संगीत, आदि के डेटा हैं।
- निर्देशिकाएँ - गांठ अनुभाग में डेटा खोजने के लिए संगठनात्मक संरचना।
<---- 32 bits ----> /------------------\ ---> 0x00 | ASCII WAD Type | 0X03 | |------------------| Header -| 0x04 | # of directories | 0x07 | |------------------| ---> 0x08 | directory offset | 0x0B -- ---> |------------------| <-- | | 0x0C | Lump Data | | | | |------------------| | | Lumps - | | . | | | | | . | | | | | . | | | ---> | . | | | ---> |------------------| <--|--- | | Lump offset | | | |------------------| | Directory -| | directory offset | --- List | |------------------| | | Lump Name | | |------------------| | | . | | | . | | | . | ---> \------------------/
हैडर प्रारूप
निर्देशिका प्रारूप
लक्ष्यों
- एक प्रोजेक्ट बनाएं।
- WAD फ़ाइल खोलें।
- हेडलाइन पढ़ें।
- सभी निर्देशिकाओं को पढ़ें और उन्हें प्रदर्शित करें।
आर्किटेक्चर
चलो अभी तक कुछ भी जटिल नहीं है। एक वर्ग बनाएं जो सिर्फ WAD को खोलता और लोड करता है, और इसे WADLoader कहते हैं। फिर हम एक वर्ग लिखते हैं जो डेटा को उनके प्रारूप के आधार पर पढ़ने के लिए जिम्मेदार है, और इसे WADReader कहते हैं। हमें एक साधारण
main
फ़ंक्शन की भी आवश्यकता है जो इन कक्षाओं को कॉल करता है।
नोट: यह आर्किटेक्चर इष्टतम नहीं हो सकता है, और यदि आवश्यक हो तो हम इसे बदल देंगे।
कोड के लिए हो रही है
चलो एक खाली C ++ प्रोजेक्ट बनाकर शुरू करते हैं। Visual Studio में, फ़ाइल-> नया -> प्रोजेक्ट पर क्लिक करें। चलो इसे DIYDoom कहते हैं।
चलो दो नई कक्षाएं जोड़ते हैं: WADLader और WADReader। आइए WADLoader के कार्यान्वयन के साथ शुरू करें।
class WADLoader { public: WADLoader(std::string sWADFilePath);
कंस्ट्रक्टर को लागू करना सरल होगा: डेटा पॉइंटर को इनिशियलाइज़ करें और ट्रांसफर किए गए पाथ की कॉपी को WAD फाइल में स्टोर करें।
WADLoader::WADLoader(string sWADFilePath) : m_WADData(NULL), m_sWADFilePath(sWADFilePath) { }
अब चलिए
OpenAndLoad
को लोड करने के सहायक कार्य के कार्यान्वयन के लिए नीचे
OpenAndLoad
: हम केवल फ़ाइल को बाइनरी के रूप में खोलने का प्रयास करते हैं और, विफलता के मामले में, एक त्रुटि प्रदर्शित करते हैं।
m_WADFile.open(m_sWADFilePath, ifstream::binary); if (!m_WADFile.is_open()) { cout << "Error: Failed to open WAD file" << m_sWADFilePath << endl; return false; }
यदि सब कुछ ठीक हो जाता है, और हम फ़ाइल को ढूंढ सकते हैं और खोल सकते हैं, तो हमें फ़ाइल की प्रतिलिपि बनाने के लिए मेमोरी को आवंटित करने के लिए फ़ाइल का आकार जानना होगा।
m_WADFile.seekg(0, m_WADFile.end); size_t length = m_WADFile.tellg();
अब हम जानते हैं कि पूर्ण WAD कितना स्थान लेता है, और हम आवश्यक मात्रा में मेमोरी आवंटित करेंगे।
m_WADData = new uint8_t[length];
फ़ाइल की सामग्री को इस मेमोरी में कॉपी करें।
आपने देखा होगा कि मैंने
m_WADData
के लिए डेटा प्रकार के रूप में
m_WADData
प्रकार का उपयोग किया था। इसका मतलब है कि मुझे 1 बाइट (1 बाइट * लंबाई) के सटीक सरणी की आवश्यकता है। Unint8_t का उपयोग यह सुनिश्चित करता है कि आकार एक बाइट के बराबर है (8 बिट्स, जिसे टाइप नाम से समझा जा सकता है)। यदि हम 2 बाइट्स (16 बिट्स) आवंटित करना चाहते हैं, तो हम अनइंट 16_t का उपयोग करेंगे, जिसके बारे में हम बाद में बात करेंगे। इस प्रकार के कोड का उपयोग करके, कोड प्लेटफ़ॉर्म स्वतंत्र हो जाता है। मैं समझाता हूं: यदि हम "इंट" का उपयोग करते हैं, तो मेमोरी में इंट का सटीक आकार सिस्टम पर निर्भर करेगा। यदि हम 32-बिट कॉन्फ़िगरेशन में "int" संकलित करते हैं, तो हमें 4 बाइट्स (32 बिट्स) का मेमोरी साइज़ मिलता है, और 64-बिट कॉन्फ़िगरेशन में समान कोड को कंपाइल करते समय, हमें 8 बाइट्स (64 बिट्स) का मेमोरी साइज़ मिलता है! इससे भी बदतर, 16-बिट प्लेटफ़ॉर्म पर कोड को संकलित करना (आप एक डॉस प्रशंसक हो सकते हैं) हमें 2 बाइट्स (16 बिट्स) देगा!
आइए संक्षेप में कोड की जांच करें और सुनिश्चित करें कि सब कुछ काम करता है। लेकिन पहले हमें LoadWAD को लागू करने की आवश्यकता है। जबकि लोडवाड "OpenAndLoad" कहेगा
bool WADLoader::LoadWAD() { if (!OpenAndLoad()) { return false; } return true; }
और चलो मुख्य फ़ंक्शन कोड में जोड़ते हैं जो वर्ग का एक उदाहरण बनाता है और WAD लोड करने का प्रयास करता है
int main() { WADLoader wadloader("D:\\SDKs\\Assets\\Doom\\DOOM.WAD"); wadloader.LoadWAD(); return 0; }
आपको अपनी WAD फ़ाइल में सही पथ दर्ज करना होगा। चलो इसे चलाते हैं!
आउच! हमें एक कंसोल विंडो मिली जो कुछ सेकंड के लिए खुलती है! विशेष रूप से उपयोगी कुछ भी नहीं ... कार्यक्रम काम करता है? विचार! चलो स्मृति पर एक नज़र डालें और देखें कि इसमें क्या है! शायद वहाँ हम कुछ विशेष पाएंगे! सबसे पहले, लाइन नंबर के बाईं ओर डबल-क्लिक करके एक ब्रेकपॉइंट रखें। आपको कुछ इस तरह से देखना चाहिए:
मैंने मेमोरी सरणी को देखने के लिए फ़ाइल से सभी डेटा पढ़ने के तुरंत बाद एक ब्रेकपॉइंट रखा और देखा कि इसमें क्या लोड था। अब कोड फिर से चलाएं! स्वचालित विंडो में, मैं पहले कुछ बाइट्स देखता हूं। पहले 4 बाइट्स कहते हैं "IWAD"! महान, यह काम करता है! मैंने कभी नहीं सोचा था कि यह दिन आएगा! तो, ठीक है, आपको शांत करने की आवश्यकता है, अभी भी बहुत काम आगे है!
हेडर पढ़ें
हेडर का कुल आकार 12 बाइट्स (0x00 से 0x0b तक) है, इन 12 बाइट्स को 3 समूहों में विभाजित किया गया है। पहले 4 बाइट्स WAD का एक प्रकार है, आमतौर पर "IWAD" या "PWAD"। IWAD को ID Software द्वारा जारी किया गया आधिकारिक WAD होना चाहिए, "PWAD" को मॉड्स के लिए इस्तेमाल किया जाना चाहिए। दूसरे शब्दों में, यह केवल यह निर्धारित करने का एक तरीका है कि क्या WAD फ़ाइल एक आधिकारिक रिलीज़ है, या modders द्वारा जारी की गई है। ध्यान दें कि स्ट्रिंग पूरी तरह से समाप्त नहीं हुई है, इसलिए सावधान रहें! अगले 4 बाइट्स अहस्ताक्षरित int हैं, जिसमें फ़ाइल के अंत में निर्देशिका की कुल संख्या शामिल है। अगले 4 बाइट्स पहली निर्देशिका की ऑफसेट इंगित करते हैं।
आइए एक संरचना जोड़ते हैं जो जानकारी संग्रहीत करेगी। मैं एक नई हेडर फ़ाइल जोड़ूंगा और इसे "DataTypes.h" नाम दूंगा। इसमें हम उन सभी संरचनाओं का वर्णन करेंगे जिनकी हमें ज़रूरत है।
struct Header { char WADType[5];
अब हमें WADReader वर्ग को लागू करने की आवश्यकता है, जो लोड किए गए WAD बाइट सरणी से डेटा पढ़ेंगे। आउच! यहां एक ट्रिक है- WAD फाइलें बड़े-एंडियन फॉर्मेट में होती हैं, यानी हमें बाइट्स को थोड़ा-सा एंडियन बनाने के लिए शिफ्ट करने की जरूरत होगी (आज, ज्यादातर सिस्टम थोड़ा एंडियन का इस्तेमाल करते हैं)। ऐसा करने के लिए, हम दो कार्यों को जोड़ेंगे, 2 बाइट्स (16 बिट्स) के प्रसंस्करण के लिए, दूसरा 4 बाइट्स (32 बिट्स) के प्रसंस्करण के लिए; यदि हमें केवल 1 बाइट पढ़ने की आवश्यकता है, तो कुछ भी करने की आवश्यकता नहीं है।
uint16_t WADReader::bytesToShort(const uint8_t *pWADData, int offset) { return (pWADData[offset + 1] << 8) | pWADData[offset]; } uint32_t WADReader::bytesToInteger(const uint8_t *pWADData, int offset) { return (pWADData[offset + 3] << 24) | (pWADData[offset + 2] << 16) | (pWADData[offset + 1] << 8) | pWADData[offset]; }
अब हम हेडर पढ़ने के लिए तैयार हैं: पहले चार बाइट्स को चार के रूप में गिनें, और फिर हमारे काम को आसान बनाने के लिए उनमें NULL जोड़ें। निर्देशिकाओं की संख्या और उनके ऑफसेट के मामले में, आप उन्हें सही प्रारूप में बदलने के लिए बस सहायक कार्यों का उपयोग कर सकते हैं।
void WADReader::ReadHeaderData(const uint8_t *pWADData, int offset, Header &header) {
आइए इसे सभी एक साथ रखें, इन कार्यों को कॉल करें और परिणाम प्रिंट करें
bool WADLoader::ReadDirectories() { WADReader reader; Header header; reader.ReadHeaderData(m_WADData, 0, header); std::cout << header.WADType << std::endl; std::cout << header.DirectoryCount << std::endl; std::cout << header.DirectoryOffset << std::endl; std::cout << std::endl << std::endl; return true; }
कार्यक्रम चलाएं और देखें कि क्या सब कुछ काम करता है!
बहुत बढ़िया! IWAD लाइन स्पष्ट रूप से दिखाई देती है, लेकिन क्या अन्य दो नंबर सही हैं? आइए इन ऑफसेट का उपयोग करके निर्देशिकाओं को पढ़ने की कोशिश करें और देखें कि क्या यह काम करता है!
हमें उपरोक्त विकल्पों के अनुरूप निर्देशिका को संभालने के लिए एक नई संरचना जोड़ने की आवश्यकता है।
struct Directory { uint32_t LumpOffset; uint32_t LumpSize; char LumpName[9]; };
अब रीडडायरेक्टरीज फ़ंक्शन को जोड़ते हैं: ऑफ़सेट्स की गणना करें और उन्हें आउटपुट करें!
प्रत्येक पुनरावृत्ति में, हम अगली निर्देशिका की ऑफसेट वृद्धि में जाने के लिए i * 16 को गुणा करते हैं।
Directory directory; for (unsigned int i = 0; i < header.DirectoryCount; ++i) { reader.ReadDirectoryData(m_WADData, header.DirectoryOffset + i * 16, directory); m_WADDirectories.push_back(directory); std::cout << directory.LumpOffset << std::endl; std::cout << directory.LumpSize << std::endl; std::cout << directory.LumpName << std::endl; std::cout << std::endl; }
कोड चलाएँ और देखें कि क्या होता है। वाह! निर्देशिकाओं की एक बड़ी सूची।
गांठ नाम से देखते हुए, हम मान सकते हैं कि हम डेटा को सही ढंग से पढ़ने में कामयाब रहे, लेकिन शायद यह जांचने का एक बेहतर तरीका है। हम स्लैड 3 का उपयोग करके WAD निर्देशिका प्रविष्टियों पर एक नज़र डालेंगे।
ऐसा लगता है कि गांठ का नाम और आकार हमारे कोड का उपयोग करके प्राप्त आंकड़ों के अनुरूप है। आज हमने बहुत अच्छा काम किया!
अन्य नोट
- कुछ बिंदु पर, मुझे लगा कि निर्देशिकाओं के भंडारण के लिए वेक्टर का उपयोग करना अच्छा होगा। मैप का उपयोग क्यों नहीं करते? यह रैखिक वेक्टर खोज द्वारा डेटा प्राप्त करने की तुलना में तेज़ होगा। यह एक बुरा विचार है। मानचित्र का उपयोग करते समय, निर्देशिका प्रविष्टियों के क्रम को ट्रैक नहीं किया जाएगा, लेकिन हमें सही डेटा प्राप्त करने के लिए इस जानकारी की आवश्यकता है।
और एक और गलत धारणा: C ++ में मैप को O (लॉग एन) खोज समय के साथ लाल-काले पेड़ों के रूप में लागू किया गया है, और मानचित्र पर पुनरावृत्तियां हमेशा चाबियों का बढ़ता क्रम देती हैं। यदि आपको एक डेटा संरचना की आवश्यकता है जो औसत समय O (1) और सबसे खराब समय O (N) देता है, तो आपको एक अनियंत्रित मानचित्र का उपयोग करना होगा। मेमोरी में सभी WAD फ़ाइलों को लोड करना एक इष्टतम कार्यान्वयन विधि नहीं है। यह केवल मेमोरी हेडर में निर्देशिकाओं को पढ़ने के लिए अधिक तार्किक होगा, और फिर WAD फ़ाइल में वापस आ जाएगा और डिस्क से संसाधनों को लोड करेगा। उम्मीद है कि किसी दिन हम कैशिंग के बारे में अधिक जानेंगे।
DOOMReboot : पूरी तरह से असहमत। 15 एमबी रैम इन दिनों एक संपूर्ण ट्रिफ़ल है, और मेमोरी से पढ़ना वॉल्यूमिनस फ़ेसेक की तुलना में बहुत तेज़ होगा, जिसका उपयोग उस चीज़ को डाउनलोड करने के बाद करना होगा जो स्तर के लिए आवश्यक है। यह डाउनलोड समय को एक से दो सेकंड तक कम कर देगा (यह मुझे हर समय डाउनलोड करने के लिए 20 एमएस से कम समय लगता है)। fseek OS का उपयोग करें। रैम कैश में कौन सी फ़ाइल सबसे अधिक संभावना है, लेकिन यह नहीं हो सकता है। लेकिन यहां तक कि अगर वह वहां है, तो यह संसाधनों की एक बड़ी बर्बादी है और ये ऑपरेशन CPU कैश के संदर्भ में कई WAD रीडिंग को भ्रमित करेंगे। सबसे अच्छी बात यह है कि आप हाइब्रिड बूट मेथड बना सकते हैं और WAD डेटा को एक स्तर तक स्टोर कर सकते हैं जो आधुनिक प्रोसेसर के L3 कैश में फिट बैठता है, जहां बचत अद्भुत होगी।
स्रोत कोड
स्रोत कोडबेसिक कार्ड डेटा
WAD फ़ाइल को पढ़ना सीख लिया है, आइए पढ़े गए डेटा का उपयोग करने का प्रयास करें। मिशन डेटा (दुनिया / स्तर) को पढ़ना और उन्हें लागू करना सीखना बहुत अच्छा होगा। इन मिशनों (मिशन गांठ) का "हिस्सा" कुछ जटिल और पेचीदा होना चाहिए। इसलिए, हमें धीरे-धीरे ज्ञान को स्थानांतरित करने और विकसित करने की आवश्यकता होगी। पहले छोटे कदम के रूप में, आइए हम ऑटोमैट फ़ीचर जैसी कोई चीज़ बनाएं: शीर्ष दृश्य के साथ मानचित्र की द्वि-आयामी योजना। सबसे पहले, आइए देखें कि मिशन गांठ के अंदर क्या है।
कार्ड शरीर रचना विज्ञान
आइए फिर से शुरू करें: डीओएम स्तरों का वर्णन 2 डी ड्राइंग के समान है, जिस पर दीवारों को लाइनों के साथ चिह्नित किया गया है। हालांकि, 3 डी निर्देशांक प्राप्त करने के लिए, प्रत्येक दीवार फर्श और छत की ऊंचाई लेती है (एक्सवाई विमान है जिसके साथ हम क्षैतिज रूप से आगे बढ़ते हैं, और जेड वह ऊंचाई है जो हमें ऊपर या नीचे जाने की अनुमति देती है, उदाहरण के लिए, एक लिफ्ट से उठाकर या एक मंच से कूदकर। ये तीनों)। मिशन को 3 डी दुनिया के रूप में प्रस्तुत करने के लिए समन्वित घटकों का उपयोग किया जाता है, हालांकि, अच्छा प्रदर्शन सुनिश्चित करने के लिए, इंजन की कुछ सीमाएं हैं: कोई भी स्तर पर एक दूसरे के ऊपर स्थित कमरे नहीं हैं और खिलाड़ी ऊपर और नीचे नहीं देख सकता है। एक और दिलचस्प विशेषता: गोले और। रॉक, उदाहरण के लिए, रॉकेट, एक उच्च मंच पर स्थित लक्ष्य को हिट करने के लिए लंबवत चढ़ते हैं।
इन जिज्ञासु विशेषताओं ने अंतहीन होलीवर्स के बारे में बताया है कि क्या डीओएम 2 डी या 3 डी इंजन है। धीरे-धीरे, एक कूटनीतिक समझौता किया गया, जिसने कई लोगों की जान बचाई: पार्टियों ने दोनों के लिए स्वीकार्य "2.5D" पदनाम पर सहमति व्यक्त की।
कार्य को सरल बनाने और विषय पर वापस जाने के लिए, आइए बस इस 2D डेटा को पढ़ने की कोशिश करें और देखें कि क्या इसे किसी तरह इस्तेमाल किया जा सकता है। बाद में हम उन्हें 3 डी में रेंडर करने की कोशिश करेंगे, लेकिन अब हमें यह समझने की जरूरत है कि इंजन के अलग-अलग हिस्से एक साथ कैसे काम करते हैं।
अनुसंधान करने के बाद, मुझे पता चला कि प्रत्येक मिशन "टुकड़ों" के एक सेट से बना है। ये "गांठ" हमेशा एक ही क्रम में एक डीओएम गेम की WAD फाइल में प्रदर्शित की जाती हैं।
- वर्टेक्स: 2 डी में दीवारों की समाप्ति। दो जुड़े VERTEXs एक LINEDEF बनाते हैं। तीन जुड़े VERTEX दो दीवारों / LINEDEF, और इतने पर फार्म। उन्हें बस दो या अधिक दीवारों के कनेक्शन बिंदुओं के रूप में माना जा सकता है। (हां, ज्यादातर लोग बहुवचन "वर्टिस" पसंद करते हैं, लेकिन जॉन कार्मैक को यह पसंद नहीं आया। मेरियम-वेबस्टर के अनुसार, दोनों विकल्प लागू होते हैं।
- LINEDEFS: रेखाएँ जोड़ों के बीच और दीवारें बनाती हैं। सभी लाइनें (दीवारें) समान व्यवहार नहीं करती हैं, ऐसे झंडे होते हैं जो ऐसी रेखाओं के व्यवहार को निर्दिष्ट करते हैं।
- SIDEDDEFS: वास्तविक जीवन में, दीवारों के दो पहलू होते हैं - हम एक को देखते हैं, दूसरा दूसरी तरफ होता है। दोनों पक्षों के अलग-अलग बनावट हो सकते हैं, और SIDEDEFS दीवार (LINEDEF) के लिए बनावट की जानकारी वाली गांठ है।
- सेक्टर्स: सेक्टर "कमरे" हैं जो कि लिनडिफ में शामिल हो जाते हैं। प्रत्येक क्षेत्र में फर्श और छत की ऊँचाई, बनावट, प्रकाश मूल्य, विशेष क्रियाएं, जैसे कि चलती मंजिल / प्लेटफार्म / लिफ्ट जैसी जानकारी होती है। इनमें से कुछ पैरामीटर दीवारों के रेंडर होने के तरीके को भी प्रभावित करते हैं, उदाहरण के लिए, रोशनी के स्तर और बनावट मैपिंग निर्देशांक की गणना।
- संकेतक: (उप-क्षेत्र) एक क्षेत्र के भीतर उत्तल क्षेत्रों का निर्माण करते हैं जिनका उपयोग बीएसपी बाईपास के संयोजन में किया जाता है, और यह भी निर्धारित करने में मदद करता है कि एक खिलाड़ी किसी विशेष स्तर पर कहां है। वे काफी उपयोगी होते हैं और अक्सर खिलाड़ी की ऊर्ध्वाधर स्थिति निर्धारित करने के लिए उपयोग किए जाते हैं। प्रत्येक क्षेत्र में एक सेक्टर के जुड़े हिस्से होते हैं, उदाहरण के लिए, एक कोण बनाने वाली दीवारों का। दीवारों के ऐसे भाग, या "सेगमेंट," को अपने स्वयं के गांठ में संग्रहीत किया जाता है ...
- SEGS : दीवार भागों / LINEDEF; दूसरे शब्दों में, ये दीवार / LINEDEF के "खंड" हैं। दुनिया बसपा के पेड़ को दरकिनार करती है, यह निर्धारित करने के लिए कि कौन सी दीवारें सबसे पहले खींचनी हैं (सबसे पहले निकटतम हैं)। हालांकि प्रणाली बहुत अच्छी तरह से काम करती है, यह अक्सर लाइनेड्स को दो या अधिक सेगमेंट में विभाजित करने का कारण बनती है। इस तरह के सेगमेंट का उपयोग तब LINEDEF के बजाय दीवारों को रेंडर करने के लिए किया जाता है। प्रत्येक SSECTOR की ज्यामिति इसमें निहित सेग द्वारा निर्धारित की जाती है।
- नोड्स: एक बीएसपी नोड एक बाइनरी ट्री संरचना का एक नोड है जो सबसेंटर डेटा संग्रहीत करता है। यह जल्दी से यह निर्धारित करने के लिए उपयोग किया जाता है कि कौन सा SSECTOR (और SEG) खिलाड़ी के सामने है। खिलाड़ी के पीछे स्थित सेगमेंट को खत्म करना, और इसलिए अदृश्य, इंजन को संभावित रूप से दिखाई देने वाले सेगमेंट पर ध्यान केंद्रित करने की अनुमति देता है, जो प्रतिपादन समय को काफी कम कर देता है।
- थिंग्स: लंप्स जिसे थिंग्स कहा जाता है, दृश्यों और मिशन अभिनेताओं (दुश्मनों, हथियारों, आदि) की एक सूची है। इस गांठ के प्रत्येक तत्व में अभिनेता / सेट के एक उदाहरण के बारे में जानकारी होती है, उदाहरण के लिए, वस्तु का प्रकार, निर्माण का बिंदु, दिशा, और इसी तरह।
- संदर्भ: इस गांठ में डेटा होता है जिसके बारे में अन्य क्षेत्रों से दिखाई देते हैं। इसका उपयोग यह निर्धारित करने के लिए किया जाता है कि एक राक्षस किसी खिलाड़ी की उपस्थिति के बारे में कब सीखता है। इसका उपयोग खिलाड़ी द्वारा बनाई गई ध्वनियों के वितरण रेंज को निर्धारित करने के लिए भी किया जाता है, उदाहरण के लिए, शॉट्स। जब इस तरह की ध्वनि राक्षस के क्षेत्र में प्रेषित करने में सक्षम होती है, तो वह खिलाड़ी के बारे में पता लगा सकता है। REJECT टेबल का उपयोग हथियार के गोले के टकराव की पहचान को तेज करने के लिए भी किया जा सकता है।
- BLOCKMAP: खिलाड़ी टक्कर की पहचान जानकारी और गति। पूरे मिशन की ज्यामिति को कवर करने वाले एक ग्रिड से मिलकर बनता है। प्रत्येक ग्रिड सेल में LINEDEFs की एक सूची होती है जो इसके अंदर होती है या इसे इंटरसेक्ट करती है। इसका उपयोग टकरावों की पहचान में काफी तेजी लाने के लिए किया जाता है: टक्कर की जाँच प्रति खिलाड़ी / THING के लिए केवल कुछ LINEDEF के लिए आवश्यक है, जो कंप्यूटिंग शक्ति को महत्वपूर्ण रूप से बचाता है।
अपना 2D मैप बनाते समय, हम VERTEXES और LINEDEFS पर ध्यान केंद्रित करेंगे। यदि हम लंबों को आकर्षित कर सकते हैं और उन्हें लाइनफ द्वारा दी गई रेखाओं के साथ जोड़ सकते हैं, तो हमें मानचित्र का 2 डी मॉडल तैयार करना होगा।
ऊपर दिखाए गए डेमो कार्ड में निम्नलिखित विशेषताएं हैं:
- 4 चोटियाँ
- शीर्ष 1 में (10.10)
- शीर्ष 2 पर (10,100)
- शीर्ष 3 पर (100, 10)
- चोटी 4 में (100,100)
- 4 लाइनें
- शीर्ष 1 से 2 तक लाइन
- शीर्ष 1 से 3 तक की रेखा
- शीर्ष 2 से 4 तक की रेखा
- शीर्ष 3 से 4 तक की रेखा
वर्टेक्स प्रारूप
जैसा कि आप उम्मीद कर सकते हैं, वर्टेक्स डेटा बहुत सरल है - बस कुछ निर्देशांकों का x और y (बिंदु)।
पंक्तिबद्ध प्रारूप
Linedef में अधिक जानकारी है; यह दो रेखाओं को जोड़ने वाली रेखा और इस रेखा के गुणों का वर्णन करता है (जो बाद में दीवार बन जाएगा)।
Linedef Flag Values
सभी लाइनें (दीवारें) नहीं खींची जाती हैं। उनमें से कुछ का व्यवहार विशेष है।
लक्ष्यों
- मैप क्लास बनाएं।
- वर्टेक्स डेटा पढ़ें।
- लाइनफेड डेटा पढ़ें।
आर्किटेक्चर
सबसे पहले, एक क्लास बनाएं और इसे मैप करें। इसमें हम कार्ड से जुड़े सभी डेटा को स्टोर करेंगे।
अभी के लिए, मैं एक वेक्टर के रूप में केवल कोने और लाइनफेड स्टोर करने की योजना बनाता हूं, ताकि मैं उन्हें बाद में लागू कर सकूं।
इसके अलावा, WADLader और WADReader को पूरक करें ताकि हम जानकारी के इन दो नए टुकड़ों को पढ़ सकें।
कोडिंग
कोड WAD रीडिंग कोड के समान होगा, हम केवल कुछ और संरचनाएँ जोड़ेंगे, और फिर उन्हें WAD के डेटा से भर देंगे। आइए एक नया वर्ग जोड़कर और मानचित्र नाम को पास करके शुरू करें।
class Map { public: Map(std::string sName); ~Map(); std::string GetName();
अब इन नए क्षेत्रों को पढ़ने के लिए संरचनाओं को जोड़ें। चूंकि हमने पहले ही कई बार ऐसा किया है, बस उन सभी को एक बार में जोड़ें।
struct Vertex { int16_t XPosition; int16_t YPosition; }; struct Linedef { uint16_t StartVertex; uint16_t EndVertex; uint16_t Flags; uint16_t LineType; uint16_t SectorTag; uint16_t FrontSidedef; uint16_t BackSidedef; };
अगला, हमें उन्हें WADReader से पढ़ने के लिए एक फ़ंक्शन की आवश्यकता है, यह वही होगा जो हमने पहले किया था। void WADReader::ReadVertexData(const uint8_t *pWADData, int offset, Vertex &vertex) { vertex.XPosition = Read2Bytes(pWADData, offset); vertex.YPosition = Read2Bytes(pWADData, offset + 2); } void WADReader::ReadLinedefData(const uint8_t *pWADData, int offset, Linedef &linedef) { linedef.StartVertex = Read2Bytes(pWADData, offset); linedef.EndVertex = Read2Bytes(pWADData, offset + 2); linedef.Flags = Read2Bytes(pWADData, offset + 4); linedef.LineType = Read2Bytes(pWADData, offset + 6); linedef.SectorTag = Read2Bytes(pWADData, offset + 8); linedef.FrontSidedef = Read2Bytes(pWADData, offset + 10); linedef.BackSidedef = Read2Bytes(pWADData, offset + 12); }
मुझे लगता है कि यहां आपके लिए कुछ नया नहीं है। और अब हमें WADLoader वर्ग से इन कार्यों को कॉल करने की आवश्यकता है। मुझे तथ्यों को बताने दें: यहां गांठ का क्रम महत्वपूर्ण है, हम गांठ निर्देशिका में नक्शे का नाम पाएंगे, इसके बाद दिए गए क्रम में नक्शे से जुड़े सभी गांठ होंगे। हमारे कार्य को सरल बनाने के लिए और गांठ सूचकांकों को अलग से ट्रैक नहीं करने के लिए, हम एक गणना जोड़ेंगे जो हमें जादू की संख्या से छुटकारा पाने की अनुमति देता है। enum EMAPLUMPSINDEX { eTHINGS = 1, eLINEDEFS, eSIDEDDEFS, eVERTEXES, eSEAGS, eSSECTORS, eNODES, eSECTORS, eREJECT, eBLOCKMAP, eCOUNT };
मैं निर्देशिका सूची में इसके नाम से एक मानचित्र खोजने के लिए एक फ़ंक्शन भी जोड़ूंगा। बाद में, हम नक्शा डेटा संरचना का उपयोग करके इस कदम के प्रदर्शन को बढ़ाने की संभावना रखते हैं, क्योंकि यहां रिकॉर्ड की एक महत्वपूर्ण संख्या है, और हमें उनके माध्यम से अक्सर जाना होगा, विशेष रूप से लोडिंग संसाधनों जैसे कि बनावट, स्प्राइट, ध्वनियां आदि की शुरुआत में। int WADLoader::FindMapIndex(Map &map) { for (int i = 0; i < m_WADDirectories.size(); ++i) { if (m_WADDirectories[i].LumpName == map.GetName()) { return i; } } return -1; }
वाह, हम लगभग पूरा कर चुके हैं! अब, बस VERTEXES गिनते हैं! मैं दोहराता हूं, हम पहले भी ऐसा कर चुके हैं, अब आपको यह समझना चाहिए। bool WADLoader::ReadMapVertex(Map &map) { int iMapIndex = FindMapIndex(map); if (iMapIndex == -1) { return false; } iMapIndex += EMAPLUMPSINDEX::eVERTEXES; if (strcmp(m_WADDirectories[iMapIndex].LumpName, "VERTEXES") != 0) { return false; } int iVertexSizeInBytes = sizeof(Vertex); int iVertexesCount = m_WADDirectories[iMapIndex].LumpSize / iVertexSizeInBytes; Vertex vertex; for (int i = 0; i < iVertexesCount; ++i) { m_Reader.ReadVertexData(m_WADData, m_WADDirectories[iMapIndex].LumpOffset + i * iVertexSizeInBytes, vertex); map.AddVertex(vertex); cout << vertex.XPosition << endl; cout << vertex.YPosition << endl; std::cout << std::endl; } return true; }
हम्म, ऐसा लगता है कि हम लगातार एक ही कोड की नकल कर रहे हैं; आपको इसे भविष्य में अनुकूलित करना पड़ सकता है, लेकिन अब आप ReadMapLinedef को स्वयं कार्यान्वित करेंगे (या लिंक से स्रोत कोड देखें)।अंतिम स्पर्श - हमें इस फ़ंक्शन को कॉल करने और इसके लिए मानचित्र ऑब्जेक्ट पास करने की आवश्यकता है। bool WADLoader::LoadMapData(Map &map) { if (!ReadMapVertex(map)) { cout << "Error: Failed to load map vertex data MAP: " << map.GetName() << endl; return false; } if (!ReadMapLinedef(map)) { cout << "Error: Failed to load map linedef data MAP: " << map.GetName() << endl; return false; } return true; }
अब मुख्य फ़ंक्शन को बदलते हैं और देखते हैं कि सब कुछ काम करता है या नहीं। मैं "E1M1" मैप लोड करना चाहता हूं, जिसे मैं मैप ऑब्जेक्ट में ट्रांसफर कर दूंगा। Map map("E1M1"); wadloader.LoadMapData(map);
अब इसे सब चलाते हैं। वाह, दिलचस्प संख्याओं का एक समूह, लेकिन क्या वे सच हैं? चलो इसे बाहर की जाँच करें!आइए देखें कि क्या स्लैड इसमें हमारी मदद कर सकता है।हम स्लैड मेनू में नक्शा पा सकते हैं और गांठ के विवरण को देख सकते हैं। संख्याओं की तुलना करते हैं।बहुत बढ़िया!
Linedef के बारे में क्या?मैंने इस गणना को भी जोड़ा, जिसे हम मानचित्र प्रस्तुत करते समय उपयोग करने का प्रयास करेंगे। enum ELINEDEFFLAGS { eBLOCKING = 0, eBLOCKMONSTERS = 1, eTWOSIDED = 2, eDONTPEGTOP = 4, eDONTPEGBOTTOM = 8, eSECRET = 16, eSOUNDBLOCK = 32, eDONTDRAW = 64, eDRAW = 128 };
अन्य नोट
कोड लिखने की प्रक्रिया में, मैंने गलती से आवश्यकता से अधिक बाइट्स पढ़ लिए, और गलत मान प्राप्त किया। डिबगिंग के लिए, मैंने याद में WAD ऑफसेट को देखना शुरू किया कि क्या मैं सही ऑफसेट पर था। यह विज़ुअल स्टूडियो मेमोरी विंडो का उपयोग करके किया जा सकता है, जो बाइट्स या मेमोरी को ट्रैक करने के लिए बहुत उपयोगी उपकरण है (आप इस विंडो में ब्रेकपॉइंट भी सेट कर सकते हैं)।यदि आप मेमोरी विंडो नहीं देखते हैं, तो डीबग> मेमोरी> मेमोरी पर जाएं।अब हम हेक्साडेसिमल में स्मृति में मूल्यों को देखते हैं। इन मूल्यों की तुलना किसी भी गांठ पर राइट-क्लिक करके और इसे हेक्स के रूप में प्रदर्शित करके हेक्स प्रदर्शन के साथ की जा सकती है।स्मृति में लोड WAD के पते के साथ उनकी तुलना करें।और आज के लिए अंतिम बात: हमने इन सभी शीर्ष मूल्यों को देखा, लेकिन क्या कोड लिखने के बिना उन्हें कल्पना करने का एक आसान तरीका है? मैं इस पर समय बर्बाद नहीं करना चाहता, बस यह पता लगाना है कि हम गलत दिशा में आगे बढ़ रहे हैं।निश्चित रूप से किसी ने पहले से ही एक आलेखक बनाया। मैंने एक ग्राफ पर "ड्रा पॉइंट्स" को देखा और पहला परिणाम प्लॉट पॉइंट्स वेबसाइट - डेसमोस था । उस पर, आप क्लिपबोर्ड से नंबर पेस्ट कर सकते हैं, और वह उन्हें आकर्षित करेगा। उन्हें "x (y, y)" प्रारूप में होना चाहिए। इसे प्राप्त करने के लिए, बस आउटपुट फ़ंक्शन को स्क्रीन पर बदलें। cout << "(" << vertex.XPosition << "," << vertex.YPosition << ")" << endl;
वाह! यह पहले से ही E1M1 जैसा दिखता है! हमने कुछ हासिल किया है!यदि आप ऐसा करने के लिए आलसी हैं, तो यहां एक बिंदीदार चार्ट का लिंक दिया गया है: प्लॉट वर्टेक्स ।लेकिन चलो एक और कदम उठाएं: थोड़ा काम करने के बाद, हम इन बिंदुओं को लाइनफेड के आधार पर जोड़ सकते हैं।यहाँ लिंक है: E1M1 प्लॉट वर्टेक्सस्रोत कोड
स्रोत कोडसंदर्भ सामग्री
कयामत विकीजेडूम विकी