I am trying to work with SpriteKit and create an elbow joint that bends to allow movement of another joint. The problem is that when I create the joint, the parts all rotate 360 degrees as I swipe across the screen to action the movement, so there is no fixed joint that is kept (i.e. an elbow that has a range of movement). I have attached some of my code - some parts are commented out as I've been experimenting.
var lowerTorso: SKNode!
var upperTorso: SKNode!
var upperArmFront: SKNode!
var lowerArmFront: SKNode!
var upperArmBack: SKNode!
var lowerArmBack: SKNode!
var fistFront: SKNode!
var cricketBat: SKNode!
var bottom: SKNode!
let upperArmAngleDeg: CGFloat = 10
let lowerArmAngleDeg: CGFloat = 150
let batAngleDeg: CGFloat = 270
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0, dy: -20)
physicsWorld.speed = 0.1
//2
lowerTorso = childNode(withName: "torso_lower")
upperTorso = lowerTorso.childNode(withName: "torso_upper")
upperArmFront = upperTorso.childNode(withName: "arm_upper_front")
lowerArmFront = upperArmFront.childNode(withName: "arm_lower_front")
upperArmBack = upperArmFront.childNode(withName: "arm_upper_back")
lowerArmBack = upperArmBack.childNode(withName: "arm_lower_back")
cricketBat = lowerArmFront.childNode(withName: "cricketBat")
fistFront = cricketBat.childNode(withName: "fist_front")
let joint = SKPhysicsJointPin.joint(withBodyA: upperArmFront.physicsBody!,
bodyB: lowerArmFront.physicsBody!,
anchor: CGPoint(x: upperArmFront.frame.midX, y: upperArmFront.frame.midY))
physicsWorld.add(joint)
let joint2 = SKPhysicsJointPin.joint(withBodyA: lowerArmFront.physicsBody!,
bodyB: cricketBat.physicsBody!,
anchor: CGPoint(x: lowerArmFront.frame.maxX, y: lowerArmFront.frame.maxY))
physicsWorld.add(joint2)
/*
lowerArmFront.reachConstraints = SKReachConstraints(lowerAngleLimit: CGFloat(50).degreesToRadians(), upperAngleLimit: 160)
upperArmFront.reachConstraints = SKReachConstraints(lowerAngleLimit: CGFloat(-90).degreesToRadians(), upperAngleLimit: CGFloat(120).degreesToRadians())
lowerArmBack.reachConstraints = SKReachConstraints(lowerAngleLimit: CGFloat(50).degreesToRadians(), upperAngleLimit: 160)
upperArmBack.reachConstraints = SKReachConstraints(lowerAngleLimit: CGFloat(0).degreesToRadians(), upperAngleLimit: CGFloat(10).degreesToRadians())
*/
}
func punchAt(_ location: CGPoint) {
// 1
let punch = SKAction.reach(to: location, rootNode: upperArmFront, duration: 0.1)
/*
// 2
let restore = SKAction.run {
self.upperArmFront.run(SKAction.rotate(toAngle: self.upperArmAngleDeg.degreesToRadians(), duration: 0.1))
self.lowerArmFront.run(SKAction.rotate(toAngle: self.lowerArmAngleDeg.degreesToRadians(), duration: 0.1))
self.cricketBat.run(SKAction.rotate(toAngle: self.batAngleDeg.degreesToRadians(), duration: 0.1))
}
*/
fistFront.run(punch)
// 3
//fistFront.run(SKAction.sequence([punch, restore]))
}
// 3
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touched = true
for touch: AnyObject in touches {
location = touch.location(in: self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
location = touch.location(in: self)
}
}
According to the documentation here: https://developer.apple.com/reference/spritekit/skphysicsjointpin
you can set rotation limits on the pin joint via the joint's properties:
var shouldEnableLimits: Bool A Boolean value that indicates whether
the pin joint’s rotation is limited to a specific range of values.
var lowerAngleLimit: CGFloat The smallest angle allowed for the pin
joint, in radians.
var upperAngleLimit: CGFloat The largest angle allowed for the pin
joint, in radians.
Related
I am wanting my sprite "Character" to move and rotate "follow" my finger as I move it around. I had it working fine but somehow have managed to screw it up. I'm not sure what I've done but can't for the life of me figure it out.
Any help would be greatly appreciated. Im sure its something stupid that I'm just overlooking. Thanks in advance.
Mark
GameScene.swift
import SpriteKit
import GameplayKit
//MARK: ---------------Global Variables Section
var nodeType: String = String()
var characterNode = Character()
let characterImage: String = String("default_pose")
var characterTexture:SKTexture = SKTexture()
let characterSize: CGSize = CGSize(width: 64, height: 64)
let characterPosition: CGPoint = CGPoint(x: 0, y: 0)
var enemyNode = Enemy()
let enemyImage: String = String("blob1")
var enemyTexture = SKTexture()
let enemySize: CGSize = CGSize(width: 50, height: 50)
let enemyPosition: CGPoint = CGPoint(x: 150, y: 150)
class GameScene: SKScene, SKPhysicsContactDelegate {
//MARK: --------------------Class Variables Section
var affectedByGravity: Bool = false
var dynamic: Bool = true
var rotation: Bool = false
var lastUpdateTime: CFTimeInterval = 0
//MARK: --------------------DidMove to View Section
override func didMove(to view: SKView) {
var node = SKSpriteNode()
node = addNode(NodeType: "Character")
applyPhysics(physicsNode: node, nodeImage: characterImage, nodeSize: characterSize, nodeName: node.name!)
node.zPosition = 10
addChild(node)
node = addNode(NodeType: "Enemy")
applyPhysics(physicsNode: node, nodeImage: enemyImage, nodeSize: enemySize, nodeName: node.name!)
node.zPosition = 9
addChild(node)
}
//MARK: -----------------------Add Node Section
func addNode(NodeType: String) -> SKSpriteNode {
var node = SKSpriteNode()
switch NodeType {
case "Enemy":
node = enemyNode.setupNode(nodeName: "Enemy", nodeImage: "blob1", nodeSize: enemySize, nodePosition: enemyPosition)
default:
node = characterNode.setupNode(nodeName: "Player", nodeImage: "default_pose", nodeSize: characterSize, nodePosition: characterPosition)
}
return node
}
//MARK: ------------------------Physics Section
func applyPhysics(physicsNode: SKSpriteNode, nodeImage: String, nodeSize: CGSize, nodeName: String) {
let nodeTexture: SKTexture = SKTexture(imageNamed: nodeImage)
physicsNode.physicsBody = SKPhysicsBody(texture: nodeTexture, size: nodeSize)
physicsNode.physicsBody?.isDynamic = dynamic
physicsNode.physicsBody?.affectedByGravity = affectedByGravity
physicsNode.physicsBody?.allowsRotation = rotation
physicsNode.physicsBody?.categoryBitMask = 1
physicsNode.physicsBody?.contactTestBitMask = 1
physicsNode.physicsBody?.collisionBitMask = 1
}
//MARK: ------------------------Movement and Rotation Section
func moveAndRotate(sprite: Character, toPosition position: CGPoint) {
let angle = atan2(position.y - sprite.position.y, position.x - sprite.position.x)
let rotateAction = SKAction.rotate(toAngle: angle - .pi / 2, duration: 0.05, shortestUnitArc:true)
sprite.run(rotateAction)
let offsetX = position.x - (sprite.position.x)
let offsetY = position.y - (sprite.position.y)
let normal = simd_normalize(simd_double2(x: Double(offsetX), y:Double(offsetY)))
characterNode.velocity = CGVector(dx: CGFloat(normal.x) * sprite.movePointsPerSecond, dy: CGFloat(normal.y) * sprite.movePointsPerSecond)
}
//MARK: ----------------------Touches Section
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
characterNode.isMoving = true
moveAndRotate(sprite: characterNode, toPosition: touchLocation)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
moveAndRotate(sprite: characterNode, toPosition: touchLocation)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
characterNode.isMoving = false
}
//MARK: ------------------Update Section
override func update(_ currentTime: TimeInterval) {
let deltaTime = max(1.0/30, currentTime - lastUpdateTime)
lastUpdateTime = currentTime
update(character: characterNode, dt: deltaTime)
}
func update(character: Character, dt: CFTimeInterval) {
if characterNode.isMoving == true {
let newX = character.position.x + character.velocity.dx * CGFloat(dt)
let newY = character.position.y + character.velocity.dy * CGFloat(dt)
character.position = CGPoint(x: newX, y: newY)
}
}
}
Character.swift
import SpriteKit
import GameplayKit
class Character: SKSpriteNode {
let movePointsPerSecond: CGFloat = 150.0
var velocity = CGVector(dx: 0.0, dy: 0.0)
var isMoving = false
var node: SKSpriteNode = SKSpriteNode()
func setupNode(nodeName: String, nodeImage: String, nodeSize: CGSize, nodePosition: CGPoint) -> SKSpriteNode {
node = SKSpriteNode(imageNamed: nodeImage)
node.name = nodeName
node.position = nodePosition
return node
}
}
Your node does not move because you never assign characterNode to the node on your screen
override func didMove(to view: SKView) {
var node = SKSpriteNode()
node = addNode(NodeType: "Character")
applyPhysics(physicsNode: node, nodeImage: characterImage, nodeSize: characterSize, nodeName: node.name!)
node.zPosition = 10
addChild(node)
characterNode = node //ADD THIS LINE
node = addNode(NodeType: "Enemy")
applyPhysics(physicsNode: node, nodeImage: enemyImage, nodeSize: enemySize, nodeName: node.name!)
node.zPosition = 9
addChild(node)
}
I am building a spritekit game and have just started the project. I have a circle on the screen which starts in the center, and when i drag my finger from the circle outward, it will show a dotted line/bezierpath connected to the ball which will help the user see where it is aiming the ball. When the user lifts their finger, the ball will shoot in the opposite direction of the aim line. (Think a game like soccer stars or pool). The issue is that the maneuver works the first time when everything starts in the middle: I drag my finger and the ball shoots in opposite direction then stops. But when I try it again, the position of the aiming line says it is the same as the ball (It should be), but then it shows up like an inch away from the ball on the screen. I feel like this may be an issue that the scene(s) behind the objects may not be the same size? But I'm confused because I think I'm only using one scene.
GameViewController viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.size = view.bounds.size
//scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
GameScene Code (Doubt you need all of it but whatever):
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 35)
var touchingBall = false
var aimLine = SKShapeNode()
var startAimPoint = CGPoint()
var endAimPoint = CGPoint()
let damping:CGFloat = 0.94
override func didMove(to view: SKView) {
ball.fillColor = SKColor.orange
ball.name = "ball"
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
var physicsBody = SKPhysicsBody(circleOfRadius: 35)
ball.physicsBody = physicsBody
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.friction = 10.0
ball.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOUCHES BEGAN.")
for touch in touches {
print("TB: \(touchingBall)")
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "ball" {
// touched inside node
if ball.physicsBody!.angularVelocity <= 0.0{
touchingBall = true
startAimPoint = ball.position
print(touchingBall)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOCUHES MOVED.")
for touch in touches {
let location = touch.location(in: self)
if touchingBall{
endAimPoint = location
assignAimLine(start: startAimPoint, end: endAimPoint)
print("Moving touched ball")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches ended. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x - startAimPoint.x) * 3, dy: -(endAimPoint.y - startAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches cancelled. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x - startAimPoint.x) * 3, dy: -(endAimPoint.y - startAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
print(ball.physicsBody!.velocity)
let dx2 = ball.physicsBody!.velocity.dx * damping
let dy2 = ball.physicsBody!.velocity.dy * damping
ball.physicsBody!.velocity = CGVector(dx: dx2, dy: dy2)
}
func assignAimLine(start: CGPoint, end: CGPoint){
aimLine.removeFromParent()
var bezierPath = UIBezierPath()
bezierPath.move(to: start)
bezierPath.addLine(to: shortenedEnd(startPoint: start, endPoint: end))
var pattern : [CGFloat] = [10.0, 10.0]
let dashed = SKShapeNode(path: bezierPath.cgPath.copy(dashingWithPhase: 2, lengths: pattern))
aimLine = dashed
aimLine.position = ball.position
aimLine.zPosition = 0
self.addChild(aimLine)
}
func hypotenuse(bp: UIBezierPath) -> Double{
var a2 = bp.cgPath.boundingBox.height * bp.cgPath.boundingBox.height
var b2 = bp.cgPath.boundingBox.width * bp.cgPath.boundingBox.width
return Double(sqrt(a2 + b2))
}
func hypotenuse(startP: CGPoint, endP: CGPoint) -> Double{
var bezierPath = UIBezierPath()
bezierPath.move(to: startP)
bezierPath.addLine(to: endP)
return hypotenuse(bp: bezierPath)
}
func shortenedEnd(startPoint: CGPoint, endPoint: CGPoint) -> CGPoint{
var endTemp = endPoint
//while hypotenuse(startP: startPoint, endP: endTemp) > 150{
endTemp = CGPoint(x: endTemp.x / 1.01, y: endTemp.y / 1.01)
//}
return endTemp
}
func addTestPoint(loc: CGPoint, color: UIColor){
var temp = SKShapeNode(circleOfRadius: 45)
temp.fillColor = color
temp.position = loc
self.addChild(temp)
}
}
I tried printing the frame size for the scene and it says 400 something x 700 something (I am testing on iPhone 6 Plus), and it says the UIScreen is same size so i don't know what issue is. Overall, I just need the aiming line to be on the center of the circle more than just the first time I try the maneuver. Thanks.
Like I mentioned in the comments, your problem was how you were laying out your paths. The code below makes the path relative to the ball instead of absolute to the scene. I also fixed the issue with creating new shapes every time.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 35)
var touchingBall = false
var aimLine = SKShapeNode()
var endAimPoint = CGPoint()
override func didMove(to view: SKView) {
ball.fillColor = SKColor.orange
ball.name = "ball"
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
ball.position = CGPoint(x: frame.midX, y: frame.midY)
let physicsBody = SKPhysicsBody(circleOfRadius: 35)
physicsBody.affectedByGravity = false
physicsBody.friction = 10.0
physicsBody.linearDamping = 0.94
ball.physicsBody = physicsBody
self.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOUCHES BEGAN.")
for touch in touches {
print("TB: \(touchingBall)")
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "ball" {
// touched inside node
if ball.physicsBody!.angularVelocity <= 0.0{
touchingBall = true
aimLine.path = nil
self.addChild(aimLine)
print(touchingBall)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOCUHES MOVED.")
for touch in touches {
let location = touch.location(in: self)
if touchingBall{
endAimPoint = self.convert(location, to: ball)
assignAimLine(end: endAimPoint)
print("Moving touched ball")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches ended. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x) * 3, dy: -(endAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches cancelled. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x) * 3, dy: -(endAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
print(ball.physicsBody!.velocity)
//let dx2 = ball.physicsBody!.velocity.dx * damping
//let dy2 = ball.physicsBody!.velocity.dy * damping
//ball.physicsBody!.velocity = CGVector(dx: dx2, dy: dy2)
}
func assignAimLine(end: CGPoint){
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint.zero)
bezierPath.addLine(to: end)
let pattern : [CGFloat] = [10.0, 10.0]
aimLine.position = ball.position
aimLine.path = bezierPath.cgPath.copy(dashingWithPhase: 2, lengths: pattern)
aimLine.zPosition = 0
}
func addTestPoint(loc: CGPoint, color: UIColor){
var temp = SKShapeNode(circleOfRadius: 45)
temp.fillColor = color
temp.position = loc
self.addChild(temp)
}
}
I've been able to detect degrees, but I'm unsure on how to move the sprite in 360 degrees.
I don't want the sprite to be able to only move in certain sections, as shown in the picture above, but rather have it be able to move in a full circle.
Code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if (ball.frame.contains(location)) {
stickActive = true
}else {
stickActive = false
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if (stickActive == true) {
var v = CGVector(dx: location.x - base.position.x, dy: location.y - base.position.y)
let angle = atan2(v.dy, v.dx)
var deg = angle * CGFloat(180 / M_PI)
print(deg + 180)
let lenght:CGFloat = base.frame.size.height / 2 - 20
let xDist: CGFloat = sin(angle - 1.57079633) * lenght
let yDist: CGFloat = cos(angle - 1.57079633) * lenght
ball.position = CGPoint(x: base.position.x - xDist, y: base.position.y + yDist)
if (base.frame.contains(location)) {
ball.position = location
}else {
ball.position = CGPoint(x: base.position.x - xDist, y: base.position.y + yDist)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if (stickActive == true) {
let move: SKAction = SKAction.move(to: base.position, duration: 0.2)
move.timingMode = .easeOut
ball.run(move)
}
}
This is super simple, although it took me quite awhile to figure out how to do it.
TouchesBegan method:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if isTracking == false && DPad.contains(location) {
isTracking = true
}
}
}
TouchesMoved method:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location: CGPoint = touch.location(in: self)
if isTracking == true {
v = CGVector(dx: location.x - DPad.position.x, dy: location.y - DPad.position.y)
let angle = atan2(v.dy, v.dx)
let deg = angle * CGFloat(180 / Double.pi)
let Length:CGFloat = DPad.frame.size.height / 2
let xDist: CGFloat = sin(angle - 1.57079633) * Length
let yDist: CGFloat = cos(angle - 1.57079633) * Length
xJoystickDelta = location.x - DPad.position.x
yJoystickDelta = location.y - DPad.position.y
if DPad.contains(location) {
thumbNode.position = location
} else {
thumbNode.position = CGPoint(x: DPad.position.x - xDist, y: DPad.position.y + yDist)
}
}
}
}
TouchesEnded method:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isTracking = false
thumbNode.run(SKAction.move(to: DPad.position, duration: 0.01))
xJoystickDelta = 0
yJoystickDelta = 0
}
The update(_ currentTime:) method:
if v.dx > abs(v.dy) {
yourPlayer.texture = SKTexture(imageNamed: "rightTexture")
} else if v.dx < -abs(v.dy) {
player.texture = SKTexture(imageNamed: "leftTexture")
} else if v.dy < 0 {
yourPlayer.texture = SKTexture(imageNamed: "frontTexture")
} else if v.dy > 0 {
yourPlayer.texture = SKTexture(imageNamed: "backTexture")
}
//This code moves your character where-ever you want it too
let xScale = CGFloat(4) //adjust to your preference. Higher means slower, lower means faster
let yScale = CGFloat(4) //adjust to your preference. Higher means slower, lower means faster
let xAdd = xScale * self.xJoystickDelta
let yAdd = yScale * self.yJoystickDelta
yourPlayerNode.position.x += xAdd
yourPlayerNode.position.y += yAdd
These things need to be outside your didMove(toView:) method:
var xJoystickDelta:CGFloat = 0
var yJoystickDelta:CGFloat = 0
var v = CGVector()
var isTracking:Bool = false
var DPad = SKSpriteNode()
var thumbNode = SKSpriteNode()
-Explanation-
In the touchesBegan method, the if-statement is testing to see if you are not controlling the thumbNode and if your touch is inside the DPad Node. Then it starts the tracking.
In the touchesMoved once isTracking == true it starts calculating the neccessary math and then adjusting the various things required. (its complicated, what matters most is that it works.)
In the touchesEnded method, it is testing to see when you lift your finger off of the screen, then it resets everything for the next usage.
In the update(_ current:) method, the code is calculating the angle of the CGVector and then setting a texture (or whatever you want to do) inside of the various cases. Then its calculating the position of the thumbNode inside the DPad and moving your player (or whatever you need to move) around in the scene. Adjust the xScale and yScale floats higher to slow down the movement, and lower to increase the movement of whatever you are trying to move.
-Extra neccessary stuff-
You need to set up the DPad and thumbNode inside your didMove(toView:) method:
thumbNode.size = CGSize(width: 50, height: 50)
DPad.size = CGSize(width: 150, height: 150)
DPad.position = CGPoint(x: 0, y: 0)
thumbNode.position = DPad.position
DPad.zPosition = 3
thumbNode.zPosition = 4
DPad.texture = SKTexture(imageNamed: "yourBaseTexture")
thumbNode.texture = SKTexture(imageNamed: "yourStickTexture")
self.addChild(thumbNode)
self.addChild(DPad)
You only have to move the DPad.position to wherever you want. The thumbNode will move along with it. Also, if you have any problems be sure to ask me so I can help you.
I'm trying to set a SKShapeNode that rotating around fixed point depend on user's swipe gesture. to do so i'm using the code that answered here.
when I set the node as a rectangle - it's working just fine, the problem is when I trying to set the node as a circle (as I needed it to be). I have no idea why its happening, and what makes the difference.
here is the code that works (SKShapeNode as a rectangle):
override func didMove(to view: SKView) {
let ball = SKShapeNode(rect: CGRect(x: 100, y: 100, width: 100, height: 100))
ball.fillColor = SKColor.red
ball.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
ball.name = "BALL"
ball.physicsBody = SKPhysicsBody(circleOfRadius: 200)
ball.physicsBody?.angularDamping = 0.25
ball.physicsBody?.pinned = true
ball.physicsBody?.affectedByGravity = false
self.addChild(ball)
let om = 5.0
let er = 4.0
let omer = atan2(om, er)
print("om = \(om), er = \(er), atan2(omer) = \(omer)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in:self)
let node = atPoint(location)
if node.name == "BALL" {
let dx = location.x - node.position.x
let dy = location.y - node.position.y
// Store angle and current time
startingAngle = atan2(dy, dx)
startingTime = touch.timestamp
node.physicsBody?.angularVelocity = 0
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in:self)
let node = atPoint(location)
if node.name == "BALL" {
let dx = location.x - node.position.x
let dy = location.y - node.position.y
let angle = atan2(dy, dx)
// Calculate angular velocity; handle wrap at pi/-pi
var deltaAngle = angle - startingAngle!
if abs(deltaAngle) > CGFloat.pi {
if (deltaAngle > 0) {
deltaAngle = deltaAngle - CGFloat.pi * 2
}
else {
deltaAngle = deltaAngle + CGFloat.pi * 2
}
}
let dt = CGFloat(touch.timestamp - startingTime!)
let velocity = deltaAngle / dt
node.physicsBody?.angularVelocity = velocity
// Update angle and time
startingAngle = angle
startingTime = touch.timestamp
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
startingAngle = nil
startingTime = nil
}
but when i change the SKShapeNode (inside my didMove function) to circle is just dont work - here is the changed code:
override func didMove(to view: SKView) {
let ball = SKShapeNode(circleOfRadius: 200)
ball.fillColor = SKColor.red
ball.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
ball.name = "BALL"
ball.physicsBody = SKPhysicsBody(circleOfRadius: 200)
ball.physicsBody?.angularDamping = 0.25
ball.physicsBody?.pinned = true
ball.physicsBody?.affectedByGravity = false
self.addChild(ball)
}
does anyone see/ know what am I dong wrong?
How would you expect to see that the shape is turning? Do you have something inside the shapenode with a texture that you can actually see rotating?
A turning circle shapenode looks the same as a static one. In the update loop of the scene grab the ball and print its zRotation to see if it's actually turning.
override func update(_ currentTime: TimeInterval) {
var ball: SKShapeNode = childNodeWithName("BALL") as SKShapeNode
print(ball.zRotation)
}
(this code was written without access to Swift so you might have to fix the syntax a tiny bit)
Remember the zRotation is measured in radians so it might not look how you expect but it it's changing then the SKShapeNode is actually rotating, you just can't see it.
You'll have to add a child node that has something visible on it.
What is a good way to continuously track the velocity of a user moving their finger up and down on a sprite without lifting the finger? And if they don't go fast enough(set a minimum) to perform an action.
This is some code you can use to drag a sprite across the screen at different speeds depending on your finger acceleration. You can add your own code to detect the magnitude of change and perform whichever action you want..
class GameScene: SKScene {
// time values
var delta:NSTimeInterval = NSTimeInterval(0)
var last_update_time:NSTimeInterval = NSTimeInterval(0)
// our sprite
let sprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 20, height: 30))
// tracking difference between touches
var currentTouchY: CGFloat = CGFloat(0)
var lastTouchY: CGFloat = CGFloat(0)
// the magnitude of change between current and last touch
var forceMag: CGFloat = CGFloat(0)
// max mag so our sprite doesnt travel at crazy speed
let forceMagMax: CGFloat = CGFloat(7)
// direction of our sprites travel
var forceDir: CGFloat = CGFloat(0)
// arbitrary speed we want sprite to move
let velocity: CGFloat = CGFloat(70)
// we use this to stop moving our sprite when we let go
var intervalsWithoutChange = 0
override func didMoveToView(view: SKView) {
sprite.position.x = 100
sprite.position.y = size.width/2
self.addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let yCoord = location.y
lastTouchY = yCoord
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let yCoord = location.y
intervalsWithoutChange = 0
currentTouchY = yCoord
let diff = currentTouchY - lastTouchY
forceMag = abs(diff)
forceMag = forceMag > forceMagMax ? forceMagMax : forceMag
forceDir = diff < 0 ? -1 : 1
lastTouchY = yCoord
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
intervalsWithoutChange = 1
currentTouchY = 0
lastTouchY = 0
}
override func update(currentTime: NSTimeInterval) {
if last_update_time == 0.0 {
delta = 0
} else {
delta = currentTime - last_update_time
}
last_update_time = currentTime
if intervalsWithoutChange > 0 {
forceMag -= CGFloat(15) * CGFloat(delta)
}
if forceMag < 0.01 {
forceMag = 0
}
let change = forceDir * pow(forceMag, 1.29)
sprite.position.y += change * CGFloat(delta) * velocity
}
}