In den letzten Wochen habe ich recht intensiv unter Swift 2.n mit dem SpriteKit gearbeitet. Ein großes Ärgernis ist dabei das umständliche Hantieren mit CGPoint
, CGSize
, CGRect
– und CGVector
-Strukturen. Lästig sind auch die ständig erforderlichen Typumwandlungen zwischen den im SpriteKit üblichen CGFloat
-Zahlenformat und »gewöhnlichen« Integer- und Fließkommazahlen. Das ist umso absurder, als CGFloat
auf 64-Bit-Plattformen ohnedies eine Double
-Zahl ist. Einzig auf 32-Bit-Architekturen ist CGFloat
tatsächlich ein Float
.
Wie auch immer: Swift wäre nicht Swift, könnten wir uns das Leben nicht mit ein paar neuen Operatoren, nachträglichen Erweiterungen vorhandener Strukturen sowie mit globalen Funktionen leichter machen.
Eigene Operatoren
In Swift können Sie mühelos eigene Operatoren definieren. Der Code solte auf Anhieb verständlich sein:
// Operatoren für CGPoint-Daten
// CGPoints addieren/subtrahieren
public func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
public func += (inout left: CGPoint, right: CGPoint) {
left = left + right
}
public func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
public func -= (inout left: CGPoint, right: CGPoint) {
left = left - right
}
// CGPoint skalieren
public func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x * scalar, y: point.y * scalar)
}
public func *= (inout point: CGPoint, scalar: CGFloat) {
point = point * scalar
}
Analoge Operatoren können Sie natürlich auch für CGSize
, CGRect
und CGVector
definieren.
CGRect-Struktur um eigene Methoden erweitern
Mit extension
können Sie vorhandene Strukturen um eigene Methoden erweitern. Die folgenden drei Methoden liefern den linken unteren und den rechten oberen Eckpunkt sowie den Mittelpunkt eines Rechtecks:
public extension CGRect {
// Mittelpunkt
func middlePoint() -> CGPoint {
return CGPoint(x: CGRectGetMidX(self), y: CGRectGetMidY(self))
}
// links unten im iOS-Koordinatensystem
func minPoint() -> CGPoint {
return CGPoint(x: CGRectGetMinX(self), y: CGRectGetMinY(self))
}
// rechts oben im iOS-Koordinatensystem
func maxPoint() -> CGPoint {
return CGPoint(x: CGRectGetMaxX(self), y: CGRectGetMaxY(self))
}
}
Die folgenden Zeilen erweitern CGPoint
um nützliche Methoden:
public extension CGPoint {
// passt die Koordinaten des Punkts so an, dass dieser sich
// innerhalb des Rechtecks befindet
func withinRect(rect: CGRect) -> CGPoint {
return CGPoint(x: min(CGRectGetMaxX(rect), max(CGRectGetMinX(rect), self.x)),
y: min(CGRectGetMaxY(rect), max(CGRectGetMinY(rect), self.y)))
}
// Länge
func length() -> CGFloat {
return pythagoras(self.x, self.y)
}
// Abstand
func distanceTo(other: CGPoint) -> CGFloat {
return pythagoras(self.x-other.x, self.y-other.y)
}
}
Pythagoras
Auch die gerade verwendete Funktion pythargoras
ist selbst definiert. Sie berechnet die Hypotenuse eines rechtwinkligen Dreiecks. Die Funktion erwartet CGFloat
-Parameter und liefert natürlich auch ein CGFloat
-Ergebnis.
// Pythagoras: a^2 + b^2 = c^2
// c = pythagoras(a, b)
public func pythagoras(a:CGFloat, _ b:CGFloat) -> CGFloat {
return sqrt(a*a + b*b)
}
Update 12.9.2016: Die pythagoras
-Funktion ist überflüssig, hypot(a, b)
erfüllt dieselbe Aufgabe.
Dies und das
Beim Berechnen von Winkeln werden Sie häufig Pi al CGFloat
-Zahl brauchen. Die Konstante pi
minimiert die Tipparbeit. (Wenn Sie möchten, können Sie auch das Symbol π anstelle von pi
verwenden.)
cgrand()
liefert eine Zufallszahl zwischen 0 und 1 als CGFloat
. Achten Sie darauf, dass Sie in der Initialisierung Ihres Programms (z.B. in viewDidLoad
) den Zufallszahlengenerator initialisieren, beispielsweise so: srand48(Int(arc4random_uniform(100000000)))
. Vergessen Sie darauf, erhalten Sie bei jedem Programmablauf (bei jedem Spiel) immer wieder dieselben Zufallszahlen.
Die generische Funktion minMax
stellt sicher, dass ein Zahlenwert innerhalb der Schranken lower
und upper
bleibt.
// Pi
let pi = CGFloat(M_PI)
// CGFloat-Zufallszahlen
public func cgrand() -> CGFloat {
return CGFloat(drand48())
}
// ergebnis = minMax(x, lower, upper)
// stellt sicher, dass lower <= ergebnis <= upper gilt
public func minMax<T : Comparable>(value: T, minimum: T, maximum: T) -> T {
return max(min(value, maximum), minimum)
}
Update 12.9.2016: In Swift 3 ist die Definition von pi
überflüssig, es gibt .pi
vordefiniert für die Datentypen Double
, Float
und CGFloat
.
Links
Hier ist der komplette Code (es sind nur rund 100 Zeilen): CGOperators.swift.zip
Natürlich bin ich nicht der Erste, der auf die Idee gekommen ist, den Umgang mit CG-Strukturen eleganter zu gestalten. Sie finden im Internet diverse Bibliotheken, die allesamt deutlich umfangreicher sind als der oben präsentierte Code. Besonders empfehlenswert sind Vector-
und ScalarArithmetic
:
- https://github.com/seivan/VectorArithmetic
- https://github.com/seivan/ScalarArithmetic
- https://www.raywenderlich.com/80818/operator-overloading-in-swift-tutorial
Egal, ob eigener Code oder fertige Bibliotheken aus dem Internet: spätestens mit dem Update auf Swift 3 wird wieder alles durcheinander geraten, sind Updates an eigenem oder fremden CG-Code erforderlich. Schön wäre es, wenn sich Apple die Mühe machen würde, diesen Code selbst zu schreiben und standardmäßig zur Verfügung zu stellen und so nicht alle SpriteKit-Programmierer zu zwingen, sich entweder in Abhängigkeit von fremdem Code zu begeben oder Code wie hier beschrieben selbst zu verfassen, was ja eigentlich auch schade um die Zeit ist.
Kleiner Klugscheißerhinweis: floating point = Gleitkomma
(fließen = flow, float = schwimmen, schweben, gleiten)