एकता पर सरल ज़ोंबी शूटर

सभी को नमस्कार! जल्द ही, यूनिटी गेम्स डेवलपर कोर्स के पहले समूह में कक्षाएं शुरू होंगी। पाठ्यक्रम की शुरुआत की प्रत्याशा में, एकता पर एक ज़ोंबी शूटर बनाने का एक खुला सबक आयोजित किया गया था। रोवरियो एंटरटेनमेंट कॉर्पोरेशन के वरिष्ठ गेम डेवलपर निकोलाई ज़ापोलनोव द्वारा वेबिनार की मेजबानी की गई थी। उन्होंने एक विस्तृत लेख भी लिखा, जिसे हम आपके ध्यान में लाएंगे।



इस लेख में, मैं यह दिखाना चाहूंगा कि यूनिटी में गेम बनाना कितना आसान है। यदि आपके पास बुनियादी प्रोग्रामिंग ज्ञान है, तो आप जल्दी से इस इंजन के साथ काम करना शुरू कर सकते हैं और अपना पहला गेम बना सकते हैं।



अस्वीकरण # 1: यह लेख शुरुआती लोगों के लिए है। यदि आप एकता में एक कुत्ते को खा गए, तो यह आपको उबाऊ लग सकता है।

अस्वीकरण # 2: इस लेख को पढ़ने के लिए, आपको कम से कम बुनियादी प्रोग्रामिंग ज्ञान की आवश्यकता है। कम से कम, शब्द "वर्ग" और "विधि" आपको डरना नहीं चाहिए।

कट के तहत सावधानी, यातायात!

एकता का परिचय


यदि आप पहले से ही एकता संपादक से परिचित हैं, तो आप परिचय को छोड़ सकते हैं और सीधे "गेम वर्ल्ड बना रहे हैं" अनुभाग पर जा सकते हैं।

एकता में बुनियादी संरचनात्मक इकाई "दृश्य" है। एक दृश्य आमतौर पर खेल का एक स्तर होता है, हालांकि कुछ मामलों में एक दृश्य में एक साथ कई स्तर हो सकते हैं या, इसके विपरीत, एक बड़े स्तर को गतिशील रूप से लोड किए गए कई दृश्यों में विभाजित किया जा सकता है। दृश्यों को खेल की वस्तुओं से भरा जाता है, और वे, बदले में, घटकों से भरे होते हैं। यह विभिन्न गेम फ़ंक्शंस को लागू करने वाले घटक हैं: ड्राइंग ऑब्जेक्ट्स, एनीमेशन, भौतिकी, आदि। यह मॉडल आपको सरल ब्लॉकों से कार्यक्षमता को इकट्ठा करने की अनुमति देता है, जैसे कि लेगो कंस्ट्रक्टर के खिलौने से।

इसके लिए C # प्रोग्रामिंग लैंग्वेज का इस्तेमाल कर आप खुद कंपोनेंट लिख सकते हैं। इस तरह से गेम लॉजिक लिखा जाता है। नीचे हम देखेंगे कि यह कैसे किया जाता है, लेकिन अब हम इंजन पर एक नज़र डालते हैं।

जब आप इंजन शुरू करते हैं और एक नया प्रोजेक्ट बनाते हैं, तो आपके सामने एक विंडो दिखाई देगी, जहाँ आप चार मुख्य तत्वों का चयन कर सकते हैं:



स्क्रीनशॉट के ऊपरी बाएँ कोने में "पदानुक्रम" विंडो है। यहां हम वर्तमान खुले दृश्य में गेम ऑब्जेक्ट्स के पदानुक्रम को देख सकते हैं। एकता ने हमारे लिए दो गेम ऑब्जेक्ट बनाए: एक कैमरा ("मुख्य कैमरा") जिसके माध्यम से खिलाड़ी हमारे खेल की दुनिया और एक "दिशात्मक प्रकाश" देखेंगे जो हमारे दृश्य को रोशन करेगा। इसके बिना, हम केवल एक काला वर्ग देखेंगे।

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

ऊपरी दाहिने भाग में "इंस्पेक्टर" विंडो है। इस विंडो में, यूनिटी चयनित ऑब्जेक्ट के मापदंडों को दिखाती है और हम उन्हें संपादित कर सकते हैं। विशेष रूप से, हम देख सकते हैं कि चयनित कैमरे के दो घटक हैं - "ट्रांसफ़ॉर्म", जो गेम की दुनिया में कैमरे की स्थिति निर्धारित करता है, और वास्तव में, "कैमरा", जो कैमरे की कार्यक्षमता को लागू करता है।

वैसे, ट्रांसफॉर्म घटक यूनिटी में सभी गेम ऑब्जेक्ट्स में एक या दूसरे रूप में है।

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

"प्रोजेक्ट" टैब के दाईं ओर, निष्क्रिय "कंसोल" टैब दिखाई देता है। एकता कंसोल के लिए चेतावनी और त्रुटि संदेश लिखती है, इसलिए समय-समय पर वापस जांचना सुनिश्चित करें। खासकर अगर कुछ काम नहीं करता है - सबसे अधिक संभावना है, कंसोल समस्या के कारण पर संकेत देगा। इसके अलावा, कंसोल डिबगिंग के लिए गेम कोड से संदेश प्रदर्शित कर सकता है।

एक खेल की दुनिया बनाएँ


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

इन परिसंपत्तियों से, मैंने एक साधारण स्तर इकट्ठा किया, जिसके साथ हम काम करेंगे:



कोई जादू नहीं, मैंने सिर्फ प्रोजेक्ट विंडो से पसंद की वस्तुओं को खींचा और माउस का उपयोग करके उन्हें मेरी तरह व्यवस्थित किया:



वैसे, एकता आपको एक क्लिक के साथ दृश्य में मानक वस्तुओं को जोड़ने की अनुमति देती है, जैसे कि घन, गोला या विमान। ऐसा करने के लिए, बस पदानुक्रम विंडो में राइट-क्लिक करें और उदाहरण के लिए, 3D Object⇨Plane चुनें। तो, मेरे स्तर में डामर सिर्फ विमानों के एक सेट से इकट्ठा किया जाता है, जिस पर मैंने संपत्ति के एक सेट से एक बनावट "खींचा"।

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

एक तरह से खोज में लाश


इसलिए, हमारे पास एक गेम स्तर है, लेकिन अभी तक इसमें कुछ भी नहीं हो रहा है। हमारे खेल में, खिलाड़ी खिलाड़ी का पीछा करेगा और उस पर हमला करेगा, और इसके लिए उन्हें खिलाड़ी की ओर बढ़ने और बाधाओं के चारों ओर जाने में सक्षम होना चाहिए।

इसे लागू करने के लिए, हम "नेविगेशन मेष" टूल का उपयोग करेंगे। दृश्य डेटा के आधार पर, यह उपकरण उन क्षेत्रों की गणना करता है जहां आप स्थानांतरित कर सकते हैं, और डेटा का एक सेट उत्पन्न करता है जिसका उपयोग खेल के दौरान किसी भी स्तर से किसी अन्य स्तर के लिए इष्टतम मार्ग की खोज के लिए किया जा सकता है। यह डेटा परिसंपत्ति में संग्रहीत होता है और भविष्य में इसे बदला नहीं जा सकता है - इस प्रक्रिया को "बेकिंग" कहा जाता है। यदि आपको गतिशील रूप से बदलती बाधाओं की आवश्यकता है, तो आप NavMeshOblele घटक का उपयोग कर सकते हैं, लेकिन यह हमारे खेल के लिए आवश्यक नहीं है।

एक महत्वपूर्ण बिंदु: प्रत्येक वस्तु के लिए इंस्पेक्टर में किन वस्तुओं को गणना में शामिल किया जाना चाहिए, यह जानने के लिए एकता के लिए (आप पदानुक्रम विंडो में एक ही बार में सब कुछ चुन सकते हैं), "स्टेटिक" विकल्प के बगल में नीचे तीर पर क्लिक करें और "नेविगेशन स्टेटिक" जांचें:



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

अब हम मेनू आइटम का उपयोग करेंगे Window⇨AI⇨Navigation और खुलने वाली विंडो में, "सेंकना" टैब चुनें। यहां एकता हमें ऐसे मापदंडों को स्थापित करने की पेशकश करेगी, जैसे कि चरित्र की ऊंचाई और त्रिज्या, पृथ्वी के झुकाव के अधिकतम कोण, जिस पर आप अभी भी चल सकते हैं, चरणों की अधिकतम ऊंचाई, और इसी तरह। हम अभी तक कुछ भी नहीं बदलेंगे और केवल "सेंकना" बटन दबाएँ।



एकता आवश्यक गणना करेगी और हमें परिणाम दिखाएगी:



यहां, नीला उस क्षेत्र को इंगित करता है जहां आप चल सकते हैं। जैसा कि आप देख सकते हैं, एकता ने बाधाओं के चारों ओर एक छोटा सा पक्ष छोड़ा - इस तरफ की चौड़ाई चरित्र की त्रिज्या पर निर्भर करती है। इस प्रकार, यदि चरित्र का केंद्र नीले क्षेत्र में है, तो वह बाधाओं से "गिर" नहीं जाएगा।

गणना किए गए नेविगेशन ग्रिड के होने के बाद, हम आंदोलन के मार्ग की खोज करने और अपने स्तर पर गेम ऑब्जेक्ट्स की गति को नियंत्रित करने के लिए NavMeshAgent घटक का उपयोग कर सकते हैं।

चलो एक "ज़ोंबी" गेम ऑब्जेक्ट बनाते हैं, इसमें परिसंपत्तियों से लाश का एक 3 डी मॉडल जोड़ते हैं, और नवमेशअर्जेंट घटक भी:



यदि आप अभी खेल शुरू करते हैं, तो कुछ भी नहीं होगा। हमें NavMeshAgent घटक को बताने की आवश्यकता है कि कहां जाना है। ऐसा करने के लिए, हम C # में अपना पहला घटक बनाएंगे।

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



स्क्रिप्ट पर डबल-क्लिक करने से यह संपादक में खुल जाएगा। आइए देखें कि एकता ने हमारे लिए क्या बनाया है।

using System.Collections; using System.Collections.Generic; using UnityEngine; public class Zombie : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } } 

यह एक मानक घटक रिक्त है। जैसा कि हम देख सकते हैं, एकता ने System.Collections और System.Collections.Generic लाइब्रेरी को हमसे कनेक्ट किया है (अब उन्हें ज़रूरत नहीं है, लेकिन उन्हें अक्सर Unity गेम्स कोड की आवश्यकता होती है, इसलिए वे मानक टेम्पलेट में शामिल होते हैं), साथ ही UnityEngine लाइब्रेरी, जिसमें सभी शामिल हैं कोर इंजन एपीआई।

साथ ही, एकता ने हमारे लिए ज़ोंबी वर्ग बनाया (नाम फ़ाइल नाम के साथ मेल खाता है; यह महत्वपूर्ण है: यदि वे मेल नहीं खाते हैं, तो एकता दृश्य में घटक के साथ स्क्रिप्ट का मिलान नहीं कर पाएगी)। वर्ग MonoBehaviour से विरासत में मिला है - यह उपयोगकर्ता-निर्मित घटकों के लिए आधार वर्ग है।

कक्षा के अंदर, एकता ने हमारे लिए दो विधियाँ बनाईं: प्रारंभ और अद्यतन। इंजन इन विधियों को स्वयं कॉल करेगा: प्रारंभ - दृश्य लोड होने के तुरंत बाद, और अपडेट - प्रत्येक फ्रेम। वास्तव में, ऐसे बहुत सारे कार्य हैं जिन्हें इंजन द्वारा बुलाया जाता है, लेकिन उनमें से अधिकांश को आज हमें आवश्यकता नहीं होगी। पूरी सूची, साथ ही उनके कॉल का क्रम, हमेशा दस्तावेज़ में पाया जा सकता है: https://docs.unity3d.com/Manual/ExecutionOrder.html

चलो नक्शे पर लाश को स्थानांतरित करते हैं!

सबसे पहले, हमें UnityEngine.AI लाइब्रेरी को कनेक्ट करना होगा। इसमें NavMeshAgent वर्ग और नेविगेशन ग्रिड से संबंधित अन्य वर्ग शामिल हैं। ऐसा करने के लिए, फ़ाइल की शुरुआत में UnityEngine.AI निर्देश का उपयोग करके जोड़ें।

इसके बाद, हमें NavMeshAgent घटक का उपयोग करने की आवश्यकता है। ऐसा करने के लिए, हम मानक GetComponent पद्धति का उपयोग कर सकते हैं। यह आपको उसी गेम ऑब्जेक्ट में किसी भी घटक का लिंक प्राप्त करने की अनुमति देता है जिसमें जिस घटक से हम इस पद्धति को कहते हैं वह स्थित है (हमारे मामले में, यह "ज़ोंबी" गेम ऑब्जेक्ट है)। हम कक्षा में NavMeshAgent NavMeshAgent फ़ील्ड बनाएंगे, प्रारंभ विधि में हमें NavMeshAgent का लिंक मिलेगा और इसे बिंदु (0, 0, 0) पर ले जाने के लिए कहेंगे। हमें यह स्क्रिप्ट मिलनी चाहिए:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Zombie : MonoBehaviour { NavMeshAgent navMeshAgent; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); navMeshAgent.SetDestination(Vector3.zero); } // Update is called once per frame void Update() { } } 

खेल शुरू करते हुए, हम देखेंगे कि ज़ोंबी नक्शे के केंद्र में कैसे जाता है:



पीड़ित का पीछा करते हुए लाश


अद्भुत। लेकिन हमारी लाश ऊब गई है और अकेला है, चलो उसके लिए एक खिलाड़ी के शिकार को खेल में जोड़ें।

लाश के साथ समानता से, हम एक "प्लेयर" गेम ऑब्जेक्ट बनाएंगे (इस बार जब हम एक पुलिस अधिकारी के 3 डी मॉडल का चयन करेंगे), हम इसमें नवमेशअर्जेंट घटक और हौसले से बनाए गए प्लेयर स्क्रिप्ट को भी जोड़ देंगे। हमने अभी तक प्लेयर स्क्रिप्ट की सामग्री को स्पर्श नहीं किया है, लेकिन हमें ज़ोंबी स्क्रिप्ट में परिवर्तन करने की आवश्यकता होगी। इसके अलावा, मैं NavMeshAgent घटक (या मानक 50 से कम किसी भी अन्य मूल्य, जो खिलाड़ी को उच्च प्राथमिकता दे रहा हूं) में खिलाड़ी की प्राथमिकता संपत्ति मान को 10 पर सेट करने की सलाह देता हूं। इस मामले में, यदि खिलाड़ी और लाश मानचित्र पर मिलते हैं, तो लाश खिलाड़ी को स्थानांतरित करने में सक्षम नहीं होगी, जबकि खिलाड़ी लाश को बाहर धकेलने में सक्षम होगा।

एक खिलाड़ी का पीछा करने के लिए, एक ज़ोंबी को उसकी स्थिति जानने की आवश्यकता होती है। और इसके लिए हमें मानक FindObjectOfType विधि का उपयोग करके अपने ज़ोंबी वर्ग में इसका लिंक प्राप्त करना होगा। लिंक को याद रखने के बाद, हम खिलाड़ी के रूपांतरण घटक की ओर मुड़ सकते हैं और उससे स्थिति का मूल्य पूछ सकते हैं। और हमेशा खिलाड़ी का पीछा करने के लिए ज़ोंबी के लिए, और खेल की शुरुआत में ही नहीं, हम अपडेट विधि में NavMeshAgent के लिए एक लक्ष्य निर्धारित करेंगे। आपको निम्न स्क्रिप्ट मिलती है:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Zombie : MonoBehaviour { NavMeshAgent navMeshAgent; Player player; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); player = FindObjectOfType<Player>(); } // Update is called once per frame void Update() { navMeshAgent.SetDestination(player.transform.position); } } 

खेल को चलाएं और सुनिश्चित करें कि ज़ोंबी ने अपना शिकार पाया है:



बच बच


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

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

एनबी सामान्य तौर पर, इनपुट प्राप्त करने का यह तरीका पूरी तरह से विहित नहीं है। Project Settings.Input Manager के माध्यम से Input.GetAxis और बाइंडिंग का उपयोग करना बेहतर है। बेहतर अभी तक, नया इनपुट सिस्टम । लेकिन यह लेख बहुत लंबा निकला, और इसलिए, हम इसे सरल रूप में करते हैं।

प्लेयर स्क्रिप्ट खोलें और इसे निम्नानुसार बदलें:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Player : MonoBehaviour { NavMeshAgent navMeshAgent; public float moveSpeed; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); } // Update is called once per frame void Update() { Vector3 dir = Vector3.zero; if (Input.GetKey(KeyCode.LeftArrow)) dir.z = -1.0f; if (Input.GetKey(KeyCode.RightArrow)) dir.z = 1.0f; if (Input.GetKey(KeyCode.UpArrow)) dir.x = -1.0f; if (Input.GetKey(KeyCode.DownArrow)) dir.x = 1.0f; navMeshAgent.velocity = dir.normalized * moveSpeed; } } 

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

गति के रूप में 10 सेट करें:



अपडेट विधि में हम यह जांचने के लिए Input.GetKey का उपयोग करेंगे कि क्या कीबोर्ड पर कोई भी तीर दबाया गया है और खिलाड़ी के लिए एक दिशा वेक्टर बना है। ध्यान दें कि हम X और Z निर्देशांक का उपयोग करते हैं। यह इस तथ्य के कारण है कि एकता में Y अक्ष आकाश में दिखता है, और पृथ्वी XZ विमान में स्थित है।

मोशन डायर की दिशा के लिए हमने एक दिशा सदिश का गठन किया है, हम इसे सामान्य करते हैं (अन्यथा, यदि खिलाड़ी तिरछे चलना चाहता है, तो वेक्टर एक एकल की तुलना में थोड़ा लंबा होगा और यह गति सीधे गति से अधिक होगी) और गति की दी गई गति से गुणा करें। परिणाम navMeshAgent.velocity को पारित किया जाता है और एजेंट बाकी काम करेगा।

गेम लॉन्च करके, हम अंततः लाश से सुरक्षित स्थान पर भागने की कोशिश कर सकते हैं:



खिलाड़ी के साथ कैमरा को स्थानांतरित करने के लिए, आइए एक और सरल स्क्रिप्ट लिखें। चलो इसे "प्लेयरकैमरा" कहते हैं:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerCamera : MonoBehaviour { Player player; Vector3 offset; // Start is called before the first frame update void Start() { player = FindObjectOfType<Player>(); offset = transform.position - player.transform.position; } // Update is called once per frame void LateUpdate() { transform.position = player.transform.position + offset; } } 

इस लिपि का अर्थ काफी हद तक समझा जाना चाहिए। फीचर्स से - यहां अपडेट के बजाय हम लेटअप का इस्तेमाल करते हैं। यह विधि अद्यतन के समान है, लेकिन दृश्य में सभी लिपियों के लिए अद्यतन पूरा होने के बाद इसे हमेशा कड़ाई से कहा जाता है। इस मामले में, हम लेटअपडेट का उपयोग करते हैं, क्योंकि हमारे लिए यह महत्वपूर्ण है कि कैमरा ले जाने से पहले नवमेशजेंट खिलाड़ी की नई स्थिति की गणना करता है। अन्यथा, एक अप्रिय "मरोड़ते" प्रभाव हो सकता है।

यदि आप अब इस घटक को "मुख्य कैमरा" गेम ऑब्जेक्ट से जोड़ते हैं और गेम शुरू करते हैं, तो खिलाड़ी का चरित्र हमेशा सुर्खियों में रहेगा!

एनीमेशन पल


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

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

चलो लाश के लिए एक एनिमेटर नियंत्रक बनाते हैं। ऐसा करने के लिए, प्रोजेक्ट में एनिमेशन डायरेक्टरी बनाएं (प्रोजेक्ट में ऑर्डर याद रखें), और इसमें - राइट बटन का उपयोग करके - एनिमेटर कंट्रोलर। और चलो उसे "ज़ोंबी" कहते हैं। डबल क्लिक करें - और संपादक हमारे सामने आएगा:



अब तक यहां कोई राज्य नहीं हैं, लेकिन दो प्रवेश बिंदु ("प्रवेश" और "कोई भी राज्य") और एक निकास बिंदु ("बाहर निकलें") हैं। संपत्ति से कुछ एनिमेशन खींचें:



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

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

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



अब हमें एकता को बताने की आवश्यकता है कि गति "Z_run" को तब खेला जाना चाहिए जब गति 0 से अधिक हो और "Z_idle_A" जब गति शून्य हो। ऐसा करने के लिए, हमें दो बदलाव करने होंगे: एक "Z_idle_A" से "Z_run" और दूसरा विपरीत दिशा में।

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



सादृश्य से, हम विपरीत दिशा में एक संक्रमण बनाते हैं, लेकिन एक शर्त के रूप में अब हम 0.0001 से कम "गति" निर्दिष्ट करते हैं। प्रकार फ्लोट के मापदंडों के लिए कोई समानता जांच नहीं है, उनकी तुलना केवल अधिक / कम के लिए की जा सकती है:



अब आपको कंट्रोलर को गेम ऑब्जेक्ट में बाँधने की आवश्यकता है। हम दृश्य में ज़ोंबी के 3 डी मॉडल का चयन करेंगे (यह "ज़ोंबी" ऑब्जेक्ट का एक बच्चा है) और एनिमेटर घटक में संबंधित फ़ील्ड में माउस के साथ नियंत्रक को खींचें:



यह केवल एक स्क्रिप्ट लिखने के लिए रहता है जो गति पैरामीटर को नियंत्रित करेगा!

निम्नलिखित सामग्री के साथ MoveAAimimator स्क्रिप्ट बनाएं:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MovementAnimator : MonoBehaviour { NavMeshAgent navMeshAgent; Animator animator; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); animator = GetComponentInChildren<Animator>(); } // Update is called once per frame void Update() { animator.SetFloat("speed", navMeshAgent.velocity.magnitude); } } 

यहाँ, अन्य लिपियों की तरह, स्टार्ट मेथड में नवमेशजेंट की पहुँच मिलती है। हम एनिमेटर घटक तक भी पहुँच प्राप्त करते हैं, लेकिन चूंकि हम "MovementAnimator" घटक को "ज़ोंबी" गेम ऑब्जेक्ट में संलग्न करेंगे और एनिमेटर चाइल्ड ऑब्जेक्ट में है, GetComponent के बजाय हमें मानक GetComententInChildren विधि का उपयोग करने की आवश्यकता है।

अपडेट विधि में, हम NavMeshAgent से इसके वेग वेक्टर के लिए पूछते हैं, इसकी लंबाई की गणना करते हैं और इसे एनिमेटर को गति पैरामीटर के रूप में पास करते हैं। कोई जादू नहीं, सब विज्ञान में!

अब ज़ोंबी गेम ऑब्जेक्ट में MoveAnimator घटक जोड़ें और यदि खेल शुरू होता है, तो हम देखते हैं कि लाश अब एनिमेटेड हैं:



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

एक छोटा जोड़ जो बनाने के लिए उपयोगी है, वह निम्नलिखित पंक्तियों को ज़ोंबी वर्ग में जोड़ना है:

प्रारंभ विधि में:

 navMeshAgent.updateRotation = false; 

अद्यतन विधि में:

 transform.rotation = Quaternion.LookRotation(navMeshAgent.velocity.normalized); 

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

NB हम रोटेशन को निर्दिष्ट करने के लिए चतुर्धातुक का उपयोग करते हैं। त्रि-आयामी ग्राफिक्स में, किसी ऑब्जेक्ट के रोटेशन को निर्दिष्ट करने के मुख्य तरीके हैं, यूलर एंगल्स, रोटेशन मैट्रिसेस और क्वाटरनियन। पहले दो हमेशा उपयोग करने के लिए सुविधाजनक नहीं होते हैं, और "गिम्बल लॉक" जैसे अप्रिय प्रभाव के अधीन भी होते हैं। क्वाटरनियन इस खामी से वंचित हैं और अब लगभग सार्वभौमिक रूप से उपयोग किए जाते हैं। एकता quaternions के साथ काम करने के लिए सुविधाजनक उपकरण प्रदान करता है (साथ ही मैट्रिस और यूलर कोण), जो आपको इस गणितीय उपकरण के उपकरण के विवरण में नहीं जाने की अनुमति देता है।

मैं लक्ष्य देखता हूं


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

ताकि खिलाड़ी शूटिंग कर सके, आपको उसे लक्ष्य चुनने का मौका देने की जरूरत है। ऐसा करने के लिए, माउस-नियंत्रित कर्सर को जमीन पर रखें।

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



प्रेक्षक की आँख केवल वही देखती है जो इस पिरामिड में आता है। इसके अलावा, इंजन विशेष रूप से इस पिरामिड को दो तरफ से काटता है: सबसे पहले, पर्यवेक्षक की तरफ से एक मॉनिटर स्क्रीन है, तथाकथित "निकट विमान" (चित्र में यह पीले रंग में चित्रित किया गया है)। मॉनिटर भौतिक रूप से स्क्रीन की तुलना में वस्तुओं को प्रदर्शित नहीं कर सकता है, इसलिए इंजन उन्हें काट देता है। दूसरे, चूंकि कंप्यूटर में संसाधनों की एक सीमित मात्रा है, इसलिए इंजन किरणों को अनंत तक विस्तारित नहीं कर सकता है (उदाहरण के लिए, गहराई बफर के लिए संभावित मानों की एक निश्चित सीमा निर्धारित की जानी चाहिए; इसके अलावा, यह व्यापक है, सटीकता कम है), इसलिए पिरामिड तथाकथित पीछे काट दिया जाता है; "सुदूर विमान"।

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



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

यदि आप दृश्य में किसी गेम ऑब्जेक्ट का चयन करते हैं, तो निरीक्षक के ऊपरी भाग में आप ड्रॉप-डाउन सूची "लेयर" देख सकते हैं। डिफ़ॉल्ट रूप से "डिफ़ॉल्ट" का एक मूल्य होगा। ड्रॉप-डाउन सूची को खोलकर, आप इसमें "परत जोड़ें ..." आइटम पा सकते हैं, जो परत संपादक विंडो खोल देगा। संपादक में आपको एक नई परत जोड़ने की जरूरत है (इसे "ग्राउंड" कहते हैं):



अब आप दृश्य में सभी जमीन के विमानों का चयन कर सकते हैं और उन्हें ग्राउंड लेयर असाइन करने के लिए इस ड्रॉप-डाउन सूची का उपयोग कर सकते हैं। यह हमें भौतिकी में स्क्रिप्ट में इंगित करने की अनुमति देगा ।aycast विधि कि केवल इन वस्तुओं के लिए बीम के चौराहे की जांच करना आवश्यक है।

अब आइए एसेट स्प्राइट से कर्सर को दृश्य में खींचें (मैं Spags AssetsextTextures⇨Demo⇨white_hip⇨white_hip_14 का उपयोग करता हूं):



मैंने कर्सर के लिए एक्स अक्ष के चारों ओर 90 डिग्री का घुमाव जोड़ा ताकि यह जमीन पर क्षैतिज रूप से बिछे, स्केल को 0.25 पर सेट करें ताकि यह इतना बड़ा न हो और वाई को 0.01 तक समन्वयित कर सके। उत्तरार्द्ध महत्वपूर्ण है ताकि "जेड-फाइटिंग" नामक कोई प्रभाव न हो। वीडियो कार्ड फ्लोटिंग पॉइंट गणनाओं का उपयोग करके यह निर्धारित करता है कि कौन सी वस्तुएं कैमरे के करीब हैं। यदि आप कर्सर को 0 पर सेट करते हैं (यानी, जमीन के विमान के समान), तो इन गणनाओं में त्रुटियों के कारण, कुछ पिक्सेल के लिए, वीडियो कार्ड यह तय करेगा कि कर्सर करीब है, और दूसरों के लिए, कि पृथ्वी। इसके अलावा, विभिन्न फ़्रेमों में, पिक्सेल के सेट अलग-अलग होंगे, जो जमीन के माध्यम से कर्सर के चमकते हुए टुकड़ों और "चंचल" होने पर एक अप्रिय प्रभाव पैदा करेगा। 0.01 का मान वीडियो कार्ड की गणना में त्रुटियों को ऑफसेट करने के लिए काफी बड़ा है, लेकिन इतना बड़ा नहीं है कि आंख ने देखा कि कर्सर हवा में लटका हुआ है।

अब कर्सर पर गेम ऑब्जेक्ट का नाम बदलें और उसी नाम और निम्न सामग्री के साथ एक स्क्रिप्ट बनाएं:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Cursor : MonoBehaviour { SpriteRenderer spriteRenderer; int layerMask; // Start is called before the first frame update void Start() { spriteRenderer = GetComponent<SpriteRenderer>(); layerMask = LayerMask.GetMask("Ground"); } // Update is called once per frame void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (!Physics.Raycast(ray, out hit, 1000, layerMask)) spriteRenderer.enabled = false; else { transform.position = new Vector3(hit.point.x, transform.position.y, hit.point.z); spriteRenderer.enabled = true; } } } 

चूंकि कर्सर एक स्प्राइट (दो-आयामी ड्राइंग) है, एकता इसे रेंडर करने के लिए स्प्राइटरेंडर घटक का उपयोग करती है। हमें प्रारंभ विधि में इस घटक का लिंक मिलता है ताकि इसे चालू / बंद किया जा सके।

प्रारंभ विधि में भी, हम उस "ग्राउंड" परत का नाम परिवर्तित करते हैं जिसे हमने पहले एक बिटमास्क में बनाया था। एकता चौराहों की तलाश करते समय ऑब्जेक्ट्स को फ़िल्टर करने के लिए बिटवाइज़ ऑपरेशंस का उपयोग करती है, और LayerMask.GetMask पद्धति बिटकॉस् को निर्दिष्ट परत के अनुरूप लौटाती है।

अद्यतन विधि में, हम Camera.main का उपयोग करके दृश्य के मुख्य कैमरे तक पहुंचते हैं और इसे तीन-आयामी किरण में माउस के दो-आयामी निर्देशांक (Input.mousePosition का उपयोग करके प्राप्त) को पुनर्गणना करने का अनुरोध करते हैं। अगला, हम इस किरण को Physics.Raycast विधि से पास करते हैं और जाँचते हैं कि यह दृश्य में किसी वस्तु के साथ है या नहीं। 1000 का मान अधिकतम दूरी है। गणित में, किरणें अनंत हैं, लेकिन कंप्यूटर के कंप्यूटिंग संसाधन और मेमोरी नहीं हैं। इसलिए, एकता हमें कुछ उचित अधिकतम दूरी निर्धारित करने के लिए कहती है।

यदि कोई चौराहा नहीं था, तो हम स्प्राइट रेंडरर को बंद कर देते हैं और स्क्रीन से कर्सर की छवि गायब हो जाती है। यदि चौराहा पाया गया था, तो हम कर्सर को चौराहे बिंदु पर ले जाते हैं।कृपया ध्यान दें कि हम Y समन्वय को नहीं बदलते हैं, क्योंकि जमीन के साथ किरण के प्रतिच्छेदन का बिंदु Y के बराबर शून्य होगा और इसे हमारे कर्सर को असाइन करने पर हमें फिर से जेड-फाइटिंग प्रभाव मिलता है, जिसे हमने ऊपर से छुटकारा पाने की कोशिश की। इसलिए, हम चौराहे बिंदु से केवल एक्स और जेड निर्देशांक लेते हैं, और वाई एक ही रहता है।

कर्सर घटक को कर्सर गेम ऑब्जेक्ट में जोड़ें।

अब, प्लेयर स्क्रिप्ट को अंतिम रूप दें: पहले, कर्सर कर्सर फ़ील्ड जोड़ें। फिर प्रारंभ विधि में, निम्न पंक्तियाँ जोड़ें:

 cursor = FindObjectOfType<Cursor>(); navMeshAgent.updateRotation = false; 

और अंत में, ताकि खिलाड़ी हमेशा अद्यतन पद्धति में कर्सर की ओर मुड़ जाए:

 Vector3 forward = cursor.transform.position - transform.position; transform.rotation = Quaternion.LookRotation(new Vector3(forward.x, 0, forward.z)); 

यहां हम वाई समन्वय को भी ध्यान में नहीं रखते हैं।

जीवित रहने के लिए गोली मारो


कर्सर की ओर मुड़ने का एकमात्र तथ्य हमें लाश से नहीं बचाएगा, लेकिन केवल खिलाड़ी के चरित्र को आश्चर्य के प्रभाव से छुटकारा दिलाएगा - अब आप उसके पीछे चुपके नहीं कर सकते। ताकि वह वास्तव में हमारे खेल की कठोर वास्तविकताओं में जीवित रह सके, आपको उसे सिखाना होगा कि कैसे शूट करना है। और अगर यह दिखाई नहीं दे रहा है तो यह किस तरह का शॉट है? हर कोई जानता है कि कोई भी सम्मानजनक शूटर हमेशा ट्रैसर गोलियों को मारता है।

एक शॉट गेम ऑब्जेक्ट बनाएं और इसमें मानक लाइनरेंडर घटक जोड़ें। संपादक में "चौड़ाई" फ़ील्ड का उपयोग करते हुए, इसे एक छोटी चौड़ाई दें, उदाहरण के लिए, 0.04। जैसा कि हम देख सकते हैं, एकता एक उज्ज्वल बैंगनी रंग के साथ इसे पेंट करती है - इस तरह से बिना सामग्री के वस्तुओं को उजागर किया जाता है।

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

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



अब जब सामग्री बनाई गई है, तो आप इसे लाइनरेंडर को सौंप सकते हैं:



एक शॉट स्क्रिप्ट बनाएँ:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Shot : MonoBehaviour { LineRenderer lineRenderer; bool visible; // Start is called before the first frame update void Start() { lineRenderer = GetComponent<LineRenderer>(); } // Update is called once per frame void FixedUpdate() { if (visible) visible = false; else gameObject.SetActive(false); } public void Show(Vector3 from, Vector3 to) { lineRenderer.SetPositions(new Vector3[]{ from, to }); visible = true; gameObject.SetActive(true); } } 

यह स्क्रिप्ट, जैसा कि आप शायद पहले ही अनुमान लगा चुके हैं, शॉट गेम ऑब्जेक्ट में जोड़ने की आवश्यकता है।

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

GameObject.SetActive पद्धति पूरे गेम ऑब्जेक्ट को चालू या बंद करती है जिस पर हमारा घटक स्थित है। स्क्रीन पर बंद वस्तुओं को स्क्रीन पर नहीं खींचा जाता है और उनके घटक अपडेट, फिक्स्डपेड विधियों आदि को कॉल नहीं करते हैं। इस पद्धति का उपयोग करके आप खिलाड़ी को शूटिंग नहीं करने पर शॉट को अदृश्य बनाने की अनुमति देते हैं।

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

लेकिन पहले आपको बंदूक बैरल के निर्देशांक प्राप्त करने में सक्षम होने की आवश्यकता है ताकि शॉट सही छेद से आए। ऐसा करने के लिए, Bip001⇨Bip001 PelvisipBip001 SpineipBip001 R Clavicle RBip001 R UpperArm⇨Bip001 R Forearm⇨Bip001 R Hand⇨R_hand_container⇨w_handgun ऑब्जेक्ट को प्लेयर के 3D मॉडल में ढूंढें और इसमें GunBarrel चाइल्ड ऑब्जेक्ट जोड़ें। इसे बंदूक की बैरल के ठीक बगल में रखें:



अब प्लेयर स्क्रिप्ट में, फ़ील्ड्स जोड़ें:

 Shot shot; public Transform gunBarrel; 


प्लेयर स्क्रिप्ट की आरंभ विधि में जोड़ें:

 shot = FindObjectOfType<Shot>(); 

और अद्यतन विधि में:

 if (Input.GetMouseButtonDown(0)) { var from = gunBarrel.position; var target = cursor.transform.position; var to = new Vector3(target.x, from.y, target.z); shot.Show(from, to); } 

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



यदि हम अब गेम शुरू करते हैं, तो हम अंततः लाश को शूट कर सकते हैं!



यहाँ कुछ गड़बड़ है! ऐसा लगता है कि शॉट्स लाश को नहीं मारते हैं, लेकिन बस इसके माध्यम से उड़ते हैं!

ठीक है, निश्चित रूप से, यदि आप हमारे शॉट कोड को देखते हैं, तो हम किसी भी तरह से ट्रैक नहीं करते हैं कि हमारा शॉट दुश्मन को मारता है या नहीं। बस कर्सर के लिए एक रेखा खींचना।

यह तय करना बहुत आसान है। प्लेयर क्लास में माउस क्लिक को संसाधित करने के लिए कोड में, लाइन वर्जन के बाद = ... और लाइन शॉट से पहले।शो (...), निम्न लाइनें जोड़ें:

 var direction = (to - from).normalized; RaycastHit hit; if (Physics.Raycast(from, to - from, out hit, 100)) to = new Vector3(hit.point.x, from.y, hit.point.z); else to = from + direction * 100; 

यहाँ हम परिचित Physics.Raycast का उपयोग करते हैं, बीम को बंदूक के बैरल से बाहर निकालने के लिए और यह निर्धारित करते हैं कि क्या यह किसी गेम ऑब्जेक्ट के साथ इंटरसेक्ट करता है।

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

एक कोलाइडर एक घटक है जिसके साथ भौतिकी इंजन वस्तुओं के बीच टकराव को निर्धारित करता है। आमतौर पर सरल ज्यामितीय आकृतियों का उपयोग कोलाडर - क्यूब्स, गोले आदि के रूप में किया जाता है। यद्यपि यह दृष्टिकोण कम सटीक टकराव प्रदान करता है, इस तरह की वस्तुओं के बीच प्रतिच्छेदन सूत्र काफी सरल हैं और बड़े कम्प्यूटेशनल संसाधनों की आवश्यकता नहीं है। बेशक, अगर आपको अधिकतम सटीकता की आवश्यकता है, तो आप हमेशा प्रदर्शन का त्याग कर सकते हैं और MeshCollider का उपयोग कर सकते हैं। लेकिन हमें उच्च सटीकता की आवश्यकता नहीं है, इसलिए हम CapsuleCollider घटक का उपयोग



नहीं करेंगे : अब गोली लाश के माध्यम से नहीं उड़ जाएगी। हालांकि, लाश अभी भी अमर है।

लाश - ज़ोंबी मौत!


आइए सबसे पहले ज़ोंबी एनीमेशन नियंत्रक में एक मौत का एनीमेशन जोड़ें। ऐसा करने के लिए, AssetPacks⇨ToonyTinyPeopleTTT_demo⇨animation⇨zombieeathZ_death_A एनीमेशन इसमें खींचें। इसे सक्रिय करने के लिए, ट्रिगर प्रकार के साथ एक नया पैरामीटर बनाया गया। अन्य मापदंडों (बूल, फ्लोट, आदि) के विपरीत, ट्रिगर अपने राज्य को याद नहीं करते हैं और एक फ़ंक्शन कॉल की तरह अधिक हैं: उन्होंने एक ट्रिगर सक्रिय किया - संक्रमण काम किया, और ट्रिगर वापस रीसेट हो गया। और चूंकि एक ज़ोंबी किसी भी राज्य में मर सकता है - और अगर यह अभी भी खड़ा है, और अगर यह चल रहा है, तो हम किसी भी राज्य राज्य से संक्रमण जोड़ देंगे:



निम्नलिखित क्षेत्रों को ज़ोंबी स्क्रिप्ट में जोड़ें:

 CapsuleCollider capsuleCollider; Animator animator; MovementAnimator movementAnimator; bool dead; 

ज़ोंबी वर्ग प्रारंभ विधि में, सम्मिलित करें:

 capsuleCollider = GetComponent<CapsuleCollider>(); animator = GetComponentInChildren<Animator>(); movementAnimator = GetComponent<MovementAnimator>(); 

अपडेट विधि की शुरुआत में, आपको एक चेक जोड़ने की आवश्यकता है:

 if (dead) return; 

और अंत में, ज़ोंबी वर्ग में किल पब्लिक मेथड जोड़ें:

 public void Kill() { if (!dead) { dead = true; Destroy(capsuleCollider); Destroy(movementAnimator); Destroy(navMeshAgent); animator.SetTrigger("died"); } } 

मुझे लगता है कि नए क्षेत्रों का कार्यभार काफी स्पष्ट है। किल विधि के लिए - इसमें हम (यदि हम मृत नहीं हैं) ज़ोंबी मौत का झंडा लगाते हैं और अपने गेम ऑब्जेक्ट से CapsuleCollider, MovementAnimator और NavMeshAgent घटकों को हटा देते हैं, जिसके बाद हम एनीमेशन नियंत्रक से मृत्यु एनीमेशन के प्लेबैक को सक्रिय करते हैं।

घटकों को क्यों हटाएं? ताकि जैसे ही एक ज़ोंबी मर जाए, वह नक्शे के चारों ओर घूमना बंद कर देता है और अब गोलियों के लिए बाधा नहीं है। अच्छे के लिए, आपको अभी भी किसी तरह से शरीर से छुटकारा पाने के लिए कुछ सुंदर तरीके से मौत के एनीमेशन के बाद खेलना चाहिए। अन्यथा, मृत लाश दूर संसाधनों को खाने के लिए जारी रहेगी और, जब बहुत अधिक लाशें होती हैं, तो खेल काफ़ी धीमा हो जाएगा। सबसे आसान तरीका है कि डिस्टल कॉल (gameObject, 3) को यहां जोड़ें। यह इस कॉल के 3 सेकंड बाद इस गेम ऑब्जेक्ट को हटाने के लिए एकता का कारण होगा।

ताकि यह सब अंत में काम आए, आखिरी स्पर्श बना रहे। खिलाड़ी वर्ग में, अपडेट विधि में, जहाँ हम Physics.Raycast को कॉल करते हैं, मामले में शाखा के लिए जब एक चौराहा मिला, हम एक चेक जोड़ते हैं:

 if (hit.transform != null) { var zombie = hit.transform.GetComponent<Zombie>(); if (zombie != null) zombie.Kill(); } 

Phys..Raycast हिट वैरिएबल में प्रतिच्छेदन जानकारी को कॉल करता है। विशेष रूप से, ट्रांसफ़ॉर्मिंग फ़ील्ड में गेम ऑब्जेक्ट के ट्रांसफ़ॉर्म घटक के लिए एक लिंक होगा जिसके साथ किरण को इंटरसेप्ट किया गया है। यदि इस गेम ऑब्जेक्ट में एक ज़ोंबी घटक है, तो यह एक ज़ोंबी है और हम इसे मारते हैं। प्राथमिक!

खैर, ताकि दुश्मन की मौत शानदार दिखे, हम लाश में एक सरल कण प्रणाली जोड़ते हैं।

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

ज़ोंबी गेम ऑब्जेक्ट में एक कण सिस्टम जोड़ें (उस पर राइट-क्लिक करें और EffectartParticle System) चुनें:

मैं निम्नलिखित सुझाव देता हूं
:

  • पद: वाई 0.5
  • रोटेशन: X -90

कण प्रणाली
  • अवधि: 0.2
  • लूपिंग: झूठा
  • लाइफटाइम शुरू करें: 0.8
  • प्रारंभ का आकार: 0.5
  • रंग शुरू: हरा
  • गुरुत्वाकर्षण संशोधक: १
  • जागने पर खेलें: असत्य
  • उत्सर्जन:
  • समय के साथ दर: 100
  • आकार:
  • त्रिज्या: 0.25

यह कुछ इस तरह दिखना चाहिए: यह



ज़ोंबी वर्ग की मार विधि में इसे सक्रिय करने के लिए बना हुआ है:

 GetComponentInChildren<ParticleSystem>().Play(); 

और अब एक पूरी तरह से अलग मामला!



झुंड में लाश पर हमला


वास्तव में, एक भी ज़ोंबी से लड़ना उबाऊ है। आपने उसे और उसे मार दिया है नाटक कहां है? युवा के मरने का डर कहां है? सर्वनाश और निराशा की सच्ची वातावरण बनाने के लिए, बहुत सारी लाश होनी चाहिए।

सौभाग्य से, यह बहुत आसान है। जैसा कि आपने अनुमान लगाया होगा, हमें एक और स्क्रिप्ट की जरूरत है। इसे EnemySpawner कहें और इसे निम्नलिखित सामग्रियों से भरें:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemySpawner : MonoBehaviour { public float Period; public GameObject Enemy; float TimeUntilNextSpawn; // Start is called before the first frame update void Start() { TimeUntilNextSpawn = Random.Range(0, Period); } // Update is called once per frame void Update() { TimeUntilNextSpawn -= Time.deltaTime; if (TimeUntilNextSpawn <= 0.0f) { TimeUntilNextSpawn = Period; Instantiate(Enemy, transform.position, transform.rotation); } } } 

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

एक प्रश्न शेष है - शत्रु क्षेत्र में शत्रु से कैसे पूछें? ऐसा करने के लिए, हम “Prefabs” जैसे एक एकता उपकरण का उपयोग करेंगे। वास्तव में, एक प्रीफ़ैब एक अलग फ़ाइल में सहेजे गए दृश्य का एक टुकड़ा है। फिर हम इस फ़ाइल को अन्य दृश्यों (या उसी में) में सम्मिलित कर सकते हैं और हमें इसे हर बार फिर से टुकड़ों से इकट्ठा करने की आवश्यकता नहीं है। उदाहरण के लिए, हमने दीवारों, फर्श, छत, खिड़कियों और दरवाजों, एक सुंदर घर की वस्तुओं से एकत्र किया और इसे प्रीफैब के रूप में सहेजा। अब आप इस घर को अन्य कार्डों में कलाई की झिलमिलाहट के साथ सम्मिलित कर सकते हैं। उसी समय, यदि आप प्रीफ़ैब फ़ाइल को संपादित करते हैं (उदाहरण के लिए, घर में एक पिछला दरवाजा जोड़ें), तो ऑब्जेक्ट सभी दृश्यों में बदल जाएगा। कभी-कभी यह बहुत सुविधाजनक है। हम इंस्टेंट के लिए टेम्पलेट के रूप में प्रीफैब का उपयोग भी कर सकते हैं - और हम इस अवसर का उपयोग अभी करेंगे।

एक प्रीफ़ैब बनाने के लिए, गेम ऑब्जेक्ट को पदानुक्रम विंडो से प्रोजेक्ट विंडो पर खींचें, एकता बाकी काम करेगी। चलो लाश से एक प्रीफ़ैब बनाते हैं, और फिर दृश्य में एक दुश्मन स्पॉनर को जोड़ते हैं:



मैंने प्रोजेक्ट में तीन और स्पॉवर्स को विविधता के लिए जोड़ा (इसलिए, अंत में, मेरे पास उनमें से 4 हैं)। और इसलिए, क्या हुआ:



यहाँ! यह पहले से ही एक ज़ोंबी सर्वनाश जैसा दिखता है!

निष्कर्ष


बेशक, यह एक पूर्ण खेल से दूर है। हमने कई मुद्दों पर विचार नहीं किया, जैसे कि एक उपयोगकर्ता इंटरफ़ेस, ध्वनि, जीवन और एक खिलाड़ी की मृत्यु - यह सब इस लेख के दायरे से बाहर है। लेकिन यह मुझे लगता है कि यह लेख उन लोगों के लिए एकता के लिए एक योग्य परिचय होगा जो इस उपकरण से परिचित नहीं हैं। या हो सकता है कि कोई अनुभवी व्यक्ति इसमें से कुछ ट्रिक निकाल सकेगा?

सामान्य तौर पर, दोस्तों, मुझे उम्मीद है कि आपको मेरा लेख पसंद आया होगा। अपने प्रश्न टिप्पणियों में लिखें, मैं उत्तर देने का प्रयास करूंगा। परियोजना का स्रोत कोड github: https://github.com/zapolnov/otus_zchin पर डाउनलोड किया जा सकता है आपको एकता 2019.3.0f3 या उच्चतर की आवश्यकता होगी, इसे आधिकारिक वेबसाइट से पूरी तरह से नि: शुल्क और एसएमएस के बिना डाउनलोड किया जा सकता है: https://store.unity.com/download

लेख में प्रयुक्त संपत्ति के लिंक:

Source: https://habr.com/ru/post/hi485210/


All Articles