इसे सच करें - एकता पर एक लॉजिक गेम विकसित करना



मैं एक साधारण मोबाइल गेम की विकास प्रक्रिया को दो डेवलपर्स और एक कलाकार द्वारा साझा करना चाहता हूं। यह लेख काफी हद तक तकनीकी कार्यान्वयन का विवरण है।
सावधानी, बहुत सारा पाठ!

लेख एक गाइड या पाठ नहीं है, हालांकि मुझे आशा है कि पाठक इससे उपयोगी कुछ सीख सकते हैं। कुछ प्रोग्रामिंग अनुभव के साथ एकता से परिचित डेवलपर्स के लिए डिज़ाइन किया गया।

सामग्री:


विचार
गेमप्ले
कहानी
डिज़ाइन
कोर
  1. विद्युत तत्व
  2. सॉल्वर
  3. ElementsProvider
  4. CircuitGenerator

खेल की कक्षाएं

  1. विकास दृष्टिकोण और डि
  2. विन्यास
  3. विद्युत तत्व
  4. खेल प्रबंधन
  5. स्तर लोड हो रहा है
  6. cutscene
  7. अतिरिक्त गेमप्ले
  8. मुद्रीकरण
  9. उपयोगकर्ता इंटरफ़ेस
  10. एनालिटिक्स
  11. कैमरा पोजिशनिंग और आरेख
  12. रंग योजनाओं

संपादक एक्सटेंशन


  1. जनक
  2. सॉल्वर

उपयोगी

  1. AssertHelper
  2. SceneObjectsHelper
  3. CoroutineStarter
  4. gizmo

परीक्षण
विकास सारांश

विचार


सामग्री

कम समय में एक सरल मोबाइल गेम बनाने का विचार था।

नियम और शर्तें:

  • खेल को लागू करने के लिए आसान है
  • न्यूनतम कला आवश्यकताएँ
  • लघु विकास समय (कई महीने)
  • सामग्री निर्माण (स्तरों, स्थानों, खेल तत्वों) के आसान स्वचालन के साथ
  • अगर खेल में स्तरों की एक सीमित संख्या है तो जल्दी से एक स्तर बनाएं

फैसला करने के लिए, लेकिन वास्तव में क्या करते हैं? आखिरकार, एक गेम बनाने का विचार आया, न कि एक गेम बनाने का। ऐप स्टोर से प्रेरणा लेने का निर्णय लिया गया।

उपरोक्त मदों में जोड़ा जाता है:

  • खेल को खिलाड़ियों के बीच एक निश्चित लोकप्रियता होनी चाहिए (डाउनलोड + रेटिंग की संख्या)
  • ऐप स्टोर में समान गेम के साथ भीड़ नहीं होनी चाहिए

तार्किक गेट्स के आधार पर गेमप्ले के साथ एक गेम पाया गया। बड़ी संख्या में समान नहीं थे। खेल में कई डाउनलोड और सकारात्मक रेटिंग हैं। फिर भी, कोशिश करने के बाद, कुछ कमियां थीं जिन्हें आपके खेल में ध्यान में रखा जा सकता है।

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

पेशेवरों:

  • गेमप्ले की तकनीकी सरलता
  • ऑटोटेस्ट के साथ परीक्षण करना आसान लगता है
  • ऑटो-जनरेट स्तरों की क्षमता

विपक्ष:

  • आपको पहले स्तर बनाने होंगे

अब उस खेल की खामियों का पता लगाएं, जिसने प्रेरित किया।

  • कस्टम पहलू अनुपात के लिए अनुकूलित नहीं, 18: 9 की तरह
  • एक कठिन स्तर को छोड़ने या एक संकेत प्राप्त करने का कोई तरीका नहीं है
  • समीक्षाओं में कुछ स्तरों के बारे में शिकायतें थीं
  • समीक्षा में विभिन्न प्रकार के तत्वों की कमी के बारे में शिकायत की गई थी

हम अपने खेल की योजना के लिए आगे बढ़ते हैं:

  • हम मानक तर्क गेट्स (और, नंद, या, NOR, XOR, XNOR, NOR, NOT) का उपयोग करते हैं
  • फाटकों को एक पाठ पदनाम के बजाय एक चित्र के साथ प्रदर्शित किया जाता है, जिसे भेद करना आसान है। चूंकि तत्वों में मानक एएनएसआई संकेतन है, हम उनका उपयोग करते हैं।
  • हम उस स्विच को छोड़ देते हैं जो आउटपुट के एक इनपुट को जोड़ता है। इस तथ्य के कारण कि आपको अपने आप पर क्लिक करने की आवश्यकता है और वास्तविक डिजिटल तत्वों में थोड़ा फिट नहीं है। हाँ, और एक चिप में टॉगल स्विच की कल्पना करना कठिन है।
  • एनकोडर और डिकोडर के तत्वों को जोड़ें।
  • हम एक मोड पेश करते हैं जिसमें खिलाड़ी को सर्किट के इनपुट पर निश्चित मान के साथ सेल में वांछित तत्व का चयन करना होगा।
  • हम खिलाड़ी को मदद प्रदान करते हैं: संकेत + लंघन स्तर।
  • कुछ प्लॉट जोड़ना अच्छा रहेगा।

गेमप्ले


सामग्री

मोड 1: खिलाड़ी एक सर्किट प्राप्त करता है और इनपुट पर मूल्यों को बदलने के लिए उपयोग करता है।
मोड 2: खिलाड़ी को एक सर्किट प्राप्त होता है जिसमें वह तत्वों को बदल सकता है लेकिन इनपुट में मानों को नहीं बदल सकता है।

गेमप्ले पूर्व-तैयार स्तरों के रूप में होगा। स्तर को पूरा करने के बाद, खिलाड़ी को कुछ परिणाम प्राप्त करना होगा। यह पारंपरिक तीन सितारों के रूप में किया जाएगा, जो कि पारित होने के परिणाम पर निर्भर करता है।

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

कहानी


सामग्री

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

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

विचार! एक इंजीनियर अपने तर्क सर्किट का उपयोग करके एक शांत रोबोट विकसित करता है। रोबोट एक काफी सरल समझने वाली चीज है और गेमप्ले के साथ पूरी तरह से फिट बैठता है।

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

अब चलो गेम में कटकनेसेस के प्रारूप और एकीकरण के बारे में निर्णय लेते हैं।

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

Cutscenes और स्तरों को अलग-अलग दृश्य होना चाहिए। एक निश्चित स्तर से पहले, एक विशिष्ट दृश्य लोड किया जाता है।

ठीक है, कार्य निर्धारित है, पूरा करने के लिए संसाधन हैं, काम में उबाल आना शुरू हो गया है।

डिज़ाइन


सामग्री

मैंने तुरंत मंच पर फैसला किया, यह एकता है। हां थोड़ा ओवरकिल, लेकिन फिर भी मैं उसे जानता हूं।

विकास के दौरान, कोड को तुरंत परीक्षण या उसके बाद भी लिखा जाता है। लेकिन एक समग्र कथा के लिए, परीक्षण नीचे एक अलग अनुभाग में रखा गया है। वर्तमान खंड परीक्षण से अलग विकास प्रक्रिया का वर्णन करेगा।

कोर


सामग्री

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

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





एकता अब उपयुक्त नाम के साथ एक परियोजना बनाती है। .Asmdef फ़ाइल के साथ फ़ोल्डर में निहित सब कुछ इस परियोजना और विधानसभा से संबंधित होगा।

विद्युत तत्व


सामग्री

कार्य कोड में एक दूसरे के साथ तार्किक तत्वों की बातचीत का वर्णन करना है।

  • एक तत्व में कई इनपुट और कई आउटपुट हो सकते हैं।
  • तत्व का इनपुट दूसरे तत्व के आउटपुट से जुड़ा होना चाहिए
  • तत्व में स्वयं का तर्क होना चाहिए।

चलिए शुरू करते हैं।

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



तीर डेटा की दिशा, विपरीत दिशा में तत्वों की निर्भरता को इंगित करता है।
कनेक्टर के इंटरफेस को परिभाषित करें। आप इससे मूल्य प्राप्त कर सकते हैं।

public interface IConnector { bool Value { get; } } 

बस इसे दूसरे कनेक्टर से कैसे जोड़ा जाए?

अधिक इंटरफेस परिभाषित करें।

 public interface IInputConnector : IConnector { IOutputConnector ConnectedOtherConnector { get; set; } } 

IInputConnector एक इनपुट कनेक्टर है, इसमें दूसरे कनेक्टर का लिंक है।

 public interface IOutputConnector : IConnector { IElectricalElement Element { set; get; } } 

आउटपुट कनेक्टर इसके तत्व को संदर्भित करता है जिससे यह एक मूल्य का अनुरोध करेगा।

 public interface IElectricalElement { bool GetValue(byte number = 0); } 

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

मैंने इसे IElectricalElement कहा, हालांकि यह केवल तार्किक वोल्टेज स्तरों को प्रसारित करता है, लेकिन दूसरी ओर यह एक ऐसा तत्व हो सकता है जो तर्क को बिल्कुल भी नहीं जोड़ता है, बस एक कंडक्टर की तरह एक मान देता है।

अब हम कार्यान्वयन के लिए आगे बढ़ते हैं

 public class InputConnector : IInputConnector { public IOutputConnector ConnectedOtherConnector { get; set; } public bool Value { get { return ConnectedOtherConnector?.Value ?? false; } } } 

आने वाला कनेक्टर जुड़ा नहीं हो सकता है, जिस स्थिति में यह गलत वापस आ जाएगा।

 public class OutputConnector : IOutputConnector { private readonly byte number; public OutputConnector(byte number = 0) { this.number = number; } public IElectricalElement Element { get; set; } public bool Value => Element.GetValue(number); } } 

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

 public abstract class ElectricalElementBase { public IInputConnector[] Input { get; set; } } 

सभी तत्वों के लिए आधार वर्ग, बस इनपुट की एक सरणी है।

एक तत्व का उदाहरण कार्यान्वयन:

 public class And : ElectricalElementBase, IElectricalElement { public bool GetValue(byte number = 0) { bool outputValue = false; if (Input?.Length > 0) { outputValue = Input[0].Value; foreach (var item in Input) { outputValue &= item.Value; } } return outputValue; } } 

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

उल्टे तत्वों को निम्नानुसार बनाया जाता है:

 public class Nand : And, IElectricalElement { public new bool GetValue(byte number = 0) { return !base.GetValue(number); } } 

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

पारंपरिक वाल्वों के अलावा, निम्नलिखित तत्व बनाए गए थे:
स्रोत - 0 या 1 का निरंतर मान स्रोत।
कंडक्टर - बस एक ही या कंडक्टर, केवल एक अलग आवेदन है, पीढ़ी देखें।
ऑलवेजफ्लेसे - हमेशा 0 रिटर्न, दूसरी मोड के लिए आवश्यक।

सॉल्वर


सामग्री

अगला, एक वर्ग स्वचालित रूप से संयोजन खोजने के लिए उपयोगी है जो सर्किट के आउटपुट पर 1 देता है।

  public interface ISolver { ICollection<bool[]> GetSolutions(IElectricalElement root, params Source[] sources); } public class Solver : ISolver { public ICollection<bool[]> GetSolutions(IElectricalElement root, params Source[] sources) { // max value can be got with this count of bits(sources count), also it's count of combinations -1 // for example 8 bits provide 256 combinations, and max value is 255 int maxValue = Pow(sources.Length); // inputs that can solve circuit var rightInputs = new List<bool[]>(); for (int i = 0; i < maxValue; i++) { var inputs = GetBoolArrayFromInt(i, sources.Length); for (int j = 0; j < sources.Length; j++) { sources[j].Value = inputs[j]; } if (root.GetValue()) { rightInputs.Add(inputs); } } return rightInputs; } private static int Pow(int power) { int x = 2; for (int i = 1; i < power; i++) { x *= 2; } return x; } private static bool[] GetBoolArrayFromInt(int value, int length) { var bitArray = new BitArray(new[] {value}); var boolArray = new bool[length]; for (int i = length - 1; i >= 0; i—) { boolArray[i] = bitArray[i]; } return boolArray; } 

समाधान क्रूर बल हैं। इसके लिए, अधिकतम संख्या निर्धारित की जाती है जिसे बिट्स के सेट द्वारा स्रोतों की संख्या के बराबर राशि में व्यक्त किया जा सकता है। यही है, 4 स्रोत = 4 बिट्स = अधिकतम संख्या 15. हम 0 से 15 तक सभी नंबरों के माध्यम से सॉर्ट करते हैं।

ElementsProvider


सामग्री

पीढ़ी की सुविधा के लिए, मैंने प्रत्येक तत्व के लिए एक संख्या निर्धारित करने का निर्णय लिया। ऐसा करने के लिए, मैंने IEPProvider इंटरफ़ेस के साथ ElementsProvider वर्ग बनाया।

 public interface IElementsProvider { IList<Func<IElectricalElement>> Gates { get; } IList<Func<IElectricalElement>> Conductors { get; } IList<ElectricalElementType> GateTypes { get; } IList<ElectricalElementType> ConductorTypes { get; } } public class ElementsProvider : IElementsProvider { public IList<Func<IElectricalElement>> Gates { get; } = new List<Func<IElectricalElement>> { () => new And(), () => new Nand(), () => new Or(), () => new Nor(), () => new Xor(), () => new Xnor() }; public IList<Func<IElectricalElement>> Conductors { get; } = new List<Func<IElectricalElement>> { () => new Conductor(), () => new Not() }; public IList<ElectricalElementType> GateTypes { get; } = new List<ElectricalElementType> { ElectricalElementType.And, ElectricalElementType.Nand, ElectricalElementType.Or, ElectricalElementType.Nor, ElectricalElementType.Xor, ElectricalElementType.Xnor }; public IList<ElectricalElementType> ConductorTypes { get; } = new List<ElectricalElementType> { ElectricalElementType.Conductor, ElectricalElementType.Not }; } 

पहली दो सूचियाँ कारखानों की तरह हैं जो निर्दिष्ट संख्या पर एक आइटम देती हैं। अंतिम दो सूचियाँ एक बैसाखी हैं जिनका उपयोग एकता की विशेषताओं के कारण किया जाना है। इसके बारे में आगे।

CircuitGenerator


सामग्री

अब विकास का सबसे कठिन हिस्सा सर्किट पीढ़ी है।

कार्य उन योजनाओं की एक सूची तैयार करना है जिनसे आप संपादक में अपनी पसंद का चयन कर सकते हैं। केवल साधारण वाल्व के लिए पीढ़ी की आवश्यकता होती है।

योजना के कुछ पैरामीटर सेट किए गए हैं, ये हैं: परतों की संख्या (तत्वों की क्षैतिज रेखाएं) और परत में तत्वों की अधिकतम संख्या। यह निर्धारित करना भी आवश्यक है कि किस गेट से आपको सर्किट उत्पन्न करने की आवश्यकता है।

मेरा दृष्टिकोण कार्य को दो भागों में विभाजित करना था - संरचना निर्माण और विकल्पों का चयन।

संरचना जनरेटर तर्क तत्वों के पदों और कनेक्शन को निर्धारित करता है।
वैरिएंट जनरेटर पदों में तत्वों के मान्य संयोजनों का चयन करता है।

StructureGenerator


संरचना में तर्क तत्वों की परतें और कंडक्टर / इनवर्टर की परतें शामिल हैं। पूरी संरचना में वास्तविक तत्व नहीं हैं, लेकिन उनके लिए कंटेनर हैं।

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

 ElectricalElementContainer : ElectricalElementBase, IElectricalElement 


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



तत्वों की सूची सेट करने की विधि:

 public void SetElements(IList<Func<IElectricalElement>> elements) { Elements = new List<IElectricalElement>(elements.Count); foreach (var item in elements) { Elements.Add(item()); } } 

अगला, आप इस प्रकार टाइप कर सकते हैं:

 public void SetType(int number) { if (isInitialized == false) { throw new InvalidOperationException(UnitializedElementsExceptionMessage); } SelectedType = number; RealElement = Elements[number]; ((ElectricalElementBase) RealElement).Input = Input; } 

जिसके बाद यह निर्दिष्ट आइटम के रूप में काम करेगा।

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

 public class CircuitStructure : ICloneable { public IDictionary<int, ElectricalElementContainer[]> Gates; public IDictionary<int, ElectricalElementContainer[]> Conductors; public Source[] Sources; public And FinalDevice; } 

यहां के शब्द कुंजी में परत संख्या और इस परत के लिए कंटेनरों की एक सरणी संग्रहीत करते हैं। इसके बाद स्रोतों की एक सरणी है और एक फ़ाइनलडेविस है जिसमें सब कुछ जुड़ा हुआ है।

इस प्रकार संरचनात्मक जनरेटर कंटेनर बनाता है और उन्हें एक दूसरे से जोड़ता है। यह सभी परतों में बनाया गया है, नीचे से ऊपर तक। नीचे सबसे चौड़ा (अधिकांश तत्व) है। ऊपर की परत में दो गुना कम तत्व होते हैं और इसी तरह जब तक हम न्यूनतम तक नहीं पहुंच जाते। शीर्ष परत के सभी तत्वों के आउटपुट अंतिम डिवाइस से जुड़े होते हैं।

तर्क तत्व परत में गेट्स के लिए कंटेनर होते हैं। कंडक्टरों की परत में एक इनपुट और आउटपुट के साथ तत्व होते हैं। तत्व या तो एक कंडक्टर या कोई तत्व हो सकते हैं। कंडक्टर आउटपुट के पास जाता है जो इनपुट के लिए आया था, और कोई तत्व आउटपुट पर उल्टे मूल्य को वापस नहीं करता है।

स्रोतों की एक सरणी बनाने के लिए सबसे पहले। पीढ़ी नीचे से होती है, कंडक्टरों की परत पहले उत्पन्न होती है, फिर तर्क की परत, और आउटपुट में फिर से कंडक्टर।



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

ठीक है, "सरलीकृत" कहने के लिए - इसका मतलब है कि आपके जीवन को किसी और चीज़ में उलझा देना।
अधिकतम परिवर्तनशीलता के साथ उत्पन्न सर्किट एक श्रमसाध्य और काफी व्यावहारिक कार्य नहीं निकला। इसलिए, हमारी टीम ने इन मानदंडों को पूरा करने का निर्णय लिया:
इस कार्य के विकास में अधिक समय नहीं लगा।
संशोधित संरचनाओं की कम या ज्यादा पर्याप्त पीढ़ी।
कंडक्टरों के बीच कोई चौराहा नहीं था।
एक लंबी और कठिन प्रोग्रामिंग के परिणामस्वरूप, समाधान शाम 4 बजे लिखा गया था।
कोड पर एक नज़र डालें और the a̶̶̶̶̶̶̶̶।

यहाँ पर OverflowArray क्लास का सामना किया गया है। ऐतिहासिक कारणों से, इसे मूल संरचनात्मक पीढ़ी के बाद जोड़ा गया था और इसका विभिन्न प्रकार की पीढ़ी के साथ संबंध है, इसलिए यह नीचे स्थित है। लिंक

 public IEnumerable<CircuitStructure> GenerateStructure(int lines, int maxElementsInLine, StructureModification modification) { var baseStructure = GenerateStructure(lines, maxElementsInLine); for (int i = 0; i < lines; i++) { int maxValue = 1; int branchingSign = 1; if (modification == StructureModification.All) { maxValue = 2; branchingSign = 2; } int lengthOverflowArray = baseStructure.Gates[(i * 2) + 1].Length; var elementArray = new OverflowArray(lengthOverflowArray, maxValue); double numberOfOption = Math.Pow(2, lengthOverflowArray); for (int k = 1; k < numberOfOption - 1; k++) { elementArray.Increase(); if (modification == StructureModification.Branching || modification == StructureModification.All) { if (!CheckOverflowArrayForAllConnection(elementArray, branchingSign, lengthOverflowArray)) { continue; } } // Clone CircuitStructure var structure = (CircuitStructure) baseStructure.Clone(); ConfigureInputs(lines, structure.Conductors, structure.Gates); var sources = AddSourcesLayer(structure.Conductors, maxElementsInLine); var finalElement = AddFinalElement(structure.Conductors); structure.Sources = sources; structure.FinalDevice = finalElement; int key = (i * 2) + 1; ModifyStructure(structure, elementArray, key, modification); ClearStructure(structure); yield return structure; } } } 

इस कोड को देखने के बाद, मैं समझना चाहूंगा कि इसमें क्या हो रहा है।
चिंता मत करो! बिना विवरण के एक संक्षिप्त विवरण आपको प्रभावित करता है।

पहली चीज जो हम करते हैं वह एक साधारण (आधार) संरचना है।

 var baseStructure = GenerateStructure(lines, maxElementsInLine); 

फिर, एक साधारण जाँच के परिणामस्वरूप, हमने शाखा चिन्ह (ब्रांचिंगसाइन) को उचित मूल्य पर सेट किया है। यह क्यों आवश्यक है? आगे यह स्पष्ट होगा।

 int maxValue = 1; int branchingSign = 1; if (modification == StructureModification.All) { maxValue = 2; branchingSign = 2; } 

अब हम अपने OverflowArray की लंबाई निर्धारित करते हैं और इसे शुरू करते हैं।

  int lengthOverflowArray = baseStructure.Gates[(i * 2) + 1].Length; var elementArray = new OverflowArray(lengthOverflowArray, maxValue); 

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

 int lengthOverflowArray = baseStructure.Gates[(i * 2) + 1].Length; 

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

 elementArray.Increase(); 

उसके बाद, हम एक सत्यापन जांच देखते हैं, जिसके परिणामस्वरूप हम आगे या अगले पुनरावृत्ति पर जाते हैं।

 if (modification == StructureModification.Branching || modification == StructureModification.All) { if (!CheckOverflowArrayForAllConnection(elementArray, branchingSign, lengthOverflowArray)) { continue; } } 

यदि सरणी सत्यापन जांच पास कर गई है, तो हम अपने आधार संरचना को क्लोन करते हैं। क्लोनिंग की आवश्यकता है क्योंकि हम अपनी संरचना को कई और पुनरावृत्तियों के लिए संशोधित करेंगे।

 // Clone CircuitStructure var structure = (CircuitStructure) baseStructure.Clone(); ConfigureInputs(lines, structure.Conductors, structure.Gates); var sources = AddSourcesLayer(structure.Conductors, maxElementsInLine); var finalElement = AddFinalElement(structure.Conductors); structure.Sources = sources; structure.FinalDevice = finalElement; 

और अंत में, हम संरचना को संशोधित करना शुरू करते हैं और इसे अनावश्यक तत्वों से साफ करते हैं। संरचनात्मक संशोधन के परिणामस्वरूप वे अनावश्यक हो गए।

 ModifyStructure(structure, elementArray, key, modification); ClearStructure(structure); 

मुझे उन दर्जनों छोटे कार्यों का विश्लेषण करने के लिए और अधिक विस्तार से बात नहीं दिखती है जो "कहीं न कहीं" गहराई में किए जाते हैं।

VariantsGenerator


संरचना + तत्व जो इसमें होने चाहिए, उन्हें सर्किट वेरिएंट कहा जाता है।

 public struct CircuitVariant { public CircuitStructure Structure; public IDictionary<int, int[]> Gates; public IDictionary<int, int[]> Conductors; public IList<bool[]> Solutions; } 

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

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

अर्थात्, तीन अंकों की एक हेक्साडेसिमल संख्या 3 तत्व होगी। यह केवल विचार करने योग्य है कि तत्वों की संख्या 6 नहीं है, लेकिन 4 प्रेषित की जा सकती है।

इस तरह की संख्या का निर्वहन करने के लिए, मैंने संरचना निर्धारित की


 public struct ClampedInt { public int Value { get => value; set => this.value = Mathf.Clamp(value, 0, MaxValue); } public readonly int MaxValue; private int value; public ClampedInt(int maxValue) { MaxValue = maxValue; value = 0; } public bool TryIncrease() { if (Value + 1 <= MaxValue) { Value++; return false; } // overflow return true; } } 


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

प्रत्येक ClampedInt के अनुसार, संबंधित ElectricalElementContainer के मान सेट किए जाते हैं। इस प्रकार, सभी संभव संयोजनों के माध्यम से छांटना संभव है। यह ध्यान देने योग्य है कि यदि आप तत्वों के साथ एक योजना बनाना चाहते हैं (उदाहरण के लिए, और (0) और Xor (4)), तो आपको 1,2,3 तत्वों सहित सभी विकल्पों के माध्यम से क्रमबद्ध करने की आवश्यकता नहीं है। इसके लिए, पीढ़ी के दौरान, तत्वों को उनके स्थानीय नंबर मिलते हैं (उदाहरण के लिए, और = 0, Xor = 1), और उसके बाद उन्हें वापस वैश्विक संख्या में परिवर्तित किया जाता है।

तो आप सभी तत्वों में सभी संभावित संयोजनों पर पुनरावृति कर सकते हैं।

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

सर्किट उत्पन्न होने के बाद, समाधान की संख्या की जांच की जाती है। यह सीमा से अधिक नहीं होनी चाहिए और इसमें पूरी तरह से 0 या 1 से संबंधित निर्णय नहीं होने चाहिए।

ढेर सारा कोड
  public interface IVariantsGenerator { IEnumerable<CircuitVariant> Generate(IEnumerable<CircuitStructure> structures, ICollection<int> availableGates, bool useNot, int maxSolutions = int.MaxValue); } public class VariantsGenerator : IVariantsGenerator { private readonly ISolver solver; private readonly IElementsProvider elementsProvider; public VariantsGenerator(ISolver solver, IElementsProvider elementsProvider) { this.solver = solver; this.elementsProvider = elementsProvider; } public IEnumerable<CircuitVariant> Generate(IEnumerable<CircuitStructure> structures, ICollection<int> availableGates, bool useNot, int maxSolutions = int.MaxValue) { bool manyGates = availableGates.Count > 1; var availableLeToGeneralNumber = GetDictionaryFromAllowedElements(elementsProvider.Gates, availableGates); var gatesList = GetElementsList(availableLeToGeneralNumber, elementsProvider.Gates); var availableConductorToGeneralNumber = useNot ? GetDictionaryFromAllowedElements(elementsProvider.Conductors, new[] {0, 1}) : GetDictionaryFromAllowedElements(elementsProvider.Conductors, new[] {0}); var conductorsList = GetElementsList(availableConductorToGeneralNumber, elementsProvider.Conductors); foreach (var structure in structures) { InitializeCircuitStructure(structure, gatesList, conductorsList); var gates = GetListFromLayersDictionary(structure.Gates); var conductors = GetListFromLayersDictionary(structure.Conductors); var gatesArray = new OverflowArray(gates.Count, availableGates.Count - 1); var conductorsArray = new OverflowArray(conductors.Count, useNot ? 1 : 0); do { if (useNot && conductorsArray.EqualInts) { continue; } SetContainerValuesAccordingToArray(conductors, conductorsArray); do { if (manyGates && gatesArray.Length > 1 && gatesArray.EqualInts) { continue; } SetContainerValuesAccordingToArray(gates, gatesArray); var solutions = solver.GetSolutions(structure.FinalDevice, structure.Sources); if (solutions.Any() && solutions.Count <= maxSolutions && !(solutions.Any(s => s.All(b => b)) || solutions.Any(s => s.All(b => !b)))) { var variant = new CircuitVariant { Conductors = GetElementsNumberFromLayers(structure.Conductors, availableConductorToGeneralNumber), Gates = GetElementsNumberFromLayers(structure.Gates, availableLeToGeneralNumber), Solutions = solutions, Structure = structure }; yield return variant; } } while (!gatesArray.Increase()); } while (useNot && !conductorsArray.Increase()); } } private static void InitializeCircuitStructure(CircuitStructure structure, IList<Func<IElectricalElement>> gates, IList<Func<IElectricalElement>> conductors) { var lElements = GetListFromLayersDictionary(structure.Gates); foreach (var item in lElements) { item.SetElements(gates); } var cElements = GetListFromLayersDictionary(structure.Conductors); foreach (var item in cElements) { item.SetElements(conductors); } } private static IList<Func<IElectricalElement>> GetElementsList(IDictionary<int, int> availableToGeneralGate, IReadOnlyList<Func<IElectricalElement>> elements) { var list = new List<Func<IElectricalElement>>(); foreach (var item in availableToGeneralGate) { list.Add(elements[item.Value]); } return list; } private static IDictionary<int, int> GetDictionaryFromAllowedElements(IReadOnlyCollection<Func<IElectricalElement>> allElements, IEnumerable<int> availableElements) { var enabledDic = new Dictionary<int, bool>(allElements.Count); for (int i = 0; i < allElements.Count; i++) { enabledDic.Add(i, false); } foreach (int item in availableElements) { enabledDic[item] = true; } var availableToGeneralNumber = new Dictionary<int, int>(); int index = 0; foreach (var item in enabledDic) { if (item.Value) { availableToGeneralNumber.Add(index, item.Key); index++; } } return availableToGeneralNumber; } private static void SetContainerValuesAccordingToArray(IReadOnlyList<ElectricalElementContainer> containers, IOverflowArray overflowArray) { for (int i = 0; i < containers.Count; i++) { containers[i].SetType(overflowArray[i].Value); } } private static IReadOnlyList<ElectricalElementContainer> GetListFromLayersDictionary(IDictionary<int, ElectricalElementContainer[]> layers) { var elements = new List<ElectricalElementContainer>(); foreach (var layer in layers) { elements.AddRange(layer.Value); } return elements; } private static IDictionary<int, int[]> GetElementsNumberFromLayers(IDictionary<int, ElectricalElementContainer[]> layers, IDictionary<int, int> elementIdToGlobal = null) { var dic = new Dictionary<int, int[]>(layers.Count); bool convert = elementIdToGlobal != null; foreach (var layer in layers) { var values = new int[layer.Value.Length]; for (int i = 0; i < layer.Value.Length; i++) { if (!convert) { values[i] = layer.Value[i].SelectedType; } else { values[i] = elementIdToGlobal[layer.Value[i].SelectedType]; } } dic.Add(layer.Key, values); } return dic; } } 


पैदावार विवरण का उपयोग करके प्रत्येक जनरेटर एक संस्करण लौटाता है। इस प्रकार, StructureGenerator और VariantsGenerator का उपयोग करने वाले सर्किट जेनरेटर IEnumerable उत्पन्न करता है। (उपज के साथ दृष्टिकोण ने भविष्य में बहुत मदद की, नीचे देखें)

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

खेल की कक्षाएं


विकास दृष्टिकोण और डि


सामग्री

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

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

प्रोजेक्ट में DI कंटेनर के रूप में, Zenject का उपयोग किया जाता है।

Zenject के कई संदर्भ हैं, मैं उनमें से केवल दो का उपयोग करता हूं:

  • परियोजना का संदर्भ - पूरे आवेदन के भीतर निर्भरता का पंजीकरण।
  • दृश्य संदर्भ: उन कक्षाओं का पंजीकरण जो केवल एक विशेष दृश्य में मौजूद हैं और उनका जीवनकाल दृश्य के जीवन काल तक सीमित है।
  • एक स्थिर संदर्भ सामान्य रूप से सब कुछ के लिए एक सामान्य संदर्भ है, ख़ासियत यह है कि यह संपादक में मौजूद है। मैं संपादक में इंजेक्शन के लिए उपयोग करता हूं

कक्षा का पंजीकरण इंस्टॉलर s में संग्रहित है। मैं प्रोजेक्ट संदर्भ के लिए ScriptableObjectInstaller और दृश्य संदर्भ के लिए MonoInstaller का उपयोग करता हूं।

AsSingle, , . AsTransient .

MonoBehaviour , . Unity Core .



MonoBehaviour . , , MonoBehaviour.

DI , MonoBehaviour . , Start Update , , MonoBehaviour - Start Update. “” , DI .

विन्यास




. , , , .. ScriptableObject':

  1. ScriptableObject
  2. DI
  3. लाभ

 public interface ITags { string FixedColor { get; } string BackgroundColor { get; } string ForegroundColor { get; } string AccentedColor { get; } } [CreateAssetMenu(fileName = nameof(Tags), menuName = "Configuration/" + nameof(Tags))] public class Tags : ScriptableObject, ITags { [SerializeField] private string fixedColor; [SerializeField] private string backgroundColor; [SerializeField] private string foregroundColor; [SerializeField] private string accentedColor; public string FixedColor => fixedColor; public string BackgroundColor => backgroundColor; public string ForegroundColor => foregroundColor; public string AccentedColor => accentedColor; private void OnEnable() { fixedColor.AssertNotEmpty(nameof(fixedColor)); backgroundColor.AssertNotEmpty(nameof(backgroundColor)); foregroundColor.AssertNotEmpty(nameof(foregroundColor)); accentedColor.AssertNotEmpty(nameof(accentedColor)); } } 

( ):

 CreateAssetMenu(fileName = nameof(ConfigurationInstaller), menuName = "Installers/" + nameof(ConfigurationInstaller))] public class ConfigurationInstaller : ScriptableObjectInstaller<ConfigurationInstaller> { [SerializeField] private EditorElementsPrefabs editorElementsPrefabs; [SerializeField] private LevelCompletionSteps levelCompletionSteps; [SerializeField] private CommonValues commonValues; [SerializeField] private AdsConfiguration adsConfiguration; [SerializeField] private CutscenesConfiguration cutscenesConfiguration; [SerializeField] private Colors colors; [SerializeField] private Tags tags; public override void InstallBindings() { Container.Bind<IEditorElementsPrefabs>().FromInstance(editorElementsPrefabs).AsSingle(); Container.Bind<ILevelCompletionSteps>().FromInstance(levelCompletionSteps).AsSingle(); Container.Bind<ICommonValues>().FromInstance(commonValues).AsSingle(); Container.Bind<IAdsConfiguration>().FromInstance(adsConfiguration).AsSingle(); Container.Bind<ICutscenesConfiguration>().FromInstance(cutscenesConfiguration).AsSingle(); Container.Bind<IColors>().FromInstance(colors).AsSingle(); Container.Bind<ITags>().FromInstance(tags).AsSingle(); } private void OnEnable() { editorElementsPrefabs.AssertNotNull(); levelCompletionSteps.AssertNotNull(); commonValues.AssertNotNull(); adsConfiguration.AssertNotNull(); cutscenesConfiguration.AssertNotNull(); colors.AssertNOTNull(); tags.AssertNotNull(); } } 




-

 public interface IElectricalElementMb { GameObject GameObject { get; } string Name { get; set; } IElectricalElement Element { get; set; } IOutputConnectorMb[] OutputConnectorsMb { get; } IInputConnectorMb[] InputConnectorsMb { get; } Transform Transform { get; } void SetInputConnectorsMb(InputConnectorMb[] inputConnectorsMb); void SetOutputConnectorsMb(OutputConnectorMb[] outputConnectorsMb); } [DisallowMultipleComponent] public class ElectricalElementMb : MonoBehaviour, IElectricalElementMb { [SerializeField] private OutputConnectorMb[] outputConnectorsMb; [SerializeField] private InputConnectorMb[] inputConnectorsMb; public Transform Transform => transform; public GameObject GameObject => gameObject; public string Name { get => name; set => name = value; } public virtual IElectricalElement Element { get; set; } public IOutputConnectorMb[] OutputConnectorsMb => outputConnectorsMb; public IInputConnectorMb[] InputConnectorsMb => inputConnectorsMb; } 

  /// <summary> /// Provide additional data to be able to configure it after manual install. /// </summary> public interface IElectricalElementMbEditor : IElectricalElementMb { ElectricalElementType Type { get; } } public class ElectricalElementMbEditor : ElectricalElementMb, IElectricalElementMbEditor { [SerializeField] private ElectricalElementType type; public ElectricalElementType Type => type; } 

 public interface IInputConnectorMb : IConnectorMb { IOutputConnectorMb OutputConnectorMb { get; set; } IInputConnector InputConnector { get; } } 

  public class InputConnectorMb : MonoBehaviour, IInputConnectorMb { [SerializeField] private OutputConnectorMb outputConnectorMb; public Transform Transform => transform; public IOutputConnectorMb OutputConnectorMb { get => outputConnectorMb; set => outputConnectorMb = (OutputConnectorMb) value; } public IInputConnector InputConnector { get; } = new InputConnector(); #if UNITY_EDITOR private void OnDrawGizmos() { if (outputConnectorMb != null) { Handles.DrawLine(transform.position, outputConnectorMb.Transform.position); } } #endif } 

public IElectricalElement Element { get; set; }

?
generic:
public class ElectricalElementMb: MonoBehaviour, IElectricalElementMb where T: IElectricalElement
, Unity generic MonoBehaviour-. , Unity .

, IElectricalElement Element { get; set; }
.

enum ElectricalElementType . Enum Unity . : . , IElectricalElementMb IElectricalElementMbEditor, ElectricalElementType.

. , enum . :

 private static readonly Dictionary<ElectricalElementType, Func<IElectricalElement>> ElementByType = new Dictionary<ElectricalElementType, Func<IElectricalElement>> { {ElectricalElementType.And, () => new And()}, {ElectricalElementType.Or, () => new Or()}, {ElectricalElementType.Xor, () => new Xor()}, {ElectricalElementType.Nand, () => new Nand()}, {ElectricalElementType.Nor, () => new Nor()}, {ElectricalElementType.NOT, () => new NOT()}, {ElectricalElementType.Xnor, () => new Xnor()}, {ElectricalElementType.Source, () => new Source()}, {ElectricalElementType.Conductor, () => new Conductor()}, {ElectricalElementType.Placeholder, () => new AlwaysFalse()}, {ElectricalElementType.Encoder, () => new Encoder()}, {ElectricalElementType.Decoder, () => new Decoder()} }; 

Game Management




, ( , )?.. , .

-, .

DataManager . AsSingle . , . , DataManager.
IFileStoreService , IFileSerializer .

LevelGameManager .
GodObject, UI, , . , . .

. LevelGameManager1 LevelGameManager2 1 2 .

.

दूसरे मामले में, तर्क एक तत्व परिवर्तन घटना के लिए प्रतिक्रिया करता है और सर्किट आउटपुट पर मूल्यों की भी जांच करता है।

कुछ वर्तमान स्तर की जानकारी है जैसे कि स्तर संख्या और खिलाड़ी सहायता।

वर्तमान स्तर के बारे में डेटा CurrentLevelData में संग्रहीत है । एक स्तर संख्या वहां संग्रहीत की जाती है - मदद के लिए चेक के साथ एक बूलियन संपत्ति, खिलाड़ी की मदद करने के लिए खेल और डेटा का मूल्यांकन करने के लिए एक झंडा।

 public interface ICurrentLevelData { int LevelNumber { get; } bool HelpExist { get; } bool ProposeRate { get; } } public interface ICurrentLevelDataMode1 : ICurrentLevelData { IEnumerable<SourcePositionValueHelp> PartialHelp { get; } } public interface ICurrentLevelDataMode2 : ICurrentLevelData { IEnumerable<PlaceTypeHelp> PartialHelp { get; } } 

पहले मोड के लिए मदद स्रोत संख्या और उन पर मान है। दूसरे मोड में, यह उस प्रकार का तत्व है जिसे सेल में सेट करने की आवश्यकता होती है।

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

विभिन्न मोड के दृश्यों के बीच अंतर यह है कि दृश्य के संदर्भ में, एक अन्य LevelGameManager और एक अन्य ICurrentLevelData सेट किया गया है

, -. , . — , . , , . . , .




Unity-, , “Level23”. . , . LevelsManager .




unity , .
Timeline. , Timeline , “ — , ”.



, . , , : .






. . 2 , — 1 . . : ( 3 ) (1 ).

, , . . :



. , Unity, .

— , . , .

, . 1 .

, , .

मुद्रीकरण




: . .

. .

AdsService ,

 public interface IAdsService { bool AdsDisabled { get; } void LoadBetweenLevelAd(); bool ShowBetweenLevelAd(int level, bool force = false); void LoadHelpAd(Action onLoaded = null); void ShowHelpAd(Action onRewarded, Action onClosed); bool HelpAdLoaded { get; } } 

HelpAd — . . , .

, .

Google Mobile Ads Unity Plugin .

— , . Unity. , .



 public interface IPurchaseService { bool IsAdsDisablePurchased { get; } event Action DisableAdsPurchased; void BuyDisableAds(); void RemoveDisableAd(); } 

Unity IAP

. Google Play . , . Complete Pending hasReceipt . true .

. .

RemoveDisableAd , .




- . , , Unity. .

  public abstract class UiElementBase : MonoBehaviour, IUiElement { public event Action ShowClick; public event Action HideCLick; public void Show() { gameObject.SetActive(true); ShowClick?.Invoke(); } public void Hide() { gameObject.SetActive(false); HideCLick?.Invoke(); } } public class PauseMenu : UiElementEscapeClose, IPauseMenu { [SerializeField] private Text levelNumberText; [SerializeField] private LocalizedText finishedText; [SerializeField] private GameObject restartButton; private int levelNumber; public event Action GoToMainMenuClick; public event Action RestartClick; public int LevelNumber { set => levelNumberText.text = $"{finishedText.Value} {value}"; } public void DisableRestartButton() { restartButton.SetActive(false); } public void GoToMainMenu() { GoToMainMenuClick?.Invoke(); } public void Restart() { RestartClick?.Invoke(); } } 

. View, , , .

एनालिटिक्स




Unity . , — . — 100/ .
- AnalyticsService . , Unity. , - .
CustomEvent . . AnalyticsService e .

. ScriptableObject, .

:

 public void LevelComplete(int number, int stars, int actionCount, TimeSpan timeSpent, int levelMode) { CustomEvent(LevelCompleteEventName, new Dictionary<string, object> { {LevelNumber, number}, {LevelStars, stars}, {LevelActionCount, actionCount}, {LevelTimeSpent, timeSpent}, {LevelMode, levelMode} }); } 




FinalDevice , Sources . , , .

CameraAlign . :

  1. FinalDevice

  public class CameraAlign : ICameraAlign { private readonly ISceneObjectsHelper sceneObjectsHelper; private readonly ICommonValues commonValues; public CameraAlign(ISceneObjectsHelper sceneObjectsHelper, ICommonValues commonValues) { this.sceneObjectsHelper = sceneObjectsHelper; this.commonValues = commonValues; } public void Align(Camera camera) { var elements = sceneObjectsHelper.FindObjectsOfType<IElectricalElementMb>(); var finalDevice = sceneObjectsHelper.FindObjectOfType<IFinalDevice>(); var sources = elements.OfType<ISourceMb>().ToArray(); if (finalDevice != null && sources.Length > 0) { float leftPos = elements.Min(s => s.Transform.position.x); float rightPos = elements.Max(s => s.Transform.position.x); float width = Mathf.Abs(leftPos - rightPos); var fPos = finalDevice.Transform.position; float height = Mathf.Abs(sources.First().Transform.position.y - fPos.y) * camera.aspect; float size = Mathf.Max(width * commonValues.CameraOffset, height * commonValues.CameraOffset); camera.orthographicSize = Mathf.Clamp(size, commonValues.MinCameraSize, float.MaxValue); camera.transform.position = GetCenterPoint(elements, -1); fPos = new Vector2(fPos.x, camera.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height)).y - commonValues.FinalDeviceTopOffset * camera.orthographicSize); finalDevice.Transform.position = fPos; float sourceY = camera.ScreenToWorldPoint(Vector2.zero).y + commonValues.SourcesBottomOffset; foreach (var item in sources) { item.Transform.position = new Vector2(item.Transform.position.x, sourceY); } } else { Debug.Log($"{nameof(CameraAlign)}: No final device or no sources in scene"); } } private static Vector3 GetCenterPoint(ICollection<IElectricalElementMb> elements, float z) { float top = elements.Max(e => e.Transform.position.y); float bottom = elements.Min(e => e.Transform.position.y); float left = elements.Min(e => e.Transform.position.x); float right = elements.Max(e => e.Transform.position.x); float x = left + ((right - left) / 2); float y = bottom + ((top - bottom) / 2); return new Vector3(x, y, z); } } 

-.




, .



  public interface IColors { Color ColorAccent { get; } Color Background { get; set; } Color Foreground { get; set; } event Action ColorsChanged; } 

Unity . .

Background Foreground, .

, . , .

: CameraColorAdjustment — , UiColorAdjustmentTextMeshColorAdjustment- स्रोतों पर संख्याओं का रंग सेट करता है। UiColorAdjustment टैग का भी उपयोग करता है। संपादक में, आप प्रत्येक तत्व को एक टैग के साथ चिह्नित कर सकते हैं जो यह संकेत देगा कि यह किस प्रकार का रंग होना चाहिए (पृष्ठभूमि, अग्रभूमि, AccentColor और FixedColor)। यह सभी दृश्य की शुरुआत में या एक रंग योजना परिवर्तन की घटना से निर्धारित होता है।

परिणाम:





संपादक एक्सटेंशन




, . Unity - EditorWindow. UiElements, , .

, - UnityEditor , , . :

  • Assets/Editor
  • #if UNITY_EDITOR

#if UNITY_EDITOR , , .

, , . .



DI . Zenject.StaticContext. InitializeOnLoad , .

 [InitializeOnLoad] public class EditorInstaller { static EditorInstaller() { var container = StaticContext.Container; container.Bind<IElementsProvider>().To<ElementsProvider>().AsSingle(); container.Bind<ISolver>().To<Solver>().AsSingle(); .... } } 

ScriptableObject- :

 BindFirstScriptableObject<ISceneNameConfiguration, SceneNameConfiguration>(container); private static void BindFirstScriptableObject<TInterface, TImplementation>(DiContainer container) where TImplementation : ScriptableObject, TInterface { var obj = GetFirstScriptableObject<TImplementation>(); container.Bind<TInterface>().FromInstance(obj).AsSingle(); } private static T GetFirstScriptableObject<T>() where T : ScriptableObject { var guids = AssetDatabase.FindAssets("t:" + typeof(T).Name); string path = AssetDatabase.GUIDToAssetPath(guids.First()); var obj = AssetDatabase.LoadAssetAtPath<T>(path); return obj; } 

TImplementation AssetDatabase.LoadAssetAtPath(path)

. [Inject] -
StaticContext.Container.Inject(this);

null - . , Unity Awake.

Generator


सामग्री


जनरेटर का प्रारंभिक दृश्य है।

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

विंडो में बाएं से दाएं तीन खंड होते हैं:

  • पीढ़ी सेटिंग्स
  • बटन के रूप में विकल्पों की सूची
  • पाठ के रूप में चयनित विकल्प

कॉलम EditorGUILayout.BeginVertical () और EditorGUILayout.EndVertical () का उपयोग करके बनाए गए हैं। दुर्भाग्य से, यह आकारों को ठीक करने और सीमित करने के लिए काम नहीं करता था, लेकिन यह इतना महत्वपूर्ण नहीं है।

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

तब मुझे लगा कि शायद डिबग मोड में संपादक एक्सटेंशन के सभी कोड काम करते हैं। रिलीज के तहत, डिबग इतनी अच्छी तरह से काम नहीं करता है, ब्रेकप्वाइंट बंद नहीं होते हैं, लाइनें छोड़ दी जाती हैं, आदि। दरअसल, प्रदर्शन को मापने के बाद, यह पता चला कि यूनिटी में जनरेटर की गति कंसोल एप्लिकेशन से लॉन्च की गई डिबग असेंबली से मेल खाती है, जो रिलीज की तुलना में ~ 6 गुना धीमी है। इसे ध्यान में रखें।

Unity DLL , .

Task :
circuitGenerator.Generate(lines, maxElementsInLine, availableLogicalElements, useNOT, modification).ToList()

, . , ( 20 ). , .

 internal static class Ext { public static IEnumerable<CircuitVariant> OrderVariants(this IEnumerable<CircuitVariant> circuitVariants) { return circuitVariants.OrderBy(a => a.Solutions.Count()) .ThenByDescending(a => a.Solutions .Select(b => b.Sum(i => i ? 1 : -1)) .OrderByDescending(b=>b) .First()); } } public interface IEditorGenerator : IDisposable { CircuitVariant[] FilteredVariants { get; } int LastPage { get; } void FilterVariants(int page); void Start(int lines, int maxElementsInLine, ICollection<int> availableGates, bool useNOT, StructureModification? modification, int maxSolutions); void Stop(); void Fetch(); } public class EditorGenerator : IEditorGenerator { private const int PageSize = 100; private readonly ICircuitGenerator circuitGenerator; private ConcurrentBag<CircuitVariant> variants; private List<CircuitVariant> sortedVariants; private Thread generatingThread; public EditorGenerator(ICircuitGenerator circuitGenerator) { this.circuitGenerator = circuitGenerator; } public void Dispose() { generatingThread?.Abort(); } public CircuitVariant[] FilteredVariants { get; private set; } public int LastPage { get; private set; } public void FilterVariants(int page) { CheckVariants(); if (sortedVariants == null) { Fetch(); } FilteredVariants = sortedVariants.Skip(page * PageSize) .Take(PageSize) .ToArray(); int count = sortedVariants.Count; LastPage = count % PageSize == 0 ? (count / PageSize) - 1 : count / PageSize; } public void Fetch() { CheckVariants(); sortedVariants = variants.OrderVariants() .ToList(); } public void Start(int lines, int maxElementsInLine, ICollection<int> availableGates, bool useNOT, StructureModification? modification, int maxSolutions) { if (generatingThread != null) { Stop(); } variants = new ConcurrentBag<CircuitVariant>(); generatingThread = new Thread(() => { var v = circuitGenerator.Generate(lines, maxElementsInLine, availableGates, useNOT, modification, maxSolutions); foreach (var item in v) { variants.Add(item); } }); generatingThread.Start(); } public void Stop() { generatingThread?.Abort(); sortedVariants = null; variants = null; generatingThread = null; FilteredVariants = null; } private void CheckVariants() { if (variants == null) { throw new InvalidOperationException("VariantsGeneration is not started. Use Start before."); } } ~EditorGenerator() { generatingThread.Abort(); } } 


, , . . , . “”: , . 1 1 1 1 1 0 1 1.



, , . , .

Unity , Play , . , . . , , .



 if (Input.Length == 2) { return Input[0].Value && Input[1].Value; } 

.

Solver




, . “”.



“backend”:

 public string GetSourcesLabel() { var sourcesMb = sceneObjectsHelper.FindObjectsOfType<SourceMb>().OrderBy(s => s.name); var sourcesLabelSb = new StringBuilder(); foreach (var item in sourcesMb) { sourcesLabelSb.Append($"{item.name.Replace("Source", "Src")}\t"); } return sourcesLabelSb.ToString(); } public IEnumerable<bool[]> FindSolutions() { var elementsMb = sceneObjectsHelper.FindObjectsOfType<IElectricalElementMbEditor>(); elementsConfigurator.Configure(elementsMb); var root = sceneObjectsHelper.FindObjectOfType<FinalDevice>(); if (root == null) { throw new InvalidOperationException("No final device in scene"); } var sourcesMb = sceneObjectsHelper.FindObjectsOfType<SourceMb>().OrderBy(s => s.name); var sources = sourcesMb.Select(mb => (Source) mb.Element).ToArray(); return solver.GetSolutions(root.Element, sources); } 




AssertHelper



, , , OnEnable

 public static class AssertHelper { public static void AssertType(this IElectricalElementMbEditor elementMbEditor, ElectricalElementType expectedType) { if (elementMbEditor.Type != expectedType) { Debug.LogError($"Field for {expectedType} require element with such type, but given element is {elementMbEditor.Type}"); } } public static void AssertNOTNull<T>(this T obj, string fieldName = "") { if (obj == null) { if (string.IsNullOrEmpty(fieldName)) { fieldName = $"of type {typeof(T).Name}"; } Debug.LogError($"Field {fieldName} is not installed"); } } public static string AssertNOTEmpty(this string str, string fieldName = "") { if (string.IsNullOrWhiteSpace(str)) { Debug.LogError($"Field {fieldName} is not installed"); } return str; } public static string AssertSceneCanBeLoaded(this string name) { if (!Application.CanStreamedLevelBeLoaded(name)) { Debug.LogError($"Scene {name} can't be loaded."); } return name; } } 

, , . Unity.

:

 mainMenuSceneName.AssertNOTEmpty(nameof(mainMenuSceneName)).AssertSceneCanBeLoaded(); levelNamePrefix.AssertNOTEmpty(nameof(levelNamePrefix)); editorElementsPrefabs.AssertNOTNull(); not.AssertType(ElectricalElementType.NOT); //     enum     

SceneObjectsHelper




, SceneObjectsHelper:

 namespace Circuit.Game.Utility { public interface ISceneObjectsHelper { T[] FindObjectsOfType<T>(bool includeDisabled = false) where T : class; T FindObjectOfType<T>(bool includeDisabled = false) where T : class; T Instantiate<T>(T prefab) where T : Object; void DestroyObjectsOfType<T>(bool includeDisabled = false, bool immediate = false) where T : class; void Destroy<T>(T obj, bool immediate = false) where T : Object; void DestroyAllChildren(Transform transform); void Inject(object obj); T GetComponent<T>(GameObject obj) where T : class; } public class SceneObjectsHelper : ISceneObjectsHelper { private readonly DiContainer diContainer; public SceneObjectsHelper(DiContainer diContainer) { this.diContainer = diContainer; } public T GetComponent<T>(GameObject obj) where T : class { return obj.GetComponents<Component>().OfType<T>().FirstOrDefault(); } public T[] FindObjectsOfType<T>(bool includeDisabled = false) where T : class { if (includeDisabled) { return Resources.FindObjectsOfTypeAll(typeof(Object)).OfType<T>().ToArray(); } return Object.FindObjectsOfType<Component>().OfType<T>().ToArray(); } public void DestroyObjectsOfType<T>(bool includeDisabled = false, bool immediate = false) where T : class { var objects = includeDisabled ? Resources.FindObjectsOfTypeAll(typeof(Object)).OfType<T>().ToArray() : Object.FindObjectsOfType<Component>().OfType<T>().ToArray(); foreach (var item in objects) { if (immediate) { Object.DestroyImmediate((item as Component)?.gameObject); } else { Object.Destroy((item as Component)?.gameObject); } } } public void Destroy<T>(T obj, bool immediate = false) where T : Object { if (immediate) { Object.DestroyImmediate(obj); } else { Object.Destroy(obj); } } public void DestroyAllChildren(Transform transform) { int childCount = transform.childCount; for (int i = 0; i < childCount; i++) { Destroy(transform.GetChild(i).gameObject); } } public T FindObjectOfType<T>(bool includeDisabled = false) where T : class { if (includeDisabled) { return Resources.FindObjectsOfTypeAll(typeof(Object)).OfType<T>().FirstOrDefault(); } return Object.FindObjectsOfType<Component>().OfType<T>().FirstOrDefault(); } public void Inject(object obj) { diContainer.Inject(obj); } public T Instantiate<T>(T prefab) where T : Object { var obj = Object.Instantiate(prefab); if (obj is Component) { var components = ((Component) (object) obj).gameObject.GetComponents<Component>(); foreach (var component in components) { Inject(component); } } else { Inject(obj); } return obj; } } } 


, . , .

CoroutineStarter




Coroutine MonoBehaviour. CoroutineStarter .

 public interface ICoroutineStarter { void BeginCoroutine(IEnumerator routine); } public class CoroutineStarter : MonoBehaviour, ICoroutineStarter { public void BeginCoroutine(IEnumerator routine) { StartCoroutine(routine); } } 

, . :

 coroutineStarter.When(x => x.BeginCoroutine(Arg.Any<IEnumerator>())).Do(info => { var a = (IEnumerator) info[0]; while (a.MoveNext()) { } }); 

Gizmo




-gizmo, . . :

 private void OnDrawGizmos() { if (outputConnectorMb != null) { Handles.DrawLine(transform.position, outputConnectorMb.Transform.position); } } 



परीक्षण




, .

Unit- mock- . NSubstitute . .

Unity NuGet, DLL, , AssemblyDefinition .



Unity TestRunner, NUnit . TestRunner :

  • EditMode — , . Nunit . , . GameObject Monobehaviour . , EditMode .
  • PlayMode — .
EditMode. , . . Start, Update .

PlayMode , NUnit . PlayMode . Coroutine . IEnumerator/IEnumerable , , , :

 yield return null; 

या

 yield return new WaitForSeconds(1); 

.

UnityTest .
UnitySetUp UnityTearDown .

, , EditMode .

. , , .

unit , Core .
,

 public class ElectricalElementTestsBase<TElement> where TElement : ElectricalElementBase, IElectricalElement, new() { protected TElement element; protected IInputConnector mInput1; protected IInputConnector mInput2; protected IInputConnector mInput3; protected IInputConnector mInput4; [OneTimeSetUp] public void Setup() { element = new TElement(); mInput1 = Substitute.For<IInputConnector>(); mInput2 = Substitute.For<IInputConnector>(); mInput3 = Substitute.For<IInputConnector>(); mInput4 = Substitute.For<IInputConnector>(); } protected void GetValue_3Input(bool input1, bool input2, bool input3, bool expectedOutput) { // arrange mInput1.Value.Returns(input1); mInput2.Value.Returns(input2); mInput3.Value.Returns(input3); element.Input = new[] {mInput1, mInput2, mInput3}; // act bool result = element.GetValue(); // assert Assert.AreEqual(expectedOutput, result); } protected void GetValue_2Input(bool input1, bool input2, bool expectedOutput) { // arrange mInput1.Value.Returns(input1); mInput2.Value.Returns(input2); element.Input = new[] {mInput1, mInput2}; // act bool result = element.GetValue(); // assert Assert.AreEqual(expectedOutput, result); } protected void GetValue_1Input(bool input, bool expectedOutput) { // arrange mInput1.Value.Returns(input); element.Input = new[] {mInput1}; // act bool result = element.GetValue(); // assert Assert.AreEqual(expectedOutput, result); } } 

:

 public class AndTests : ElectricalElementTestsBase<And> { [TestCase(false, false, false)] [TestCase(false, true, false)] [TestCase(true, false, false)] [TestCase(true, true, true)] public new void GetValue_2Input(bool input1, bool input2, bool output) { base.GetValue_2Input(input1, input2, output); } [TestCase(false, false)] [TestCase(true, true)] public new void GetValue_1Input(bool input, bool expectedOutput) { base.GetValue_1Input(input, expectedOutput); } } 

, , 11 .

GameManager-. , . . . , :

 [Test] public void FullHelpAgree_FinishLevel() { // arrange levelGameManager.Start(); helpMenu.ClearReceivedCalls(); dataManager.ClearReceivedCalls(); // act helpMenu.FullHelpClick += Raise.Event<Action>(); fullHelpWindow.Agreed += Raise.Event<Action<bool>>(true); // assert dataManager.Received().SaveGame(); helpMenu.Received().Hide(); } [Test] public void ChangeSource_RootOutBecomeTrue_SavesGameOpensMenu() { // arrange currentLevelData.IsTestLevel.Returns(false); rootOutputMb.OutputConnector.Value.Returns(true); // act levelGameManager.Start(); levelFinishedMenu.ClearReceivedCalls(); dataManager.ClearReceivedCalls(); source.ValueChanged += Raise.Event<Action<bool>>(true); // assert dataManager.Received().SaveGame(); levelFinishedMenu.Received().Show(); } 

, DI . , .

 public class PlacerTests { [Inject] private ICircuitEditorPlacer circuitEditorPlacer; [Inject] private ICircuitGenerator circuitGenerator; [Inject] private IEditorSolver solver; [Inject] private ISceneObjectsHelper sceneObjectsHelper; [TearDown] public void TearDown() { sceneObjectsHelper.DestroyObjectsOfType<IElectricalElementMb>(immediate: true); } [OneTimeSetUp] public void Setup() { var container = StaticContext.Container; container.Inject(this); } [TestCase(1, 2)] [TestCase(2, 2)] [TestCase(3, 4)] public void PlaceSolve_And_NoModifications_AllVariantsSolved(int lines, int elementsInLine) { var variants = circuitGenerator.Generate(lines, elementsInLine, new List<int> {0}, false); foreach (var variant in variants) { circuitEditorPlacer.PlaceCircuit(variant); var solutions = solver.FindSolutions(); CollectionAssert.IsNOTEmpty(solutions); } } [TestCase(1, 2, StructureModification.Branching)] [TestCase(1, 2, StructureModification.ThroughLayer)] [TestCase(1, 2, StructureModification.All)] [TestCase(2, 2, StructureModification.Branching)] [TestCase(2, 2, StructureModification.ThroughLayer)] [TestCase(2, 2, StructureModification.All)] public void PlaceSolve_And_Modifications_AllVariantsSolved(int lines, int elementsInLine, StructureModification modification) { var variants = circuitGenerator.Generate(lines, elementsInLine, new List<int> {0}, false, modification); foreach (var variant in variants) { circuitEditorPlacer.PlaceCircuit(variant); var solutions = solver.FindSolutions(); CollectionAssert.IsNOTEmpty(solutions); } } 

यह परीक्षण सभी निर्भरता के वास्तविक कार्यान्वयन का उपयोग करता है और मंच पर वस्तुओं को भी सेट करता है, जो कि EditMode परीक्षणों में काफी संभव है। यह परीक्षण करना सही है कि इसने उन्हें समझदार बना दिया - मुझे पता नहीं है कि कैसे, इसलिए मैं जांचता हूं कि पोस्ट किए गए सर्किट में समाधान हैं।

एकीकरण में, सर्किटगेंनेटर (स्ट्रक्चनरगेंनेटर + वेरिएंटजेनरेटर) और सॉल्वर के परीक्षण भी हैं

 public class CircuitGeneratorTests { private ICircuitGenerator circuitGenerator; private ISolver solver; [SetUp] public void Setup() { solver = new Solver(); var gates = new List<Func<IElectricalElement>> { () => new And(), () => new Or(), () => new Xor() }; var conductors = new List<Func<IElectricalElement>> { () => new Conductor(), () => new Not() }; var elements = Substitute.For<IElementsProvider>(); elements.Conductors.Returns(conductors); elements.Gates.Returns(gates); var structGenerator = new StructureGenerator(); var variantsGenerator = new VariantsGenerator(solver, elements); circuitGenerator = new CircuitGenerator(structGenerator, variantsGenerator); } [Test] public void Generate_2l_2max_ReturnsVariants() { // act var variants = circuitGenerator.Generate(2, 2, new[] {0, 1, 2}, false).ToArray(); // assert Assert.True(variants.Any()); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Nand)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Nor)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Xnor)); AssertLayersNotContains(variants.First().Structure.Conductors, typeof(Not)); AssertLayersContains(variants.First().Structure.Gates, typeof(Or)); AssertLayersContains(variants.First().Structure.Gates, typeof(Xor)); AssertLayersContains(variants.First().Structure.Conductors, typeof(Conductor)); } [Test] public void Generate_2l_2max_RestrictedElementsWithConductors() { // arrange var available = new[] {0}; // act var variants = circuitGenerator.Generate(2, 2, available, true).ToArray(); // assert Assert.True(variants.Any()); var lElements = new List<int>(); var layers = variants.Select(v => v.Gates); foreach (var layer in layers) { foreach (var item in layer.Values) { lElements.AddRange(item); } } Assert.True(lElements.Contains(0)); Assert.False(lElements.Contains(1)); Assert.False(lElements.Contains(2)); AssertLayersContains(variants.First().Structure.Gates, typeof(And)); AssertLayersContains(variants.First().Structure.Conductors, typeof(Conductor)); AssertLayersContains(variants.First().Structure.Conductors, typeof(Not)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Nand)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Or)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Nor)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Xnor)); AssertLayersNotContains(variants.First().Structure.Gates, typeof(Xor)); } private static void AssertLayersContains(IDictionary<int, ElectricalElementContainer[]> layers, Type elementType) { AssertLayersContains(layers, elementType, true); } private static void AssertLayersNotContains(IDictionary<int, ElectricalElementContainer[]> layers, Type elementType) { AssertLayersContains(layers, elementType, false); } private static void AssertLayersContains(IDictionary<int, ElectricalElementContainer[]> layers, Type elementType, bool shouldContain) { bool contains = false; foreach (var layer in layers) { foreach (var item in layer.Value) { contains |= item.Elements.Select(e => e.GetType()).Contains(elementType); } } Assert.AreEqual(shouldContain, contains); } } } 


PlayMode , . , .. . , . PlayMode , , , , ( ).

, Unity .

मुझे एक समस्या मिली कि एकता को 2018.3 में अपग्रेड करने के साथ, परीक्षणों ने बहुत धीमी गति से काम करना शुरू कर दिया, 10 गुना तक धीमा (एक सिंथेटिक उदाहरण में)। इस परियोजना में 288 EditMode परीक्षण शामिल हैं, जो 11 सेकंड के लिए चलते हैं, हालांकि इतने लंबे समय तक वहां कुछ भी नहीं किया गया है।

विकास सारांश







. .

DI . , Unity , .

Unity . GameObject mock- Collider, SpriteRenderer, MeshRenderer .. . GetComponent . , .

, . ., , / . DI, , scriptable objects , , , Zenject, , .

Unity , . . . (ToString() “null”) , . . , , — .

, Library.

Google Play . 3, .

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


All Articles