مدخل إلى unity3d إنشاء الوحدات البنائية وشخصيات الخصوم في Unity3D


ياسر جفال

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

ملاحظة: يمكن تحميل الملفات المصدرية لكامل هذه السلسلة عبر حساب أكاديمية حسوب على Github، يمكن أيضا تحميل ملف APK لتجريب اللعبة على أجهزة Android.

إنشاء الوحدات البنائية للعبة

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

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

image00.thumb.png.11a91dd06e57ee82d218f6

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

image01.thumb.png.3889ae2a77a928711401bf

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

سأبدأ مع المثلثات لأنها حالة خاصة تحتاج لعناية عند بناء القالب الخاص بها، بينما ستكون الأشكال التالية أسهل. لبناء قالب المثلث متساوي الضلعين والذي تراه أيمن الصورة، علينا أولا أن نضيف كائنا فارغا ونسميه Triangle ومن ثم نضيف إليه المكوّن Sprite Renderer ومكوّن التصادم المسمى Polygon Collider 2D والذي سيظهر مبدئيا على شكل خماسي كما توضح الصورة في الأسفل. بعد إضافة مكوّن التصادم يمكننا سحب صورة المثلث إلى داخل الخانة Sprite في المكوّن Sprite Renderer. أنوه هنا إلى أنه من الضروري إضافة مكوّن التصادم قبل تحديد الصورة، وذلك لأن Unity سيحاول تغيير شكل مكوّن التصادم ليطابق شكل الصورة وهو ما لا يتم بدقة. سنقوم الآن بتغيير شكل مكوّن التصادم يدويا وذلك عبر الضغط على الزر Edit Collider.

image02.thumb.png.29f973dd5f34ce82beb516

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

image03.thumb.png.684d6f0a7435cbb2acb789

بعد أن انتهينا من تجهيز شكل مكوّن التصادم يمكننا تثبيته بالضغط مجددا على Edit Collider ليتم حفظ الشكل الجديد. أصبح لدينا الآن مثلث يمكن أن تصطدم به الأجسام الأخرى، إلا أنه نفسه ثابت ولا يتأثر بأي عوامل فيزيائية. لنجعله نشطا فيزيائيا علينا أن نضيف مكوّن الجسم الصلب المسمى Rigid Body 2D، وهو المكوّن المسؤول عن وضع الكائن تحت تحكم المحاكي الفيزيائي الخاص بمحرك Unity، مما يجعله يتأثر بالجاذبية وقوى الدفع وغيرها.

image04.thumb.png.09e988a8ca6197ae0c0a8c

هذا المكوّن يحتوي على مجموعة خصائص أذكر هنا أهمها بالنسبة لنا:

  • الكتلة Mass: كلما زادت يصبح تحريك الجسم أصعب لأن إزاحته ستحتاج لقوة أكبر، لكن هذا لا يؤثر على سرعة سقوطه.
  • مقاومة الهواء لحركة الجسم Linear Drag: كلما زادت سيصبح توقف الجسم عن الحركة بعد زوال القوى الخارجية عنه أسرع.
  • مقاومة الهواء للدوران Angular Drag: كلما زادت سيصبح توقف الجسم عن الدوران بعد زوال القوى الخارجية عنه أسرع.
  • معامل الجاذبية Gravity Scale: الجاذبية الافتراضية هي 9.8 كما هو الحال مع الجاذبية الأرضية، وسيتم ضرب هذه القيمة بالمعامل لحساب الجاذبية الخاصة بهذا الكائن تحديدا.

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

البداية ستكون مع البريمج الذي يحدد مدى قوة تحمل الوحدة البنائية وسنسميه Breakable. مهمة هذا البريمج هي تلقي الصدمات وإنقاص صحة الكائن بناء عليها؛ ومن ثم تدميره حين تصل صحته إلى صفر. السرد التالي يوضح البريمج المذكور.

using UnityEngine;
using System.Collections;

public class Breakable : MonoBehaviour {
    
    //مقدار الصحة الأولية للكائن
    public float initialHealth = 100;
    
    //متغير داخلي لمتابعة تغير قيمة صحة الكائن
    private float health;
    
    //تستدعى مرة واحدة عند بداية التشغيل
    void Start () {
        health = initialHealth;
    }

    //تستدعى مرة عند تصيير كل إطار
    void Update (    
    }
    
    //تقوم باستقبال الضربات وإنقاص الصحة بناء عليها
    public void TakeDamage(float amount){
           //الصحة منتهية فلا يمكن تلقي المزيد من الضربات
              if (health <= 0.0f)
           {
                   return;
           }

        if(health <= amount){
            //في هذه الحالة تم تدمير الكائن
                   health = 0.0f;
            SendMessageUpwards("BreakableDestroyed");
            Destroy(gameObject);
            return;
        }
        
        //قم بتقليل قيمة الصحة بمقدار قوة الضربة
        health -= amount;
        
        //أرسل رسالة تخبر بتغير الصحة
        SendMessage("DamageTaken", health);
    }
}

كل ما يفعله هذا البريمج البسيط هو تخزين قيمة الصحة الأولية في متغير خاص داخلي وذلك ليمنع البريمجات الأخرى من تغييرها (تذكر فصل الاهتمامات). يمكن إنقاص الصحة عن طريق إرسال رسالة لهذا البريمج اسمها TakeDamage والتي تأتي مرفقة بمقدار قوة الضربة التي تلقاها الكائن. تقوم الدّالة باستقبال هذه الرسالة وإنقاص قوة الضربة من صحة الكائن، فإذا وصلت صفرا أو أقل من ذلك تقوم بتدميره. ترسل هذه الدّالة رسالتين هما DamageTaken حين تلقي الضربة وترفق معها قيمة الصحة الجديدة بعد إنقاصها. الرسالة الأخرى هي BreakableDestroyed والتي يتم إرسالها عند وصول الصحة إلى صفر أي عند تدمير الكائن.

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

image05.thumb.png.8f3a6b5e005471a6ecd105

ما سنقوم به الآن هو كتابة بريمج آخر يقوم بالربط بين مقدار صحة الكائن والصورة المعروضة. بما أن لدينا ثلاث صور فإن كل واحدة منها ستمثل ثلث الصحة. فإذا كانت الصحة الكاملة 60 مثلا، فإن الصورة الأولى ستبقى ظاهرة طالما أن الصحة بين 60 و40، فإذا قلّت عن 40 ستظهر الصورة الثانية، وإذا قلّت عن 20 ستظهر الثالثة. وبهذه الطريقة نعطي اللاعب فكرة عن قرب تحطم هذه الوحدة. البريمج المسؤول عن هذا الأمر هو BreakableDislay والموضح في السرد التالي:

using UnityEngine;
using System.Collections;

public class BreakableDislay : MonoBehaviour {
    
    //مصفوفة لتخزين صور جميع مراحل التدمير
    //مرتبة من الحالة الأسوأ للحالة الأفضل
    public Sprite[] destructionStages;
    
    //مرجع لبريمج الوحدة البنائية
    private Breakable brk;
    
    //مرجع لمكوّن تصيير الصورة
    private SpriteRenderer sr;
    
    //متغير لتخزين صحة الكائن الأصلية في البداية
    private float fullHealth;
    
    //يتم استدعاؤها مرة واحدة عند بداية التشغيل
    void Start () {
        brk = GetComponent<Breakable>();
        fullHealth = brk.initialHealth;
        sr = GetComponent<SpriteRenderer>();
        UpdateDisplay (fullHealth);
    }
    
    //يتم استدعاؤها مرة واحدة عند تصيير كل إطار
    void Update () {
    
    }
    
    //تقوم باستقبال رسالة تلقي الضربة وتحديث الصورة بناء على قيمة الصحة الجديدة
    void DamageTaken(float newHealth){
        UpdateDisplay(newHealth);
    }
    
    //تقوم بتغيير الصورة المعروضة بناء على قيمة الصحة المزودة
    void UpdateDisplay(float health){
        //قم بتغيير مرحلة التدمير بناء على
        //قيمة الصحة الجديدة
        float healthRatio = health / fullHealth;
        int maxIndex = destructionStages.Length - 1;
        int matchingIndex = (int)(healthRatio * maxIndex);
        sr.sprite = destructionStages[matchingIndex];
    }
}

الدّالة الأهم في هذا البريمج هي ()UpdateDisplay، والتي تقوم باختيار صورة مرحلة التشقق الملائمة لمقدار الصحة المتبقية. لنفترض أن لدينا 3 مراحل مختلفة كما هو الحال مع المثلث الذي نعمل عليه. عندما تتغير صحة الكائن نتيجة لتلقيه ضربة معينة، سيقوم بإرسال الرسالة DamageTaken مرفقا معها قيمة الصحة الجديدة. عند استقبال هذه الرسالة من قبل البريمج BreakableDisplay يقوم باستدعاء الدّالة ()UpdateDisplay مباشرة ويزودها بقيمة الصحة الجديدة، عندها تقوم الدّالة بقسمة الصحة الجديدة على الصحة الكاملة التي سبق تخزينها في fullHealth. بقسمة الرقمين نحصل على نسبة الصحة الجديدة من الصحة الكاملة، ومن ثم نقوم بتحويل هذه النسبة إلى عدد صحيح يمثل القيمة الأقرب لها إذا ما قسمناه على عدد العناصر الكلي في مصفوفة صور مراحل التشقق.

مثلا إذا كانت الصحة الكاملة هي 100 والجديدة هي 25 وعدد المراحل 3. فإن 0.25 * 2 = 0.5 وبتحويله إلى عدد صحيح (دون جبر الكسر) سيصبح الناتج صفرا أي صورة آخر مرحلة للتشقق قبل التدمير.

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

image06.thumb.png.292b72f6b3130357725c8f

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

  • الأول هو Sprite Renderer والذي سيعرض الصورة كما نعرف،
  • والمكوّن الآخر هو الجسم الصلب Rigid Body 2D والذي سيمكننا من إضافة قوة دافعة لتحريك القطعة.

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

image07.thumb.png.cdc273987001e74e75cc1b

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

using UnityEngine;
using System.Collections;

public class BreakablePieces : MonoBehaviour {
    
    //قالب كائن قطع الحطام
    public GameObject piecePrefab;
    
    //الصور المستخدمة لقطع الحطام
    public Sprite[] piecesSprites;
    
    //كم عدد القطع التي سيتم إنشاؤها؟
    public int pieceCount;
    
    //مقدار تصغير أو تكبير قطع الحطام الناتجة
    public float scale = 1.0f;
    
    //ما هي قوة الانفجار الناتجة من تناثر القطع؟
    //القيمة صفر تعني عدم وجود انفجار
    public float explosionForce = 0.0f;
    
    //تستدعى مرة واحدة عند بداية التشغيل
    void Start () {
    
    }
    
    //تستدعى مرة واحدة عند تصيير كل إطار
    void Update () {
    
    }
    
    //BreakableDestroyed تقوم باستقبال الرسالة
    public void BreakableDestroyed(){
        //قم بجلب مكوّن التصادم
        Collider2D myCollider;
        myCollider = GetComponent<Collider2D>();
        
        //قم بإيجاد حدود مكوّن التصادم
        Bounds myBounds = myCollider.bounds;
        
        //قم بإنشاء العدد المطلوب من قطع الحطام
        for(int i = 0; i < pieceCount; i++){
            GameObject piece = (GameObject) Instantiate(piecePrefab);
            
            //قم بوضع الصورة التالية على القطعة الجديدة
            int index = i % piecesSprites.Length;
            SpriteRenderer sr = piece.GetComponent<SpriteRenderer>();
            sr.sprite = piecesSprites[index];
            
            //قم بتوليد موقع عشوائي ضمن حدود مكوّن التصادم
            float posX = Random.Range(myBounds.min.x, myBounds.max.x);
            float posY = Random.Range(myBounds.min.y, myBounds.max.y);
            
            //قم بوضع قطعة الحطام في الموقع العشوائي
            piece.transform.position = new Vector2(posX, posY);
            
            //قم بتحديد الكائن الأب لقطعة الحطام بحيث
            //يكون الكائن الأب للوحدة البنائية التي تم تدميرها
            piece.transform.parent = transform.parent;
            
            //قم بتغيير حجم القطعة بالمقدار المطلوب
            piece.transform.localScale *= scale;
            
            //قم بجلب مكوّن الجسم الصلب لقطعة الحطام
            Rigidbody2D rb = piece.GetComponent<Rigidbody2D>();
            
            //قم بإضافة قوة دفع صغيرة
            //لتحريك قطعة الحطام بحيث يكون اتجاه القوة من مركز
            //الجسم الأصلي إلى الموقع العشوائي حيث القطعة
            Vector2 force = piece.transform.position - transform.position;
            if(explosionForce == 0.0f){
                //لا يوجد انفجار
                rb.AddForce (force, ForceMode2D.Impulse);
            } else {
                //يوجد انفجار
                rb.AddForce(force * explosionForce, ForceMode2D.Impulse);
            }

                   //احذف قطعة الحطام من المشهد بعد 5 ثوان
                  Destroy(piece, 5.0f);
        }

               if (explosionForce > 0.0f)
            {
                    //تأثير الانفجار على الأجسام الأخرى
             //قم بحساب نصف قطر مجال تأثير الانفجار
                float radius = myBounds.max.magnitude * 2.0f;
                   //قم بإيجاد جميع الوحدات البنائية والأجسام القابلة للتحطيم
                   Breakable[] breakables = FindObjectsOfType<Breakable>();
                //قم بإرسال قوة الانفجار كمقدار تدمير للأجسام الواقعة في محيط التأثير
                  foreach (Breakable target in breakables)
                {
                       //استثن الكائن نفسه الذي نتج عنه الانفجار
                   if (target.gameObject != gameObject)
                    {
                           //قم بحساب المسافة بين الكائن وموقع الانفجار وقارنها بنصف قطر محيط التأثير
                           float dist= Vector2.Distance(
                        transform.position, target.transform.position);
                          if (dist <= radius)
                      {
                               //قم بإضافة قوة الانفجار كمقدار للتدمير
                              target.TakeDamage(explosionForce / dist);
                             //قم بحساب مقدار واتجاه قوة دفع الانفجار
                           Vector2 expDir = target.transform.position - transform.position;
                           expDir = expDir * (explosionForce / radius);
                           //قم بإضافة قوة الانفجار للكائن
                             Rigidbody2D targetRB = target.GetComponent<Rigidbody2D>();
                            targetRB.AddForce(expDir, ForceMode2D.Impulse);
                       }
                     }
                 }
           }
    }
}

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

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

بعد أن تصبح قطعة الحطام جاهزة وفي مكانها الصحيح، سنحتاج لإضافة قوة فيزيائية تحركها بعيدا عن مركز القطعة الأصلية. نقوم أولا باستخراج مكوّن الجسم الصلب Rigid Body 2D وهو المسؤول عن الخصائص الفيزيائية لقطعة الحطام، ومن ثم نحسب المتجه المطلوب لهذه القوة وذلك بطرح موقع قطعة الحطام من موقع الكائن الأصلي. إذا كانت قوة الانفجار تساوي صفرا فلن نحتاج سوى لإضافة هذه القوة للجسم الصلب على شكل قوة اندفاع Impulse. أما في حالة وجود انفجار فالأمر أكثر تعقيدا، حيث يتوجب علينا أولا ضرب القوة الدافعة بمقدار قوة الانفجار مما ينتج عنه اندفاع الحطام بسرعة أكبر ولمسافة أبعد. إضافة لذلك سيكون علينا إحداث تأثير على القطع المجاورة وهذا الأمر يتم عن طريق حساب نصف قطر محيط تأثير الانفجار، والذي اخترناه هنا ليكون طول متجه الحد الأقصى مضروبا في 2. نقوم بعدها باستخدام الدّالة ()FindObjectsOfType من أجل البحث عن جميع الكائنات التي تحتوي على البريمج Breakable (أي الوحدات البنائية)، وأي واحد من هذه الوحدات يقع في محيط التأثير يجب أن تضاف له قوة الانفجار الدافعة متناسبة عكسيا مع المسافة بين مركز الانفجار في الجسم المدمر وبين الوحدة المستهدفة. قوة الانفجار الدافعة ستقوم فقط بتحريك الوحدة البنائية، لكننا نريد أيضا إنقاص صحتها بسبب الانفجار ولذلك نستدعي ()TakeDamage.

بعد إضافة البريمجات أصبح الكائن جاهزا لنصنع منه قالب المثلث المعدني. لكون عدد قوالب الأشكال كبيرا نسبيا، من الأفضل وضع كل مجموعة في مجلد خاص بها. مثلا مجلد Metal للأشكال المعدنية و Wood للخشبية وهكذا. بعد صناعة القالب يمكنك أن تغير الكائن نفسه وتغير اسمه ومن ثم تسحبه مرة أخرى إلى مستعرض المشروع لصناعة قالب جديد. مثلا يمكنك استبدال صورة المثلث بالدائرة ومن ثم تغيير الاسم إلى Circle. أيضا سيتوجب عليك تغيير شكل مكوّن التصادم عن طريق إزالة المكوّن Polygon Collider 2D وإضافة المكوّن Circle Collider 2D لأنه الأنسب للدائرة. وبعد صناعة قالب الدّائرة نضيف واحدا آخر للمستطيل مع تغيير مكوّن التصادم مرة أخرى ليصبح Box Collider 2D وهكذا حتى ننجز الأشكال التي نرغب بها. أثناء عملك راعي الأمور التالية:

  1. لحذف مكوّن موجود على كائن، قم بالضغط على رمز الترس في أعلى يمين المكوّن في نافذة الخصائص ومن ثم اختر Remove Component.
  2. هناك بعض الأشكال مثل المثلث القائم تحتوي على صور لمرحلتي تشقق فقط بينما الأشكال الباقية تحتوي على 3 مراحل. هذا يؤثر فقط على عدد عناصر المصفوفة destructionStages ولا يلزمنا تغيير أي شيء في الكود.
  3. مقدار الكتلة للمواد هي 6 للمعدن والمواد المتفجرة و2.5 للزجاج و5 للصخور و1 للخشب.
  4. مقدار الصحة الافتراضية هي 100 للمعدن و 45 للمواد المتفجرة و20 للزجاج و 80 للصخور و 35 للخشب.

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

لإضافة طبقة جديدة قم باختيار قالب الخلفية BGElement ومن ثم افتح القائمة Layer الموجودة في أعلى يمين نافذة الخصائص واختر Add Layer كما ترى في الصورة:

image08.thumb.png.fbf0e463646cb8b3641943

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

image09.thumb.png.f8d2576bb143448b64fda7

image10.thumb.png.73c87b407db47b67dbf2d8

بعدها علينا أن نطلب من محاكي الفيزياء الخاص بالمحرك أن يهمل التصادمات الحاصلة بين الطبقة الافتراضية Default وطبقة الخلفية Background بحيث لا تتصادم عناصر الأرضية أو الوحدات البنائية مع الخلفية. لتنفيذ هذا الأمر اذهب إلى القائمة:

Edit > Project Settings > Physics 2D

حيث ستجد في أسفل نافذة الخصائص مصفوفة تصادم الطبقات Layer Collision Matrix والتي تحتوي على جميع الطبقات الموجودة في المشروع منتظمة في صفوف وأعمدة. في كل نقطة تقاطع بين صف وعمود ستجد مربعا يمكنك تفعيله أو تعطيله. إذا قمت بتعطيل أحد هذه المربعات فإن التصادم الحاصل بين الطبقتين المتقاطعتين فيه سيتم إهماله. بالتالي ما نريده هو تعطيل المربع الذي تتقاطع فيه الطبقة Default مع الطبقة Background كما ترى في الصورة أدناه:

image11.thumb.png.486cc15d44e606b56c934f

بعد تجهيز قوالب الوحدات البنائية يتبقى علينا إضافة قالب قطع الحطام للبريمج BreakablePieces، كما أن علينا تحديد صور القطع الخاصة بكل وحدة بنائية وتحديد عددها وأحجامها. لاختصار الوقت سأعتمد قيما ثابتة لجميع الوحدات البنائية، وهو 5 قطع حطام لكل وحدة وتحجيم بمقدار 0.5 (صور قطع الحطام موجودة في مجلد يسمى Debris). للوقوف على التقدم في بناء قالب الوحدة البنائية، يفترض أن يكون ما أنشأناه حتى الآن شبيها بما تراه في الصورة أدناه:

image12.thumb.png.a71957631582976ce87be1

السؤال التالي الذي نريد الإجابة عليه هو: كيف تتناقص قيمة صحة هذه الوحدات البنائية عند سقوطها أو اصطدامها ببعضها البعض أو بمقذوفات اللاعب؟

يساعدنا Unity بالإجابة على هذا السؤال حيث أنه يوفر لنا رسائل إعلام بحدوث أي عمليات تصادم بين الكائنات المختلفة. سنقوم الآن بكتابة بريمج يستقبل رسائل التصادم هذه ويقوم بتحليل محتوياتها وحساب مقدار التدمير الذي يجب أن يتم على كل وحدة بنائية. هذا البريمج هو CollisionDamageTaker، وهو موضح في السرد التالي:

using UnityEngine;
using System.Collections;

public class CollisionDamageTaker : MonoBehaviour {

   //متغير لتخزين آخر عملية تصادم
   //يساعد هذا على منع تكرار التصادمات
   private float lastCollisionTime = 0.0f;

   //يتم استدعاؤها مرة واحدة عند بداية التشغيل
   void Start () {
    
   }
    
   //يتم استدعاؤها مرة عند تصيير كل إطار
   void Update () {
    
   }

   //يتم استدعاؤها عند حدوث تصادم بين هذا الكائن وكائن آخر
   void OnCollisionEnter2D(Collision2D col)
   {
       if (Time.time - lastCollisionTime < 0.25f)
       {
           //لم يمر وقت كاف منذ آخر تصادم، قم بإهمال هذا التصادم ولا تفعل شيئا
           return;
       }

       //قم بتسجيل وقت حدوث التصادم
       lastCollisionTime = Time.time;

       //قم باستخراج مكوّن الموقع من الكائن الآخر الذي حدث معه التصادم
       Transform otherObject = col.transform;

       //احسب ارتفاع الكائنين المتصادمين
       float myHeight = transform.position.y;
       float otherHeight = otherObject.position.y;

       //ما هو مقدار قوة الإصابة التي تم تلقيها؟
       float damageAmount = 1.0f;

       //قم بحساب قوة الإصابة بناء على موقع
       //الكائن الآخر
       if (myHeight < otherHeight)
       {
           //سقط كائن آخر فوق هذا الكائن

           //كم كانت سرعة الجسم الساقط حين اصطدامه؟
           float fallVelocity = col.relativeVelocity.magnitude;
           //اضرب مقدار الإصابة بسرعة الاصطدام
           damageAmount *= fallVelocity;

           //ما هي كتلة الكائن الذي سقط فوق هذا الكائن؟
           Rigidbody2D otherRB = otherObject.GetComponent<Rigidbody2D>();
           float otherMass = otherRB.mass;
           //اضرب مقدار الإصابة مرة أخرى بالكتلة
           damageAmount *= otherMass;
           //قم أخيرا بمضاعفة مقدار الإصابة لأن الكائن الآخر جاء من مكان أعلى
           damageAmount *= 2.0f;
       }
       else
       {
           //الكائن الحالي هو الذي سقط
           //الكائنات الساقطة بسرعة أكبر ستحصل على إصابة أكبر
           float myFallVelocity = col.relativeVelocity.magnitude;
           //اضرب مقدار الإصابة بسرعة الاصطدام
           damageAmount *= myFallVelocity;
       }
       
       //قم بإرسال رسالة لإنقاص الصحة بمقدار الإصابة المحسوبة
       SendMessage("TakeDamage", damageAmount);
   }
}

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

عند تصادم كائنين مع بعضهما يقوم محرك Unity بإرسال الرسالة OnCollisionEnter إلى هذين الكائنين في نفس الوقت، ويرفق مع هذه الرسالة متغيرا من نوع Collision2D والذي يحتوي على معلومات حول التصادم، إضافة لأنه يحتوي على مرجع للكائن الآخر بالتالي يمكن لكل منهما التعرف على الكائن الآخر الذي اصطدم به وخصائصه. الخطوة الأولى للدّالة ()OnCollisionEnter هي أن تتأكد من مرور ربع ثانية على الأقل منذ آخر مرة تم فيها إنقاص صحة الكائن، فإن لم يتحقق هذا الشرط سيتوقف تنفيذ الدّالة على الفور.

طريقة حساب مقدار الإصابة (المقدار الذي سيتم إنقاصه من الصحة) يعتمد على عدة عوامل ويبدأ من قيمة افتراضية هي 1. العامل الأول هو ارتفاع كل من الكائنين المتصادمين. لنفترض أن الدّالة اكتشفت أثناء التنفيذ أن الكائن الآخر otherObject ذو ارتفاع أعلى من الكائن الحالي. هذا الأمر يفسر على أن الكائن الآخر قد سقط فوق الكائن الحالي واصطدم به، بالتالي يجب أن يكون مقدار الإصابة أعلى. من أهم المعلومات التي تحملها الرسالة هي col.relativeVelocity، والتي تحسب السرعة النسبية بين الكائنين لحظة الاصطدام. منطقيا يجب أن يتناسب مقدار هذه السرعة طرديا مع قوة الإصابة، لذا فإننا نقوم باستخراج هذا المقدار relativeVelocity.magnitude (السرعة هنا هي متجه لذلك نستخرج مقدارها) وضربه بقيمة قوة الإصابة. بعد ذلك نقوم باستخراج مكوّن الجسم الصلب RigidBody2D من الكائن الآخر وضرب كتلته بمقدار الإصابة، حيث يجب أن تزيد الإصابة كلما كان الكائن الذي سقط ذا كتلة أكبر. أخيرا نقوم بمضاعفة القيمة النهائية التي حسبناها؛ وذلك لجعل سقوط كائن فوق آخر ذا تأثير أكبر من سقوط الكائن نفسه على الأرض أو على جسم آخر.

الاحتمال الآخر هو أن يكون الكائن الحالي ذا ارتفاع أعلى من الكائن الآخر الذي سقط فوقه، بالتالي لا حاجة لنا بأخذ أية معلومات من هذا الجسم الآخر. كل ما سنفعله في هذه الحالة هو ضرب قيمة الإصابة بسرعة الاصطدام، مما يجعل سقوط الجسم نفسه أقل أذى من سقوط جسم آخر فوقه. بعد المرور على أحد هذين الاحتمالين نحصل على قيمة الإصابة النهائية في المتغير damageAmount وبالتالي نرسل الرسالة TakeDamage ونرفق معها القيمة المحسوبة. لاحظ أن البريمج المرسل CollisionDamageTaker والمستقبل Breakable موجودان على نفس الكائن، ولا يوجد أي كائن آخر مهتم باستقبال هذه الرسالة. لهذا السبب استخدمنا الدّالة()SendMessage في عملية الإرسال.

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

using UnityEngine;
using System.Collections;

public class BreakableSounds : MonoBehaviour {

   //يتم تشغيل هذا الصوت عند تلقي إصابة
   public AudioClip damageSound;

   //يتم تشغيل هذا الصوت عند تحطم الوحدة البنائية
   public AudioClip destructionSound;

   //الحد الأدنى لمقدار الإصابة التي تؤدي لتشغيل الصوت
   public float minDamage = 25.0f;

   //المدة الزمنية التي يمنع خلالها تشغيل أي صوت من قبل هذا الكائن
   private float suspensionTime = 0.0f;

   //الوقت الذي بدأ فيه تشغيل آخر صوت من قبل هذا الكائن
   private float playTime = 0.0f;

   //تستدعى مرة واحدة عند بداية التشغيل
    void Start () {
    
   }
    
    //تستدعى مرة عند تصيير كل إطار
    void Update () {
    
    }

   //DamageTaken تقوم باستقبال رسالة الإصابة
   void DamageTaken(float amount)
   {
       //سيتم تشغيل الصوت فقط للإصابات التي
       //يزيد مقدارها عن الحد الأدنى
       if (amount >= minDamage)
       {
           CheckAndPlay(damageSound);
       }
   }

   //BreakableDestroyed تقوم باستقبال رسالة تدمير الوحدة البنائية
   void BreakableDestroyed()
   {
       AudioSource.PlayClipAtPoint(destructionSound, transform.position);
   }

   //تقوم بحساب الوقت الذي يمنع فيه تشغيل ملف الصوت وتقرر بناء عليه تشغيل الملف مجددا من عدمه
   void CheckAndPlay(AudioClip clip)
   {
       if (Time.time - playTime > suspensionTime)
       {
           AudioSource.PlayClipAtPoint(clip, transform.position, 0.25f);
           playTime = Time.time;
           suspensionTime = clip.length + 0.5f;
       }
   }
}

لعل المهام الرئيسية لهذا البريمج واضحة: فهو يستقبل الرسالتين DamageTake و BreakableDestroyed من البريمج Breakable ويقوم بناء عليها بتشغيل واحد من ملفي الأصوات المحددين وهما من نوع AudioClip، واللذين يمكننا اختيارهما من نافذة الخصائص كما سنرى بعد قليل. المتغير minDamage يمكننا من اختيار الحد الأدنى للإصابة التي ستؤدي إلى تشغيل ملف الصوت، وهو افتراضيا 25. لاحظ أن تشغيل صوت الإصابة يتم بطريقة تختلف عن تشغيل صوت التدمير؛ ذلك أن الأخير لا يمكن أن يتكرر كما هو الحال في الأول. بطبيعة الحال فإن تلقي عدة إصابات في فترة قصيرة أمر ممكن، لذلك علينا أن نمنع تكرار تشغيل ملف الصوت مرات متتابعة بشكل مزعج. لأجل ذلك نقوم عند تشغيل ملف صوت الإصابة بحساب طول الملف وإضافة نصف ثانية إليه، ومن ثم تخزين الناتج في المتغير suspentionTime. عند محاولة تشغيل ملف صوت الإصابة مرة أخرى نقوم بمقارنة الوقت المنقضي منذ آخر تشغيل بوقت منع التشغيل suspentionTime. إذا كانت هذه المدة أقل من وقت المنع فلن يتم تشغيل الملف. هذه الخطوات تقوم بها الدّالة ()CheckAndPlay.

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

image13.thumb.png.18ebdf6f920112788896ad

image14.thumb.png.4deeb8e835b778385877bd

بقي أمر واحد سنضيفه كلمسة جمالية على لعبتنا، وهي حدوث انفجار مرئي عند تحطم الوحدات البنائية المتفجرة. هذا الانفجار موجود في الحزمة Explosion Pack في موقع kenney.nl والذي سبق وضع روابطه، وهو يحمل الاسم Regular Explosion. الانفجار عبارة عن 9 صور متتابعة كما ترى في الصورة في الأسفل:

image15.thumb.png.ea158b01dc0529ca85201a

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

using UnityEngine;
using System.Collections;

public class SpriteAnimator : MonoBehaviour {

   //مصفوفة تحتوي على اللقطات التي تشكل الحركة
   public Sprite[] frames;

   //سرعة التحريك مقدرة بعدد اللقطات في الثانية
   public float framesPerSecond = 16.0f;

   //هل يجب أن يتم تدمير الكائن وحذفه بعد انتهاء التحريك؟
   public bool destroyOnCompletion = true;

   //متغير لتسجيل الوقت الذي تم فيه تغيير اللقطة آخر مرة
   private float lastChange = 0.0f;

   //متغير مرجعي لمكوّن تصيير الصور ثنائية الأبعاد
   private SpriteRenderer renderer;

   //موقع اللقطة المعروضة حاليا في مصفوفة اللقطات
   private int currentFrame = 0;

   //يتم استدعاؤها مرة واحدة عند بداية التشغيل
   void Start () {
       renderer = GetComponent<SpriteRenderer>();
   }
    
   //يتم استدعاؤها مرة عند تصيير كل إطار
   void Update () {
       Animate();
   }

   //تقوم بعملية التحريك عن طريق عرض اللقطات بشكل متتابع
   void Animate()
   {
       //احسب المدة التي يجب أن تقضيها اللقطة الواحدة
       float frameTime = 1.0f / framesPerSecond;
       //قم بالتبديل للإطار التالي في حال انقضى الوقت اللازم للقطة السابقة
       if (Time.time - lastChange > frameTime)
       {
           lastChange = Time.time;
           currentFrame = (currentFrame + 1) % frames.Length;
           renderer.sprite = frames[currentFrame];
           if (currentFrame == 0 && destroyOnCompletion)
           {
               Destroy(gameObject);
           }
       }
   }
}

ببساطة شديدة يقوم هذا البريمج عند تصيير كل إطار باستدعاء الدّالة Animate، والتي تقوم بحساب الوقت اللازم لعرض كل لقطة عن طريق قسمة الرقم 1 على السرعة. بعد ذلك تقارن الوقت المنقضي منذ تم التبديل للقطة الحالية مع الوقت اللازم للقطة الواحدة، فإذا انقضى هذا الوقت تقوم بالتبديل إلى اللقطة التالية في المصفوفة frames. من البديهي هنا أن تتم إضافة لقطات الانفجار بشكل مرتب إلى المصفوفة frames وذلك حتى تظهر بالتتابع الصحيح كما هو موضح في الصورة التالية. لاحظ أخيرا أننا نقوم بتدمير الكائن بعد انتهاء عرض اللقطات لأننا لا نريد تكرار مشهد الانفجار سوى مرة واحدة.

image16.thumb.png.ff9a49000ebd7f70fb37bb

بعد إنشاء قالب الانفجار علينا أن نربط إنشاء الانفجار بتدمير الوحدة البنائية المتفجرة. هذه عملية بسيطة لا تتطلب أكثر من استقبال الرسالة BreakableDestroyed ومن ثم إنشاء كائن جديد من قالب الانفجار في نفس موقع الوحدة البنائية التي تم تدميرها. هذه المهمة يقوم بها البريمج BreakableSpawn والموضح في السرد التالي:

using UnityEngine;
using System.Collections;

public class BreakableSpawn : MonoBehaviour {

    //قالب الكائن الذي سيتم إنشاؤه بعد التدمير
    public GameObject prefab;

   //يتم استدعاؤها مرة عند بداية التشغيل
   void Start () {
    
   }
    
   //يتم استدعاؤها مرة عند تصيير كل إطار
   void Update () {
    
   }

   //BreakableDestroyed تقوم باستقبال رسالة تدمير الوحدة البنائية
   void BreakableDestroyed()
   {
       //قم بإنشاء كائن جديد كم القالب وضعه في نفس موقع الكائن الحالي الذي تم تدميره
       GameObject newObject = (GameObject)Instantiate(prefab);
       newObject.transform.position = transform.position;
       newObject.transform.parent = transform.parent;
   }
}

إنشاء كائنات الخصوم

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

كل ما علينا هو إضافة البريمجات Breakable و BreakablePieces و CollisionDamageTaker و BreakableSounds، ومن ثم تحديد القيم المناسبة لكل نوع من أنواع الوحوش. اختصارا للوقت سأعتبر الأشكال الأربعة ذات نفس الخصائص، إضافة لمنح الوحوش التي تحتوي على خوذة مقدارا أعلى من الصحة. ستكون صحة الوحشين المربع والدائري هي 50، بينما الوحش ذو الخوذة ستكون صحته 100. وهذا سينطبق على الألوان الخمسة للوحوش.

بطبيعة الحال علينا أيضا إضافة مكوّن الجسم الصلب RigidBody2D و أحد مكوّني التصادم BoxCollider2D أو CircleCollider2D وذلك حسب شكل الوحش. الشكل التالي يوضح القالب الخاص بأحد الوحوش التي ترتدي خوذة. يمكنك الاطلاع على قوالب الوحوش في المجلد Prefabs\Enemies:

image17.thumb.png.b280d141f90f198cd6cf10

هناك اختلافات أخرى يجب مراعاتها بين الوحدات البنائية والوحوش، فهناك ملفات صوتية مختلفة للإصابة والتدمير، إضافة لأن تدمير الوحوش ينتج عنه نجوم بدلا من قطع الحطام. صور النجوم هذه موجودة في المجلد PhysicsPack\Others وهي موضحة في الشكل التالي. لاحظ أيضا أن عدد النجوم الناتجة كبير نسبيا وهو 10:

image18.thumb.png.89312181e0a3a71b286afa

كل الفروق السابقة بين الوحدات البنائية والوحوش هي فروق شكلية، لكن الفرق الأهم هو أن نكون قادرين على التمييز بينهما برمجيا؛ وذلك لأن تدمير الوحوش يختلف عن تدمير الوحدات البنائية وهو ذو علاقة مباشرة بحالة اللعبة. فتدمير جميع الوحوش في المشهد سيؤدي لفوز اللاعب بالمرحلة وانتقاله للمرحلة التالية، إلا أن هذا ليس هو الحال بالنسبة للوحدات البنائية. ولأن تدمير الوحوش أمر مهم، علينا أن نضيف لقالب كل وحش منها بريمجا يقوم بإرسال رسالة للكائن الجذري حين تدمير كائن الوحش، وسيتكفل بريمج آخر سنأتي عليه بعد حين بمهمة استقبال الرسالة وعمل ما يلزم حيالها. ما يهم الآن هو أن نضيف البريمج Enemy والموضح في السرد التالي لكل قوالب الوحوش التي أنشأناها.

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {

   //يتم استدعاؤها مرة عند بداية التشغيل
   void Start () {
    
   }
    
   //يتم استدعاؤها مرة عند تصيير كل إطار
   void Update () {
    
   }

   //ومن ثم BreakableDestroyed تقوم باستقبال الرسالة
   //ترسل رسالة للأعلى أي للكائن الجذري بأن الوحش تم تدميره
   void BreakableDestroyed()
   {
       SendMessageUpwards("EnemyDestroyed");
   }
}

:بهذا أصبح لدينا كل ما يلزم لبناء مشهد، وما عليك سوى رص الوحدات البنائية والوحوش بأي ترتيب ترغب به من أجل صنع مرحلة كما في الصورة التالية

image19.thumb.png.b20f2e31042312d93740e6

.وبهذا نكون انتهينا من تجهيز مشهد اللعبة. سنقوم في الدرس القادم ببناء واجهة المستخدم والشاشة الرئيسية للعبة





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن