مرحبا بالجميع! أود اليوم أن أتحدث عن الرياضيات. الرياضيات علم مثير للاهتمام للغاية ويمكن أن تكون مفيدة جدًا عند تطوير الألعاب ، وفي الواقع عند العمل مع رسومات الكمبيوتر. كثير (خاصة المبتدئين) ببساطة لا يعرفون كيف يتم استخدامه في التنمية. هناك العديد من المشاكل التي لا تتطلب فهمًا عميقًا لمفاهيم مثل: التكاملات ، والأرقام المعقدة ، والمجموعات ، والخواتم ، وما إلى ذلك ، ولكن بفضل الرياضيات يمكنك حل العديد من المشكلات المثيرة للاهتمام. في هذه المقالة ، نعتبر المتجهات والتكاملات. إذا كانت مهتمة ، مرحبا بكم في القط. يتم إرفاق مشروع توضيح الوحدة ، كما هو الحال دائمًا.
ناقلات الرياضيات.المتجهات ورياضيات المتجهات أدوات أساسية لتطوير اللعبة. ترتبط العديد من العمليات والإجراءات به بالكامل. من المضحك أنه لتنفيذ فصل يعرض سهم متجه في Unity ، فقد قام بالفعل بمعظم العمليات النموذجية. إذا كنت على دراية جيدة في الرياضيات الموجهة ، فلن تكون هذه الكتلة مثيرة للاهتمام بالنسبة لك.
متجه حسابية ووظائف مفيدةمن السهل البحث عن الصيغ التحليلية والتفاصيل الأخرى ، لذا لن نضيع الوقت في ذلك. سيتم توضيح العمليات نفسها بواسطة الرسوم المتحركة gif أدناه.
من المهم أن نفهم أن أي نقطة في الجوهر هي متجه يبدأ عند نقطة الصفر.تم إنشاء صور متحركة باستخدام Unity ، لذلك سيكون من الضروري تنفيذ فئة مسؤولة عن عرض الأسهم. يتكون سهم المتجه من ثلاثة مكونات رئيسية - خط ونصيحة ونص باسم متجه. لرسم خط ونصيحة ، استخدمت LineRenderer. لنلق نظرة على فئة المتجه نفسه:
فئة السهمusing System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; public class VectorArrow : MonoBehaviour { [SerializeField] private Vector3 _VectorStart; [SerializeField] private Vector3 _VectorEnd; [SerializeField] private float TextOffsetY; [SerializeField] private TMP_Text _Label; [SerializeField] private Color _Color; [SerializeField] private LineRenderer _Line; [SerializeField] private float _CupLength; [SerializeField] private LineRenderer _Cup; private void OnValidate() { UpdateVector(); } private void UpdateVector() { if(_Line == null || _Cup == null) return; SetColor(_Color); _Line.positionCount = _Cup.positionCount = 2; _Line.SetPosition(0, _VectorStart); _Line.SetPosition(1, _VectorEnd - (_VectorEnd - _VectorStart).normalized * _CupLength); _Cup.SetPosition(0, _VectorEnd - (_VectorEnd - _VectorStart).normalized * _CupLength); _Cup.SetPosition(1, _VectorEnd ); if (_Label != null) { var dv = _VectorEnd - _VectorStart; var normal = new Vector3(-dv.y, dv.x).normalized; normal = normal.y > 0 ? normal : -normal; _Label.transform.localPosition = (_VectorEnd + _VectorStart) / 2 + normal * TextOffsetY; _Label.transform.up = normal; } } public void SetPositions(Vector3 start, Vector3 end) { _VectorStart = start; _VectorEnd = end; UpdateVector(); } public void SetLabel(string label) { _Label.text = label; } public void SetColor(Color color) { _Color = color; _Line.startColor = _Line.endColor = _Cup.startColor = _Cup.endColor = _Color; } }
نظرًا لأننا نريد أن يكون المتجه بطول معين ويتوافق تمامًا مع النقاط التي نحددها ، يتم حساب طول الخط بالصيغة:
_VectorEnd - (_VectorEnd - _VectorStart).normalized * _CupLength
في هذه الصيغة
(_VectorEnd - _VectorStart). هو اتجاه المتجه. يمكن فهم ذلك من الرسوم المتحركة مع اختلاف المتجهات ، بافتراض أن
_VectorEnd و
_VectorStart متجهين مع بداية عند (0،0،0).
بعد ذلك ، نقوم بتحليل العمليتين الأساسيتين المتبقيتين:
يعد العثور على العادي (المتعامد) ووسط الناقل مهمة شائعة جدًا عند تطوير الألعاب. دعونا نحللهم بمثال وضع التوقيع على ناقل.
var dv = _VectorEnd - _VectorStart; var normal = new Vector3(-dv.y, dv.x).normalized; normal = normal.y > 0 ? normal : -normal; _Label.transform.localPosition = (_VectorEnd + _VectorStart) / 2 + normal * TextOffsetY; _Label.transform.up = normal;
من أجل وضع النص عموديًا على المتجه ، نحتاج إلى عادي. في الرسومات ثنائية الأبعاد ، الوضع الطبيعي بسيط للغاية.
var dv = _VectorEnd - _VectorStart; var normal = new Vector3(-dv.y, dv.x).normalized;
لذلك وصلنا إلى الوضع الطبيعي للجزء.
عادي = عادي. y> 0؟ عادي: عادي. - هذه العملية مسؤولة عن ضمان عرض النص دائمًا فوق المتجه.
ثم يبقى وضعه في منتصف المتجه ورفعه بشكل طبيعي إلى مسافة تبدو جميلة.
_Label.transform.localPosition = (_VectorEnd + _VectorStart) / 2 + normal * TextOffsetY;
يستخدم الرمز المواضع المحلية بحيث يمكنك تحريك السهم الناتج.
لكنه كان حول 2D ، ولكن ماذا عن 3D؟
في 3D ، زائد أو ناقص هو نفسه. تختلف الصيغة العادية فقط ، حيث لا يتم أخذ العادي بالفعل إلى الجزء ، ولكن إلى المستوى.
البرنامج النصي للكاميرا using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class SphereCameraController : MonoBehaviour { [SerializeField] private Camera _Camera; [SerializeField] private float _DistanceFromPlanet = 10; [SerializeField] private float _Offset = 5; private bool _IsMoving; public event Action<Vector3, Vector3, Vector3, float, float> OnMove; private void Update() { if (Input.GetMouseButtonDown(0) && !_IsMoving) { RaycastHit hit; Debug.Log("Click"); var ray = _Camera.ScreenPointToRay(Input.mousePosition); if(Physics.Raycast(ray, out hit)) { Debug.Log("hit"); var startPosition = _Camera.transform.position; var right = Vector3.Cross(hit.normal, Vector3.up).normalized; var endPosition = hit.point + hit.normal * _DistanceFromPlanet + right * _Offset; StartCoroutine(MoveCoroutine(startPosition, endPosition, hit.point + right * _Offset)); OnMove?.Invoke(startPosition, hit.point, hit.normal, _DistanceFromPlanet, _Offset); } } } private IEnumerator MoveCoroutine(Vector3 start, Vector3 end, Vector3 lookAt) { _IsMoving = true; var startForward = transform.forward; float timer = 0; while (timer < Scenario.AnimTime) { transform.position = Vector3.Slerp(start, end, timer / Scenario.AnimTime); transform.forward = Vector3.Slerp(startForward, (lookAt - transform.position).normalized, timer / Scenario.AnimTime); yield return null; timer += Time.deltaTime; } transform.position = end; transform.forward = (lookAt - transform.position).normalized; _IsMoving = false; } }
في مثال التحكم هذا ، يتم استخدام العادي إلى المستوى لتحويل نقطة النهاية للمسار إلى اليمين بحيث لا تحجب الواجهة الكوكب. عادي في الرسومات ثلاثية الأبعاد هو منتج متجه طبيعي تمت كتابته من متجهين. ما هو مناسب ، في Unity هناك كلتا هاتين العمليتين ونحصل على سجل مضغوط جميل:
var right = Vector3.Cross(hit.normal, Vector3.up).normalized;
أعتقد أنه بالنسبة للكثيرين الذين يعتقدون أن الرياضيات ليست ضرورية ولماذا تحتاج إلى معرفتها على الإطلاق ، فقد أصبح من الواضح قليلاً ما هي المشكلات التي يمكن حلها معها ببساطة وأناقة. ولكن كان خيارًا بسيطًا أن كل مطور ألعاب يجب ألا يعرف كمتدرب. ارفع الشريط - تحدث عن التكاملات.
التكاملاتبشكل عام ، تحتوي التكاملات على الكثير من التطبيقات ، مثل: المحاكاة المادية ، VFX ، التحليلات ، وأكثر من ذلك بكثير. لست على استعداد لوصف كل شيء بالتفصيل الآن. أريد أن أصف طريقة بسيطة ومفهومة بصريا. لنتحدث عن الفيزياء.
افترض أن هناك مهمة - لنقل كائن إلى نقطة معينة. على سبيل المثال ، عند دخول مشغل معين ، يجب أن تطير الكتب من الرفوف. إذا كنت ترغب في التحرك بشكل موحد وبدون فيزياء ، فإن المهمة تافهة ولا تتطلب تكاملات ، ولكن عندما يدفع شبح كتابًا من على الرف ، فإن توزيع السرعة هذا سيبدو مختلفًا تمامًا.
ما هو جزء لا يتجزأ؟
في الواقع ، هذه هي المنطقة تحت المنحنى. ولكن ماذا يعني هذا في سياق الفيزياء؟ لنفترض أن لديك توزيعًا سريعًا بمرور الوقت. في هذه الحالة ، تكون المساحة تحت المنحنى هي المسار الذي سيمر به الكائن ، وهذا بالضبط ما نحتاجه.

بالانتقال من النظرية إلى الممارسة ، تمتلك Unity أداة رائعة تسمى AnimationCurve. باستخدامه ، يمكنك تحديد توزيع السرعة بمرور الوقت. لنقم بإنشاء مثل هذا الفصل.
الطبقة MoveObj using System.Collections; using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class MoveObject : MonoBehaviour { [SerializeField] private Transform _Target; [SerializeField] private GraphData _Data; private Rigidbody _Rigidbody; private void Start() { _Rigidbody = GetComponent<Rigidbody>(); Move(2f, _Data.AnimationCurve); } public void Move(float time, AnimationCurve speedLaw) { StartCoroutine(MovingCoroutine(time, speedLaw)); } private IEnumerator MovingCoroutine(float time, AnimationCurve speedLaw) { float timer = 0; var dv = (_Target.position - transform.position); var distance = dv.magnitude; var direction = dv.normalized; var speedK = distance / (Utils.GetApproxSquareAnimCurve(speedLaw) * time); while (timer < time) { _Rigidbody.velocity = speedLaw.Evaluate(timer / time) * direction * speedK; yield return new WaitForFixedUpdate(); timer += Time.fixedDeltaTime; } _Rigidbody.isKinematic = true; } }
طريقة GetApproxSquareAnimCurve هي تكاملنا. نحن نجعلها أبسط طريقة رقمية ، ما عليك سوى مراجعة قيم الدالات وجمعها لعدد معين من المرات. قمت بتعيين 1000 للولاء ، بشكل عام ، يمكنك اختيار الأفضل.
private const int Iterations = 1000; public static float GetApproxSquareAnimCurve(AnimationCurve curve) { float square = 0; for (int i = 0; i <= Iterations; i++) { square += curve.Evaluate((float) i / Iterations); } return square / Iterations; }
بفضل هذه المنطقة ، نعرف بالفعل ما هي المسافة النسبية. ومن ثم بمقارنة المسارين المنتقلين ، نحصل على معامل السرعة speedK ، وهو المسؤول عن التأكد من أننا نسير على مسافة معينة.
قد تلاحظ أن الكائنات لا تتطابق تمامًا ، ويرجع ذلك إلى خطأ تعويم. بشكل عام ، يمكنك إعادة حسابها بالعدد العشري ، ثم تجاوزها في تعويم للحصول على دقة أكبر.
في الواقع هذا كل شيء لهذا اليوم. كما هو الحال دائمًا ، في النهاية يوجد
رابط إلى مشروع GitHub ، حيث توجد جميع مصادر هذه المقالة. ويمكنك اللعب معهم.
إذا ظهرت المقالة ، فسأقوم بتكملة سأخبرك فيها عن استخدام مفاهيم أكثر تعقيدًا قليلاً ، مثل الأرقام المعقدة والحقول والمجموعات والمزيد.