السلام عليكم ورحمة الله وبركاته
سنقوم في هذا المقال ببرمجة متابعة الكاميرا للشخصية بانسيابية وبشكل سلس، بعد أن قمنا ببرمجة حركات الشخصية الأفقية والقفز في المقالات السابقة.
لاحظوا أنني حددت بالتفصيل حركة الكاميرا بأنها انسيابية وسلسة Smooth، وهذا أمر مهم جدًا في الألعاب، وبشكل خاص ألعاب الـSide Scrollers أو الـPlatformers.
سنقوم بتطبيق هذه الحركة الانسيابية باستخدام نظام يسمى بالحركة التوافقية Harmonic Motion.
ولكي نفهم أهميته، سنقوم أولاً ببرمجة المتابعة الاعتيادية للكاميرا، ثم نطبق هذا النظام، ونلاحظ الفرق في النهاية
وهذا ما سنحصل عليه:
ملاحظة:
مع أن هذا المقال يعتمد على مقالين سابقين وقد يعرض ملفات برمجية أو أنظمة وترتيبات متعلقة بهما..
ولكننا سنقوم بتطبيق حركة الكاميرا بشكل منفصل قدر المستطاع، لنجعل من الممكن الاستفادة من الملفات البرمجية بأي مشروع آخر.
إضافة ملف برمجي مسؤول عن تحريك الكاميرا
نذهب إلى المجلد Scripts وفي الأعلى Create -> C# Script نضيف ملف برمجي جديد، أسميه CameraFollowPlayer.
بعدها نذهب إلى المشهد Basic Mechanics وفي الكائن Main Camera نضيف الملف البرمجي الجديد.
تطبيق أبسط أسلوب لحركة الكاميرا
أبسط ما يمكنني التفكير به هو تحريك الكاميرا إلى موقع الشخصية ذاته.
سأقوم بذلك كل إطار، بعد أن تتم حركة الشخصية الأصلية، أي بداخل الدالة LateUpdate
دالة الـLateUpdate يتم استدعائها بعد أن يتم كل إطار Update، بعد تحديث المواقع في الذاكرة وقبل أن يتم عرضها على الشاشة.
في الملف البرمجي CameraFollowPlayer سنضيف مرجع لكائن الشخصية Transform.
public class CameraFollowPlayer : MonoBehaviour{ [SerializeField] Transform target; void LateUpdate(){ //بعد كل إطار، نقوم بتعيين موقع الكاميرا لموقع الشخصية. transform.position = target.position; } }
ثم قمت بالذهاب لإعدادات عنصر الكاميرا وغيرتها كالآتي:
عند تشغيل اللعبة الآن قد لا تستطيع أن ترى شيئًا، بسبب أن الكاميرا تكون قد تحركت إلى نفس موقع الشخصية على المحور z، لذلك لا تستطيع أن ترى الشخصية ولا الأرضية.
لحل هذه المشكلة نقوم بالآتي:
... [SerializeField] Vector3 targetOffset; void LateUpdate(){ Vector3 targetPos = target.position; transform.position = targetPos + targetOffset; }
وفي الـinspector:
والآن لنجرب:
تحديد نقاط الضعف
يمكن بكل سهولة ملاحظة المشاكل في الأسلوب الذي اتبعناه:
- الكاميرا تتحرك بشكل سريع وفجائي دون أي انسيابية
- أي تغيير طفيف في موقع الشخصية يسبب تغيير في موقع الكاميرا، وهذا يجعل من الصعب متابعة اللعب أثناء القفز بشكل خاص
- لا يتم كشف المشهد بشكل كافي، حيث أنه عندما يتحرك اللاعب لليمين أو اليسار، تبقى الشخصية في المنتصف، ولا ينكشف لنا ماذا يوجد على طريق الشخصية
سنقوم بتطبيق ميزات تحل هذه المشاكل.
تطبيق حركة بالأسلوب الاعتيادي
قبل أن أشرح النمط الذي سأتبعه لتحريك الكاميرا، سأوضح تطبيق الأسوب الاعتيادي وهو باستخدام دوال الحركة الخطية Linear Interpolation أو Lerp.
هذا الأسلوب هو الأكثر شهرة، وسأطبقه لأسباب سأذكرها، عندما أذكر سلبياته.
( لمن ليس لديه وقت للقراءة، يمكن أن يختصر إلى قسم مشكلة التعليق والاهتزاز)
الحركة الخطية
فِكرتها: نبدأ من نقطة البداية A ونتوجه لنقطة النهاية B عبر رسم خط مستقيم بينهما والتحرك عليه.
عند تطبيق الحركة على هذا الخط، أي لكي نبدأ من نقطة ونتحرك رويدًا رويدًا إلى نقطة أخرى، نستعمل ما يسمى بالـInterpolation.
وهو باختصار، إذا كانت لديك نقطتان A و B، وتريد الحصول على قيمة بينهما بنسبة معينة، يمكن استقراء هذه النقطة واعتبارها موجودة عبر معادلة رياضية محددة.
هذه المعادلة تعتمد على نمط الحركة المتبع، وللحركة الخطية، نحصل على معادلة بسيطة:
(C = A( 1 – t) + B(t
حيث C هي النقطة التي تقع بينهما بالنسبة (أو الزمن) t. هذه النسبة أو الزمن يجب أن تكون بين الـ0 والـ1 للحصول على قيمة بين النقطتين الأصليتين.
عند تطبيق هذا الأسلوب على الكاميرا، نحصل على حركة غير جذابة ومملة نوعًا ما.
والسبب أنه لا يوجد تسارع. فالحركة الخطية تعني أن معدل التغير ثابت، هكذا:
ولكن هناك خدعة يمكننا توظيفها لجعل الحركة بهذا الأسلوب أفضل قليلاً.
Vector3.Lerp
هناك دالة تسمى Lerp تستعمل مع المتجهات Vector، يمكننا استخدامها للانتقال بين نقطتين، ويمكن الاعتماد عليها للحركة الخطية.
تأخذ هذه الدالة ثلاث عوامل: الموقع الابتدائي A، والموقع النهائي B، والنسبة أو الزمن. وتعطينا الموقع بين النقطتين في هذا الزمن.
هل تعمل الحركة الخطية مع متجهات أو نقاط في مستويات إحداثية متعددة الأبعاد؟
نعم، لاحظوا أننا عرّفنا النقاط A و B دون تحديد مستوىً إحداثي، أي أنه إذا كانت لدينا أي نقطتين، فسنستطيع التنقل بينهما خطيّا. ولكن هذا يلزم تعريف خط في المستوى الإحداثي. لذا إذا كانت إحدى النقطتان (أو كلتاهما) في مستوى إحداثي ثلاثي الأبعاد، فسيحتوي الخط الواصل بين النقطتين على مركبة في البعد الثالث. الدالة Vector3.Lerp تأخذ موقعين في مستوى إحداثي ثلاثي الأبعاد و Vector2.Lerp لمستوى ثنائي الأبعاد وهكذا.
بالنسبة لنا، النقطة A هي موقع الكاميرا. والنقطة B هي موقع الشخصية، والزمن t هو.. ماذا عن الزمن؟
كيف سنحدد نسبة ننتقل إليها؟ تذكروا أننا نريد الانتقال بشكل تدريجي، أي نبدأ من موقعنا الحالي ونذهب شيئًا فشيئًا إلى موقع الشخصية.
ولكننا لا نمتلك متغير يحدد لنا النسبة المطلوبة، لذلك علينا استنتاجه بأنفسنا.
هناك متغير جاهز يمكن استخدامه في المحرك Unity (أو أي محرك ألعاب آخر) يسمى بالـDelta Time، وبمكن أن تجد شرح كامل عن هذا الموضوع هنا.
أما الآن، ما يهمنا أن نعرف أن هذا الـDelta Time هو الزمن الذي استغرقه آخر إطار للقيام بعملياته.
يمكننا اعتباره أيضًا بأنه الزمن الذي يُتَوَقع أن يستغرقه الإطار الحالي للانتهاء. لنستطيع الاستفادة منه للانتقال بين الموقع الحالي والموقع المطلوب.
ولكن نحن نريد نسبة بين الموقعين، وهذه النسبة تقاس بالمتر (في المحرك Unity)
أما هذا المتغير يقاس بالثواني. لذا يجب ضربه بالسرعة المطلوبة، لنحصل على المسافة (السرعة = المسافة / الزمن، لذا المسافة = السرعة x الزمن)
مع أنه يمكننا الضرب بالسرعة 1 والحصول على معادلة صحيحة، فهذا سيجعل الحركة بطيئة جدًا.
لهذا في الملف البرمجي نفسه، سأقوم بتعريف متغير moveSpeed يحدد سرعة حركة الكاميرا (تقريبًا).
... [SerializeField] float moveSpeed = 5; void LateUpdate(){ Vector3 targetPos = target.position + targetOffset; //النسبة المطلوبة : السرعة ضرب الزمن float t = Time.deltaTime * moveSpeed; transform.position = Vector3.Lerp(transform.position, targetPos, t); } ...
والنتيجة:
ما هي الخدعة التي غيرت من تأثير الحركة؟
لاحظوا في الأعلى أنه عندما يتوقف اللاعب، وتقترب الكاميرا من موقع اللاعب، فإن سرعتها تقل شيئًا فشيئًا كلما اقتربت أكثر.
وإن دققتم النظر في الـInspector، ستجدوا أن موقع الكاميرا لا يصل حقًا إلى موقع الشخصية، فهو لا يساويه، ولكن يقترب جدًا.
مثلأً، إذا كان موقع الشخصية على المحور x هو 7، فموقع الكاميرا قد يصل إلى 6.999، ولا يصبح 7 إلا عندما يصل إلى 6.999999 ويتم تقريبه.
ماذا يعني ذلك؟ الحركة أصبحت غير خطية، فالسرعة (معدل تغير الموقع) تختلف من البداية للنهاية، وفي الحقيقة، هذا يعطي تأثيرًا أفضل.
لماذا لا أُفَضّل الاعتماد على هذا الأسلوب
مع أن التأثير أصبح أفضل، من الصعب التحكم بمعدل تغير الموقع باستخدام دالة الـLerp فقط.
وأيضًا، تذكروا متطلبنا الثاني في الأعلى، لا نريد أن تكون الحركة فجائية جدًا، وهو ما يصعب تحقيقه باستخدام هذه الطريقة. (إذا زدنا سرعة الحركة)
نعم، يمكن التحكم بالمتغير moveSpeed ولكن هذه أقصى درجة للحرية تعطينا إياها هذه الطريقة.
إذا كان هذا كافٍ لمتلطبات المشروع الخاص بكم، يمكن التوقف هنا والاعتماد عليها.
ولكنني أنصح بتجنب اتباع هذا الأسلوب، حيث أرى أن هذه الطريقة غير مستقرة ومقَيّدة.
لماذا عرَضتَها لنا إذًا!
هذه الطريقة في استخدام دالة الـ Lerp والحركة الخطية منتشرة بشكل رهيب في الكثير من المصادر التعليمية والـTutorials على الانترنت، ومع أنها في بعض الحالات تكون طريقة ممتازة، إلا أن معظم المبتدئين سيستخدمونها في غير مواضعها، مثلاً عند الحاجة للحركة بمعدل ثابت من نقطة A إلى نقطة B. أو لتحريك الكاميرا. لذلك أردتُ عرضها لكم وشرحها وذكر سلبياتها لكي تكونوا على دراية بها ومتى يمكن استخدامها.
مشكلة الاهتزاز والتعليق – Stuttering
من المحتمل أنكم قد لاحظتم اهتزاز الشخصية في المقطع أعلاه، وهذا لأننا نحرك الشخصية في الدالة FixedUpdate عبر الفيزياء.
أما الكاميرا يتم تحريكها باستخدام الدالة LateUpdate، لذلك في بعض الأحيان، لن تتوافق الإطارات الخاصة باللعبة مع إطارات الفيزياء.
ماذا تعني بأن الإطارات لن تتوافق؟
لنفترض أن الشخصية عند بداية خطوة (إطار) الفيزياء FixedUpdate كانت عند النقطة A، وبعد حساب موقعها أصبحت عند النقطة B.
ثم بدأ إطار اللعبة Update بالعمل، ولسبب ما، أخذ وقتًا طويلاً نوعًا ما (وهو ما يحدث كثيرًا)،
وقبل أن ينتهي من أشغاله، مر زمن كافي للدالة FixedUpdate لكي تعود وتحدث الفيزياء، ثم حدثت موقع الشخصية وأصبحت عند النقطة C.
بعد ذلك، انتهى الـUpdate من العمل، ووصلنا للدالة LateUpdate، والآن الكاميرا سوف تتحرك من النقطة A إلى النقطة C..
إذا كنا نُحَدّث الكاميرا في الـLateUpdate، ستظهر الشخصية وكانها انتقلت من النقطة A إلى النقطة C، أي كانها تخطت إطارًا كاملاً.
أما إذا كانت الكاميرا تُحَدّث في الـFixedUpdate، فعند العرض، سيكون موقعها متناسبًا مع موقع الشخصية في النقطة C، لذلك ستظهر الشخصية بشكل طبيعي
(تخيل أن الكاميرا مرتبطة بالشخصية، فإن تحركت بشكل مفاجئ، ستقبى الكاميرا تتحرك بشكل متناسق معها)
ولكن سيكون هناك تعليق في عرض البيئة (أي شيء عدا الشخصية أو الأجسام الفيزيائية)
في حال ما إذا مر إطاران Update بعد خطوة FixedUpdate واحدة، بغض النظر عن طريقة حركة الكاميرا، سيقل ظهور تعليق أو اهتزاز.
وذلك لأن الـUpdate ستعرض جميع المواقع بشكل متناسب.
وأيضًا، إلغاء الـVSync قد يجعل الحركة أكثر سلاسة. (لأن إلغائه قد يزيد من عدد الإطارات في اللعبة، ولكن هذه ليست فكرة جيدة)
هناك حلان لهذه المشكلة:
- استخدام الدالة FixedUpdate لتحديث موقع الكاميرا بدلاً من الدالة Late Update (فقط نقوم بتغير اسم الدالة)
- تفعيل الخاصية Interpolate على Rigidbody2D للشخصية.
بالنسبة لي سأعتمد الطريقة الثانية، لأن تحريك الكاميرا في الدالة LateUpdate يضمن أن تظهر كل الأجسام في اللعبة ، عدا الأجسام المتحركة، بشكل سلس،
أما اذا استخدمنا الدالة FixedUpdate، فبناءً على اعدادات الـVSync، قد نلاحظ أن البيئة لا تبدو سلسة عندما تتحرك الكاميرا.
تصبح إعدادات الـRigidbody2D للشخصية كالآتي:
عند تجربة اللعبة الآن، لن نلاحظ التعليق أو الاهتزاز Stuttering.
التعرف على الحـركة التـوافقية – Harmonic Motion
هي الحركة الدورية أو الذبذبية، التي تحدث بسبب قوى تعاكس اتجاه الحركة الأصلي،
مع أنها في العادة تستخدم لنمذجة حركة الزنبرك أو “البندول Pendulum”، فهي أيضًا تستخدم في كاميرات ألعاب الفيديو، وهذه مقالة توضح تطبيق نظام حركة توافقية كامل لكاميرا Third Person
الفكرة ببساطة:
لنفهمها بشكل أسرع، نتخيل أن هناك سلكًا يوصل الكاميرا بالشخصية، يحاول إبقائها قريبًا منها دائمًا.
نعتبر أن الإزاحة الأصلية للكاميرا تتجه بعيدًا عن الشخصية، أي أنه عندما تتحرك الشخصية، فالاتجاه والمسافة التي ستبعد الكاميرا عن الشخصية بها، تعتبر هي الإزاحة.
نُكَوّن متجه قوة يتجه عكس هذه الإزاحة، ثم نقوم بتغيير حجمه عبر ضربه بثابت مناسب (ثابت المرونة). أبسط شكل لنظام كهذا يكون بالمعادلة:
F = -k(xf – xi)
حيث K هو الثابت و xf – xi الإزاحة.
لنعتبر أن العجلة (التسارع) هي القوة ذاتها، حيث تكون الكتلة F = a <– 1kg
نضرب متجه العجلة بالزمن DeltaTime لنحصل على السرعة التي يجب أن نتحرك بها..
ولكن، من تعريف السرعة المتجهة:
Vf = Vi + a * dT
يجب أن نضيف هذه القيمة إلى السرعة الابتدائية. ما السرعة الابتدائية؟ تكون صفرًا في البداية، لكنها تتغير كلما تحركت الكاميرا.
لذلك سنضيف متغير جديد نسميه velocity (أو أي اسم آخر مثل v)، ثم نضيف إليه العجلة مضروبة في الزمن كل إطار، لنحصل على Vf.
بعد ذلك، من المعادلة: (حيث X تمثل موقع الكاميرا)
Xf = Xi + V * dT
نضرب السرعة النهائية بالزمن، ونضيف الناتج إلى الموقع الحالي، لنحصل على الموقع النهائي للكاميرا.
لماذا لا نستخدم المعادلة Xf = Xi + Vi*dT + 0.5*a*dT^2 لحساب الموقع؟
لأننا استخدمنا التسارع لحساب السرعة النهائية، فلا حاجة أن نستخدمه مرة أخرى. وأيضًا، لاحظوا أن هذه المعادلة تعتمد على السرعة الابتدائية، وليس السرعة النهائية. (وعند إجراء الاشتقاق، نحصل على المعادلة التي استخدمناها). ولكن يمكن استعمال هذه المعادلة واختصار خطوة.
التطبيق البرمجي
لنذهب إلى الملف CameraFollowPlayer، نحذف ما كان بداخل دالة الـLateUpdate فقط. (أو FixedUpdate، حسب الدالة التي تستخدمها)
ونضيف الآتي :
... [SerializeField] float k = 1; Vector3 newPos; void Start(){ newPos = transform.position; } void LateUpdate(){ Vector3 targetPos = target.position; newPos = HarmonicMotion(transform.position, targetPos, k, Time.deltaTime); transform.position = newPos + targetOffset; } private Vector2 velocity = Vector2.zero; private Vector2 HarmonicMotion(Vector2 first, Vector2 second, float k, float time) { //F = - kx. //x = first - second // x = second - first- Vector2 force = (second - first) * k; //vf = vi + a * time velocity += force* time; //xf = xi +vf*time return first + (velocity * time); }
الدالة Harmonic Motion تأخذ الموقعين – الابتدائي والنهائي، والثابت K – ثابت المرونة، والزمن الذي مضى deltaTime.
تطبق الدالة معادلات القوى والحركة وتعطينا الموقع الجديد. ثم في دالة الLateUpdate نحدث موقع الكاميرا بناءً على هذا الموقع.
ملاحظة: في السطر 15، نضيف الإزاحة targetOffset بعد تحديد الموقع الجديد. لكي لا تحسب الإزاحة مع الموقع المطلوب targetPos ونحصل على موقع خاطئ.
نعين قيمة للمتغير k، بالنسبة لي k = 25 كفت بالغرض.
والآن..
سنصاب بالدوار!
التخفيف والتلطيف Dampening
السبب لذلك أن الحركة هذه هي حركة ذبذبية بحتة، ستستمر هكذا مثل رقاص الساعة، فأنا لم أكمل شرح الحركة المتناسقة بعد.
هنا يأتي دور التخفيف أو التلطيف Dampening.
ما يفعله ببساطة هو تخفيف القوة المطبقة عندما تزيد السرعة نسبيَا، وهو قيمة متجهة بالتأكيد، يتجه عكس السرعة الحالية.
حسابه يكون بهذه الطريقة:
Fd = – Vi * 2 * D
حيث Fd هي قوة التخفيف (تجمع بشكل مباشر مع القوة المذبذبة الأصلية)
من الضروري استخدام السرعة الابتدائية Vi، أي قبل تغيير السرعة بالقوة المذبذبة.
D هو ثابت التخفيف. يمكننا استخدام متغير خاص آخر، ولكن سيصعب الأمر علينا لأنه يجب أن يكون متناسبًا مع الثابت k.
لذلك، سنتبع أحد الطرق وهي استخدام ضعف الجذر التربيعي للثابت k كالثابت D.
وبهذا نعدل على الدالة Harmonic Motion:
... private Vector2 HarmonicMotion(Vector2 first, Vector2 second, float k, float time) { //نغير السطر الأول فقط Vector2 force = (second - first) * k - velocity * 2 * Mathf.Sqrt(k); velocity += force* time; return first + (velocity * time); }
والآن نحصل على:
لقد ضربنا عصفورين بحجر، فالحركة الآن سلسة وانسيابية وأيضًا غير فجائية.
عرض مشهد أكبر عند الحركة
عند حركة الشخصية لليمين، نريد أن نكشف جزءًا أكبر من الجانب الأيمن، أي أن تصبح الشخصية على يسار الكاميرا، هكذا:
الخوارزمية:
لكي نجعل العملية سهلة، تذكروا أنه لدينا متغير targetPos وهو حاليا معرّف كموقع الشخصية.
ما سنقوم به ببساطة هو تغيير هذا الموقع بناءً على حركة الشخصية السابقة (إزاحة الموقع لليمين واليسار) وبعدها الكاميرا ستتحرك إلى هذا الموقع الجديد بنفسها.
لنفعل ذلك، أولاً نتأكد ما إذا تحركت الشخصية، ثم نعطي قيمة لمتغير جديد مسؤول عن الكشف، نسميه exposeSceneDisplacement مثلا.
إذا تحركت الشخصية لليمين تكون القيمة موجبة، وإذا تحركت لليسار تكون القيمة سالبة (لاحظ أن ما يهمنا هو الحركة على المحور x فقط)
بعدها نضرب هذه القيمة بمتجه ثابت بتجه لليمين، Vector3.right. نضيف المتجه الناتج إلى الموقع النهائي المطلوب
ولكننا لا نريد أن نجعل الكشف فجائي أيضًا، لذلك سنستخدم الحركة الخطية لتغيير قيمة الكشف بشكل تدريجي.
التطبيق البرمجي:
//بدلاً من المتغير السابق moveSpeed [SerializeField] float exposeSceneSpeed = 1; [SerializeField] float exposeSceneFactor = 2; float exposeSceneDisplacement; float lerpingEpsilon = 0.001f; Vector3 lastPos; void Start(){ lastPos = target.position; } float ModifiedLerp(float a, float b,float time, float epsilon) { if (Mathf.Abs(b - a) <= epsilon) return b; else return Mathf.Lerp(a, b, time); } void LateUpdate(){ Vector3 targetPos = target.position; float diff = target.position.x - lastPos.x; if (Mathf.Abs(diff) > lerpingEpsilon){ exposeSceneDisplacement = ModifiedLerp(exposeSceneDisplacement, Mathf.Sign(diff) * exposeSceneFactor, Time.deltaTime * exposeSceneSpeed , lerpingEpsilon); }else{ exposeSceneDisplacement = ModifiedLerp(exposeSceneDisplacement, 0, Time.deltaTime * exposeSceneSpeed , lerpingEpsilon); } targetPos += exposeSceneDisplacement * Vector2.right; newPos = HarmonicMotion(transform.position, targetPos, k, Time.deltaTime); transform.position = newPos + targetOffset; lastPos = target.position; ... }
الدالة Modified Lerp هي ذاتها Lerp ولكن تتأكد ما إذا اقتربت القيمتان بشكل كبير، لتعطي القيمة المطلوبة مباشرة.
ما قمنا به الآن هو أولاً أن نتأكد إذا تحركت الشخصية مسافة كافية (أكبر من epsilon، وهو رقم صغير جدًا كما عرفناه)
لتحديد المسافة التي تحركتها الشخصية، نطرح الموقع الحالي لها من موقع آخر إطار على المحور x، ولهذا احتجنا لمتغير جديد lastPos.
بعدها نقوم بتغيير قيمة متغير الكشف exposeSceneDisplacement بشكل تدريجي من القيمة الحالية إلى القيمة التي عرفناها كمعامل أقصى للكشف.
نقوم بضرب المعامل بإشارة الإزاحة، لنحصل على اتجاه صحيح كما ذكرت سابقًا.
في معامل النسبة نستخدم الطريقة السابقة، السرعة * الزمن.
وإذا لم تكن الشخصية تتحرك، نغير قيمة المتغير بشكل تدريجي إلى القيمة 0، بنفس الطريقة.
ألم أقل أن هذه طريقة سيئة؟
لا، ولكن المبرمجون يستخدمونها بطريقة خاطئة في غالب الأحيان. نعم، لا يجب استعمالها للانتقال من موقع لموقع، ولكن هنا 1) نحن نغير قيمة لا يهم ما إذا وصلت لقيمتها النهائية بشكل تام أم لا، يكفي أنها تقترب منها، 2) استخدام هذه الطريقة يعطي حركة غير خطية، مما يعني أن عملية الكشف ستتم بشكل سلس، ولن نحصل على حركة فجائية.
بعد ذلك، نضرب قيمة متغير الكشف بالمتجه الأيمن ونضيفه إلى متغير موقع الشخصية.
ثم نكمل عملية اتباع الشخصية كالسابق، وأخيرًا، نعطي قيمة لـlastPos بأنها موقع الشخصية في هذا الإطار.
قمت بتعيين القيمة 7 للمتغير exposeSceneDisplacement و 2 لـexposeSceneSpeed
النتيجة:
إضافة لمسة أخيرة
باستخدام الطرق التي عرضناها، لن تصل الكاميرا تمامًا إلى الشخصية، وهو ليس شيئًا سيئًا بالنسبة لحالتنا، ولكن كتحسين للأداء، سأقوم بتطبيق الآتي:
... void LateUpdate(){ ... targetPos += exposeSceneDisplacement * Vector3.right; if (Vector3.Magnitude(targetPos - transform.position) > lerpingEpsilon) newPos = HarmonicMotion(transform.position, targetPos, k, Time.deltaTime); else newPos = targetPos; lastPos = target.position; transform.position = newPos + targetOffset; }
ما يفعله هذا الجزء هو: بعد أن يتم حساب الموقع الذي يجب أن تتحرك إليه الكاميرا، يقارن الموقع الجديد بالموقع الحالي للكاميرا، فإذا كان هناك فرق جيد، تتحرك الكاميرا كالسابق، وإلا يتم تغيير موقعها بشكل مباشر.
هكذا نكون قد أتممنا برمجة حركة الكاميرا لتتبع الشخصية، يمكن تطبيق هذا الأسلوب على عدد من ألعاب الـPlatformers والـSide Scrollers.
وبالطبع، ما زالت هناك العديد من الخيارات والإضافات، لكن هذا يكفي كبداية.
لا تترددوا بمشاركتي رأيكم في الأسلوب الذي اتبعته، أو السؤال والاستفسار عن أي شيء لم يكن واضحًا.