how to define the area of a node to make it a button - swift

AS always, I am not familiar with swift and SpriteKit, so apologies.
I am creating a button that changes scene. I am using a simple code that detects if the touch is in the SKNode area, if yes it changes the scene.
My problem is that the node.position is defined by a CGPoint, not an area, so when you touch the screen you never actually touch the node.
Any suggestion?
I don't know how to solve the problem.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
if touchedNode.name == "B" {
let menuScene = MenuScene(size: view!.bounds.size)
view!.presentScene(menuScene)
}
}
}

SOLVED! I created the node "B" with text initialiser and not with name initialiser, that is why I could not find it.
let b = SKLabelNode(name: "B")
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let nodeB = self.childNode(withName: "B")
let frameB = nodeB!.calculateAccumulatedFrame()
let minX = frameB.minX
let maxX = frameB.maxX
let minY = frameB.minY
let maxY = frameB.maxY
if location.x > minX && location.x < maxX {
if location.y > minY && location.y < maxY {
let gameScene = GameScene(size: view!.bounds.size)
view!.presentScene(gameScene)
}
}
}
}

Related

SpriteKit - Touch moved, how to prevent node jump to touch location

I wrote code for touch moved like this:
"override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
player.position = location"
The problem is the node/player will jump to wherever I touch and moved in the scene.
How may I fix this? Thank you.
I am going to answer this in pseudo code for two reasons, I am here to help you not do it for you and as a beginner figuring out how to do this yourself could be greatly beneficial.
var ball: SKSpriteNode!
var minWidth: CGFloat = 0
var maxWidth: CGFloat = 0
var startPosX: CGFloat = 0
var startBallPosX: CGFloat = 0
func didMove() {
//did move func of the scene
//setup ball and min and max widths
ball = SKSpriteNode()
addChild(ball)
minWidth = scene.frame.minX
maxWidth = scene.frame.maxY
}
func touchesBegan() {
let location = touch.location(in: self)
startPosX = location.x
startBallPosX = ball.position.x
}
func touchesMoved() {
let location = touch.location(in: self)
var moveXValue = location.x - startPosX
if startBallPosX + moveXValue > maxWidth {
//if ball wants to go past right edge of screen keep it at the edge
ball.position.x = maxWidth
}
else if startBallPosX + moveXValue < minWidth {
//if ball wants to go past left edge of screen keep it at the edge
ball.position.x = minWidth
}
else {
ball.position.x = startBallPosX + moveXValue
}
}

Sprite follows finger when dragged on screen Spritekit

I am attempting to create a walking animation where the sprite moves towards a touch location that can be dragged around the screen. I have managed to get Xcode to print updated X and Y coordinates where the finger is dragged but the sprite will barely move and I haven't been able to solve this issue. The touchesMoved has correctly printed the x and y coordinates but the moveGuy function will not perform correctly.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches { self.touchMoved(toPoint: touch.location(in: self))
let location = touch.location(in: self)
print(location)
moveGuy(location: location)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guyMoveEnded()
}
func moveGuy(location: CGPoint) {
var multiplierForDirection: CGFloat
// 2
let guySpeed = frame.size.width / 3.0
// 3
let moveDifference = CGPoint(x: location.x - guy.position.x, y: location.y - guy.position.y)
let distanceToMove = sqrt(moveDifference.x * moveDifference.x + moveDifference.y * moveDifference.y)
// 4
let moveDuration = distanceToMove / guySpeed
// 5
if moveDifference.x < 0 {
multiplierForDirection = -1.0
} else {
multiplierForDirection = 1.0
}
guy.xScale = abs(guy.xScale) * multiplierForDirection
// 1
if guy.action(forKey: "walkingInPlaceguy") == nil {
// if legs are not moving, start them
animateguy()
}
if guy.position == (location) {
guyMoveEnded()
}
// 2
let moveAction = SKAction.move(to: location, duration:(TimeInterval(moveDuration)))
// 3
let doneAction = SKAction.run({ [weak self] in
self?.guyMoveEnded()
})
// 4
let moveActionWithDone = SKAction.sequence([moveAction, doneAction])
guy.run(moveActionWithDone, withKey:"guyMoving")
}

Bullet Firing In Direction Of Tap

So far, my app has a big ball in the middle and a small ball in the middle also. I would like to be able to tap anywhere on the screen and the small ball shoots in that direction. I've heard people say about creating vectors but I can't seem to get these working in swift 3. I am a beginner so sorry about a stupid question!
Here is my code:
var mainBall = SKSpriteNode(imageNamed: "Ball")
override func didMove(to view: SKView) {
mainBall.size = CGSize(width: 300, height: 300)
mainBall.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
self.addChild(mainBall)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
print(position.x)
print(position.y)
}
for touch in (touches) {
touch.location(in: self)
let smallBall = SKSpriteNode(imageNamed: "Ball")
smallBall.position = mainBall.position
smallBall.size = CGSize(width: 100, height: 100)
smallBall.physicsBody = SKPhysicsBody(circleOfRadius: smallBall.size.width / 2)
smallBall.physicsBody?.affectedByGravity = false
self.addChild(smallBall)
}
}
You can use actions to animate SKSprideNodes:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let newPosition = touch.location(in: self)
...
//assuming self.smallBall exists and is visible already:
[self.smallBall runAction:[SKAction moveTo:newPosition duration:1.0]];
}

Single click needed to run function instead of double click SpriteKit

Im making a flappy bird clone and when the bird dies, the spriteNode with restart button pop ups, but the fist click is stoping animation (if there any) and second click forse the restart() function
heres how i make SpriteNode menu with button:
let menu = SKSpriteNode(texture: self.groundTex)
menu.name = "menu"
menu.position = CGPoint(x: 0, y: 0)
menu.zPosition = 20
let restartButton = SKSpriteNode(texture: self.heroTexture)
restartButton.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
restartButton.zPosition = 40
restartButton.name = "restart"
let moveMenu = SKAction.moveTo(CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2), duration: 1.0)
self.menuNode.addChild(menu)
menuNode.addChild(restartButton)
self.addChild(menuNode)
menu.runAction(SKAction.sequence([
moveMenu,
SKAction.waitForDuration(NSTimeInterval(1.0)),
makeGameEnd
]), withKey: "gameover"
here is how i detect touch:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let location = touch?.locationInNode(self)
let node: SKNode = nodeAtPoint(location!)
if node.name == "restart" {
restart()
}
UPDATE
my restart():
func restart() {
let scene = GameScene(size: self.size)
scene.scaleMode = .AspectFill
self.view?.presentScene(scene)
}
You can check for a double tap like this
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let location = touch?.locationInNode(self)
let node: SKNode = nodeAtPoint(location!)
if touch.tapCount % 2 == 0 {
if node.name == "restart" {
restart()
}
}
}

Drag Rotate a Node around a fixed point

I'm trying to create a spinable node similar to the "prize wheel" in this question. So far I have the flinging capability, adding angular impulses on a physics body using a UIPanGestureRecognizer that works really well. I can also stop the spinning using a touch.
Now I'm trying to allow fine adjustment of the wheel using a drag or swipe gesture so if the player isn't happy with what they end up with they can manually spin/drag/rotate it to their favoured rotation.
Currently I save the location of the touch in the touchesBegan and try to increment the zRotation of my node in the update loop.
The rotation doesn't follow my finger and is jerky. I'm not sure if I'm getting an accurate enough read on the finger movement or if the change position of the finger isn't being accurately translated into radians. I suspect detecting the touch and then dealing with it in the update isn't a great solution.
Here's my code.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent?) {
if let touch = touches.first as? UITouch {
var location = touch.locationInView(self.view)
location = self.convertPointFromView(location)
mostRecentTouchLocation = location
let node = nodeAtPoint(location)
if node.name == Optional("left") && node.physicsBody?.angularVelocity != 0
{
node.physicsBody = SKPhysicsBody(circleOfRadius:150)
node.physicsBody?.applyAngularImpulse(0)
node.physicsBody?.pinned = true
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if mostRecentTouchLocation != CGPointZero{
let node = nodeAtPoint(mostRecentTouchLocation)
if node.name == Optional("left")
{
var positionInScene:CGPoint = mostRecentTouchLocation
let deltaX:Float = Float(positionInScene.x) - Float(node.position.x)
let deltaY:Float = Float(positionInScene.y) - Float(node.position.y)
let angle:CGFloat = CGFloat(atan2f(deltaY, deltaX))
let maths:CGFloat = angle - (CGFloat(90) * (CGFloat(M_PI) / 180.0))
node.zRotation += maths
mostRecentTouchLocation = CGPointZero
}
}
}
I've spread some of the maths across multiple lines in the update to make debugging a bit easier.
I can add the PanGestureRecognizer code if needed but I'll try to keep it short for now.
EDIT
Here is my latest code based on GilderMan's recommendation. I think it's working better but the rotation is far from smooth. It's jumping in large increments and not following the finger well. Does this mean there's something wrong with my angle calculation?
override func didSimulatePhysics() {
if mostRecentTouchLocation != CGPointZero {
let node = nodeAtPoint(mostRecentTouchLocation)
if node.name == Optional("left")
{
var positionInScene:CGPoint = mostRecentTouchLocation
let deltaX:Float = Float(positionInScene.x) - Float(node.position.x)
let deltaY:Float = Float(positionInScene.y) - Float(node.position.y)
let angle:CGFloat = CGFloat(atan2f(deltaY, deltaX))
node.zRotation += angle
println(angle)
mostRecentTouchLocation = CGPointZero
}
}
}
The following code simulates a prize wheel that spins based on touch. As the user's finger moves, the wheel rotates proportionately to the speed of the finger. When the user swipes on the wheel, the wheel will spin proportionately to the velocity of the swipe. You can change the angularDamping property of the physics body to slow or increase the rate at which the wheel comes to a stop.
class GameScene: SKScene {
var startingAngle:CGFloat?
var startingTime:TimeInterval?
override func didMove(to view: SKView) {
let wheel = SKSpriteNode(imageNamed: "Spaceship")
wheel.name = "wheel"
wheel.setScale(0.5)
wheel.physicsBody = SKPhysicsBody(circleOfRadius: wheel.size.width/2)
// Change this property as needed (increase it to slow faster)
wheel.physicsBody!.angularDamping = 0.25
wheel.physicsBody?.pinned = true
wheel.physicsBody?.affectedByGravity = false
addChild(wheel)
}
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 == "wheel" {
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 == "wheel" {
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
}
}
The zRotation of a SKNode is in radians. You can remove your conversion to degrees.
You may wish to do the angle adjustment in the didSimulatePhysics in order to compute the zRotation after physics have been applied. (This may not apply directly to this situation, but is good practice further down the line.)
I tried two set of coodes below.
Code Sample 1
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
let dx = location.x
let dy = location.y
startingAngle = atan2(dx, dy)
startingTime = touch.timestamp
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
if !isRotating {
let dx = location.x
let dy = location.y
let angle = atan2(dx, dy)
wheel.zRotation = -angle
//update angle and time
startingAngle = angle
startingTime = touch.timestamp
}
}
}
}
Code Sample 2
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
let dx = location.x
let dy = location.y
startingAngle = atan2(dx, dy)
startingTime = touch.timestamp
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)
guard let safeNode = node.parent else { break }
if safeNode.name == "wheel" {
let wheel = node.parent!
if !isRotating {
let dx = location.x
let dy = location.y
let angle = atan2(dx, dy)
let deltaAngle = angle - startingAngle!
wheel.zRotation -= deltaAngle
//update angle and time
startingAngle = angle
startingTime = touch.timestamp
}
}
}
}
The only difference between Code Sample 1 & 2 is the method of finding zRotation of the wheel.
Code Sample 1 uses wheel.zRotation = -angle. It ensures wheel follows the finger position exactly. But there is a bug that the wheel will jump position when you touch two distant locations in wheel then move your fingure. I am still unable to fix this bug.
Code Sample 2 uses wheel.zRotation -= deltaAngle to stack the change in angle of each finger movement. It supposes to generate the same result of code sample 1 but it does not exactly follow the finger movement.
I found out the reason by comparing the zPosition of Code Sample 1 and 2 with same finger movement. The deltaAngle in Code Sample 2 is slightly less (up to 8 decimal places)so the wheel does not exactly follow the fingure movement.