Detecting when a user taps a SKSpriteNode - ios

I'm new to swift programming and I decided I would make a simple game to start with SpriteKit. I have a SpriteNode that is supposed to pick 1 of 6 locations and move there when it is tapped, however from the methods I've seen I can't figure out how to implement it (again I'm new at this) Here is my code from the GameScene.swift file:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let greenTileWidth = screenSize.width * 0.5
let greenTileHeight = screenSize.height * 0.33
let greenTilePositionY = [greenTileHeight / 2, greenTileHeight / 2 + greenTileHeight, greenTileHeight / 2 + greenTileHeight * 2 ]
let greenTilePositionX = [greenTileWidth / 2, greenTileWidth / 2 + greenTileWidth]
let backgroundTile = SKSpriteNode(imageNamed: "whiteTile")
backgroundTile.size.width = screenSize.width * 100
backgroundTile.size.height = screenSize.height * 100
addChild(backgroundTile)
let greenTile = SKSpriteNode(imageNamed: "greenTile")
greenTile.size.width = greenTileWidth
greenTile.size.height = greenTileHeight
greenTile.position.y = greenTilePositionY[0]
greenTile.position.x = greenTilePositionX[0]
greenTile.userInteractionEnabled = true
addChild(greenTile)
var randomX:Int = 0
var randomY:Int = 0
func getRandomY() -> Int{
randomY = Int(arc4random_uniform(26))%3
return randomY
}
func getRandomX() -> Int{
randomX = Int(arc4random_uniform(26))%2
return randomX
}
func moveGreenTile(){
greenTile.position.x = greenTilePositionX[randomX]
greenTile.position.y = greenTilePositionY[randomY]
}
getRandomX()
getRandomY()
moveGreenTile()
}
when the SpriteNode greenTile is tapped, getRandomY() getRandomX() and moveGreenTile() should be called.

First you have to set the name attribute of your SKSpriteNodes:
greenTile.name = "greenTile"
First I see some errors in your code. The return values of getRandomX and getRandomY never get really used. Because you set the randomX and randomY variables without actually calling getRandom. So you should update it to:
func moveGreenTile(){
greenTile.position.x = greenTilePositionX[getRandomX()]
greenTile.position.y = greenTilePositionY[getRandomY()]
}
That way you only have to call moveGreenTile and it will call the getRandom methods by itself.
Then you have to use the touchesBegan method to check if the user touches the screen. So with the name you can check if the user touched the greenTile by checking the name you've set earlier:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches{
let location = touch.locationInNode(self)
let node:SKNode = self.nodeAtPoint(location)
if(node.name == "greenTile"){
moveGreenTile()
}
}
}

This code detects tap events, not only touches, on a SKSpriteNode.
You can change how sensitive the tap gesture is by modifying TapMaxDelta.
class TapNode : SKSpriteNode {
// Tap Vars
var firstPoint : CGPoint?
var TapMaxDelta = CGFloat(10)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
let texture = SKTexture(imageNamed: "Test.png")
super.init(texture: texture, color: UIColor.clear, size: texture.size())
isUserInteractionEnabled = true
}
// ================================================================================================
// Touch Functions
// ================================================================================================
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first {
firstPoint = firstTouch.location(in: self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first, let firstPoint = firstPoint {
let curPoint = firstTouch.location(in: self)
if abs(curPoint.x - firstPoint.x) <= TapMaxDelta && abs(curPoint.y - firstPoint.y) <= TapMaxDelta {
print("tap yo")
}
}
}
}

Related

SpriteKit interplay between line and node: line should give a direction and speed to the node

I would like to have the following interplay between line and ball in my game: a line gives direction and speed to the ball. The longer the line, the faster the ball.
What I have now: a ball is following the line and stops at the end of it. But it shouldn't stop here. Of course, I understand that the ball is only following the path I made. But how could I change it?
Here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// Basic for dynamic sizes step01
var width = CGFloat()
var height = CGFloat()
var ball:SKSpriteNode!
var line:SKShapeNode!
var startPoint: CGPoint!
var location = CGPoint()
override func didMove(to view: SKView) {
self.backgroundColor = .purple
//declare dynamic size of the screen
width = self.frame.size.width
height = self.frame.size.height
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.physicsWorld.gravity = CGVector.zero
createBall()
}
func createBall(){
ball = SKSpriteNode(imageNamed: "yellowBtn")
ball.position = CGPoint(x:0, y: -(height/2.5))
ball.size = CGSize(width: width/6, height: width/6)
self.addChild(ball)
}
func drawLine(endPoint:CGPoint){
if(line != nil ){ line.removeFromParent() }
startPoint = ball.position
let path = CGMutablePath()
path.move(to: startPoint)
path.addLine(to: endPoint)
line = SKShapeNode()
line.path = path
line.lineWidth = 5
line.strokeColor = UIColor.white
self.addChild(line)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(line != nil ){ line.removeFromParent()
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let moveAction = SKAction.move(to: location, duration: 10)
ball.run(moveAction)
}
override func update(_ currentTime: TimeInterval) {
}
}
I have found at least a part of answer to my question: I need to calculate CGVector manually (lastPoint - firstPoint) and then apply impulse to the ball:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let dx = location.x - startPoint.x
let dy = location.y - startPoint.y
let movement = CGVector(dx: dx, dy: dy)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 50)
ball.physicsBody?.applyImpulse(movement)
}
Now I should find the second part: how to set the speed of the ball accordingly to the length of the line.

Character is not moving in desired scene in scenekit

i want to move my character in scene kit .i have a map as at github Fpscontroller and i want my character move in this map but when ever i touch to move character it jumps and don,t know where is goes here is my code.`
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
if CGRectContainsPoint(gameView.virtualDPadBounds(), touch.locationInView(gameView)) {
if padTouch == nil {
padTouch = touch
controllerStoredDirection = float2(0.0)
}
} else if panningTouch == nil {
panningTouch = touches.first
}
if padTouch != nil && panningTouch != nil {
break
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = panningTouch {
let displacement = (float2(touch.locationInView(view)) - float2(touch.previousLocationInView(view)))
panCamera(displacement)
}
if let touch = padTouch {
let displacement = (float2(touch.locationInView(view)) - float2(touch.previousLocationInView(view)))
controllerStoredDirection = clamp(mix(controllerStoredDirection, displacement, t: ViewController.controllerAcceleration), min: -ViewController.controllerDirectionLimit, max: ViewController.controllerDirectionLimit)
}
}
func commonTouchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = panningTouch {
if touches.contains(touch) {
panningTouch = nil
}
}
if let touch = padTouch {
if touches.contains(touch) || event?.touchesForView(view)?.contains(touch) == false {
padTouch = nil
controllerStoredDirection = float2(0.0)
}
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
commonTouchesEnded(touches!, withEvent: event)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
commonTouchesEnded(touches, withEvent: event)
}
i followed apple fox example for moving with touch and in my viewcontroller
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func walkGestureRecognized(gesture: UIPanGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.Ended || gesture.state == UIGestureRecognizerState.Cancelled {
gesture.setTranslation(CGPointZero, inView: self.view)
}
}
func renderer(aRenderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
//get walk gesture translation
let translation = walkGesture.translationInView(self.view)
//create impulse vector for hero
let angle = heroNode.presentationNode.rotation.w * heroNode.presentationNode.rotation.y
var impulse = SCNVector3(x: max(-1, min(1, Float(translation.x) / 50)), y: 0, z: max(-1, min(1, Float(-translation.y) / 50)))
impulse = SCNVector3(
x: impulse.x * cos(angle) - impulse.z * sin(angle),
y: 0,
z: impulse.x * -sin(angle) - impulse.z * cos(angle)
)
heroNode.physicsBody?.applyForce(impulse, impulse: true)
let scene = gameView.scene!
let direction = characterDirection()
let groundNode = character.walkInDirection(direction, time: time, scene: scene, groundTypeFromMaterial:groundTypeFromMaterial)
setting character position and direction....
private func characterDirection() -> float3 {
let controllerDirection = self.controllerDirection()
var direction = float3(controllerDirection.x, 0.0, controllerDirection.y)
if let pov = gameView.pointOfView {
let p1 = pov.presentationNode.convertPosition(SCNVector3(direction), toNode: nil)
let p0 = pov.presentationNode.convertPosition(SCNVector3Zero, toNode: nil)
direction = float3(Float(p1.x - p0.x), 0.0, Float(p1.z - p0.z))
if direction.x != 0.0 || direction.z != 0.0 {
direction = normalize(direction)
}
}
return direction
}
there are actually two scenes called in view controller class one is empty scene called for mapNode as hereoNode and other is character sceneNode
let scene = SCNScene()
scene.rootNode.addChildNode(heroNode)
let personScene = SCNScene(named: "game.scnassets/person.scn")!
personNode = personScene.rootNode.childNodeWithName("person", recursively: true)
personNode?.position = SCNVector3(x:4.5,y:0,z:25)
node.addChildNode(personNode)
scene.rootNode.addChildNode(character.node)
i am updating my question with some more details as you can see in this screen shot image i m attatching here enter image description here i want to move this fox in map with touch but it is not working it just jump and go out from map when i touch .....plz someone tell me where is my fault in code

Giving properties of a UIButton to a SKSpriteNode in SpriteKit

I was wondering if there was a way to give the properties of a UIButton like darkening the button once it has been pressed,... to a SKSpiteNode since an SKSpiteNode has more customization and because I am using SpriteKit. I have seen 1 other question like this but none of the answers worked. Here is the code I have to create the SKSpriteNode and to detect a touch on it:
import SpriteKit
class StartScene: SKScene {
var startButton = SKSpriteNode()
override func didMoveToView(view: SKView) {
startButton = SKSpriteNode(imageNamed: "playButton")
startButton.size = CGSize(width: 100, height: 100)
startButton.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 - 50)
self.addChild(startButton)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
if startButton.containsPoint(location){
// When it has been selected
}
}
}
//...
Pleas help. Thanks in advance... Anton
I have always achieved this by adding code to the touches began, and touches ended method. Inside of these methods I simply set the sprites color to black, and then change its color blend factor. Let me know if this works for you!
//This will hold the object that gets darkened
var target = SKSpriteNode()
//This will keep track if an object is darkened
var have = false
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var first = touches.first as! UITouch
var location:CGPoint = first.locationInNode(self)
touchP = location
mouse.position = touchP
var node:SKNode = self.nodeAtPoint(location)
if let button = node as? SKSpriteNode
{
target = button
have = true
}
else
{
have = false
}
if (have == true)
{
target.color = UIColor.blackColor()
target.colorBlendFactor = 0.2
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if (havet == true)
{
target.color = UIColor.blackColor()
target.colorBlendFactor = 0
target = SKSpriteNode()
have = false
}
}

Sprite Kit stop Impulse

I want to increase a CGFloat every time while the Screen is tapped.
The float has a set maximum and minimum value.
I tried to go through this suggestion: StackOverflow
However, this only increases the CGFloat after the touch is made, or the maximum is reached. I want to increase the CGFloat during the touch, meaning the longer you touch the higher the Jump/CGFloat.
The problem probably lies within the impulse, that you cant change it after it was applied. That means, after the 'Player' gets an impulse of 20, and the screen is longer touched, the Float may increase, but the impulse won't.
If you look at my current code, the impulse is set at maximum while the screen is touched, but if released the action should be removed. However, it doesn't work, the impulse does not stop.
I know that you can set the velocity of the body at a value after the press is made, and if the press has ended the velocity back to 0 so it stops it 'jump', but that doesn't look quite smooth as it would be with an impulse.
Has anybody a solution?
struct Constants {
static let minimumJumpForce:CGFloat = 20.0
static let maximumJumpForce:CGFloat = 60.0
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var force: CGFloat = 20.0
func longPressed(longPress: UIGestureRecognizer) {
if (longPress.state == UIGestureRecognizerState.Began) {
println("Began")
self.pressed = true
let HigherJump = SKAction.runBlock({Player.physicsBody?.applyImpulse(CGVectorMake(0, Constants.maximumJumpForce))})
self.runAction(HigherJump , withKey:"HighJump")
}else if (longPress.state == UIGestureRecognizerState.Ended) {
println("Ended")
self.pressed = false
self.removeActionForKey("HighJump")
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
1.Create ‘Game’ from Xcode template based on SpriteKit
2.Copy paste listed code to GameScene class
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var location = CGPoint()
var floorSize = CGSize()
var floorColor = UIColor()
var player = SKSpriteNode()
override func didMoveToView(view: SKView) {
view.showsFPS = true;
view.showsNodeCount = true;
view.showsDrawCount = true;
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody?.categoryBitMask = 1
self.physicsBody?.contactTestBitMask = 1
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self;
location = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
player = SKSpriteNode(imageNamed:"Spaceship")
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 320, height: 320))
player.physicsBody?.categoryBitMask = 1
player.physicsBody?.collisionBitMask = 1
player.physicsBody?.contactTestBitMask = 1
player.physicsBody?.linearDamping = 0;
player.xScale = 1
player.yScale = 1
player.position = location
self.addChild(player)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
let direction = Float(1.5708)//Float(player.zRotation) + Float(M_PI_2)
player.physicsBody?.applyForce(CGVector(dx: 150000*CGFloat(cosf(direction)), dy: 150000*CGFloat(sinf(direction))))
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.physicsWorld.gravity = CGVectorMake(0, -7.9)
}
}
3.Run the app
This should give you start point for you 'Jump' game :)
Try changing this:
if(self.pressed){
let HigherJump = SKAction.runBlock({if(self.force < Constants.maximumJumpForce){
self.force += 2.0
}else{
self.force = Constants.maximumJumpForce
}})
self.runAction(HigherJump)
}
to this:
if(self.pressed){
if(self.force < Constants.maximumJumpForce) {
self.force += 2.0
}
else {
self.force = Constants.maximumJumpForce
}
}
Theres no need to use a runBlock SKAction here.

Issue with recognising touch in SKLabelNode

I have been programming in Swift for about four months now using Sprite Kit to build some simple Arcade games for IOS. Until recently I haven't had any problems with recognising touches in specific nodes. In the main screen in one of my projects, I have added another SKLabelNode for the latest addition to the app, following the same layout of implementation as the others, but this one doesn't work. When the label is tapped it is supposed run a function but doesn't even get that far, I figured out using breakpoints. Here is all of the relevant code, please have a look, I have going over this four hours and it has been driving me crazy.
import SpriteKit
let twistedLabelName = "twisted"
class StartScene: SKScene {
var play1P = SKLabelNode(fontNamed: "HelveticaNeue-Thin")
var play2P = SKLabelNode(fontNamed: "HelveticaNeue-Thin")
var playTwisted = SKLabelNode(fontNamed: "HelveticaNeue-Thin")
override func didMoveToView(view: SKView) {
initializeValues()
self.userInteractionEnabled = true
}
func initializeValues () {
backgroundColor = UIColor.whiteColor()
play1P.name = "1p"
play1P.text = "Play 1P"
play1P.fontColor = SKColor.blackColor()
play1P.fontSize = 40
play1P.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetHeight(self.frame) * 0.8)
play1P.zPosition = 100
self.addChild(play1P)
play2P.name = "2p"
play2P.text = "Play 2P"
play2P.fontColor = SKColor.blackColor()
play2P.fontSize = 40
play2P.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetHeight(self.frame) * 0.6)
play2P.zPosition = 100
self.addChild(play2P)
playTwisted.text = "Twisted"
playTwisted.fontColor = SKColor.blackColor()
playTwisted.name = twistedLabelName
playTwisted.fontSize = 40
playTwisted.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetHeight(self.frame) * 0.4)
self.addChild(playTwisted)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "1p" {
// Some function
}
else if theName == "2p" {
// Some function
}
else if theName == twistedLabelName {
// This is the one that doesn't work
// Some function
}
}
}
}
}
Note that you can extend any SKNode (including SKLabelNode) and handle touches in the subclass. Set isUserInteractionEnabled = true first though. e.g.
import SpriteKit
class MyLabelNode: SKLabelNode {
override init() {
super.init()
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print( #file + #function )
}
}

Resources