Hallo allerseits! Heute möchte ich über Mathematik sprechen. Mathematik ist eine sehr interessante Wissenschaft und kann bei der Entwicklung von Spielen und bei der Arbeit mit Computergrafiken sehr nützlich sein. Viele (insbesondere Anfänger) wissen einfach nicht, wie es in der Entwicklung verwendet wird. Es gibt viele Probleme, die kein tiefes Verständnis solcher Konzepte erfordern: Integrale, komplexe Zahlen, Gruppen, Ringe usw., aber dank der Mathematik können Sie viele interessante Probleme lösen. In diesem Artikel betrachten wir Vektoren und Integrale. Bei Interesse herzlich willkommen bei cat. Das Illustrating Unity-Projekt ist wie immer beigefügt.
Vektormathematik.Vektoren und Vektormathematik sind wesentliche Werkzeuge für die Spieleentwicklung. Viele Operationen und Aktionen sind vollständig damit verbunden. Es ist lustig, dass für die Implementierung einer Klasse, die den Pfeil eines Vektors in Unity anzeigt, bereits die meisten typischen Operationen ausgeführt wurden. Wenn Sie sich mit Vektormathematik auskennen, ist dieser Block für Sie nicht interessant.
Vektorarithmetik und nützliche FunktionenAnalytische Formeln und andere Details sind einfach zu googeln, sodass wir keine Zeit damit verschwenden. Die Operationen selbst werden unten durch GIF-Animationen veranschaulicht.
Es ist wichtig zu verstehen, dass jeder Punkt im Wesentlichen ein Vektor mit einem Start am Nullpunkt ist.Gifs wurden mit Unity erstellt, daher müsste eine Klasse implementiert werden, die für das Rendern von Pfeilen verantwortlich ist. Ein Vektorpfeil besteht aus drei Hauptkomponenten - einer Linie, einer Spitze und einem Text mit dem Namen eines Vektors. Um eine Linie und einen Tipp zu zeichnen, habe ich LineRenderer verwendet. Schauen wir uns die Klasse des Vektors selbst an:
Pfeilklasseusing 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; } }
Da der Vektor eine bestimmte Länge haben soll und genau den von uns angegebenen Punkten entspricht, wird die Länge der Linie nach folgender Formel berechnet:
_VectorEnd - (_VectorEnd - _VectorStart).normalized * _CupLength
In dieser Formel
(_VectorEnd - _VectorStart) ist .normalized die Richtung des Vektors. Dies kann aus der Animation mit der Differenz der Vektoren verstanden werden, vorausgesetzt, dass
_VectorEnd und
_VectorStart Vektoren mit einem Start bei (0,0,0) sind.
Als nächstes analysieren wir die beiden verbleibenden Grundoperationen:
Das Finden der Normalen (senkrecht) und der Mitte des Vektors sind sehr häufige Aufgaben bei der Entwicklung von Spielen. Analysieren wir sie am Beispiel einer Signatur auf einem Vektor.
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;
Um den Text senkrecht zum Vektor zu platzieren, benötigen wir eine Normalen. In 2D-Grafiken ist das Normale recht einfach.
var dv = _VectorEnd - _VectorStart; var normal = new Vector3(-dv.y, dv.x).normalized;
Also haben wir das Normal zum Segment gebracht.
normal = normal.y> 0? normal: -normal; - Diese Operation ist dafür verantwortlich, dass der Text immer über dem Vektor angezeigt wird.
Dann bleibt es, um es in der Mitte des Vektors zu platzieren und es normal auf eine Entfernung anzuheben, die schön aussehen wird.
_Label.transform.localPosition = (_VectorEnd + _VectorStart) / 2 + normal * TextOffsetY;
Der Code verwendet lokale Positionen, damit Sie den resultierenden Pfeil verschieben können.
Aber es ging um 2D, aber was ist mit 3D?
In 3D ist Plus oder Minus dasselbe. Nur die Normalformel unterscheidet sich, da die Normalform bereits nicht zum Segment, sondern zur Ebene geführt wird.
Skript für die Kamera 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; } }
In diesem Steuerbeispiel wird die Normalen zur Ebene verwendet, um den Endpunkt der Flugbahn nach rechts zu verschieben, damit die Schnittstelle den Planeten nicht blockiert. Normal in 3D-Grafiken ist ein normalisiertes Vektorprodukt aus zwei Vektoren. Was praktisch ist, in Unity gibt es beide Operationen und wir erhalten eine schöne kompakte Aufzeichnung:
var right = Vector3.Cross(hit.normal, Vector3.up).normalized;
Ich denke, dass für viele, die denken, dass Mathematik nicht benötigt wird und warum man sie überhaupt wissen muss, ein wenig klarer geworden ist, welche Probleme damit einfach und elegant gelöst werden können. Aber es war eine einfache Option, die jeder Spieleentwickler als Praktikant nicht kennen sollte. Erhöhen Sie die Messlatte - sprechen Sie über die Integrale.
IntegraleIm Allgemeinen haben Integrale viele Anwendungen, wie z. B. physikalische Simulationen, VFX, Analytik und vieles mehr. Ich bin jetzt nicht bereit, alles im Detail zu beschreiben. Ich möchte eine einfache und visuell verständliche beschreiben. Reden wir über Physik.
Angenommen, es gibt eine Aufgabe - ein Objekt an einen bestimmten Punkt zu verschieben. Wenn Sie beispielsweise einen bestimmten Auslöser eingeben, sollten Bücher aus den Regalen herausfliegen. Wenn Sie sich gleichmäßig und ohne Physik bewegen möchten, ist die Aufgabe trivial und erfordert keine Integrale. Wenn jedoch ein Geist ein Buch aus dem Regal schiebt, sieht eine solche Geschwindigkeitsverteilung völlig anders aus.
Was ist ein Integral?
Tatsächlich ist dies der Bereich unter der Kurve. Aber was bedeutet das im Kontext der Physik? Angenommen, Sie haben eine Geschwindigkeitsverteilung über die Zeit. In diesem Fall ist der Bereich unter der Kurve der Pfad, den das Objekt durchlaufen wird, und genau das benötigen wir.

Unity hat ein großartiges Tool namens AnimationCurve, das von der Theorie zur Praxis übergeht. Mit ihm können Sie die Verteilung der Geschwindigkeit über die Zeit festlegen. Lassen Sie uns eine solche Klasse erstellen.
Klasse 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; } }
Die GetApproxSquareAnimCurve-Methode ist unsere Integration. Wir machen es zur einfachsten numerischen Methode. Gehen Sie einfach die Werte der Funktionen durch und fassen Sie sie eine bestimmte Anzahl von Malen zusammen. Ich habe 1000 für die Wiedergabetreue eingestellt. Im Allgemeinen können Sie das Beste auswählen.
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; }
Dank dieses Bereichs wissen wir bereits, wie groß die relative Entfernung ist. Wenn wir dann die beiden zurückgelegten Wege vergleichen, erhalten wir den Geschwindigkeitskoeffizienten speedK, der dafür verantwortlich ist, dass wir die angegebene Strecke zurücklegen.
Möglicherweise stellen Sie fest, dass die Objekte nicht genau übereinstimmen. Dies liegt an einem Float-Fehler. Im Allgemeinen können Sie dasselbe dezimal neu berechnen und dann für eine höhere Genauigkeit im Float überholen.
Eigentlich ist das alles für heute. Wie immer gibt es am Ende einen
Link zum GitHub-Projekt , in dem alle Quellen für diesen Artikel enthalten sind. Und du kannst mit ihnen spielen.
Wenn der Artikel eingeht, mache ich eine Fortsetzung, in der ich Sie über die Verwendung etwas komplexerer Konzepte wie komplexe Zahlen, Felder, Gruppen und mehr informieren werde.