How do I rotate a SpriteNode with a one finger “touch and drag” so that:
- It doesn’t jerk around
- It moves in a circle (I’ve successfully accomplished this part several times- both with a code only solution and with an SKS file)
- It produces a meaningful value (as a physical control knob would)
- It moves while my finger is on it but not when my finger is off
The things I’ve tried:
Using CGAffineTransform’s CGAffineTransformMakeRotation to effect a rotation of the knob in SpriteKit. But I cannot figure out how to use CGAffineTransformMakeRotation on a SpriteNode. I could put a different sort of object into my Scene or on top of it, but that’s just not right.
For example, Matthijs Hollemans’ MHRotaryKnob https://github.com/hollance/MHRotaryKnob
. I translated Hollemans knob from Objective C to Swift 4 but ran into trouble attempting to use it in SpriteKit to rotate sprites. I didn’t get that because I could not figure out how to use knobImageView.transform = CGAffineTransformMakeRotation (newAngle * M_PI/180.0);
in Swift with SpriteKit. I know I could use Hollemans Objective C class and push a UIImage over the top of my scene, but that doesn’t seem like the best nor most elegant solution.
I also translated Wex’s solution from Objective C to Swift Rotate image on center using one finger touch Using Allan Weir’s suggestions on dealing with the CGAffineTransform portions of the code https://mcmap.net/q/1776199/-how-could-i-skew-shear-a-sprite-via-spritekit-like-cocos2d But that doesn’t work.
I've tried setting the zRotation on my sprite directly without using .physicalBody to no avail. It has the same jerky movement and will not stop where you want it to stop. And moves in the opposite direction of your finger drag- even when you put the '-' in front of the radian angle.
I’ve also tried 0x141E’s solution on Stack Overflow: Drag Rotate a Node around a fixed point This is the solution posted below using an .sks file (somewhat modified- I've tried the un-modified version and it is no better). This solution jerks around, doesn’t smoothly follow my finger, cannot consistently move the knob to a specific point. Doesn’t matter if I set physicsBody attributes to create friction, mass, or angularDamping and linearDamping or reducing the speed of the SKSpriteNode.
I have also scoured the Internet looking for a good solution in Swift 4 using SpriteKit, but so far to no avail.
import Foundation
import SpriteKit
class Knob: SKSpriteNode
{
var startingAngle: CGFloat?
var currentAngle: CGFloat?
var startingTime: TimeInterval?
var startingTouchPoint: CGPoint?
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.setupKnob()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupKnob()
}
func setupKnob() {
self.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.size.height))
self.physicsBody?.pinned = true
self.physicsBody?.isDynamic = true
self.physicsBody?.affectedByGravity = false
self.physicsBody?.allowsRotation = true
self.physicsBody?.mass = 30.0
//self.physicsBody?.friction = 0.8
//self.physicsBody?.angularDamping = 0.8
//self.physicsBody?.linearDamping = 0.9
//self.speed = 0.1
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in:self)
let node = atPoint(location)
startingTouchPoint = CGPoint(x: location.x, y: location.y)
if node.name == "knobHandle" {
let dx = location.x - node.position.x
let dy = location.y - node.position.y
startingAngle = atan2(dy, dx)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in:self)
let node = atPoint(location)
guard startingAngle != nil else {return}
if node.name == "knobHandle" {
let dx:CGFloat = location.x - node.position.x
let dy:CGFloat = location.y - node.position.y
var angle: CGFloat = atan2(dy, dx)
angle = ((angle) * (180.0 / CGFloat.pi))
let rotate = SKAction.rotate(toAngle: angle, duration: 2.0, shortestUnitArc: false)
self.run(rotate)
startingAngle = angle
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
var touch: UITouch = touches.first!
var location: CGPoint = touch.location(in: self)
self.removeAllActions()
startingAngle = nil
startingTime = nil
}
}
Edit: If I remove the conversion to degrees and change the duration of the SKAction to 1.0 in SKAction.rotate(toAngle:duration: 1.0, shortestUnitArc:)
then it almost works: not as jerky, but still jerks; the lever doesn't change directions well- meaning sometimes if you attempt to move it opposite of the direction it was traveling it continues to go the old direction around the anchorPoint instead of the new direction you're dragging it.
Edit 2: GreatBigBore and I discussed both the SKAction rotation and the self.zRotation- the code above and the code below.
Edit 3: sicvayne suggested some code for the SKScene and I've adapted to SKSpriteNode (below). It doesn't move consistently or allow to you stop in a specific place.
import Foundation
import SpriteKit
class Knob: SKSpriteNode {
var fingerLocation = CGPoint()
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.setupKnob()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupKnob()
}
func setupKnob() {
self.isUserInteractionEnabled = true
}
func rotateKnob(){
let radians = atan2(fingerLocation.x - self.position.x, fingerLocation.y - self.position.y)
self.zRotation = -radians
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
fingerLocation = touch.location(in: self)
}
self.rotateKnob()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
/*override func update(_ currentTime: TimeInterval) { //this is a SKScene function
rotateKnob()
}*/
}
SKAction
either. – Regelaterotation
property of the sprite directly, without the transforms and stuff. – RegelatezRotation
--someone might find a clue in there. – Regelatedx
anddy
calculations seem strange. I have an app that's doing stuff duringtouchesMoved()
. IntouchesBegan()
I calculate my sprite-to-mouse offset withoffset = mousePosition - centerOfSpritePosition
, and intouchesMoved()
I set the effective offset withmousePosition + offset
. Also, myzRotation
is in radians. – Regelatedx/dy
. I figured you could just change your math to be similar to mine. – Regelate