بلدي ماغنوم التأليف من عالم الألعاب المحمولة

مرحبا يا هبر! اليوم ، 26 سبتمبر ، هو عيد ميلادي ، وهذا يعني بالنسبة لي هذا سببًا كبيرًا لنشر مقال حول تتمة لغزازي. أنا أحذرك من أنني هواة ، مما يعني أنه سيكون هناك العديد من الأخطاء في جميع جوانب التنمية (إذا وجدت ، والكتابة ، وسأخذ بعين الاعتبار بكل سرور). في هذه المقالة ، أود أن أخبر كل شيء (جيدًا ، أو كل شيء تقريبًا) عن كيفية صنع التكملة ، وكيف ذهبت إلى هذا وما أتيت إليه.

من أجل عدم الخلط ، أقصد هنا معاني المصطلحات الموجودة في المقالة:
الأصلي هو الجزء الأول ، لعبة مع محرك تحت الأرض للتكنولوجيا التجريبي. يمكنك أن تقرأ عنها هنا .

الجزء الثاني من السلسلة هو اللعبة التي تمت مناقشتها في هذه المقالة.

سأقارن بشكل دوري اللعبة الأصلية مع تتمة للتأكيد على الفرق بين الاثنين.

باختصار عن التنمية

بدأت العمل في اللعبة في أواخر شهر يناير وبنهاية شهر مارس تم إكمال الجزء الفني (شهرين). بعد أن أخذت لعبة أخرى وعدت لمواصلة تطوير هذه اللعبة في منتصف مايو. انتهيت بوضوح في نهاية الصيف وطوال هذا الوقت (3.5 أشهر) ملأت اللعبة بالمحتوى. وكنتيجة لذلك ، أصبحت اللعبة تتمة أسرع مني في اللعبة الأصلية (6 أشهر مقابل 5.5 أشهر).

لقد صنعت لعبة على محرك الوحدة. أرغب في أن يصنع هؤلاء الشباب محركهم الخاص والمضي قدمًا في البرمجة ، لكن حدث خطأ ما وقرر جعل اللعبة على مستوى قياسي ، ولكن تم اختبارها من قِبلي.

بين الأصل و تتمة

جاءت فكرة إنشاء تكملة قبل شهر من إطلاق اللعبة الأصلية (في مكان ما في أغسطس). رؤية الأخطاء التي ارتكبتها ، أردت حذف كل شيء والبدء في العمل مرة أخرى بإنجازات ناجحة. لكنني لم أبدأ في تغيير أي شيء نظرًا لحقيقة وجود الكثير من التعليمات البرمجية للمشكلة ، وكان كل المحتوى جاهزًا ، وقد أخرت التطوير. كان من الضروري أن تذهب إلى إطلاق سراح.

بعد الإصدار ، تعذبت مرة أخرى من فكرة تتمة. هذه المرة لم أبدأ ، لأنني كنت كسولًا من الناحية الأخلاقية ، بعد الإصدار ، كان الأمر هادئًا تمامًا. أردت شيئا جديدا ومثيرا للاهتمام. بدأت التجارب الجماعية.

في الأشهر الثلاثة التالية حاولت تنفيذ أي فكرة أو أي إعداد أو أي مفهوم في الألعاب. فعلت على الرغم من حجم الطموحات ، وصعوبات التنفيذ ، وأحيانًا على الرغم من المنطق والحس السليم. نتيجة لذلك ، حصلت على حوالي 50 مشروعًا. كانوا جميعًا من أنواع مختلفة: من الرماة إلى الاستراتيجيات ، ومن المنصات إلى ألعاب آر بي جي.

لذلك ستستمر التجارب حتى أتعب. لقد سئمت من عدم صنع الألعاب ، لكنني لم أكمل الألعاب التي صنعتها. لقد منحت نفسي هدفًا: أن أقوم ببعض الألعاب على الأقل قبل أسبوع من النهاية. وهكذا ظهرت لعبتي الثانية.

لعبة برو 2
هذه اللعبة بسيطة جدا ومعقدة في نفس الوقت. من الضروري قطع الخطوط وعدم قطع الرسوم البيانية. معنى اللعبة هو أن كل سطر مقطوع مقسوم على 2 ، وظهر رسم بياني في وسطها. كانت ميزة اللعبة أن جميع الهندسة كانت ديناميكية. تم نقل الرسوم البيانية ، وكانت الخطوط مرتبطة دائمًا ببعض الرسوم البيانية.




بعد ذلك ، كنت متحمسًا (أنا متحمس) وعلى استعداد لمشروع جديد. شعرت بزيادة في القوة وما زلت أحمل اللعبة.

فكرة

قبل البدء في القيام بشيء ما ، قررت إلقاء نظرة على النص الأصلي بالكامل. ومروع. من الجودة. إن اللعبة هي الأهم من ذلك كله في إطار الألغاز القياسية: الحاجة إلى فتح المستويات ، وجمع النجوم ، وجهاز ضبط الوقت ، والانتهاء ، ولكن كل هذا تم دون ميزانية وبدون مذاق كبير. الأصلي تفتقر حقا الرسوم المتحركة! رغم أنه كان هناك شيء أصلي فيه ، وشيء ، على الأرجح ، صادق. رغم أنهم حتى هنا تمكنوا من الحصول على أمامي.

لقد وجدت شيء مشابه
اتضح أن هناك لعبة مماثلة للغاية مع اسم متطابق تقريبا. وهي تبدو أكثر تنوعًا في لعبتي. اكتشفت عنها من هذا الفيديو .
بعد أن اكتشفت أن هذه اللعبة عبارة عن أجهزة تلفاز LG ذكية حصرية. تم إنشاؤه بواسطة القسم الروسي من LG R&D Lab في عام 2014:



يتم التحكم فيه بواسطة أسهم "يسار" و "يمين" على جهاز التحكم عن بعد. بنفس الطريقة كما في لعبتي (2 أجزاء من الشاشة). ماذا يمكنني أن أقول ، زاوية الميل هي نفسها - 30 درجة. من الناحية الفنية البحتة ، يمكن القول أن لعبتي منتحلة بسبب هذا. على الرغم من أنني اكتشفت عنها حوالي 2 أشهر بعد إصدار اللعبة الأولى.




من خلال فهم الموقف الأصلي المؤسف ، قررت إحياء اللعبة من خلال تغييرات جذرية لجعلها أفضل. ثم طار الخيال: فليكن هناك مؤامرة وسيكون هناك خيار فيها ، سيكون هناك زعماء بهجمات مدروسة ، سيكون هناك إنتاج يفتقر إليه الأصلي ، سيكون هناك إحساس بمغامرة واحدة مكتملة ، إلخ. بشكل عام ، كل أفضل الأفكار التي جاءت لي خلال الوقت بين الأصل والتكملة. وكل ما لم ينجح أو كان يعمل بشكل سيء كان مقدرًا لي أن أتخلص من التتمة.

التجريبي الأول

بدأ كل شيء بالطبع معه. قررت: "إذا قمت بحل المشكلات ، فقم بذلك تمامًا." وكانت الضحية الأولى لمثل هذه التغييرات هي الإدارة. يمكن أن أسرقها من لعبة مماثلة (انظر أعلاه). هذه بالضبط هي الإدارة التي أردت في الأصل ، لكن لم أكن أعرف كيفية القيام بذلك. سيكون الملحق بسيطًا جدًا: ما عليك سوى إضافة رسوم متحركة للتناوب في كل مرة تنقر فيها. ولكن هذا لم يكن بالنسبة لي. على الأقل حتى يتم إدراك التحكم كما هو الحال في لعبة مشابهة ، كان من الضروري إنشاء نفس الكاميرا الثابتة وتقليل المستويات بوضوح مع سرعة اللعبة. لكنني أردت العمل والديناميات والسرعة ، لذلك قمت بتطوير منطقي لعنصر التحكم الأصلي. الآن ، بدلاً من الضغط ودوران درجة معينة ، كان هناك تحامل وتم تحديد درجة الدوران النهائي حسب مدته. بدا بوضوح أفضل مما كانت عليه في الأصل.

كان ذلك لأنني كنت أتحكم عادةً في التحكم ، حيث اختفى الخطأ الرئيسي الأصلي وأصبح الآن من الممكن جعل المستويات التي يتم تحميلها أكثر من مثيلاتها في النسخة الأصلية دون خوف من التأخرات والأفاريز. ثم جاء الجزء التجريبي.

الرسم التجريبي

لم أكن أعرف مطلقًا كيف أرسم رسومات عادية ، واستبدلت دائمًا بالجزء التكنولوجي ، أو بالأحرى تنفيذها العادي. وكانت هذه اللعبة ليست استثناء. بدلاً من العفاريت البسيطة البسيطة ، ظهر ضوء واقعي. كان وهم ضوء 2D. في الواقع ، هذا هو ضوء ثلاثي الأبعاد ، مقابل سطح معدني ، وجميع الأشياء تحتوي على مواد ذات تظليل محدد. تبدو جيدة جدا:



في الاختبارات ، أنتجت 60 إطارًا في الثانية ، ولكن على الهاتف ، حتى في هاتف sony xperia ، كان حجمها حوالي 20 إطارًا في الثانية وغرقت إلى 10 إطارات في الثانية. وركضت إلى سقف الأداء. كان علي أن أذهب بطريقة مختلفة ، طريق التدمير ...

التدمير

في البداية ، بدا لي وكأنه فكرة سيئة بالنسبة لي. لكنني قررت أن تجرب والآن هذا هو عرضي الرئيسي للألعاب. وفقًا للخطة ، أردت مرة أخرى مزيدًا من الواقعية ، أي تدمير الأجزاء المتولدة ، اعتمادًا على اتجاه وقوة التأثير. لكن الخطة استقرت مرة أخرى على السقف ، هذه المرة علمي. اضطررت لتبسيط إلى أبسط.

الآن ، استند التدمير إلى مبدأ أبسط من التشغيل ، أي أنه أنشأ نسخة من نفسه ، فقط من الأجزاء المادية ، وقام الكائن الأصلي بإزالة مكونات SpriteRenderer و Collider2D ، وإذا كان هناك واحد ، تم تعطيل Rigidbody2D.

ولكن السؤال الآخر نشأ - المصادمات. من ناحية ، يمكنك استخدام PolygonCollider2D وعدم تعذبك ، ولكن من ناحية أخرى ، يجب أن تعاني لاحقًا في تصميم اللعبة وتحسينها. لذلك ، كان كل شظايا الكتل المدمرة BoxCollider2D (حتى شظايا الأجسام المستديرة).

أيضا ، تم تقديم مساهمة كبيرة في التحسين من خلال الإعداد الصحيح للمعلمة fixstep للوقت (أصبح يساوي 0.0 (3) أو 30 في الثانية). لكن الآن ، بسرعات عالية ، طار الكائن من خلاله ، وهذا بالتأكيد أثر على تصميم اللعبة.

وصلت هذه العناصر إلى مستوى مقبول ، والآن يمكن أن يكون هناك ما يصل إلى مئات الأشياء المادية على المسرح! بعد الأصل ، كان بالتأكيد طفرة ، ثورة ، إلخ. أدركت أنني كنت أسير في الاتجاه الصحيح ، قررت أن أقوم بإصلاح مشكلة أخرى في اللعبة منذ فترة طويلة: المتشددين المتشددين. من أجل إثارة بطريقة ما اللعبة التي أدليت بها ...

نظام الضرر

بالنسبة لي ، هذا هو الجزء الأكثر غموضًا في التطوير ، والذي تمت إعادة كتابته مرتين. كان العمل مستمرًا. نتيجةً لذلك ، خرج نظام متطور للغاية ، لكنه كان يعمل على نطاق واسع.

ولكن أولاً ، تجدر الإشارة إلى كيفية عمل تصور الضرر هنا. قد يبدو أنه يعمل وفقًا لمبدأ "كلما أصبت بقوة ، كان الضرر أقوى" ، لكن هذا ليس كذلك. في معظم الحالات ، يعمل على مبدأ "كلما طال الاتصال - كلما زاد الضرر" ، حيث تم استبدال مكان شيء مهم مثل "قوة التأثير" بمعامل التلف الذي تم تكوينه يدويًا لكل كائن يتعامل مع الضرر ، تبعًا للموقف. حدث هذا بسبب حقيقة أن وقت إصلاح الحالة الثابتة كان كبيرًا لدرجة أنه تم إنشاء خطأ قوي: لم تتمكن اللعبة من معالجة Enter2D. وهذا خلق حالات مثل: تحطمت بسرعة عالية - لم تتلق الضرر. لماذا لم أصلحها؟ حتى لا أستطيع أن أقول ذلك.

إذن من أين بدأ نظام الضرر؟ من الصحة. يتمتع اللاعب بصحة تساوي 1 (تم زيادته لاحقًا إلى 2). نعم ، هذا لا يزال غير كافٍ ، وفي أول اتصال قوي بالمصيدة سيموت ، لكن على الأقل في السرعة المنخفضة هناك فرصة للبقاء على قيد الحياة (حتى عدة مرات). لم أكن أريد تغيير الأصل. "لكن ماذا سيحدث ضرر للاعب؟" - اعتقدت وخرجت مع الفخاخ الرئيسية.

الفخاخ الرئيسية

يتكون أساس اللغز من الفخاخ ، لكنهم يتناقضون مع اسم اللعبة. من الاسم ، يجب أن تكون اللعبة عبارة عن كرات تقع تحت تأثير الجاذبية. ولكن لم يكن هناك الكثير منهم. بدلا من ذلك ، كان هناك المزيد من الألغاز القياسية.

الرئيسي والأول كان منشارا. لغز بسيط وواضح. لقد تمت كتابته بشكل غير مثالي ، خلال فترة ما بعد الإنتاج ، قمت بإصلاحه.


رأى النصي
using UnityEngine; public class Saw : GlobalFunctions { public AudioClip setClip; private TypePlaying typePlaying = TypePlaying.Sound; private AudioBase audioBase; private float speed = 4f; private Transform tr; private void Awake() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); tr = transform; } private void Update() { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localEulerAngles = new Vector3(0f, 0f, tr.localEulerAngles.z - speed * s); } private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == "Player") { audioBase.SetSound(setClip, 1, 0.2f, typePlaying, false); } } public float GetSpeed() { return speed; } } 


بعد ذلك كان الليزر ، الذي ، كل شيء ، محمّل بشدة. إذا وضعت 40 قطعة على المسرح ، ستبدأ اللعبة في التراجع بشكل كبير. لكنني كنت أرغب أيضًا في إضافة قوانين مادية كاملة للضوء ، أي الانعكاس أو الانكسار. ولكن لم يكن هناك وقت ، لم أكمله. على الرغم من أنني قمت بتحسين بعض الأشياء ، إلا أنها لم تساعد كثيرًا.


سيناريو الليزر
 using UnityEngine; public class Laser : MonoBehaviour { public Vector2 vector2; public bool active = true; public GameObject laserActive; public LineRenderer lr1; public Transform tr; public BoxCollider2D bcl; public Damage dmg; private void Start() { lr1.startColor = lr1.endColor = LaserColor(); } public Color LaserColor() { Color c = new Color(0f, 0f, 0f, 1f); switch (dmg.GetTypeLaser().Type2int()) { case 1: c = new Color(1f, 0f, 0f, 1f); break; case 2: c = new Color(0f, 1f, 0f, 1f); break; case 3: c = new Color(0f, 0f, 0f, 0.4901961f); break; case 4: c = new Color(1f, 0.8823529f, 0f, 1f); break; case 5: c = new Color(0.6078432f, 0.8823529f, 0f, 1f); break; case 6: c = new Color(1f, 0.2745098f, 0f, 1f); break; } return c; } private void Update() { LaserUpdate(); } private void LaserUpdate() { if (active == true) { Vector2[] act1 = Points(tr.position, -tr.up); lr1.SetPosition(0, act1[0]); lr1.SetPosition(1, act1[1]); bcl.size = new Vector2(0.1f, 0.1f); bcl.offset = act1[2]; } return; } private Vector2[] Points(Vector2 start, Vector2 end) { Vector2[] ret = new Vector2[3]; RaycastHit2D hit = Physics2D.Raycast(tr.position, -tr.up, 200f); ret[0] = tr.position; ret[1] = hit.point; vector2 = ret[1]; float distance = Vector2.Distance(tr.position, hit.point); bcl.size = new Vector2(0.1f, 0.1f); if (hit.collider == bcl) { ret[2] = new Vector2(0f, 0.5f); } else { ret[2] = new Vector2(0f, -distance - 0.2f); } return ret; } } 



كانت القنبلة هي الفخ الأخير ، وقبل إضافتها ، أعيد كتابة نظام التلف ، بشكل خاص ، نقل كل ما يتعلق بصحة اللاعب إلى نص HealthBar منفصل (مفيد لأغراض أخرى). بعد ظهور القنبلة ، وتركت فيزياءها الكثير مما هو مرغوب فيه ، في العملية تم الانتهاء من ذلك مرة أخرى. وفي النهاية اتضح مرة أخرى تستحق.


سيناريو الانفجار
 using System.Collections; using UnityEngine; public class Explosion : GlobalFunctions { public float power = 1f; public float radius = 5f; public float health = 20f; public float timeOffsetExplosion = 1f; public GameObject[] contacts = new GameObject[0]; public Animator expAnim; public bool writeContacs = true; public AudioClip setClip; private float timeOffsetExplosionCount; private float alphaTimer; private bool isTimerOn = false; private bool firstAPEvirtual = true; private Collider2D cl; private Rigidbody2D rb; private SpriteRenderer sr; private AudioBase audioBase; private Explosion explosion; private void Awake() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); cl = GetComponent<Collider2D>(); rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); explosion = GetComponent<Explosion>(); } private void Start() { alphaTimer = sr.color.a; StartCoroutineTimerOffsetExplosion(); } private void OnCollisionEnter2D(Collision2D collision) { if (writeContacs == true) { int cont = contacts.Length; GameObject[] n = new GameObject[cont + 1]; if (cont != 0) { for (int i = 0; i < cont; i++) { n[i] = contacts[i]; } } n[cont] = collision.gameObject; contacts = n; } } private void OnCollisionExit2D(Collision2D collision) { if (writeContacs == true) { int cont = contacts.Length; if (cont != 1) { int counter = 0; GameObject[] n = new GameObject[cont - 1]; for (int i = 0; i < cont; i++) { if (contacts[i] != collision.gameObject) { n[counter] = contacts[i]; counter++; } } contacts = n; } else { contacts = new GameObject[0]; } } } public void ActionExplosionEmulation(GameObject obj) { float damage = 0f; if (obj.CompareTag("Laser")) { damage = obj.GetComponent<Damage>().senDamage; } else { damage = obj.GetComponent<Power>().power; } health = health - damage; StartCoroutineTimerOffsetExplosion(); return; } public void StartCoroutineTimerOffsetExplosion() { if (health <= 0f && isTimerOn == false) { isTimerOn = true; timeOffsetExplosionCount = timeOffsetExplosion; StartCoroutine(TimerOffsetExplosion(0.1f)); } } private IEnumerator TimerOffsetExplosion(float timeTick) { yield return new WaitForSeconds(timeTick); timeOffsetExplosionCount = timeOffsetExplosionCount - timeTick; if (timeOffsetExplosionCount > 0f) { float c = timeOffsetExplosionCount / timeOffsetExplosion; sr.color = new Color(1f, c, c, alphaTimer); StartCoroutine(TimerOffsetExplosion(timeTick)); } else { ExplosionAction(); } } private void ExplosionAction() { rb.gravityScale = 0f; rb.velocity = Vector2.zero; audioBase.SetSound(setClip, 2, 1f, TypePlaying.Sound, false); Destroy(cl); CircleCollider2D c = gameObject.AddComponent<CircleCollider2D>(); c.isTrigger = true; c.radius = radius; tag = "Explosion"; if (PlayerPrefs.GetString("graphicsquality") != "high") { Destroy(sr); StartCoroutine(Off()); } else { expAnim.enabled = true; StartCoroutine(Off2High()); } } public IEnumerator Off() { yield return new WaitForSecondsRealtime(0.1f); gameObject.SetActive(false); } public IEnumerator OffHigh(CircleCollider2D c) { yield return new WaitForSecondsRealtime(0.1f); c.enabled = false; } public IEnumerator Off2High() { yield return new WaitForSecondsRealtime(1.5f); gameObject.SetActive(false); } public void APEvirtual() { int cont = contacts.Length; if (cont != 0 && firstAPEvirtual == true) { firstAPEvirtual = false; for (int i = 0; i < cont; i++) { if (contacts[i] != null) { if (contacts[i].GetComponent<PhysicsEmulation>()) { contacts[i].GetComponent<PhysicsEmulation>().ExplosionPhysicsEmulation(explosion); } } } } } public void AnimFull() { sr.color = new Color(1f, 1f, 1f, 1f); sr.size = new Vector2(3f * radius, 3f * radius); return; } } 


بعد الاطلاع على نظام الضرر بالكامل ، قررت إعادة كتابته بالكامل. وهذه المرة ، وضعت شركة Damage جميع أشكال التلف المحتملة في برنامج نصي واحد للتلف ، وصنعت طريقة ActionPhysicsEmulation مماثلة للكتل القابلة للتدمير (في النهاية ، لكل نوع من أنواع التلف الفردية ، تمت كتابة الطريقة المُحسَّنة الخاصة بها). أيضا ، تم تحديد شدة الضرر من خلال شدة "قوة" الكائن (كان البرنامج النصي فقط على اللاعب).

وفي النهاية ، كانت هذه الألغاز 3 فقط قطع فوق الأصلي. لكن هذا لم يكن سببًا للتوقف: لم أنس أيضًا التجربة طوال عملية التطوير. هكذا بدا.

مجال القوة (يعطل الجاذبية ، يبطئ ويقتل ببطء)


سكريبت فيلوسيفيلد
 using UnityEngine; public class VelocityField : GlobalFunctions { public float percent = 10f; public float damage = 0.01f; public float heal = 0.01f; public GameObject[] contacts = new GameObject[0]; private HealthBar hb; private void Awake() { hb = GameObject.FindWithTag("MainCamera").GetComponent<Management>().healthBar; } private void FixedUpdate() { if (contacts.Length != 0) { for (int i = 0; i < contacts.Length; i++) { if (contacts[i] != null) { if (contacts[i].GetComponent<Rigidbody2D>()) { float s = Time.fixedDeltaTime / 0.03f; Vector2 vel = contacts[i].GetComponent<Rigidbody2D>().velocity; contacts[i].GetComponent<Rigidbody2D>().velocity = vel / 100f * (100f - percent * s); } } else { contacts = Remove(contacts, i); } } } } private void OnTriggerEnter2D(Collider2D collision) { if (collision.GetComponent<Rigidbody2D>()) { Rigidbody2D rb2 = collision.GetComponent<Rigidbody2D>(); if (rb2.isKinematic == false) { VelocityInput vi = collision.GetComponent<VelocityInput>(); vi.fields = Add(vi.fields, gameObject); rb2.gravityScale = 0f; rb2.freezeRotation = true; vi.inVelocityField = true; if (collision.GetComponent<Destroy>()) { collision.GetComponent<Destroy>().ActiveTimerDeleteChange(300f); } if (collision.tag == "Player") { hb.StartVFRad(damage); } contacts = Add(contacts, collision.gameObject); } } } public void OnTriggerExit2D(Collider2D collision) { if (collision.GetComponent<Rigidbody2D>()) { Rigidbody2D rb2 = collision.GetComponent<Rigidbody2D>(); if (rb2.isKinematic == false) { VelocityInput vi = collision.GetComponent<VelocityInput>(); vi.fields = Remove(vi.fields, gameObject); if (vi.fields.Length != 0) { rb2.gravityScale = 0f; rb2.freezeRotation = true; vi.inVelocityField = true; } else { rb2.gravityScale = 1f; rb2.freezeRotation = false; vi.inVelocityField = false; } if (collision.GetComponent<Destroy>()) { collision.GetComponent<Destroy>().ActiveTimerDeleteChange(60f); } if (collision.tag == "Player") { hb.EndVFRad(heal); } contacts = Remove(contacts, collision.gameObject); } } } } 


ستومب (قتل اللاعبين وسحقهم)

الصرع النصي
 using UnityEngine; public class TrampAnim : MonoBehaviour { public float speed = 0.1f; public float speedOffset = 0.01f; public float damage = 1f; private float sc; private float maxDis; public Vector3 start; public Vector3 end; public TrampAnim ender; public bool active = true; public bool trigPlayer = false; private AudioSet audioSet; private bool audioAct; private Transform tr; private HealthBar hb; public int count = 0; public void Start() { if (active) { tr = transform; maxDis = Vector2.Distance(start, end); sc = Vector2.Distance(tr.localPosition, start) / maxDis; hb = Camera.main.GetComponent<Management>().healthBar; audioAct = GetComponent<AudioSet>(); if (audioAct) { audioSet = GetComponent<AudioSet>(); } } } public void Update() { if (active) { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); if (count == 0) { tr.localPosition = Vector2.MoveTowards(tr.localPosition, end, (speed * sc + speedOffset) * s); if (tr.localPosition == end) { count = 1; if (trigPlayer && ender.trigPlayer) { hb.Damage(100f, tag, Vector2.zero); } if (audioAct) { audioSet.SetMusic(); } } } else { tr.localPosition = Vector2.MoveTowards(tr.localPosition, start, (speed * sc + speedOffset) * s); if (tr.localPosition == start) { count = 0; } } sc = Vector2.Distance(tr.localPosition, start) / maxDis; } } public void OnCollisionEnter2D(Collision2D collision) { Transform trans = collision.transform; string tag = trans.tag; if (tag == "Player") { trigPlayer = true; } else if (active == false) { if (trans.GetComponent<PhysicsEmulation>()) { trans.GetComponent<PhysicsEmulation>().TrampAnimPhysicsEmulation(GetComponent<TrampAnim>()); } } } public void OnCollisionExit2D(Collision2D collision) { string tag = collision.transform.tag; if (tag == "Player") { trigPlayer = false; } } } 


الإشعاع (الذي يقلل ببطء من الصحة)


النصي الإشعاع
 using System.Collections; using UnityEngine; public class Radiation : MonoBehaviour { public bool isActiveRadiation = false; private Management m; private HealthBar hb; private void Awake() { gameObject.SetActive(PlayerPrefs.GetString("ai") == "off"); m = GameObject.FindWithTag("MainCamera").GetComponent<Management>(); hb = m.healthBar; } private void Start() { StartCoroutine(RadiationDamage()); } public IEnumerator RadiationDamage() { yield return new WaitForSeconds(0.0002f); if (isActiveRadiation) { hb.StraightDamage(0.0002f, "Radiation"); } StartCoroutine(RadiationDamage()); } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { isActiveRadiation = true; hb.animator.SetBool("isVisible", true); } } public void OnTriggerExit2D(Collider2D collision) { if (collision.tag == "Player") { isActiveRadiation = false; hb.animator.SetBool("isVisible", false); if (hb.healthBarImage.fillAmount == 0f) { m.StartGraphics(); } } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.tag == "Player") { hb.animator.SetBool("isVisible", false); PlayerPrefs.SetString("ai", "on"); gameObject.SetActive(false); } } } 


فخ (كرة زرقاء تقتل عند لمسها ، وهي إشارة إلى أصعب لعبة في العالم)


البرنامج النصي DeathlessScript
 using UnityEngine; public class DeathlessScript : MonoBehaviour { private HealthBar hb; private void Awake() { hb = Camera.main.GetComponent<Management>().healthBar; } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { hb.Damage(10f, tag, Vector2.zero); } } } 


لم أسجل كل هذه الأنواع من الضرر في البرنامج النصي للضرر ، لكنها عملت بشكل جيد مع العكازات. بعد هذا ، جاء ميكانيكا إضافية في الخط.

ميكانيكا إضافية

كانت مصنوعة متنوعة. كان هناك عدد غير قليل منهم ، بحيث كان كل منهم من الاهتمام وليس بما يكفي لأنها كانت وظيفية للتفاعل مع معظم ميكانيكا اللعبة.

أول هذه الميكانيكا كانت البوابات. أول جدا وأكثر وظيفية للجميع. بالتأكيد مفيدة في جميع المواقع التي كانت هناك حاجة الحواجز الوظيفية. كما أن لديها وظائف إضافية: isActive لتحديد حالة البدء و isState من أجل إصلاح الموضع بعد التنشيط (يتم خلط الأسماء ، ولكن عندما لاحظت أنه كان متأخراً للغاية).


بوابة النصي
 using UnityEngine; using System.Collections; public class Gate : MonoBehaviour { [Header("StartSet")] public Vector2 gateScale = new Vector2(1, 4); public float speed = 0.1f; public bool isReverse = false; public bool isEnd = true; public Vector2 animSetGateScale = new Vector2(); public Vector2 target = new Vector2(); [Header("SpriteEditor")] public Sprite mainSprite; [Header("Assets")] public GameObject door1; public GameObject door2; private IEnumerator fixUpdate; private void Start() { SpriteRenderer ds1 = door1.GetComponent<SpriteRenderer>(); SpriteRenderer ds2 = door2.GetComponent<SpriteRenderer>(); ds1.sprite = mainSprite; ds2.sprite = mainSprite; if (isReverse == false) { animSetGateScale = target = gateScale; } fixUpdate = FixUpdate(); SetGate(animSetGateScale); } private IEnumerator FixUpdate() { yield return new WaitForSeconds(0.03f); if (animSetGateScale != target) { float s = Time.fixedDeltaTime / 0.03f; animSetGateScale = Vector2.MoveTowards(animSetGateScale, target, speed * s); SetGate(animSetGateScale); StartCoroutine(FixUpdate()); } } private void SetGate(Vector2 scale) { SpriteRenderer ds1 = door1.GetComponent<SpriteRenderer>(); SpriteRenderer ds2 = door2.GetComponent<SpriteRenderer>(); Vector2 size = new Vector2(mainSprite.texture.width, mainSprite.texture.height); float k = size.x / size.y; ds1.size = new Vector2(gateScale.x, scale.y / 2f); ds2.size = new Vector2(gateScale.x, scale.y / 2f); BoxCollider2D d1 = door1.GetComponent<BoxCollider2D>(); BoxCollider2D d2 = door2.GetComponent<BoxCollider2D>(); d1.size = new Vector2(gateScale.x, scale.y / 2f); d2.size = new Vector2(gateScale.x, scale.y / 2f); door1.transform.localScale = new Vector3(1f, 1f, 1f); door2.transform.localScale = new Vector3(1f, 1f, 1f); door1.transform.localPosition = new Vector3(0f, (gateScale.y / 2f) - (scale.y / 4f), 0f); door2.transform.localPosition = new Vector3(0f, -(gateScale.y / 2f) + (scale.y / 4f), 0f); } public void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player")) { if (isReverse == false) { target = Vector2.zero; } else { target = gateScale; } StopCoroutine(fixUpdate); fixUpdate = FixUpdate(); StartCoroutine(fixUpdate); } } private void OnTriggerExit2D(Collider2D collision) { if (collision.CompareTag("Player") && isEnd == true) { if (isReverse == false) { target = gateScale; } else { target = Vector2.zero; } StopCoroutine(fixUpdate); fixUpdate = FixUpdate(); StartCoroutine(fixUpdate); } } } 


امتلك وظيفة مماثلة من قبل الأشياء المادية. لا ، هذه ليست أشياء من التدمير ، إنها مجرد أشياء مادية (على الرغم من إمكانية تدميرها أيضًا ، لكنها لم تستخدم هذه الميكانيكا). لا يوجد الكثير منهم في الألغاز ، لكنهم يندمجون جيدًا مع الميكانيكا الأخرى. على سبيل المثال ، مع بوابة: عندما يلمس كائن ما بوابة الزناد ، تفتح البوابة.

منذ أن تعلمت "القوة الخاصة" ، كان هناك ما يصل إلى ثلاثة من الميكانيكيين يسيطرون عليها. كانت هذه مشغلات بنفس الكود للتفاعل مع الكائنات ، لكن كل منها كان يؤدي المهام بطريقته الخاصة. الأول كان مجال قوة (أبطأ الكائن ، وضرب القوة بعامل معين). وأضاف القوة الثانية في اتجاه هذه النقطة وكان نقطة "الجاذبية". تم إجراء المصادفة الثالثة: عندما لم ينجح اللغز المتعلق بخطورة صفرية ، قام هذا البرنامج النصي بحفظه. في ذلك ، يغير الكائن اتجاه القوة ، دون تغييرها بنفسها ، شدتها.


كيف يعمل؟
أولاً ، من خلال نظرية فيثاغورس ، يتم حساب انخفاض التوتر ، وهو معامل المتجه وهو مفيد لاستعادة القوة. ثم يتم حساب الزاوية باستخدام وظيفة Atan2. بعد ذلك ، يتم إضافة offsetAngle إلى الزاوية ويتم إنشاء ناقل جديد بناءً على الجيب وجيب التمام ، والذي يتم ضربه بمعامل ويتم الحصول على اتجاه متغير دون تغيير القوة.
 public Vector2 RotateVector(Vector2 a, float offsetAngle) { float power = Mathf.Sqrt(ax * ax + ay * ay); float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; return Quaternion.Euler(0, 0, angle) * Vector2.up * power; } 


على هذا ، تجف خيالي كله من إضافات. نعم ، كانت هناك أفكار مثل قنبلة على الحبل ، التلفريك ، إلخ. ولكن بعد ذلك جاءت الفكرة العادية: عليك تقديم اللعبة مرة أخرى. ومع ذلك ، سأكون صادقًا مع نفسي: الغالبية العظمى من الناس يلعبون ألعابًا محمولة ، وبالكاد يلعب أي منهم لعبتي إذا كانت اللعبة معقدة بشكل لا يطاق. قررت أن أبدأ مع الألغاز التي قتلت اللاعب بضربة واحدة ، لكنني لا أريد تغيير الضرر بسبب التدمير. ثم جاءت فكرة الميكانيكا الإضافية العادية: التعزيز أو المعدلات.

وفقًا للمفهوم ، فقد قدموا تحسينات مؤقتة مرتبطة ببعض القيم الأساسية. كان هناك 5 معززات: العلاج ، الخلود ، تمدد الوقت (بطئ مو) ، تغيير في الجاذبية وتغيير كتلة اللاعب.

ولكن يبدو وكأنه نوع من المعيار: كرات الزناد المنتشرة في جميع أنحاء المستوى للمساعدة في تمرير. وهكذا أضفت هذه التعزيزات إلى الليزر. غيرت الميكانيكا قليلاً وعملت.


يحتوي الليزر الآن على 5 طرق للتفاعل مع اللاعب: التلف والشفاء ، والخلود ، واتساع الوقت (بطيء مو) ، وتغيير الجاذبية والتغيير في كتلة اللاعب. هذا هو نفس الشيء ، لكن بفارق واحد: يعمل الليزر على المشغل باستمرار وإذا تركت الليزر ، فسيختفي التأثير فورًا (أو بعد فترة). نعم ، التعزيز لديهم نفس الشيء تقريبًا ، لكن الليزر ليس معياريًا (وبالتالي فإن اللعبة بأكملها).

جعل المظهر المادي للعبة من الممكن إنشاء الترامبولين ، والذي يستخدم عادة لتفريق اللاعب ثم تدمير الجدار (على الرغم من أن هذا هو BoxCollider2D بسيط مع PhysicsMaterial ، والذي كان له معلمة ترتد الملتوية لنقاط قوة ترتد مختلفة).

ويتيح لك روعة اللعبة إنشاء نصوصك الخاصة للرسوم المتحركة. قاموا في الأساس بنقل الكائن من نقطة إلى نقطة أو تدوير الكائن. في السابق ، كانت لديهم وظائف أكثر بكثير: القدرة على تحريك (بالنقاط) تدوير كائن ، تغيير المقياس (بالنقاط) ، ملصقات أكثر دقة لبداية ونهاية رسم متحرك لكائن ، إلخ. ولكن نظرًا لحقيقة أن هذه كانت عبارة عن أعمال فظيعة ، والتي في مجمل الإنتاجية المستهلكة المحمومة ، اضطررت إلى قطعها باسم التحسين. يتم استخدام البرنامج النصي للرسوم المتحركة أينما كنت في حاجة لإظهار رسم متحرك بسيط ، لأنه كما قلت: "الأصل كان يفتقر إلى الرسوم المتحركة!" هناك نصان فقط:

BasicAnimation و PointsAnimation.

BasicAnimation Script
 using UnityEngine; using System.Collections; public class BasicAnimation : GlobalFunctions { public AnimationType animationType = AnimationType.Infinity; public float speedSpeed = 0.05f; public float rotation = 0f; private bool make = true; private bool animMake = false; private bool isMoved = false; private Transform tr; private float rotationActive = 0f; public void SetPos(bool pos, float m) { rotationActive = rotation * (pos ? 1 : m); } private void Start() { tr = transform; animMake = false; switch (animationType) { case AnimationType.Infinity: make = true; isMoved = true; rotationActive = rotation; break; case AnimationType.Start: make = false; isMoved = false; break; case AnimationType.End: make = true; isMoved = true; rotationActive = rotation; break; case AnimationType.All: make = false; isMoved = false; break; } } public void TimerAnim(float timer, bool anim) { StartAnim(anim); StartCoroutine(TimerTimerAnim(timer, anim)); } private IEnumerator TimerTimerAnim(float timer, bool anim) { yield return new WaitForSeconds(timer); EndAnim(anim); } public void StartAnim(bool anim) { make = true; if (anim == true) { animMake = true; isMoved = true; } else { rotationActive = rotation; } } public void EndAnim(bool anim) { if (anim == true) { animMake = true; isMoved = false; } else { make = false; rotationActive = 0f; } } private void FixedUpdate() { if (animMake == true) { if (isMoved == true) { if (rotationActive != rotation) { rotationActive = Mathf.MoveTowards(rotationActive, rotation, speedSpeed); } else { animMake = false; isMoved = false; } } else { if (rotationActive != 0f) { rotationActive = Mathf.MoveTowards(rotationActive, 0f, speedSpeed); } else { animMake = false; isMoved = true; } } } } private void Update() { if (make == true) { float rot = tr.localEulerAngles.z; float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localEulerAngles = new Vector3(0f, 0f, rot + rotationActive * s); } } } 

برنامج PointAnimation Script
 using UnityEngine; using System.Collections; public class PointsAnimation : GlobalFunctions { public AnimationType animationType = AnimationType.Infinity; public float speedSpeedPosition = 0.001f; public float speedPosition = 0.1f; public Vector3[] pointsPosition = new Vector3[0]; public int counterPosition = 0; private float speedPositionActive = 0f; private int pointsPositionLength = 0; private bool make = true; private bool animMake = false; private bool isMoved = false; private Transform tr; public void SetPos(bool pos, float m) { speedPositionActive = speedPosition * (pos ? 1 : m); } private void Awake() { pointsPositionLength = pointsPosition.Length; tr = transform; switch (animationType) { case AnimationType.Infinity: make = true; isMoved = true; speedPositionActive = speedPosition; break; case AnimationType.Start: make = false; isMoved = false; break; case AnimationType.End: make = true; isMoved = true; speedPositionActive = speedPosition; break; case AnimationType.All: make = false; isMoved = false; break; } } public void TimerAnim(float timer, bool anim) { StartAnim(anim); StartCoroutine(TimerTimerAnim(timer, anim)); } private IEnumerator TimerTimerAnim(float timer, bool anim) { yield return new WaitForSeconds(timer); EndAnim(anim); } public void StartAnim(bool anim) { make = true; if (anim == true) { animMake = true; isMoved = true; } else { speedPositionActive = speedPosition; } } public void EndAnim(bool anim) { if (anim == true) { animMake = true; isMoved = false; } else { make = false; speedPositionActive = 0f; } } private void FixedUpdate() { if (animMake == true) { if (isMoved == true) { if (speedPositionActive != speedPosition) { Vector2 ends = new Vector2(-speedPosition, speedPosition); speedPositionActive = Mathf.MoveTowards(speedPositionActive, speedPosition, speedSpeedPosition); } else { animMake = false; isMoved = false; } } else { if (speedPositionActive != 0f) { Vector2 ends = new Vector2(-speedPosition, speedPosition); speedPositionActive = Mathf.MoveTowards(speedPositionActive, 0f, speedSpeedPosition); } else { animMake = false; isMoved = true; } } } } private void Update() { if (make) { if (tr.localPosition == pointsPosition[counterPosition]) { counterPosition++; if (counterPosition == pointsPositionLength) { counterPosition = 0; } } else { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localPosition = Vector3.MoveTowards(tr.localPosition, pointsPosition[counterPosition], speedPositionActive * s); } } } } 


UI

بالمقارنة مع الأصل ، وهذا هو تحفة حقيقية.

للمقارنة ، هنا هو الأصلي:


هنا تتمة:


هنا هو الأصلي:


هنا تتمة:


هنا الأصل ... أعتقد أنه واضح. بساطتها في تتمة أحضرت إلى الذهن ، وبدلاً من زر الإيقاف المؤقت الملون بشكل غير مناسب وموقت التداخل بصراحة ، يوجد الآن زر إيقاف مؤقت lokanic ملحوظ بشكل ما في الزاوية اليسرى السفلى. تتمة لا يزال يفوز في القائمة. على عكس النسخة الأصلية ، توجد رسوم متحركة في كل مكان ، والخلفية عبارة عن 11 تظليلًا كتبت عن طريق الخطأ في Shader Graph. تتحسن الوظيفة أيضًا ، فهناك إعدادات رسومات وإعدادات منفصلة للصوت والموسيقى ، وهي وحدة تحكم تسمح لك بتغيير الحفظ - لا يوجد شيء في هذا في القائمة الأصلية.

اتضح أنه جيد جدًا لأنني قررت أن ألقي نظرة على الألعاب الأخرى. هنا وهناك ، بشكل عام ، أخذت (سرقت) الأفضل من كل مكان. وهنا هو ما أخذت خاص:

  1. قائمة التشغيل
    مأخوذة من مغامرة ألتو ، تحولت التجارب فقط إلى السخرية والنكات والتعليقات الساخرة ، إلخ.
  2. وقفة
    أيضًا من Alto ، لكن ليس وظيفيًا ، ولكنه يناسب بشكل أكثر ملاءمة ويلعب بشكل أكثر ملاءمة.
  3. إعدادات
    مأخوذة جزئيًا من Vector 2 ، أي شكل منزلقات القائمة ومستوى الصوت.
    أخذ قليلا بشكل عام ، ولكن على خلاف ذلك فعل كل شيء من تلقاء نفسه.

وحدة التحكم

أولاً ، قم بالحجز حول كيفية عمل الحفظ. هناك نوعان من المتغيرات المسؤولة عن الحفظ العالمي والمحلي: هذه هي أرقام التقدم والمصاعد ، على التوالي. متغير التقدم مسؤول عن الحفظ بين المشاهد ، ومتغير المصاعد مسؤول عن الحفظ داخل المشهد. عندما تضغط على الزر "ابدأ" أو "إعادة التشغيل" ، تنقل اللعبة التقدم إلى المشهد وتفرز اللاعب عند الحفظ تحت رقم المصاعد.

تسمح لك وحدة التحكم بتغيير أو إنشاء أي متغير. كانت هذه الأداة البسيطة والقوية مفيدة جدًا بالنسبة لي لاختبار اللعبة وتحديد الأخطاء فيها. وحدة التحكم نفسها هي أمر مكتوب بخط اليد يحاكي لوحات المفاتيح الأخرى.

البرنامج النصي DebugConsole
 using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using System.Collections; public class DebugConsole : MonoBehaviour { public Animator animatorBlackScreen; public Language l; public InputField inputField; public Text textDebug; private bool access = false; public void AnalyzeText() { string txt = inputField.text.ToLower(); string[] output = new string[0]; string txtLoc = ""; for (int i = 0; i < txt.Length; i++) { if (txt[i] == ' ') { if (txtLoc != "") { output = Add(output, txtLoc); txtLoc = ""; } } else { txtLoc = txtLoc + txt[i]; } } if (txtLoc != "") { output = Add(output, txtLoc); txtLoc = ""; } Analyze(output); } public void Analyze(string[] commands) { switch (commands[0]) { case "playerprefs": if (access == true) { if (commands.Length < 2) { Log(l.ConsoleLanguage(1));//1 } else { switch (commands[1]) { case "f": case "float": float f = 0f; if (float.TryParse(commands[3], out f)) { PlayerPrefs.SetFloat(commands[2], float.Parse(commands[3])); Log(l.ConsoleLanguage(2, commands[2]));//2 } else { Log(l.ConsoleLanguage(3));//3 } break; case "i": case "int": int i = 0; if (int.TryParse(commands[3], out i)) { PlayerPrefs.SetInt(commands[2], int.Parse(commands[3])); Log(l.ConsoleLanguage(4, commands[2]));//4 } else { Log(l.ConsoleLanguage(5));//5 } break; case "s": case "string": PlayerPrefs.SetString(commands[2], commands[3]); Log(l.ConsoleLanguage(6, commands[2]));//6 break; case "clear": PlayerPrefs.DeleteAll(); SceneManager.LoadScene(0); break; default: Log(l.ConsoleLanguage(7, commands[1]));//7 break; } } } else { Log(l.ConsoleLanguage(8));//8 } break; case "next": if (access == true) { if (commands.Length > 1) { switch (commands[1]) { case "level": int p = PlayerPrefs.GetInt("progress"); PlayerPrefs.SetInt("progress", p + 1); Log("ok level"); break; case "save": int s = PlayerPrefs.GetInt("elevatorsave"); PlayerPrefs.SetInt("elevatorsave", s + 1); Log("ok save"); break; case "start": PlayerPrefs.SetInt("elevatorsave", 0); Log("ok start"); break; case "end": PlayerPrefs.SetInt("elevatorsave", 1); Log("ok end"); break; } } } else { Log(l.ConsoleLanguage(8));//8 } break; case "echo": if (commands.Length == 1) { Log(l.ConsoleLanguage(9));//9 } else { switch (commands[1]) { case "vertogpro"://echo vertogpro access = true; Log(l.ConsoleLanguage(10));//10 break; default: Log(l.ConsoleLanguage(11));//11 break; } } break; case "restart": if (access == true) { SceneManager.LoadScene(0); } else { Log(l.ConsoleLanguage(12));//12 } break; case "authors": Log(l.ConsoleLanguage(13));//13 break; case "discharge": animatorBlackScreen.SetBool("isActive", true); PlayerPrefs.SetString("start", "key"); PlayerPrefs.SetString("language", "nothing"); PlayerPrefs.SetString("graphicsquality", "medium"); PlayerPrefs.SetFloat("sound", 0.5f); PlayerPrefs.SetFloat("music", 0.5f); PlayerPrefs.SetFloat("rotatenextlevel", 0f); PlayerPrefs.SetInt("elevatorsave", 0); PlayerPrefs.SetInt("progress", 1); PlayerPrefs.SetInt("deaths", 0); PlayerPrefs.SetInt("discharge", PlayerPrefs.GetInt("discharge") + 1); PlayerPrefs.SetInt("lastmenueffect", -1); PlayerPrefs.SetString("isshotmode", "false"); PlayerPrefs.SetString("boss1", "life"); PlayerPrefs.SetString("boss2", "life"); PlayerPrefs.SetString("ai", "off"); PlayerPrefs.SetString("boss3", "life"); PlayerPrefs.SetString("end", "none"); StartCoroutine(StartGame()); break; case "clear": Clear(); break; case "info": if (access == false) { Log(l.ConsoleLanguage(14));//14 } else { Log(l.ConsoleLanguage(15));//15 } break; default: Log(l.ConsoleLanguage(16, commands[0]));//16 break; } } public void Log(object message) { textDebug.text = message.ToString(); } public void Clear() { inputField.text = ""; textDebug.text = ""; } public string[] Add(string[] old, string addComponent) { string[] n = new string[old.Length + 1]; if (old.Length != 0) { for (int i = 0; i < old.Length; i++) { n[i] = old[i]; } } n[old.Length] = addComponent; return n; } public IEnumerator StartGame() { yield return new WaitForSeconds(1f); SceneManager.LoadSceneAsync(0); } } 


وخاصة بالنسبة لك ، سأترك قائمة بالفرق السائلة فيها:

  1. التفريغ - إعادة ضبط تقدم اللعبة (وجميع المعلومات الأخرى أيضًا)
  2. صدى vertogpro - فريق لتوفير الوصول إلى فرق التطوير
  3. playerprefs [type given (string، int، float)] [اسم متغير] [بيانات] - يغير أو ينشئ أي متغير. على سبيل المثال: playerprefs int progress 14
  4. التالي - نوع فرعي للتنقل بمستوى مبسط ، مع أوامره الخاصة:
    • البدء - يحفظ في بداية المستوى (البداية التالية)
    • نهاية - يحفظ في نهاية المستوى (النهاية التالية)
    • الحفظ - النقل عن بعد إلى الحفظ التالي (الحفظ التالي)
    • المستوى - النقل عن بعد إلى المستوى التالي (المستوى التالي)

الرسومات

خلال العام لم أكن أتعلم كيفية الرسم ، لذلك فعلت نفس الشيء تقريبًا كما في النسخة الأصلية: لقد قمت بتنزيل حوالي 30 حزمة نسيجية لمايكرافت ، واخترت الأفضل من كل حزمة ومن ثم فقد تم إيقاف الرسومات الرئيسية. لم تختلف الرسومات كثيرًا عن الأصل وأغضبتني ، أغضبتني كثيرًا لدرجة أنني ما زلت أجد العديد من المؤثرات المتحركة (الانفجارات ، النار ، إلخ) وضخمت حزم مواد متنوعة من متجر الأصول. حتى بالنسبة للألعاب المحمولة ، فإن الرسومات سيئة للغاية ، على الرغم من التقدم الذي لا يزال قيد الملاحظة. هنا هو الأصلي:


وهنا تتمة:


الحفظ

إذا كان مبدأ الحفظ بسيطًا ، فإن تطبيقه ليس غاية. يتكون نظام الحفظ من 3 نصوص:

  1. ElevatorBase هو الأساس الذي تحدث فيه فرق بدء التشغيل. في ذلك ، بواسطة متغير elevatorsave ، يتم تحديد الحفظ النشط من صفيف الحفظ.

    Script ElevatorBase
     using UnityEngine; using System.Collections; public class ElevatorBase : MonoBehaviour { public GameObject[] savers = new GameObject[0]; public float inputStartBlock = 1f; private GameUI gameUI; public void Awake() { int l = savers.Length; if (l != 0) { for (int i = 0; i < l; i++) { if (savers[i] != null) { if (savers[i].GetComponent<Saving>()) { Saving saving = savers[i].GetComponent<Saving>(); saving.isFirst = false; saving.idElevatorBase = i; } else if (savers[i].GetComponent<Elevator>()) { savers[i].GetComponent<Elevator>().isFirst = false; } } } int es = PlayerPrefs.GetInt("elevatorsave"); if (savers[es] != null) { if (savers[es].GetComponent<Saving>()) { savers[es].GetComponent<Saving>().isFirst = true; } else if (savers[es].GetComponent<Elevator>()) { savers[es].GetComponent<Elevator>().isFirst = true; } } else { gameUI = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); StartCoroutine(BlockEnabled()); GameObject.Find("TipsInput").GetComponent<TipsGamePlayInput>().active = true; } } else { gameUI = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); gameUI.ChangeisBlocked(); } } public IEnumerator BlockEnabled() { yield return new WaitForSeconds(inputStartBlock); GameObject block = gameUI.block.gameObject; block.SetActive(false); } } 

  2. Saving — , , , elevatorsave id.

    Saving
     using System.Collections; using UnityEngine; public class Saving : MonoBehaviour { public Saving[] savings; public Vector2 startPos; public float startRot; public bool isActive = true; public bool isFirst = true; public int idElevatorBase = 0; public TipsGamePlayInput tgpi; private GameObject player; private GameObject cam; private Transform trp; private GameUI gameui; private Management m; private Saving self; private void Start() { self = GetComponent<Saving>(); cam = GameObject.FindWithTag("MainCamera"); m = cam.GetComponent<Management>(); gameui = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); player = m.player; trp = player.GetComponent<Transform>(); if (isFirst) { trp.position = startPos; m.Set(startRot); OfferSaves(); } isActive = !isFirst; tgpi.SetActive(!isFirst); StartCoroutine(BlockFalse()); } public IEnumerator BlockFalse() { yield return new WaitForSeconds(1f); gameui.block.gameObject.SetActive(false); } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player") && isActive == true) { isActive = false; PlayerPrefs.SetInt("elevatorsave", idElevatorBase); OfferSaves(); } } public void OfferSaves() { if (savings.Length != 0) { for (int i = 0; i < savings.Length; i++) { savings[i].isActive = false; savings[i].tgpi.SetActive(false); } } } } 

  3. Elevator — , . : ( ).

    Elevator
     using System.Collections; using UnityEngine; public class Elevator : GlobalFunctions { public Vector2 endPos; public Vector2 startPos; public int nextScene = 1; public int nextElevatorSave = 0; public float speed = 0.1f; public bool isFirst = true; public bool isActive = true; public bool isReverse = false; public bool isMake = false; private GameObject player; private Rigidbody2D rb; private Transform tr; private Transform trp; private GameUI gameui; private AudioBase audioBase; private Transform cam; private void Start() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); gameui = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); player = gameui.m.player; rb = player.GetComponent<Rigidbody2D>(); trp = player.GetComponent<Transform>(); tr = GetComponent<Transform>(); cam = gameui.m.transform; startPos = tr.position; if (isFirst) { trp.position = startPos; rb.velocity = new Vector2(); rb.gravityScale = 0f; gameui.m.Set(); } else { tr.position = endPos; isMake = true; } isActive = isFirst; isReverse = false; } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player") && isMake == true) { isReverse = true; isActive = true; rb.velocity = new Vector2(); rb.gravityScale = 0f; gameui.block.gameObject.SetActive(true); PlayerPrefs.SetInt("elevatorsave", nextElevatorSave); gameui.animatorBlackScreenGame.SetBool("isActive", true); audioBase.LowerSound(0.05f, 16, 0, TypePlaying.Music); StartCoroutine(NumSaveRotate()); StartCoroutine(gameui.StartGame(1.5f, nextScene)); } } private IEnumerator NumSaveRotate() { yield return new WaitForSeconds(1.5f); PlayerPrefs.SetFloat("rotatenextlevel", Stable(cam.localEulerAngles.z, -180f, 180f)); } private void FixedUpdate() { if (isActive == true) { float s = Time.fixedDeltaTime / 0.03f; if (isReverse == false) { rb.velocity = new Vector2(); tr.position = Vector2.MoveTowards(tr.position, endPos, speed * s); trp.position = tr.position; if ((Vector2)tr.position == endPos) { isMake = true; isActive = false; rb.gravityScale = 1f; gameui.block.gameObject.SetActive(false); } } else if (isReverse == true) { tr.position = Vector2.MoveTowards(tr.position, startPos, speed * s); trp.position = tr.position; if (tr.position == (Vector3)startPos) { isActive = false; rb.gravityScale = 1f; } } } } } 

تصميم اللعبة

كانت فوضى حقيقية. كان تصميم اللعبة هو الذي امتد دورة التطوير من 4 إلى 6 أشهر. في المجموع ، تحتوي اللعبة على 34 مستوى: 30 مستوى ، 3 رؤساء ونهائي واحد (مستوى). كل عام قمت به 2-3 أيام ، كل مدرب 2 أسابيع والمستوى النهائي فعل أسبوع. لتحقيق التوازن بين كل شيء ، لقد بنيت لهم مثل هذا: 10 مستويات => 1 مدرب => 10 مستويات => 2 boss => 10 مستويات => 3 boss => المستوى النهائي.

المستويات المحلية هي اعتزازي. فهي غير عادية ومتنوعة وحتى مثيرة للاهتمام قليلاً. تم تصميم المستويات في شكل محدد لخلق إحساس بالعالم المفتوح. لهذا ، قمت برسم خريطة حتى:


الخريطة ليست أفضل رسم ومعلومات ، لكنها قدمت معلومات مهمة عن الأشكال الضرورية للمستويات. في البداية ، كانت الخطط لجعل جميع المستويات على الخريطة ، لكنني لم أفعل تلك المظلمة. بالمناسبة ، هذه خريطة بحجم 1000 × 1000 بكسل ، ومن هذه الخريطة خرج المقياس: كتلة واحدة = 1 بكسل = حجم اللاعب.

بين المستويات ، يمر اللاعب عبر المصعد. يمكن أن تقدم إلى أي مستوى ، وبالتالي فمن الممكن التنقل بين المستويات ، وخلق لاعب مع إحساس أكبر بانفتاح العالم. وأيضًا ، يتم إخفاء المشغلات في بعض الأماكن لتفعيل المصاعد السرية ، والتي يمكنها رفع مستويات 10-15.

للمستويات العادية ، كان هناك خوارزمية البناء:

  1. خلفية من شأنها أن يكون لها شكل وحجم على الخريطة
  2. الجدران الخارجية (سمك ثلاثي بسبب الفيزياء الخاصة)
  3. الجدران داخلية
  4. مستويات أنفسهم
  5. المصاعد ، يحفظ ومشغلات الصوت

الأمر أكثر تعقيدًا مع الرؤساء ، لأن كل مدرب قدم في الوقت نفسه أنماطًا مختلفة ومتشابهة في السلوك. جميع الرؤساء لديهم 100 صحة وكل مستوى لديه شيء لتدمير. من الأفضل التحدث عن كل منهما على حدة:

1 مدرب بسيط للغاية في السلوك: يتحرك بشكل عشوائي في جميع أنحاء الغرفة ، وينتظر 5 ثوان ويتكرر. أن نكون صادقين ، وهذا مثال سيء على رئيسه: بسيط ، متخلف ولا ينسى. ويمكن قتله فقط عن طريق الضرب عليه. ولكن هناك دفاع في شكل 4 مناشير: 3 منها تتحرك بذكاء في جميع أنحاء الغرفة وواحدة تحمي رئيسه عندما يتحرك. بعد الموت ، ينفجر.

النصي BossManagement1
 using UnityEngine; using System.Collections; public class BossManagement1 : GlobalFunctions { public float hp = 100f; public float speed = 0.2f; public bool startActivated = false; public bool activated = false; public bool activatedSaw = false; public bool activatedAngle = false; public bool activatedCoroutine = true; private bool active; private float maxhp; public Vector2 target; public Vector2 targetSaw1; public Vector2 targetSaw2; public Vector2 minBorder; public Vector2 maxBorder; public DeadBoss1 deadBoss; public GameObject backGround; public GameObject healthBar; public Transform tr; public Transform sawMain; public Transform saw1; public Transform saw2; public Arrow arrow; public AudioSet setStart; public AudioSet setEnd; public Transform player; public Power playerPower; private Transform bg, hb; private float targethp = 0f; private Vector2 startMove = new Vector2(-20f, 0f); public void Awake() { maxhp = hp; bg = backGround.transform; hb = healthBar.transform; } public void Start() { if (PlayerPrefs.GetString("boss1") == "death") { Dead(false); } } public void FixedUpdate() { if (startActivated && !activatedCoroutine) { if ((Vector2)tr.position != startMove) { tr.position = Vector2.MoveTowards(tr.position, startMove, speed); saw1.position = Vector2.MoveTowards(saw1.position, startMove, speed); saw2.position = Vector2.MoveTowards(saw2.position, startMove, speed); } else { activatedCoroutine = true; startActivated = false; StartCoroutine(ActivatedOn()); } } if (activated) { if ((Vector2)tr.position != target) { tr.position = Vector2.MoveTowards(tr.position, target, speed); } else { activated = false; sawMain.localScale = new Vector2(0f, 0f); StartCoroutine(TargetRotate()); } } if (activatedSaw) { if ((Vector2)saw1.position != targetSaw1) { saw1.position = Vector2.MoveTowards(saw1.position, targetSaw1, speed); } else { float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); targetSaw1 = new Vector2(x, y); } if ((Vector2)saw2.position != targetSaw2) { saw2.position = Vector2.MoveTowards(saw2.position, targetSaw2, speed); } else { float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); targetSaw2 = new Vector2(x, y); } } if (activatedAngle) { Vector2 dir = player.position - tr.position; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; tr.localEulerAngles = new Vector3(0f, 0f, Mathf.LerpAngle(tr.localEulerAngles.z, angle, 0.1f)); } } public IEnumerator TargetRotate() { yield return new WaitForSeconds(3f + 3f * hp / maxhp); sawMain.localScale = new Vector2(6f, 6f); float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); target = new Vector2(x, y); activated = true; } public IEnumerator ActivatedOn() { yield return new WaitForSeconds(3f); sawMain.localScale = new Vector2(6f, 6f); target = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); targetSaw1 = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); targetSaw2 = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); activatedSaw = true; activated = true; arrow.isActive = true; } public IEnumerator ActivatedCoroutineOff() { yield return new WaitForSeconds(1f); activatedCoroutine = false; activatedAngle = true; } public void Update() { if (active == true) { if (hp != targethp) { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); hp = MoveToward(hp, targethp, speed * s, new Vector2(-0f, maxhp)); } else { active = false; if (targethp == 0f) { Dead(true); } } } UpdateHP(); } public void UpdateHP() { float h = hp / maxhp; bg.localScale = new Vector3(5f, 0.9f, 1f); hb.localScale = new Vector3(4.8f * h, 0.7f, 1f); hb.localPosition = new Vector3(-2.4f + 4.8f * h / 2f, 0f, 0f); } private bool oneTimeMusic = true; public void Damage(float damage) { if (oneTimeMusic == true) { oneTimeMusic = false; deadBoss.StartBoss(); deadBoss.Boom(); setStart.SetMusic(); startActivated = true; StartCoroutine(ActivatedCoroutineOff()); } if (hp != 0f) { targethp = Stable2(hp - damage, 0f, maxhp); speed = speed + damage * 0.02f; active = true; } } public void Dead(bool boom) { active = false; activated = false; activatedSaw = false; startActivated = false; activatedAngle = false; activatedCoroutine = false; backGround.SetActive(false); healthBar.SetActive(false); sawMain.gameObject.SetActive(false); saw1.gameObject.SetActive(false); saw2.gameObject.SetActive(false); setEnd.SetMusic(); arrow.obj.SetActive(false); PlayerPrefs.SetString("boss1", "death"); deadBoss.Dead(tr.position, boom); } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.CompareTag("Player")) { Damage(playerPower.power); } } } 


مدرب 2 هو بالفعل أفضل في الجودة ، ولكن لا يزال بعيدا عن المثالية. نمطه أكثر تعقيدًا: إنه يحدد موقع اللاعب وبعد المنطقة التي يتواجد فيها. بعد أن يختار الرئيس نقطة عشوائية في المنطقة وينتقل إليها. إن دفاعه أكثر أهمية بالفعل: صحة رئيسه لها مراحل وأسلحة مختلفة لكل مرحلة:

  1. 2 مناشير في المسافة
  2. 2 مناشير على مسافة ، عندما يحميها المنشار
  3. 2 ليزر محدود ، محمي بواسطة منشار أثناء الحركة
  4. 2 ليزر ، عندما يحميها المنشار
  5. 2 ليزر ، عندما تتحرك محمية بواسطة منشار و 2 مناشير عن بعد

وأيضًا ، من الجزء الرسومي ، يكون الرئيس الثاني أفضل من الأول: وقت الخمول في شكل استعادة التحمل ونشاط الجهة الخارجية في شكل إلغاء تنشيط أشعة الليزر الحادة إذا تم تنشيط المشغلات في وسط الغرفة.

سيناريو BossManagement2
 using System.Collections; using UnityEngine; public class BossManagement2 : GlobalFunctions { public float hp = 100f; public float speed = 0.5f; public float speedRotate = 0.5f; public int stage = 1; public bool isAlive = true; public bool isActivated = false; public bool isMove = false; public bool isWorkingLaser = true; private float timeStamina = 0f; private float timeRetarget = 0f; public Vector2 region = Vector2.zero; public Vector3 target = Vector3.zero; public GameObject player; public Transform saw; public Transform laser1; public Transform laser2; public Laser laserL1; public Laser laserL2; public Transform laserOffset1; public Transform laserOffset2; public Explosion explosion; public GameObject explosionAsset; public CircleCollider2D trigStart; public BoxCollider2D laserDetected1; public BoxCollider2D laserDetected2; public GameObject saw1; public GameObject saw2; public Transform health; public Transform stamina; public SpriteRenderer srStamina; private Transform pl; private Transform tr; public Transform state; public Laser state1; public Laser state2; public Laser state3; public Laser state4; private Coroutine coroutineStamina; public SpriteRenderer bossBase; public SpriteRenderer laserD1; public SpriteRenderer laserD2; public Gate gateStart; public Gate gateEnd; public GameObject blockWin; public GameObject physicsIn; public GameObject stateLasers; public GameObject expStart; public AudioSet setStart; public AudioClip setEnd; public AudioBase audioBase; public void Awake() { bool isDeath = PlayerPrefs.GetString("boss2") == "death"; blockWin.SetActive(false); if (isDeath) { isAlive = false; gateStart.isReverse = true; gateEnd.isReverse = true; physicsIn.SetActive(false); stateLasers.SetActive(false); expStart.SetActive(false); gameObject.SetActive(false); } else { tr = transform; pl = player.transform; timeStamina = 5.4f / speedRotate / 100f; timeRetarget = 5.4f / speedRotate; saw.localScale = Vector3.zero; stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 0f); saw1.SetActive(false); saw2.SetActive(false); LaserDisable(); LaserBlockEnable(); } } public void Update() { if (isAlive) { if (isActivated == true) { switch (stage) { case 1: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw1.SetActive(true); saw2.SetActive(true); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget1()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 2: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; saw1.SetActive(true); saw2.SetActive(true); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget2()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 3: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget3()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 4: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget4()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 5: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); saw1.SetActive(false); saw2.SetActive(false); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget5()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; } } else { if (trigStart.enabled == false) { isActivated = true; float musicValue = PlayerPrefs.GetFloat("music"); audioBase.UpSound(0.01f, 5, 0, TypePlaying.Music); explosion.health = 0f; explosion.StartCoroutineTimerOffsetExplosion(); RegionDetected(); LaserDisable(); target = Target(); } } } } public void FixedUpdate() { if (!isMove && isActivated) { laserOffset1.localEulerAngles = new Vector3(0f, 0f, laserOffset1.localEulerAngles.z + speedRotate); laserOffset2.localEulerAngles = new Vector3(0f, 0f, laserOffset2.localEulerAngles.z + speedRotate); if (isWorkingLaser) { state.localEulerAngles = new Vector3(0f, 0f, state.localEulerAngles.z + speedRotate); } } } public void RotatePlayer() { Vector2 p = pl.position; float angle = Mathf.Atan2(py, px) * Mathf.Rad2Deg; laserOffset1.localEulerAngles = new Vector3(0f, 0f, angle); laserOffset2.localEulerAngles = new Vector3(0f, 0f, angle - 180f); } private Vector3[] posLasers = new Vector3[] { Vector3.zero, Vector3.zero}; public void TriggerLaserDefect(int id) { switch (id) { case 1: state1.active = false; state1.lr1.SetPositions(posLasers); break; case 2: state2.active = false; state2.lr1.SetPositions(posLasers); break; case 3: state3.active = false; state3.lr1.SetPositions(posLasers); break; case 4: state4.active = false; state4.lr1.SetPositions(posLasers); break; } if (!state1.active && !state2.active && !state3.active && !state4.active) { isWorkingLaser = false; state1.active = false; state2.active = false; state3.active = false; state4.active = false; laserL1.active = false; laserL2.active = false; laser1.localPosition = Vector2.zero; laser2.localPosition = Vector2.zero; } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.tag == "Player") { hp = hp - pl.GetComponent<Power>().power; health.localScale = new Vector2(hp / 50f, hp / 50f); stage = 5 - (int)(hp / 25f); if (stage == 4) { LaserBlockDisable(); } if (hp <= 0f && isAlive == true) { audioBase.LowerSound(0.1f, 50, 0, TypePlaying.Music); audioBase.SetSound(setEnd, 0, 0.8f, TypePlaying.Music, true, 1f); GameObject deadInside = Instantiate(explosionAsset, pl.position, Quaternion.identity); deadInside.GetComponent<Rigidbody2D>().isKinematic = true; deadInside.transform.localScale = new Vector2(2f, 2f); Explosion exp = deadInside.GetComponent<Explosion>(); exp.radius = 2f; exp.health = 0f; exp.timeOffsetExplosion = 3f; exp.StartCoroutineTimerOffsetExplosion(); gateStart.OnTriggerEnter2D(player.GetComponent<Collider2D>()); gateEnd.OnTriggerEnter2D(player.GetComponent<Collider2D>()); PlayerPrefs.SetString("boss2", "death"); blockWin.SetActive(false); gameObject.SetActive(false); } } } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { blockWin.SetActive(true); trigStart.enabled = false; } } public void LaserEnable() { if (isWorkingLaser) { laserL1.active = true; laserL2.active = true; state1.active = false; state2.active = false; state3.active = false; state4.active = false; } laser1.localPosition = new Vector2(0f, -1f); laser2.localPosition = new Vector2(0f, -1f); return; } public void LaserDisable() { if (isWorkingLaser) { state1.active = true; state2.active = true; state3.active = true; state4.active = true; laserL1.active = false; laserL2.active = false; } laser1.localPosition = Vector2.zero; laser2.localPosition = Vector2.zero; return; } public void LaserBlockEnable() { laserDetected1.enabled = true; laserDetected2.enabled = true; } public void LaserBlockDisable() { laserDetected1.enabled = false; laserDetected2.enabled = false; } public void RegionDetected() { Vector2 result = Vector2.zero; Vector2 pos = pl.position; if (pos.x > -45f & pos.x <= -30f) { result.x = 1; } else if (pos.x > -30f & pos.x < -5f) { result.x = 2; } else if (pos.x >= -5f & pos.x <= 5f) { result.x = 3; } else if (pos.x > 5f & pos.x <= 30f) { result.x = 4; } else if (pos.x >= 30f & pos.x < 45f) { result.x = 5; } if (pos.y > -45f & pos.y <= -30f) { result.y = 1; } else if (pos.y > -30f & pos.y < -5f) { result.y = 2; } else if (pos.y >= -5f & pos.y <= 5f) { result.y = 3; } else if (pos.y > 5f & pos.y <= 30f) { result.y = 4; } else if (pos.y >= 30f & pos.y < 45f) { result.y = 5; } region = result; return; } private readonly Vector2[] aroundCloser = new Vector2[] { new Vector2(2, 2), new Vector2(2, 3), new Vector2(2, 4), new Vector2(3, 2), new Vector2(3, 4), new Vector2(4, 2), new Vector2(4, 3), new Vector2(4, 4) }; public Vector2 Target() { Vector2 result = Vector2.zero; if (region == new Vector2(3, 3)) { region = aroundCloser[Random.Range(0, 8)]; } switch (region.x) { case 1: result.x = Random.Range(-45f, -32f); break; case 2: result.x = Random.Range(-29f, -5f); break; case 3: result.x = Random.Range(-5f, 5f); break; case 4: result.x = Random.Range(5f, 29f); break; case 5: result.x = Random.Range(32f, 45f); break; } switch (region.y) { case 1: result.y = Random.Range(-45f, -32f); break; case 2: result.y = Random.Range(-29f, -5f); break; case 3: result.y = Random.Range(-5f, 5f); break; case 4: result.y = Random.Range(5f, 29f); break; case 5: result.y = Random.Range(32f, 45f); break; } isMove = true; return result; } public IEnumerator StaminaAnim(float time, int count) { yield return new WaitForSeconds(time); float sc = hp * (100f - count) / 5000f; stamina.localScale = new Vector2(sc, sc); if (count > 1) { count = count - 1; coroutineStamina = StartCoroutine(StaminaAnim(time, count)); } } public IEnumerator Retarget1() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw1.SetActive(false); saw2.SetActive(false); RegionDetected(); target = Target(); } public IEnumerator Retarget2() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); saw1.SetActive(false); saw2.SetActive(false); RegionDetected(); target = Target(); } public IEnumerator Retarget3() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); LaserDisable(); RegionDetected(); target = Target(); } public IEnumerator Retarget4() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); LaserDisable(); RegionDetected(); target = Target(); } public IEnumerator Retarget5() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); saw1.SetActive(true); saw2.SetActive(true); LaserDisable(); RegionDetected(); target = Target(); } } 


3 مدرب هو الأفضل في الجودة بين الرؤساء! انه يستخدم raycasts للتنقل. أولاً ، يتم تدويرها عشوائياً إلى أي زاوية ، ثم من بين 12 بثًا شعاعياً تم إطلاقها في اتجاهات مختلفة ، فإنها تختار أطولها وتطير إلى نقطة البث. هناك أشياء على المستوى ، بعضها يتم تدميره أيضًا. وكيف رد فعل رئيس raycasts للأشياء؟ تمت إضافة المشغلات إلى الأجسام الساكنة ، والتي يزيد حجمها عن مرتين بنفس الشيء ، بحيث يكون للراديو نقطة لا يمكن أن يطير فيها الرئيس في الهواء ، ولن يكون في الحائط ، ولكن سيتم تثبيته على الحائط. لدى الرئيس دفاع خاص: في بداية المستوى مع الرئيس (كل مدرب مستوى كبير منفصل بدون ألغاز طرف ثالث) هناك محفزات ، ويتم ضبطها بحيث يتم تنشيط واحد فقط.مدرب لديه 5 فراغات فخ وكل المشغل يترك فقط 3-4 الفخاخ النشطة. وكان لديه أيضًا نظام محسن للمناطق ، يتكون من مناطق محددة مسبقًا لكل منطقة (يمكن أن يكون فيها اللاعب) ولكل مصيدة. وأثناء الرحلة ، يقتل اللاعب اللاعب دائمًا.

قائمة المصيدة:

  1. إن الليزر الموجود في الوسط ، والذي يبدأ بعد كل مرة يبدأ فيه المدرب في الطيران ، يبدأ في النظر إلى اللاعب.
  2. 2 ليزر تستخدم وظيفة Lerp للانتقال إلى مناطق محددة (حسب موقع اللاعب) وترسل إلى اللاعب قبل الحركة (يجب أن تكون دائمًا أمام اللاعب ، لكن حدث خطأ ما).
  3. المنشار الذي يذهب دائمًا إلى نفس منطقة اللاعب.
  4. 2 مناشير ، يتم توجيهها دائمًا إلى المناطق اليمنى واليسرى من المنطقة التي يوجد بها اللاعب.
  5. 4 كرات فخ تتحرك بشكل متناظر إلى المركز

النصي BossManagement3
 using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class BossManagement3 : MonoBehaviour { public float health = 100f; public Vector4[] boxs = new Vector4[0]; public int[] saw1Fields = new int[0]; public int[] saw2Fields = new int[0]; public int[] saw3Fields = new int[0]; public int[] laser1Fields = new int[0]; public int[] laser2Fields = new int[0]; public Transform trBoss; public SpriteRenderer srBoss; public BossTracing3 bt; public Transform saw1; public Transform saw2; public Transform saw3; public Transform laser; public Transform laser1; public Transform laser2; public Transform trap1; public Transform trap2; public Transform trap3; public Transform trap4; public LineRenderer lr1; public LineRenderer lr2; public TrailRenderer trail; public GameObject exp; public GameObject terminal1; public GameObject terminal2; public GameObject LaserTarget; public GameObject LaserMover; public GameObject TrapsMover; public GameObject SawMover; public GameObject SawsAroundMover; public Explosion explosion; public SpriteRenderer sr; public CircleCollider2D cc; public Animator animatorEnd; public bool isMove = false; public bool isMoveSaw1 = false; public bool isMoveSaw2 = false; public bool isMoveSaw3 = false; public bool isMoveLaser1 = false; public bool isMoveLaser2 = false; public bool isMoveTraps = false; public int loadScene = 35; public int fieldPlayer = 0; private bool isActive = true; private float maxHealth; private Vector2 target = Vector2.zero; private Vector2 saw1target = Vector2.zero; private Vector2 saw2target = Vector2.zero; private Vector2 saw3target = Vector2.zero; private Vector2 laser1target = Vector2.zero; private Vector2 laser2target = Vector2.zero; private Vector2 traptarget1 = Vector2.zero; private Vector2 traptarget2 = Vector2.zero; private Vector2 traptarget3 = Vector2.zero; private Vector2 traptarget4 = Vector2.zero; private Vector2 border = new Vector2(47f, 44.5f); private Vector2 borderSaw = new Vector2(46f, 43.5f); private Management m; public GameObject p { get; private set; } private HealthBar hb; private Transform tr; private Power ppl; private int lengthBoxs = 0; private bool isLife = true; public void Awake() { isActive = !(PlayerPrefs.GetString("boss1") == "life" && PlayerPrefs.GetString("boss2") == "life"); terminal1.SetActive(!isActive); terminal2.SetActive(isActive); trail.enabled = PlayerPrefs.GetString("graphicsquality") != "low"; m = GameObject.FindWithTag("MainCamera").GetComponent<Management>(); lengthBoxs = boxs.Length; maxHealth = health; hb = m.healthBar; p = m.player; tr = p.transform; ppl = m.ppl; float c = health / maxHealth; srBoss.color = new Color(0f, 0f, c); } public void Start() { if (isActive == false) { return; } StartCoroutine(Mover()); fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw1Fields[fieldPlayer]]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[saw2Fields[fieldPlayer]]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[saw3Fields[fieldPlayer]]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser1Fields[fieldPlayer]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser2Fields[fieldPlayer]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } TrapMover(); StartCoroutine(Laser1AIM()); StartCoroutine(Laser2AIM()); isMoveSaw1 = true; isMoveSaw2 = true; isMoveSaw3 = true; isMoveLaser1 = true; isMoveLaser2 = true; return; } public void SawMover1() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw1Fields[fieldPlayer]]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw1 = true; } public void SawMover2() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw2Fields[fieldPlayer]]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw2 = true; } public void SawMover3() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw3Fields[fieldPlayer]]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw3 = true; } public void LaserMover1() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[laser1Fields[fieldPlayer]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } StartCoroutine(Laser1AIM()); isMoveLaser1 = true; } public void LaserMover2() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[laser2Fields[fieldPlayer]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } StartCoroutine(Laser2AIM()); isMoveLaser2 = true; } public void TrapMover() { traptarget1 = new Vector2(Random.Range(-border.x, border.x), Random.Range(-border.y, border.y)); traptarget2 = new Vector2(-traptarget1.x, -traptarget1.y); traptarget3 = new Vector2(-traptarget1.x, traptarget1.y); traptarget4 = new Vector2(traptarget1.x, -traptarget1.y); isMoveTraps = true; } public IEnumerator Laser1AIM() { yield return new WaitForSeconds(0.5f); Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser1.rotation = Quaternion.Euler(0f, 0f, rot_z); } public IEnumerator Laser2AIM() { yield return new WaitForSeconds(0.5f); Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser2.rotation = Quaternion.Euler(0f, 0f, rot_z); } public IEnumerator Mover() { yield return new WaitForSeconds(7.5f); if (isLife) { Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser.rotation = Quaternion.Euler(0f, 0f, rot_z); target = bt.GetPosRaycast(); isMove = true; } } public void Update() { if (isActive == false) { return; } float s = Time.fixedDeltaTime / (0.03f / Time.timeScale); if (isMove) { trBoss.position = Vector2.MoveTowards(trBoss.position, target, s * 0.5f); if (trBoss.position == (Vector3)target) { isMove = false; if (isLife) { StartCoroutine(Mover()); } } } if (isMoveSaw1) { saw1.position = Vector2.MoveTowards(saw1.position, saw1target, s * 0.1f); if (saw1.position == (Vector3)saw1target) { isMoveSaw1 = false; if (isLife) { SawMover1(); } } } if (isMoveSaw2) { saw2.position = Vector2.MoveTowards(saw2.position, saw2target, s * 0.1f); if (saw2.position == (Vector3)saw2target) { isMoveSaw2 = false; if (isLife) { SawMover2(); } } } if (isMoveSaw3) { saw3.position = Vector2.MoveTowards(saw3.position, saw3target, s * 0.1f); if (saw3.position == (Vector3)saw3target) { isMoveSaw3 = false; if (isLife) { SawMover3(); } } } if (isMoveLaser1) { laser1.position = Vector2.Lerp(laser1.position, laser1target, s * 0.1f); if (laser1.position == (Vector3)laser1target) { isMoveLaser1 = false; if (isLife) { LaserMover1(); } } } if (isMoveLaser2) { laser2.position = Vector2.Lerp(laser2.position, laser2target, s * 0.1f); if (laser2.position == (Vector3)laser2target) { isMoveLaser2 = false; if (isLife) { LaserMover2(); } } } if (isMoveTraps) { trap1.position = Vector2.MoveTowards(trap1.position, traptarget1, s * 0.1f); trap2.position = Vector2.MoveTowards(trap2.position, traptarget2, s * 0.1f); trap3.position = Vector2.MoveTowards(trap3.position, traptarget3, s * 0.1f); trap4.position = Vector2.MoveTowards(trap4.position, traptarget4, s * 0.1f); lr1.SetPosition(0, trap1.position); lr1.SetPosition(1, trap2.position); lr2.SetPosition(0, trap3.position); lr2.SetPosition(1, trap4.position); if (trap1.position == (Vector3)traptarget1) { isMoveTraps = false; if (isLife) { TrapMover(); } } } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject == p) { if (isActive == false) { isActive = true; Start(); } if (isMove == true) { hb.StraightDamage(10f, "Boss3"); } else { health = health - ppl.power; float c = health / maxHealth; srBoss.color = new Color(0f, 0f, c); trail.startColor = srBoss.color; if (health <= 0f) { isLife = false; isMove = false; saw1target = trBoss.position; saw2target = trBoss.position; saw3target = trBoss.position; isMoveSaw1 = true; isMoveSaw2 = true; isMoveSaw3 = true; sr.enabled = false; cc.enabled = false; exp.SetActive(true); explosion.health = 0f; explosion.StartCoroutineTimerOffsetExplosion(); Vector2 diff = trBoss.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser.rotation = Quaternion.Euler(0f, 0f, rot_z); int fieldBoss = bt.BoxPos(trBoss.position); Vector4 r = boxs[laser1Fields[fieldBoss]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser2Fields[fieldBoss]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); StartCoroutine(Ended()); } } } } public void EndedCoroutine() { if (!isActive) { //Debug.Log("End"); isActive = true; StartCoroutine(Ended()); } } public IEnumerator Ended() { yield return new WaitForSeconds(6.5f); if (hb.healthBarImage.fillAmount != 0f) { animatorEnd.SetBool("isActive", true); StartCoroutine(EndedFunction()); } } public IEnumerator EndedFunction() { yield return new WaitForSeconds(1.5f); if (hb.healthBarImage.fillAmount != 0f) { PlayerPrefs.SetInt("progress", 35); SceneManager.LoadSceneAsync(loadScene); } } public void ControlDamagers(bool lt, bool lm, bool tm, bool sm, bool sam) { LaserTarget.SetActive(lt); LaserMover.SetActive(lm); TrapsMover.SetActive(tm); SawMover.SetActive(sm); SawsAroundMover.SetActive(sam); } } 

لا

أستطيع أيضًا كتابة الصوت والموسيقى ، لكن لديّ ما يكفي من الذوق الموسيقي للعثور على الموسيقى المناسبة. في خطتي ، كان من الضروري اختيار مسار لكل مستوى. وبالنسبة للجزء الأكبر ، استوفيت الخطة: التقطت 25 مسارًا. لقد فتشت جميع المسارات في متجر الأصول. أخذت الأصوات للباقي على freesound.org أو مواقع مشابهة.

تم إنشاء الصوت من الجزء الفني وفقًا لمبدأ بسيط: على الكاميرا كان هناك 5 مصدر صوت معطل وبرنامج نصي AudioBase للتحكم في الصوت. في هذا البرنامج النصي ، كانت الوظيفة الرئيسية لـ SetSound مع معلمات مستوى الصوت والحلقة والنوع (الموسيقى أو الصوت) وملف الصوت نفسه. بعد الإشارة ، بدأ الصوت في التشغيل و (إن لم يكن محلقًا) تم تشغيل IEnumerator بوقت مساوٍ لطول المسار وبعد انتهاء صلاحيته ، تم إيقاف تشغيل المكون.

البرنامج النصي AudioBase
 using UnityEngine; using System.Collections; public class AudioBase : GlobalFunctions { public AudioSource[] layerSounds = new AudioSource[0]; public GameObject music; private float musicValue, soundValue; private int lengthLayerSounds = 0; private bool soundActive = true; private Coroutine offsetActive; private int lowerSoundCoroutineCounter = 100; private int upSoundCoroutineCounter = 0; public void Awake() { soundActive = PlayerPrefs.GetString("graphicsquality") != "low"; musicValue = PlayerPrefs.GetFloat("music"); soundValue = PlayerPrefs.GetFloat("sound"); lengthLayerSounds = layerSounds.Length; for (int i = 0; i < lengthLayerSounds; i++) { layerSounds[i].enabled = false; } } public void LowerSound(float timer, int upd, int id, TypePlaying typePlaying) { lowerSoundCoroutineCounter = upd; if (typePlaying == TypePlaying.Music) { StartCoroutine(LowerSoundCoroutine(timer, upd, id, musicValue)); } else { StartCoroutine(LowerSoundCoroutine(timer, upd, id, soundValue)); } } public void UpSound(float timer, int upd, int id, TypePlaying typePlaying) { upSoundCoroutineCounter = 0; if (typePlaying == TypePlaying.Music) { StartCoroutine(UpSoundCoroutine(timer, upd, id, musicValue)); } else { StartCoroutine(UpSoundCoroutine(timer, upd, id, soundValue)); } } public IEnumerator LowerSoundCoroutine(float timer, int upd, int id, float volumeSen) { yield return new WaitForSeconds(timer); layerSounds[id].volume = Stable2((layerSounds[id].volume / volumeSen - timer) * volumeSen, 0f, 1f); if (lowerSoundCoroutineCounter > 1) { StartCoroutine(LowerSoundCoroutine(timer, upd, id, volumeSen)); lowerSoundCoroutineCounter -= 1; } } public IEnumerator UpSoundCoroutine(float timer, int upd, int id, float volumeSen) { yield return new WaitForSeconds(timer); layerSounds[id].volume = Stable2((layerSounds[id].volume / volumeSen + timer) * volumeSen, 0f, 1f); if (upSoundCoroutineCounter < upd) { StartCoroutine(UpSoundCoroutine(timer, upd, id, volumeSen)); upSoundCoroutineCounter += 1; } } public void UpdateSound() { if (soundActive) { float time = Time.timeScale; for (int i = 0; i < lengthLayerSounds; i++) { AudioSource audioSource = layerSounds[i]; if (audioSource.enabled == true) { audioSource.pitch = time; } } } } public void SetSound(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop, float time) { StartCoroutine(SetSoundTime(audioClip, layerSound, volume, typePlaying, loop, time)); } public IEnumerator SetSoundTime(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop, float time) { yield return new WaitForSeconds(time); SetSound(audioClip, layerSound, volume, typePlaying, loop); } public void SetSound(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop) { if (volume == 0f) { return; } if (soundActive) { AudioSource audioSource = layerSounds[layerSound]; audioSource.enabled = true; audioSource.clip = audioClip; audioSource.loop = loop; if (typePlaying == TypePlaying.Sound) { audioSource.volume = soundValue * volume; } else { audioSource.volume = musicValue * volume; } audioSource.Play(); if (offsetActive != null) { StopCoroutine(offsetActive); offsetActive = null; } if (!loop) { offsetActive = StartCoroutine(Offet(layerSound, audioClip.length, audioSource)); } } } public IEnumerator Offet(int layerSound, float length, AudioSource audioSource) { yield return new WaitForSeconds(length); if (audioSource.clip == layerSounds[layerSound].clip) { AudioSource audioSource2 = layerSounds[layerSound]; audioSource2.Stop(); audioSource2.enabled = false; } } } 


أيضًا ، يحتوي مكون Tramp (الختم) على نظام صوت خاص به: عندما يدخل اللاعب ختمًا ، يتم تشغيل المكون المسؤول عن الصوت. وإذا لزم الأمر ، يحدد المنتج المسافة إلى المشغل وبعد حساب المعامل يعطي الحجم الضروري ، نوعًا من إنشاء تأثير صوتي واقعي. لكن هذا لا يعمل كما أريد ، ربما يكون في الكود الخطأ.

المؤامرة

نعم، هذه اللعبة هو القصة. وله ميزتان: إنه غير لفظي تقريبًا ولديه خيار يؤثر على نهاية اللعبة. من الأفضل معرفة التباين (لأنه في الواقع ، هذا التباين هو المخطط بأكمله).

تحتوي اللعبة على 3 خيارات: على أول مدربين وعلى مستوى 32. إن الاختيار مع الرؤساء واضح جدًا: يمكن قتلهم أو لا عن طريق بدء الهجوم أو الوصول إلى المستوى التالي ، على التوالي. وفي المستوى 32 ، يكون الأمر أكثر تعقيدًا: يمكنك تنشيط أحد المشغلات ، مما يعني استيقاظ مرساة إنقاذ القصة المحلية (شخصية تدعى AI). يؤثر اختيار أول اثنين من الرؤساء على ما إذا كانت ستكون هناك معركة مع ثلاثة رؤساء. إذا قتلت واحدًا على الأقل من أول رئيسين ، فستكون هناك معركة مع الرئيس الثالث. إذا لم يكن كذلك ، ثم لا.

لا يوجد سوى 4 نهايات: جيدة ، سيئة ، محايدة وسرية. إنهم يتأثرون بخيارين: تفعيل الذكاء الاصطناعي وقتل 3 رؤساء. سأقوم بتحليل النهايات بالترتيب:

نهاية جيدة
يحدث ذلك إذا لم يتم قتل 3 مدرب وتم تنشيط الذكاء الاصطناعي. يتم إجراء مونولوج AI فيه ، حيث يلمح باستمرار ، وتظهر العين المشتعلة (من تأثيرات اللعبة المختلفة للحريق).


إنهاء النص
شكرا لك
يمكنك إحياء لي
وتمكنت من عدم إيقاظ الحارس على
ما يبدو أنك ستكون حالته الوحيدة الناجحة
أنت تستحق قسطاً قليلاً من الراحة
فزت
أراك

نهاية سيئة
, 3 . («» ), ( ).





-1

نهاية محايدة
, 3 . , , …



,





نهاية سرية
, 3 . , - (!) . ( , )
-
, -
,

, ,

, , , , ...

ولكن لماذا هي مؤامرة غير اللفظية تقريبا؟ لم أستطع جعله غير لفظي تمامًا بسبب النهايات. ولكن هناك ما يكفي من النص في اللعبة. في الواقع ، من أجل شرح للاعب "ل ENT للعبة" ، ظهرت المحطات الطرفية مع الملاحظات في اللعبة ويتم شرح البرنامج النصي للعبة بالتفصيل.

سيناريو

السيناريو في هذه الحالة هو ما قبل التاريخ في العالم ، الذي تم الكشف عنه من وجوه وشخصيات هذه اللعبة في شكل ملاحظات وسجلات وتقارير ومونولوجات وحوارات: في النص العام. وهذا مثل هذا الهذيان الغرافيكي لمبرمج ، حتى أن غلوكوفسكي سوف يفاجأ (ليس لدي أي شيء ضده ، أنا أحب المترو). لسوء الحظ ، لم يكن لدي الكثير من الوقت لإنشاء المجلس الوطنى الكامل. على الرغم من العفاريت بالنسبة لهم في اللعبة وجدت:


مرة أخرى ، نظرًا للظروف ، تم كتابة البرنامج النصي أخيرًا ، وقد توصلت إليه بالفعل بمخطط أكملت اللعبة وفقًا له. كتب لمدة 4 أسابيع كل يوم من أيام الأسبوع ، في حافلة صغيرة ، عندما ذهبت إلى العمل الطبيعي. وحتى في مثل هذا الوقت القصير يمكنني كتابة الكثير.

إذا كان أي شيء ، اللعبة الأصلية لا يوجد لديه مؤامرة وليس تلميحات منه. والآن ، ليس من المنطقي أن أخفي هذه المؤامرة (بعد كل شيء ، لا أحد سيمر اللعبة بالكامل ويقرأ جميع الملاحظات). هناك ثلاثة أهداف لهذه الرسوم البيانية: أضف تنوعًا معقولًا في تصرفات اللاعب ، وشرح أشياء اللعبة التي لا يمكن تفسيرها ، وعلى الأقل اهتمام اللاعب بلعبته على الأقل.

كتبت السيناريو بطريقة بسيطة للغاية: أولاً كتبت في قصة لمدة 40-50 جمل في 2-3 أسابيع. بعد ذلك ، بالنسبة لكل ملاحظة ، اخترت وفقًا للجملة ، وعلى أساس جملة واحدة ، أضفت 2-3 جمل إلى الملاحظة ، وقمت بتغييرها إلى مونولوج (أو أشكال أخرى من السرد) وتلقيت ملاحظات متوازنة جاهزة. نتيجة لذلك ، من هذا الاستقبال في جميع الملاحظات ، تم تجميع ما مجموعه 160 جملة مع المعلومات.

وتحتاج إلى فهم: في لعبتي ، هناك ما يكفي من الأشياء غير المنطقية ، ومن أجل تبرير كل صدق بتنسيق القصة ، هناك حاجة إلى الكثير من النص. لذلك ، حاولت عدم سكب الماء ، وحاولت كل جملة أن تملأ المعنى ، أو أن تغلق فتحة الأرض ، أو أن ترسم وتكشف عن شخصيات القصة. ولكن رغم ذلك ، فإن مستوى الكتابة لا يزال مشكوكا فيه.

إذن ما الذي يتحدث عنه النص؟ إذا كان الأمر بسيطًا جدًا ، فهذه هي مؤامرة Portal ، فقط مع تاريخ مفتوح للعالم وشخصيات متغيرة قليلاً (أكثر مزاجية). بالمناسبة ، يحتوي هذا السيناريو على ميزة واحدة: لقد أصبح جنس الكائنات غير الحية متوسطًا على الرغم من المنطق أو المنطق السليم أو قواعد اللغة الروسية (واللغات الأخرى أيضًا). إذا كان شخص ما مهتمًا (فجأة ، فجأة) ، فسوف أترك النص الكامل وكل ملاحظات اللعبة هنا:

السيناريو
:
, , (3 )
(1 )
, RLIS (2 )

:

[1] . [3] : , , , , .. . [3] , , , ([2] , , ). (4)

[4] , . [5] , . [6] . [6] , . [7] . (5)

[8] : . [9] (- ) . [10] ( ). (3)

[11] . [12] , , , . [13] . [13] , . (3)

[15] ([14] — , , ). [15] ( ) ([16] ). (4)

[17] . [18] «». [18] . [19]- . (4)

[20] «». [21] , . [22] , . (3)

[23] . [24] . [25] « ». (3)

[26] , . [27] , - , . [28] « ». [29] . (4)

[30] - ( ?) ( , ). [31] . [32] , ([33] , ), - , . [34] . [35] , . [36] ( ): 10 (10 = 1 ) . [X]- ( ) , ( 2 1 ?). [37] 2 . (9)

أوراق
():

1) {} «» , . , . - , .

2) {} RLIS (reasonable likeness in simulation) — . . RLIS ( ) — .

3) {} RLIS 100 : , , , , .. , , , . , .

4) {} , . , , , , . magnum opus .

5) {ARSotLotC} , . , . , .

6) {} -!!! - , . , , , . , , , . 2 : .

7) {} , backup , . : , . , , .

8) {} . , . , , , ( ).

9) {} , , -. ? . , . , . …

10) {} - , . . , …

11) {ARSotLotC} . , «» , , . , … .

12) {} , «» . . «», . . .

13) {} , . , . , . .

14) {} , , ( ). — , . : , .

15) {} — . , . . , , .

16) {} ? , . .

17) {} . . , «». . , , .

18) {} «». , ? , , .

19) {} «» , . , , , , . . .

20) {} '' ''. , , '' '', , .

21) {} '' : , ''. , . .

22) {ARSotLotC} : , . , .

23) {} , . ' '. , .

24) {} , , . , . , .

25) {} . , - . , , ' '.

26) {} . , . . ' ' !

27) {} ' ' , . . : , , .

28) {ARSotLotC} - < > . . . , .

29) {ARSotLotC} . ? , (- , ) ARSotLotC (Automatic Recording System of the Logs of the Complex).

30) {ARSotLotC} «» , . , , . - , backup . , , .

31) {ARSotLotC} : . , . . backup.

32) {ARSotLotC} . . , . .

33) {ARSotLotC} ( backup'). .

34) {ARSotLotC} , . , , 10 . , . Ps: , , .

35.1) {} . . ' ' . , , ''. , - , . ' '.

35.2)

أساس الكود

نظرًا لأن تخصصي مبرمج ، كانت الكود هي المهمة الرئيسية بالنسبة لي. مقارنةً بقاعدة الكود الأصلية ، زادت قاعدة الكود المتتالي بمقدار 2-3 مرات (على الرغم من أن الأصل يحتوي على 900 سطر من طرق الكود ، لأنني كنت خائفًا من استخدام حزم مثل الحلقات والمصفوفات أو GetChild () والحلقات ).

بالإضافة إلى الكمية ، زادت الجودة الإجمالية للرمز أيضًا ، لكنني لم أستطع تجنب الأخطاء. نتيجة لذلك ، هناك الكثير من الأخطاء في الكود نفسه. وحتى على الرغم من معرفتي الهزيلة بشكل موضوعي ، أرى أخطائي تمامًا. لذلك ، سوف نقوم بتحليل خطأي الأكثر أهمية. خذ على سبيل المثال رمز بسيط:

 public class VelocityRotate : MonoBehaviour { public float rotate = 0f; public bool oneTime = true; private bool active = true; public void OnTriggerEnter2D(Collider2D collision) { if (active == true) { if (oneTime == true) { active = false; } Rigidbody2D rb = collision.GetComponent<Rigidbody2D>(); Vector2 vel = rb.velocity; rb.velocity = RotateVector(vel, rotate); } } public Vector2 RotateVector(Vector2 a, float offsetAngle) { float power = Mathf.Sqrt(ax * ax + ay * ay); float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; return Quaternion.Euler(0, 0, angle) * Vector2.up * power; } } 

هل فهمت بسرعة ما هو هذا البرنامج النصي المسؤول؟ وإذا كنت تجعل الأمر مثل هذا:

 public class VelocityRotate : MonoBehaviour { //      public float rotate = 0f;//  public bool oneTime = true;//  private bool active = true;//  public void OnTriggerEnter2D(Collider2D collision) { if (active == true) { if (oneTime == true)//   { active = false; } //   Rigidbody2D rb = collision.GetComponent<Rigidbody2D>(); Vector2 vel = rb.velocity; rb.velocity = RotateVector(vel, rotate); } } public Vector2 RotateVector(Vector2 a, float offsetAngle)//   { float power = Mathf.Sqrt(ax * ax + ay * ay);//  float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; //    offset' return Quaternion.Euler(0, 0, angle) * Vector2.up * power; //        } } 

عدم وجود تعليقات هو خطأي الأول والأكبر حقًا في تطوير اللعبة! في قاعدة الكود بأكملها ، لا يوجد تعليق واحد يشرح ما هو هذا الفرع أو ذاك من الكود المسؤول عنه. وربما هذا ليس ضروريًا للعبة إيندي الصغيرة. حسنًا ، أولاً ، بالتأكيد لا يمكنني وصف هذه اللعبة بأنها صغيرة ، وثانيًا ، كمطور في المستقبل ، سيتعين علي بالتأكيد العمل في فريق وغياب مثل هذه العادة المفيدة لأن التعليق سوف يلعب خدعة على الإطلاق. لقد أدركت هذا الخطأ: إنه يطاردني كل ما عندي من مشاريع تتعلق بالبرمجة وهذه المرة ، أخذت هذا في الاعتبار وفي المرة القادمة سأقدم تعليقات.

البق والعيوب

كان هناك العديد من الأخطاء. جدا! لمثل هذا العمل الهائل ، خصصت شهرًا كاملاً من التصحيحات (أغسطس). ليس من المنطقي تحليل الأمثلة ، فقد وضعت ملاحظة مع جميع الأخطاء التي وثقتها (على الرغم من أنني لم أوثق معظمها وقمت بتصحيحها في مكانها):

GB2 المرجعية
:
// —
\ —

//1) , ,
//2) :
//3)
//4) TipsGamePlay
//5) ( )
//6) 0:
//7) 1: ()
//8)
//9) 2: 2
//10)
//11) 4:
//12) layer Player
//13) 7: ()
//14) 8: ( 1)
//15) 8:
\16) ( )
\17) 8: zero
//18)
//19) 1:
//20) ,
//21)
//22) timescale=0
//23) 6:
//24) 0:
//25)
//26) 7:
//27) 7:
//28) AspectRatio
\29)
//30)
//31) <EXfgpy)b> //32) 7: -
//33) ,
//34) 9:
//35) 9:
//36) 'loop'
//37) 10:
//38) 11: ()
//39) 11:
//40) 11:
//41) 11:
//42) 11:
//43) 11:
//44) ( )
/45) 12:
\46) Raycast
\47) ( static, dynamic, kinematic)
//48) (next level, next start, next end)
\49) 1: elevatorsave = 0
\50) offset angle,
//51) 2:
//52)
//53) 7:
//54) next save
//55) Dynamic Graph
//56) 11: ( )
57) 11:
//58) 9: ()
//59) 11: ( )
//60) 12: ( 2 . active , . .
61) :
//62) : -
//63) :
64)
//65) (. )
//66)
//67) HealthBar
68) 0:
//69) localposition position
70) 14: bool isPresentation
//71) 17: 2 4
72) ()
\73)
//74)
//75) layer,
//76)
//77) 2: 1
\78) ( )
//79)
//80) 3: ,
//81)
//82) 6: ,
//83) 6: 1
//84) 6:
//85) 7: 40. .
//86)
//87) 9:
//88) 32:
//89) offsetAngle elevator
//90) 11:
//91) ( )
//92)
//93)
//94)
//95) 13:
//96) 15:
/97) 3 isshotmode
//98) 17:
//99) 18: ,
//100) 19: ( )
/101) 20:
\102) Tramp
//103) 20:
\104)
//105) 11: ui
//106) text arial
\107)
//108)
//109) 3:
//110) 3:
//111) 3: ,
//112) ,
//113) ()
//114) 4:
//115) ( )
//116) ()
//117) pointsAnimation basicAnimation
//118) 7:
//119) 9:
//120) AudioBase
//121) pointsAnimation
//122) , ( )
//123) 13: HealthBar
//124) 13: ,
//125) 14: kinematic (. )
//126) 14:
//127) 14: ,
//128) velocityField ( , )
//129) 16: velocityField
//130) 22:
//131) 22:
\132) 25:
//133) 26:
//134) 27:
\135) ( )
//136)
//137) :
//138) ( )
//139)
//140) 8:
//141) ( 1.5-2, -oneshot'
\142) lerp
//143) , , ( , )
//144) 22:
//145) 11:
//146) 11:
//147) 11:
//148)
//149) «Home» «Menu»
//150)
//151)
//152)
\153) ( healthEnd)
//154) :
//155) 33: ,
//156) 15: ( 0.1)
//157) 15: velocityfield healthbar
//158)
//159) basicAnimation (27)
//160) (18, 27)
//161)
\162) 19: -
//163) ( trigger collision)
//164) 20: 50 250
//165) shotmode
//166) 27:
//167) 28:
//168) 17:
//169) tag boss3
\170) ( , )
//171) 35
//172) : , 600 «I'll come back»
//173) 33:

//174)
//175) HealthBar
//176) ( damage-
//177) 27:



0) (0)
1) (2)
2) (2)
3) (1)
4) (1)
5) (1)
6) (1)
7) (1)
8) (2)
9) (1)
10) (0)
11) (1)
(13)
12) (0)
13) (2)
14) (2)
15) (0)
16) (0)
17) (1)
18) (1)
19) (3)
20) (0)
21) (3)
22) (1)
(13)
23) (1)
24) (1)
25) (0)
26) (0)
27) (0)
28) (3)
29) (1)
30) (2)
31) (0)
32) (0)
33) (1)
34) (1)
(10)


وما يعقل تفكيكه هو العيوب. ليست صغيرة يمكن أن تعزى إلى الأخطاء ، ولكن الكبيرة منها ، والتي هي أخطر الأخطاء في أداء اللعبة. أريد أيضًا أن أشير إلى أنه عن طريق العيوب لا أقصد العيوب. تحتوي اللعبة على الكثير من العيوب ، وهذا أمر مفهوم ، لكنني أريد أن أوضح الأشياء التي يمكنني إصلاحها أو منعها.

فما هي عيوبي الرئيسية؟

  1. . , . 2 3-4 . , , : 10 . , . .
  2. , . , , , , .
  3. . , « » 60% . , .

التعريب

بسبب السيناريو الكامل ، زاد حجم النص المترجم حوالي 30 مرة. لكن تقنية الترجمة لم تتغير قليلاً: حيث أني قمت بالترجمة من خلال خدمة الترجمة من Google ، أواصل. في البداية فقط قمت بترجمة مباشرة من الروسية ، والآن أترجم إلى الإنجليزية ، وقم بتصحيح الأخطاء بالفعل من وإلى لغات أخرى. أيضًا ، انخفض عدد اللغات: إذا كانت اللعبة الأصلية تحتوي على 18 لغة ، وترجمت صفحتها إلى جميع اللغات التي تدعمها google ، فقد تم نقل التتمة إلى 10 لغات فقط: ما هو موجود في اللعبة ، وما هو موجود في الصفحة (وهذه هي السلسلة الوحيدة) أدنى من الأصل).

بالنسبة للأجهزة الطرفية ذات الملاحظات العادية ، قمت بعمل مخطط كبير بدرجة كافية للتعامل مع النص. باختصار ، بدلاً من الجمل البسيطة ، كان هناك فصل خاص للعمل بلغات مختلفة:

البرنامج النصي StringLanguageMinimize
 [System.Serializable] public class StringLanguageMinimize { public string english = ""; public string spanish = ""; public string italian = ""; public string german = ""; public string russian = ""; public string french = ""; public string portuguese = ""; public string korean = ""; public string chinese = ""; public string japan = ""; public string GetString() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = english; break; case "spanish": ret = spanish; break; case "italian": ret = italian; break; case "german": ret = german; break; case "russian": ret = russian; break; case "french": ret = french; break; case "portuguese": ret = portuguese; break; case "korean": ret = korean; break; case "chinese": ret = chinese; break; case "japan": ret = japan; break; } return ret; } } 


وبالضبط نفس الفئة للمحطات:
محطة النصي
 [System.Serializable] public class StringLanguage { [TextArea] public string english = ""; [TextArea] public string spanish = ""; [TextArea] public string italian = ""; [TextArea] public string german = ""; [TextArea] public string russian = ""; [TextArea] public string french = ""; [TextArea] public string portuguese = ""; [TextArea] public string korean = ""; [TextArea] public string chinese = ""; [TextArea] public string japan = ""; public string GetString() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = english; break; case "spanish": ret = spanish; break; case "italian": ret = italian; break; case "german": ret = german; break; case "russian": ret = russian; break; case "french": ret = french; break; case "portuguese": ret = portuguese; break; case "korean": ret = korean; break; case "chinese": ret = chinese; break; case "japan": ret = japan; break; } return ret; } } 


بعد ذلك كان رمز المشغل الطرفي:

نصائح النصي الإدخال
 using UnityEngine; public class TipsInput : MonoBehaviour { public int idTips = 0; public bool isPress2Read = true; public bool oneTime = true; private bool active = true; public GameObject[] copys; private Data data; private Press2Read p2r; private TipsInput ti; private void Awake() { data = GameObject.FindWithTag("MainCamera").GetComponent<Data>(); p2r = GameObject.FindWithTag("Press2Read").GetComponent<Press2Read>(); ti = GetComponent<TipsInput>(); } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.CompareTag("Player")) { if (isPress2Read == false && active == true) { Disable(); data.SetDialoge(idTips); if (copys.Length != 0) { for (int i = 0; i < copys.Length; i++) { copys[i].GetComponent<TipsInput>().Disable(); } } } else if (isPress2Read == true) { p2r.Active(ti); } } } public void OnCollisionExit2D(Collision2D collision) { if (isPress2Read == true) { p2r.DeActive(); } } public void Disable() { if (oneTime == true) { active = false; } return; } } 


فئة البيانات الهامة:

معطيات
 using UnityEngine; using UnityEngine.UI; using System.Collections; public class Data : GlobalFunctions { public Dialoge[] dialoges; public DeadPhrases[] deadPhrases; public GamePlay[] gameplay; [Space] public Tips tips; public AudioBase audioBase; public TipsGamePlay gamePlayTips; public Image slowmobonus; public Text fpsText; public float scaleTips = 1f; public float scaleGameUI = 1f; public float scaleSlowMo = 1f; private float speed = 0f; private float target = 1f; private float timeDuration = 1f; private int updFPS = 0; public void Awake() { scaleTips = scaleGameUI = scaleSlowMo = 1f; slowmobonus.color = new Color(0f, 0f, 0f, 0f); } public void Start() { StartCoroutine(SecFPSUpdate()); } public void SetDialoge(int id) { if (dialoges.Length != 0) { tips.SetActiveTrue(dialoges[id].dialogeStrings, dialoges[id].name); } } public void FalseP2R() { tips.SetFalse(); } public string GetDeadPhrase(string typeDead) { int idType = -1; for (int i = 0; i < deadPhrases.Length; i++) { if (deadPhrases[i].typeDead == typeDead) { idType = i; break; } } if (idType == -1) { return typeDead; } int rand = Random.Range(0, deadPhrases[idType].deadPhrases.Length); return deadPhrases[idType].deadPhrases[rand].GetString(); } public string GetDeadPhrase2() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = "Tap to continue"; break; case "spanish": ret = "Pulse para continuar"; break; case "italian": ret = "Tocca per continuare"; break; case "german": ret = "Tippen Sie, um fortzufahren"; break; case "russian": ret = "  "; break; case "french": ret = "Appuyez sur pour continuer"; break; case "portuguese": ret = "Clique para continuar"; break; case "korean": ret = "계속하려면 탭하세요"; break; case "chinese": ret = "点按即可继续"; break; case "japan": ret = "タップして続行します"; break; } return ret; } public void PauseGameUI(float time) { scaleGameUI = time; Update(); audioBase.UpdateSound(); } public void SetGamePlayTips(int id) { if (id == -1) { gamePlayTips.SetActiveTrueSaved(); } else { gamePlayTips.SetActiveTrue(gameplay[id]); } } public void SlowMo(float timeDuration2, float setSlowMo, float speed2) { speed = speed2; target = setSlowMo; timeDuration = timeDuration2; Update(); audioBase.UpdateSound(); } public void SlowMo(float timeDuration2) { scaleSlowMo = 0.1f; float sb = (1f - scaleSlowMo) * 0.3921569f; slowmobonus.color = new Color(0f, 0f, 0f, sb); Update(); audioBase.UpdateSound(); } public IEnumerator EndAnim(float timeDuration) { yield return new WaitForSeconds(timeDuration); End(); } public void End() { scaleSlowMo = 1f; float sb = (1f - scaleSlowMo) * 0.3921569f; slowmobonus.color = new Color(0f, 0f, 0f, sb); Update(); audioBase.UpdateSound(); } public void End2(float timeDuration2) { if (timeDuration2 == 0) { End(); return; } StartCoroutine(EndAnim(timeDuration2)); } private void Update() { Time.timeScale = scaleTips * scaleSlowMo * scaleGameUI; Time.fixedDeltaTime = 0.03f * scaleSlowMo * scaleTips; updFPS = updFPS + 1; return; } private IEnumerator SecFPSUpdate() { yield return new WaitForSeconds(1f); fpsText.text = "FPS: " + updFPS; updFPS = 0; StartCoroutine(SecFPSUpdate()); } } 


وفئة نصائح رئيسية ، وهي المسؤولة عن تشغيل المحطة:

نصائح النصي
 using System.Collections; using UnityEngine.UI; using UnityEngine; public class Tips : GlobalFunctions { public Data data; public Press2Read p2r; public GameUI gameUI; public GameObject obj; public AudioClip setClip; public Text nameText; public Text txt; private int textID = 0; private int textsID = 0; private AudioBase audioBase; private DialogeString textActive; private DialogeString[] textsActive; private bool isMass = false; [TextArea] public string end = ""; [TextArea] public string endPast = ""; public void Start() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); data.scaleTips = 1f; obj.SetActive(false); txt.text = ""; } public void SetActiveTrue(DialogeString text, StringLanguageMinimize name) { data.scaleTips = 0.1f; audioBase.layerSounds[0].volume /= 10f; obj.SetActive(true); nameText.text = name.GetString(); gameUI.pauseButton.SetActive(false); textActive = text; isMass = false; StartCoroutine(TimerFalse()); } public void SetActiveTrue(DialogeString[] texts, StringLanguageMinimize name) { data.scaleTips = 0.1f; audioBase.layerSounds[0].volume /= 10f; obj.SetActive(true); nameText.text = name.GetString(); gameUI.pauseButton.SetActive(false); textsActive = texts; isMass = true; StartCoroutine(TimersFalse()); } public IEnumerator TimerFalse(float time = 0.02f) { yield return new WaitForSecondsRealtime(time); string ds = textActive.dialogeString.GetString(); if (textID < ds.Length && ds != end) { audioBase.SetSound(setClip, 1, 0.5f, TypePlaying.Sound, false); end = end + ds.Substring(textID, 1); txt.text = endPast + end; textID = textID + 1; if (textID + 1 != ds.Length && ds != end) { if (ds.Substring(textID + 1, 1) == ",") { StartCoroutine(TimersFalse(0.1f)); } else if (ds.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else if (ds.Substring(textID + 1, 1) == "?") { StartCoroutine(TimersFalse(0.15f)); } else if (ds.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else { StartCoroutine(TimersFalse()); } } else { StartCoroutine(TimersFalse()); } } else { endPast = txt.text; if (textActive.isSkip) { if (textActive.skipOffset == 0f) { SetActiveFalse(); } else { IsSkip(textActive.skipOffset); } } } } public IEnumerator TimersFalse(float time = 0.02f) { yield return new WaitForSecondsRealtime(time); string ds = textsActive[textsID].dialogeString.GetString(); if (textID < ds.Length && ds != end) { audioBase.SetSound(setClip, 1, 0.5f, TypePlaying.Sound, false); end = end + ds.Substring(textID, 1); txt.text = endPast + end; textID = textID + 1; string ds1 = textsActive[textsID].dialogeString.GetString(); if (textID + 1 != ds1.Length && ds1 != end) { if (ds1.Substring(textID + 1, 1) == ",") { StartCoroutine(TimersFalse(0.1f)); } else if (ds1.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else if (ds1.Substring(textID + 1, 1) == "?") { StartCoroutine(TimersFalse(0.15f)); } else if (ds1.Substring(textID + 1, 1) == "!") { StartCoroutine(TimersFalse(0.15f)); } else { StartCoroutine(TimersFalse()); } } else { StartCoroutine(TimersFalse()); } } else { endPast = txt.text; if (textsActive[textsID].isSkip) { if (textsActive[textsID].skipOffset == 0f) { SetActiveFalse(); } else { IsSkip(textsActive[textsID].skipOffset); } } } } public IEnumerator IsSkip(float time) { yield return new WaitForSecondsRealtime(time); SetActiveFalse(); } public void SetFalse() { obj.SetActive(false); gameUI.pauseButton.SetActive(true); end = ""; endPast = ""; txt.text = ""; textID = textsID = 0; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } public void SetActiveFalse() { if (isMass == false) { if (textActive.dialogeString.GetString() != end) { end = textActive.dialogeString.GetString(); if (textActive.isSkip) { SetActiveFalse(); } } else { obj.SetActive(false); gameUI.pauseButton.SetActive(true); end = ""; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } } else { if (textsActive[textsID].dialogeString.GetString() != end) { if (textsActive[textsID].isStep == true) { txt.text = end = textsActive[textsID].dialogeString.GetString(); if (textsActive[textsID].isSkip) { SetActiveFalse(); } } else { end = textsActive[textsID].dialogeString.GetString(); txt.text = endPast + end; } } else { if (textsID != textsActive.Length - 1) { textsID = textsID + 1; textID = 0; end = ""; if (textsActive[textsID].isStep == true) { endPast = ""; } StartCoroutine(TimersFalse()); } else { obj.SetActive(false); gameUI.pauseButton.SetActive(true); p2r.UnTap(); end = ""; endPast = ""; txt.text = ""; textID = textsID = 0; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } } } } } 


قررت أنه سيكون من المحزن أن يتم عرض النص ، وبالتالي بمساعدة IEnumerator ، قمت بمحاكاة لكتابة النص (نفس التأثير تمامًا في النهاية).

الإصدار في

البداية ، كانت خطتي هي وضع اللعبة في 1 سبتمبر. وفعلت ذلك: في اللحظة الأخيرة اتضح أن لدي 4 أخطاء في النهاية (ولم تتم ترجمتها أيضًا) ، وسرعان ما تم إصلاحها ووضعها في المساء. لسوء الحظ ، تم تأخير التحقق لمدة 7 أيام ، لأنني قررت التحقق من العرض مع شيء يدويًا. على الأرجح ، توجد المسألة في الحساب ، والتي أصبحت "محددة" ويتم فحصها بالفعل عن طريق الإشراف يدويًا.

كان PR أكثر صعوبة بالنسبة لي من التحضير للإصدار ، لأنه لم يكن هناك نقود واتصالات ، لكنني أردت توزيع اللعبة. لذلك ، استخدمت طرقًا بسيطة: رميت كل شيء على الأصدقاء في VK ، وأنشأت منشورات على Reddit ، وألقيتها في العرض على مواقع على ألعاب الجوال ، وحاولت الاتصال بمؤلفي الموسيقى ، إلخ. وهذا أعطى القليل من النتيجة:


النتيجة

مدهشة ، لكن في اليوم الذي نشرت فيه هذا المقال قضيت 3 سنوات في تكنولوجيا المعلومات! وعلى الرغم من عمري 16 عامًا ، في ذلك اليوم ، عندما كان عمري 13 عامًا ، حددت لنفسي الهدف: تعلم البرمجة وإنشاء لعبة أحلام. ومنذ تلك اللحظة ، إلى حد ما ، أصبح حلمي حقيقة.

ماذا عن اللعبة؟ أنا سعيد معها. لا ، في الحقيقة ، لم أتلق الكثير من المعلومات والخبرات المفيدة من أي شيء كما في هذا المشروع. حسنًا ، قد تكون جودة اللعبة أعلى بشكل واضح ، لكن حتى تلك التي تعد جيدة بالفعل بالنسبة لي. أيضا ، بالنسبة لي هذه اللعبة شيء شخصي وسيكون من غير الاحترام استثمار هذه اللعبة في المقام الأول. لذلك ، لا يوجد فيه الإعلان ، والتبرع وليس لديها نسخة مدفوعة

بعد ذلك ، أود أن أستمر في تطوير اللعبة. لكن ظروف الحياة لم تعد ممكنة. ولكي أبدأ أن أصبح مبرمجًا بشكل طبيعي ، أحتاج إلى تنمية ونمو شخصي على نفسي. لا أعرف ما الذي سأدرسه الآن وأين أذهب ، لكن هناك شيء واحد أعرفه على وجه اليقين: إنه على الأرجح لعبتي الأخيرة على محرك الوحدة.

شكرا على الأقل بعض الاهتمام. إذا تحولت قصتي إلى حالة من الفوضى ، وطرح الأسئلة ، فسأوضح ذلك.

ملاحظة: شخص ما يحب آخر مقطورة:


و هنا هي مقطورة لهذه اللعبة:

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


All Articles