كنت قد كتبت مسودة هذا الدرس قبل مدة طويلة. ثم انقطعت عن كتابة المقالات لعدة ظروف ولم تعد لدي رغبة في ذلك. الآن أريد إتمام ما بدأت وقد ذكرت ذلك في مقال التعقيبات السابق. قمت بتحديث المسودة كذلك.
قمنا في آخر درس من سلسلة صنع لعبة Platformer ببرمجة ميكانيكية التحليق، وهنا سنقوم ببرمجة ميكانيكية السباحة.
سيكون المقال مختلفًا قليلاً هذه المرة، حيث أننا سنضيف بعض المرح عبر استخدام Shader يقوم بعملية تحريك المياه كأمواج صغيرة، ويستجيب لتصادم الأجسام مع المياه.
لهذا في البداية سنقوم بتجهيز البيئة للسباحة، وسأشرح بشكل مختصر ما هو هذا الـShader وما الذي يقوم به.
بعدها سنطبق فيزيائية السباحة (والغوص) بأسلوب سهل وسريع.
وأخيرًا، سنضيف مقطع تحريك يغير وضع وصور الشخصية أثناء السباحة، وهذا الذي سنحصل عليه:
المحتوى
أول خطوة من تجهيز البيئة هي الحصول على الـShader الذي سيقوم بمهمة تحريك الأمواج بانسيابية.
تجهيز بيئة السباحة
فيما يلي شرح مختصر عن مفهوم الـShader، أو الظلل باختصار.
ما هو الـShader؟
يمكن ترجمته إلى المُظلِل أو المُلوِّن.
هو ملف برمجي يعمل على مستوى أضلاع ورؤوس المجسمات Meshes، يتم تشغيله قبل عرض أي جسم على الشاشة.
مهمته تحديد موقع ولون وعمود normal وعدة بيانات أخرى ضرورية عن كل رأس في كل مضلع في كل جسم مرتبط به.
ماذا تعني بمرتبط به؟ اعني أنه لكل جسم أو كائن في اللعبة، يجب أن نحدد ملف برمجي معين يقوم بهذه الأمور، نحدد Shader خاص مجهز مسبقًا ليعمل على هذا الكائن.
كيف نحدد الShader لكل كائن؟ لا نقوم بتحديده مباشرة، بل نكًوّن ما يسمى بالمادة أو Material، نقوم بتعيينها للكائن.
هذه المادة يكون نوعها من نوع الـShader المطلوب، ومن خلالها نستطيع تغيير الخصائص المكشوفة مثل اللون.
لجعل الموضوع أسهل، فكر في الفرق بين الـMaterial والـShader كالفرق بين ملف برمجي يحرك سيارة، وبين عدة سيارات تستعمل هذا الملف.
لنفترض أنه لدينا ملف CarMove يحتوي على خاصية MaxSpeed. الملف يقوم بتشغيل السيارة وتحريكها فيزيائيًا، بناءً على الخصائص المعطاة.
عندما ننشئ عدة كائنات تمثل السيارات، نضيف الملف لجميع هذه السيارات ونعين سرعة كل سيارة.
مثلاً السيارة الأولى سرعتها 10 والثانية 20 والثالثة 30. كل السيارات تعمل بالمنطق ذاته والفيزيائية ذاتها، ولكن سرعاتها تختلف.
هذا هو الفرق ببساطة. الـShader يعمل كالملف CarMove، والـMaterial يعمل كالسيارات.
يمكننا إنشاء أكثر من Material يحملون النوع Shader ذاته، وليس العكس. ويمكن تغيير الخصائص لكل Material بكل أريحية.
ما الذي سيقوم به ملف Shader الماء؟
مع أن المهمة الأساسية للـShaders هي تحديد لون كل مضلع وكل جزء في المضلع للكائن بناءً على الإضاءة الموجهة على هذا المضلع..
فهذا ليس كل ما يمكنه فعله. هو أيضًا يستطيع تغيير مواقع الرؤوس لمحاكاة حركة ما، وهذا بالضبط ما سنقوم به لتمثيل حركة المياه.
الحصول على الملفات المطلوبة
سأنشئ مجلدًا في المشروع داخل المجلد Sources أسميه Shaders، وذلك بالطريقة الاعتيادية.
قمت بوضع الملفات الضرورية لمحاكاة حركة المياه في مستودع جديد على GitHub تجدونه هنا: https://github.com/YaserAlOsh/2D-Water-Shader-Unity
يوجد شرح مبسط باللغة العربية بين الأسطر للمهتمين للملفات في المستودع. وسأقوم إن شاءالله بكتابة مقال منفصل مخصص يشرح عمل هذا الShader بالتفصيل.
بدايةً، احصلوا على ملف الـShader من هذا الرابط في المستودع: https://github.com/YaserAlOsh/2D-Water-Shader-Unity/blob/master/2D%20Water.shader
يمكنكم إما تنزيل الملف بشكل مباشر أو نسخ ما فيه إلى ملف ننشئه بأنفسنا. أنصحكم بإنشاء ملف جديد للتعرف على كيفية إنشاء الـShaders
لإنشاء ملف Shader جديد نتبع الصورة:
سأعيد تسمية الملف بـ 2DWater. إذا كنتم ستسخدمون اسمًا آخر، يجب أن تعدلوا الاسم المكتوب في الملف البرمجي الموجود في المستودع.
ثم نقوم بنسخ كل ما بداخل الملف من المستودع ونلصقه داخل ملف الـShader الجديد الذي أنشأناه.
بعد ذلك، لنحصل على الملف البرمجي الذي سيقوم بتفعيل الأمواج عند وقوع أجسام في الماء.
الملف موجود في هذا المستودع أيضًا: https://github.com/YaserAlOsh/2D-Water-Shader-Unity/blob/master/WaterSplash.cs
نقوم بإنشاء ملف برمجي بالطريقة الاعتيادية، ونسميه بالاسم ذاته WaterSplash، ثم ننسخ محتوى الملف من الرابط بالكامل.
أو يمكن تنزيل الملف من الرابط مباشرة ثم نقله إلى ملفات المشروع.
قمت بوضعه داخل المجلد Scripts. نبقي الملفات هنا قليلاً ونستخدمها لاحقًا.
إنشاء Material يستخدم Shader الماء
كما ذكرت سابقًا، لاستخدام أي Shader يجب أن ننشئ مادة material اولاً تعتمد على الـShader المطلوب.
لذا سأنشئ عنصر material بهذه الطريقة أولاً:
أحب أن أرتب ملفات المشروع، لذا سأضع الملف في مجلد جديد Sources -> Materials
نضغط باليمين على اسم المجلد ثم نتبع الصورة لننشئ الـMaterial:
قمت بتسميته Water. نضغط عليه ونحصل على هذه الواجهة في المحرر:
بشكل افتراضي، يتم استخدام الـStandard Shader. وهو الافتراضي ويعمل في جميع الحالات لكن يفضل استخدامه عند الحاجة لعرض فيزيائي واقعي.
نغير الـShader المستخدم من هنا ونختار 2D Water:
الآن سنغير الخصائص بما يناسب،
أولاً، يجب أن يكون اللون شفافًا، لذا نغير قيمة الـAlpha للون الأساسي هكذا:
ثانيًا، لنحصل على صورة تمثل الماء. يمكنكم استخدام أي صورة تريدونها.
قمت بتصميم صورة خاصة يمكنكم الحصول عليها من هنا:
بعد تنزيل الصورة يمكن سحبها مباشرة إلى مكان مناسب في ملفات المشروع.
قمت بإرفاق الصورة في المشروع في مجلد جديد Source -> Textures -> Water
بعد أن يتم الإرفاق، نضغط على الصورة لتغيير إعداداتها.
أولاً نتأكد أن نوعها هو الافتراضي، نُفعل خيار التكرار، ثم نستخدم الضغط ليصبح حجمها 512x512px (أو أي حجم آخر حسب المتطلبات، يمكن إبقائها دون ضغط)
ثم عدت إلى material الماء وسحبت الصورة إلى الموضع المناسب:
بالنسبة للـNoise Texture، فهي تُستخدم لإضافة بعض العشوائية إلى حركة الماء.
يجب أن تكون صورة تختلف ألوانها بين الأسود والأبيض، أي بتدرجات الرمادي.
وهذا أمر اختياري، هنا تجدون صورة كهذه لمن أراد:
https://drive.google.com/open?id=1SifWWin2C4Ar_sEuWV2n-EmCdY8wCD7n
يمكنكم أيضًا الحصول على صورة بخصائص تناسبكم من موقع Noise Texture Generator v2.1.
ملاحظة: هذه الصورة دقتها عالية، لذا تأكدوا من تفعيل الضغط. وأيضًا، فعلوا خيار التكرار كما فعلنا مع صورة الماء.
بالنسبة لهذا الدرس، لن نستخدم الصورة العشوائية.
إعدادات الأمواج: الإعدادت الأخرى تحدد شكل أو حركة الأمواج الناتجة. هناك شرح داخل لها داخل ملف الـshader.
يمكن تغيير القيم ثم ملاحظة الحركة الناتجة لاحقا حتى نجد قيمًا مناسبة. يجب فعل ذلك أثناء تشغيل اللعبة.
وجدت بأن هذه القيم تعمل بشكل جيد بالنسبة لي:
بالطبع، قد لا تعمل هذه القيم لكم. فهذا الأمر يعتمد على حجم المستوي الذي تستخدمونه، والتأثير المطلوب. لاحقًا قوموا بالتجربة حتى تحصلوا على نتيجة جيدة.
إضافة المستوي الخاص بالماء
لتطبيق تأثيرshader الماء، نحتاج إلى مجسم ما، وهنا سنستخدم مستوي plane. ثم نٌعيّن الـmaterial الذي سبق وأنشأناه.
ننشئ الآن كائن Plane ثلاثي الأبعاد من هنا:
نضغط على المجسم لنغير خصائص في واجهة الـinspector.
أولاً نزيل الـMesh Collider فلا حاجة لنا به.
ثم بالطريقة التي تعلمناها سابقًا، نغير الإحداثيات بما يلزم.
أولا ندوره على المحور x بالزاوية 90-. ثم نكبره لحجم مناسب، قمت بتكبيره ثلاث مرات.
أخيرًا، حركته إلى مكان مناسب في المشهد:
والآن نعين مادة المادء عبر سحبها مكان الـDefault Material ضمن خصائص الـMesh Renderer:
جميل ولكن حصلنا على الصورة بالمقلوب، لنقم بعكسها:
والآن لنلاحظ الحركة:
نلاحظ بأن التموجات حادة جدًا. وقليلة أيضًا. وذلك بسبب أن المجسم الذي نستخدمه، وهو plane، يحتوي على عدد رؤوس قليل نسبيًا:
نحلها ببساطة عبر زيادة عدد الأوجه. كيف؟ نصمم مجسم Plane عبر أداة أخرى ونستخدمه محل هذا الـPlane.
يجب تصميمه بنفس معايير Unity الافتراضية، وهي الY لأعلى والـZ للأمام، وبما أننا ندورها 90 درجة، يصبح الـZ هو لأعلى والـY باتجاه الشاشة.
سأختصر عليكم الوقت، صممت مجسم Plane على برنامج Blender يتحوي على 64 وجه لكل جانب.
،قوموا بتنزيله من هنا. نوعه .FBX
بعد تنزيله وإضافته للمشروع، (أضفته بمجلد جديد Models) نضغط عليه وفي المحرر نعدل الإعدادات بالآتي:
ثم نضغط زر Apply في الأسفل.
وأخيرًا، نذهب إلى الـWater Plane مستوى الماء ونغير الجسم عن طريق الـMesh Filter:
هكذا نحصل على أمواج سلسلة أكثر بكثير.
إضافة تأثير التموج عند الاصطدام
سنستخدم الآن الملف البرمجي Water Splash الذي حصلنا عليه سابقًا لتفعيل تأثير التموج والدفقات.
ولكن هذا الملف يحتاج إلى Trigger، أي مُصادم يعمل كمؤشر أو منبه. عندما يدخل جسم ما في الحيز الخاص به، يعطي إشارة.
وبما أننا نستخدم سطح مستوي لتمثيل الماء، فاستخدام مربع للمؤشر يبدو مناسبًا. ويجب أن يكون ثنائي الأبعاد لكي يتناسب مع لعبتنا.
لهذا سأنشئ الآن كائن جديد Empty في المشهد و أضيف إليه BoxCollider2D عبر Add Component
سأضع موقع هذا الكائن الجديد في الموقع ذاته لمستوي plane الماء، ثم أغير أبعاده بما يلزم:
المهم هنا أن نفعّل الخيار trigger. ما يفعله هذا الخيار هو جعل الحيز غير مرئي للأجسام الفيزيائية. أي أن الأجسام ستخترقه وكأنه غير موجود.
ولكن كلما اخترقه جسم يحتوي على مصادم خاص به، يعطي هذا الحيّز إشارة، ثم يمكننا الاستجابة للإشارة عن طريق أي ملف برمجي مضاف إلى الجسم.
وهذا مبدأ عمل الملف البرمجي WaterSplash الذي سنضيفه الآن عبر AddComponent:
لنراجع الخصائص خطوة خطوة. الأولى هي مرجع لمجسم الماء الذي نقوم بتحريكه، والذي يستخدم shader الماء.
يجب تعيينه عبر سحب الكائن water من المشهد إلى الخانة.
الثاني حد للوقت بين كل تموج أو اصطدام وهو اختياري. الثالث هو أقل زمن للتموج الناتج ويليه الزمن الأقصى.
يتم الحصول على قيمة بينهما اعتمادًا على سرعة الجسم المخترق للماء.
يليهما خاصية السرعة التي تحدد مدى زمن وقوة التأثير.
أي كلما اقتربت سرعة وقوع الجسم على الماء من ObjSpeedMaxHit فسيبلغ تأثير الموجة الناتج أقصاها. والعكس صحيح بالنسبة لـ ObjSpeedMinHit.
وأخيرًا نسبة الزمن المستغرق لوصول التأثير إلى أقصاه.
يمكنكم الآن تشغيل اللعبة وجلب أي مجسم فيزيائي لديه Rigidbody2D و أي نوع Collider2D ثم تجربة إسقاطه على مجسم الماء.
تطبيق محاكاة فيزيائية مبسطة للماء
لدينا مشكلة بسيطة هنا وهي أن المجسم ينزل إلى الأسفل بتسارع الجاذبية دون أن يتأثر بوجود مسطح الماء.
وهذا طبيعيّ لأننا لم نضف أي خاصية فيزيائية إلى مجسم سطح الماء.
لمحاكاته بسهولة يمكننا الاستفادة من أحد المكونات الموجودة في محرك الفيزياء ثنائي الأبعاد، وهو الـArea Effector 2D
ما يفعله ببساطة هو تطبيق قوة بشكل مستمر في اتجاه معين داخل حيز الـCollider الموجود لدينا.
يمكنه أيضًا تطبيق قوة مقاومة لأي جسم يدخل الحيز، وهذا ضروري لمحاكاة مسطح ماء.
لذلك في الكائن WaterTrigger نضغط AddComponent ونتوجه إلى Physics2D -> AreaEffector2D.
الـForce Angle هو اتجاه القوة و90 يعني أنها تتجه للأعلى مباشرة. استخدمت نصف قوة الجاذبية في المقدار. ثم طبقت معامل مقاومة بقيمة اعتباطية.
مرة أخرى يجب تجربة القيم في لعبتكم وتغييرها بما يلزم.
الآن نحصل على نتيجة أفضل بكثير:
الطفو على السطح
يمكننا إضافة عنصر للسماح بالطفو على سطح الماء، حتى يستطيع اللاعب السباحة على طول المسطح المائي والخروج منه بسهولة.
نستعمل لأجل ذلك العنصر Buoyancy Effector. علينا إضافته إلى كائن جديد ووضعه في المكان المناسب:
جزء البرمجة
سنقوم الآن ببرمجة الحركة الفيزيائية للشخصية أثناء التواجد في منطقة ماء.
اكتشاف مسطح الماء
يجب علينا أولاً الحصول على إشارة عندما تصبح شخصيتنا في مسطح الماء، لنستجيب بشكل مناسب فيزيائيًا ونشغل ملفات Animation مناسبة.
هناك العديد من الطرق لفعل ذلك، وأسهلها استخدام إشارة الـOnTriggerEnter2D. وهي دالة تستدعى تلقائيا من المحرك عندما ندخل في حيز أي collider trigger
من مستندات Unity:
void OnTriggerEnter2D(Collider2D other)
other | The other Collider2D involved in this collision Sent when another object enters a trigger collider attached to this object (2D physics only). |
لكي نعرف ما إذا دخلنا حيّز الماء أم شيء آخر، سنحصل على المعلومات من other. يمكننا الحصول على اسم المجسم، الـtag، والطبقة وأشياء أخرى كثيرة.
يمكننا استخدام أي طريقة لاكتشاف الجسم. سنستخدم الـtag هنا، ونعيّنه بقيمة Water لأي مسطح ماء موجود في المشهد.
ثم سنستخدم الدالة CompareTag لنتأكد إذا كان tag الخاص بـ other مساوٍ لـWater
سنحتاج أيضًا إلى الدالة OnTriggerExit2D لنحصل على إشارة عندما تخرج الشخصية من مسطح الماء. وهي مماثلة للدالة السابقة تمامًا ولكن تُستدعى عند خروج الجسم من الحيّز.
سنقوم بإضافة الدالتين إلى الملف PlayerMovement
وأيضًا سنحتاج إلى حفظ قيمة ما تخبرنا إذا كنا داخل الماء الآن أم لا.
بإمكاننا استعمال bool ولكن تذكروا أنه لدينا State Enum قمنا بكتابته في المقال السابق (منذ أكثر من سنة!)
سنضيف اسم InWater إلى الحالات الممكنة في الملف PlayerManager (يمكننا إضافة أي اسم آخر مثل swimming)
الآن في الملف PlayerMovement:
[Header("In Water")] [SerializeField] string waterAreaTag; private void OnTriggerEnter2D(Collider2D other){ //التأكد أن الحيز هو حيز ماء if (other.gameObject.CompareTag(waterAreaTag)) { //نخبر الملف المسؤول أننا دخلنا حيز الماء playerManager.InWater(); } } private void OnTriggerExit2D(Collider2D other){ if (other.gameObject.CompareTag(waterAreaTag)) { //نخبر الملف المسؤول أننا خرجنا من حيز الماء playerManager.LeaveWater(); } }
ثم في الملف PlayerManager:
... public enum State { Standard, Flying, InWater } ... public void InWater(){ currentState = State.InWater; } public void LeaveWater(){ currentState = State.Standard; } //دالة مساعدة لمعرفة ما إذا كانت الشخصية داخل مسطح ماء public bool IsInWater(){ return currentState == State.InWater; }
برمجة السباحة أو التحكم في الماء
في داخل الملف PlayerMovement، سنضيف دالة جديدة مسؤولة عن جزء تحرك الشخصية داخل مسطح ماء.
نسميها مثلا UpdateInWater كعادتنا، ونقوم باستدعائها داخل الدالة UpdateMovement، لنجعلها تحدث كل إطار.
أعلم أني لست الأفضل في تسمية الدوال! أرجو أن تستخدموا أسماء تناسب مشاريعكم. أنا أحاول هنا أن أحافظ على الاتساق مع المقالات السابقة.
أسلوب الحركة ببساطة هو خاصية للتحرك للأعلى بسرعة عبر الدفع (أو القفز؟)، مع زمن قصير بين كل قفزة وأخرى.
وأيضًا التحرك يمنة ويسرة ولكن بسرعات مختلفة عن الحركة الاعتيادية.
ما ستقوم به هذه الدالة هو: إذا كنا داخل حيز ماء:
- احصل على اتجاه الحركة وقوة الحركة المناسبة اعتمادًا على عناصر الإدخال وعلى متغير القوة swimmingForce
- طبق القوة على الـRigidbody2D
- إذا كان اللاعب يضغط زر القفز، طبق قوة للأعلى بمقدار swimmingPushUpForce.
- لا تطبق القوة مرة أخرى إلا بعد مرور زمن minTimeBetweenPushes
بالطبع، سنضظر لإضافة المتغيرات الثلاثة في الأعلى. ستكون float ونعّين قيمها من واجهة المحرر.
... [Header("In Water")] [SerializeField] float swimmingForce = 10; [SerializeField] float swimmingPushUpForce = 10; [SerializeField] float minTimeBetweenPushes = 0.2f; ... public void UpdateMovement() { ... DoJump(); UpdateFlying(); UpdateInWater(); ... } ... float lastPushTime; public void UpdateInWater(){ if(playerManager.IsInWater()) { float x_Input = playerManager.inputHandler.horizontalMovingInputValue; //مقدار القوة ضرب متجه اليمين ضرب الاتجاه Vector2 wantedForce = Vector2.right * x_Input * swimmingForce; playerManager.rig2D.AddForce(wantedForce); //إذا كان اللاعب ضاغط لزر القفز، وقد مضى أقل زمن على القفزة السابقة if(playerManager.inputHandler.pressedJump && Time.time - lastPushTime > minTimeBetweenPushes) { //عين الزمن الحالي lastPushTime = Time.time; //طبق قوة للأعلى واستخدم Impulse لجعلها فجائية playerManager.rig2D.AddForce(swimmingPushUpForce * transform.up,ForceMode2D.Impulse); } }
تحديث متغيرات السماح بالحركة والقفز
قبل تجربة اللعبة الآن هناك شيء يجب فعله. تذكروا أنه هناك دالة MoveHorizontally وهي تطبق الحركة الاعتيادية. ولكن علينا أن نمنع استدعائها إذا كنا داخل حيز الماء.
بإمكاننا إضافة شرط قبل استدعاء الدالة، مضمونه التأكد ما إذا كانت الشخصية في حيز ماء أم لا.
أيضًا، في داخل الدالة DoJump علينا القيام ببعض التعديلات.
لذلك الآن في الملف PlayerMovement:
public void UpdateMovement() { ... if (!playerManager.IsInWater() && !playerManager.IsFlying()) MoveHorizontally(); ... } public void DoJump() { ... if ((playerManager.grounded && hasJumped && jumpTimer >= maximumJumpTime) || (playerManager.IsFlying() || playerManager.IsInWater())) hasJumped = false; if (playerManager.IsInWater()) return; ... }
تغيير طبقة سطح الماء
أعتذر ولكن هناك مشكلة أخرى علينا حلها قبل تجربة اللعبة الآن وهي أن طبقة حيّز سطح الماء أو ما أسميناه الWaterTrigger هي الآن Default..
وذلك سيسبب بعض المشاكل حيث أن الشخصية ستفترض أن هذه السطح هو سطح أرض اعتيادي وستعتبر الشخصية أنها ملامسة للأرضية.
لذلك الآن نختار الـWaterTrigger من المشهد ونغير الطبقة إلى Water، وهي تكون موجودة افتراضيًا:
يمكننا تغيير طبقة الكائن Water وهو الجسم أيضًا، ذلك ليس أمر ضروري الآن ولكن قد يكون أفضل لاحقًا.
مع إبقاء المتغيرات بقيمتها الافتراضية التي عيّناها في الأعلى، لنجرب اللعبة الآن ونتجه إلى السطح المائي:
جيد. إذا لم تحصلوا على نتيجة مشابهة بالنسبة لسطح الماء، فذلك يرجع إلى القيم التي تستخدموها في الـshader وملف WaterSplash. حاولوا تعديلها لتصبح مشابهة للقيم التي عرضتها في الأعلى.
لماذا تبدو الأمواج متعرجة أو غير سلسة بالنسبة لي؟
هذا غالبا لأننا نستخدم مسطح plane من 64 وجه في كل جانب. لنحصل على حركة سلسة أكثر، علينا زيادة عدد الأضلع. 128 وجه قد يكفي.
ولكن هذا سيسبب مشكلة في الأداء. فحتى وجود 64*64 وجه في المضلع الذي نستخدمه الآن يعد مشكلة، خاصة إذا كانت اللعبة موجهة للهواتف.
هناك طريقة لحل هذه المشكلة وهي جعل المسطح قصير، أي جعله يمتد لمسافة قصيرة فقط أسفل السطح، ربما بـ128 وجه عرضًا و10 أوجه طولاً مثلاً.
بالطبع هذا يعني أن المسافة أسفل المسطح ستكون فارغة، لذا قد نحتاج لوضع texture ثابتة مناسبة.
إذا كتبت مقالاً يشرح عمل الـshader، فسوف أتطرق لهذا الموضوع إن شاءالله.
تبقى علينا إضافة ملفات الـAnimation
إضافة Animations
سنستعمل صورتي swim قمنا بإضافتهما في المقالات السابقة.
alienGreen_swim1, alienGreen_swim2
نقوم بإنشاء ملف .anim جديد. سميته swim.anim.
ثم باستعمال واجهة الـAnimation نضغط على زر Record.
ثم نقوم بتغير صورة الsprite للكائن Character Sprite بين الصورتين. نبدأ أولًا بالصورة swim1 ثم swim2 ثم swim1 مجددًا.
كما فعلناها في المقالين السابقين. ونحصل على:
إضافة animator state
الآن سنضيف حالة لتشغيل ملف السباحة أثناء وجود اللاعب في الماء. وذلك في الـanimator controller.
نضغط عليه من ملفات المشروع أو من عنصر الـAnimator في شخصية اللاعب.
ثم نضيف الstate كالآتي:
استعملت الملف swim.anim الذي أنشأناه آنفًا.
ثم نضيف bool parameter جديد باسم swim:
الآن كل ما علينا فعله هو إنشاء transitions بين كل حالة للانتقال إلى وضع السباحة.
هنا أنشئ الانتقال بين المشي Walk و Swim:
لاحظوا الconditions فهي مهمة
ثم أكمل بنفس الوتيرة لكل الحالات الأخرى:
لم أظهر في الفيديو الانتقال الأخير من Jump إلى Swim. وهو بالشرط الآتي:
swim: false
بقي نقطة أخيرة. علينا تعديل كل الـtransitions التي قمنا بإنشائها بحيث تبدأ بشكل مباشر عند حصول الشرط. دون أي تأخير.
لذا نقوم بإلغاء تفعيل خيار Has Exit Time، واختيار قيمة 0 عند Transition Duration:
قمت بالفيديو بتطبيقها على الانتقالات من حالة Swim. لا تنسو تطبيقها إلى الحالة أيضًا.
برمجة الـAnimation
كل ما تبقى الآن هو تشغيل ملف الأنيمشين عند وجود الشخصية في الماء. سنضيف الآتي في الملف PlayerAnimation:
[SerializeField] string swimmingStatusParameter = "Swim"; public void UpdateAnimation() { ... animator.SetBool(swimmingStatusParameter, playerManager.IsInWater()); }