override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "jumpbutton" {
let wait = SKAction.wait(forDuration: 3)
let boost = SKAction.applyImpulse(CGVector(dx: 0.0, dy: 160.0), duration: 0.1)
let action = SKAction.group([boost, wait])
player!.run(action)
print("jumpbutton")
}
Here is my code where I tried to set a cool down of 3 seconds before the jump button could be pressed again. Thanks for any help.
What works is creating a flag to determine whether pressing the jump button allows us to jump or it does not allow us to jump (i.e it is in cool down).
For this you create a variable called isReady near the top of the class and set it to true.
When it comes to the touches, we want to check whether the node we are touching is the jump button (touchedNode.name == "jump button"), and whether the button is ready (isReady == true). If both equate to true we can continue on. We first set the flag isReady = false, this is so we can not immediately run this section of code again (think of someone rapidly pressing the jump button).
Next part is creating the boost and running it.
Then finally, we create a wait action with a duration of 3 seconds. We run this wait action, then on completion we set the flag back to true (isReady = true).
class GameScene: SKScene {
var isReady = true
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if touchedNode.name == "jumpbutton" && isReady == true {
isReady = false
let boost = SKAction.applyImpulse(CGVector(dx: 0.0, dy: 160.0), duration: 0.1)
player!.run(boost)
print("jumpbutton")
let wait = SKAction.wait(forDuration: 3)
run(wait) { [self] in
isReady = true
}
}
}
}
You could simply implement with actions like you were inclined to do:
// Disable button
let waitAction = SKAction.wait(forDuration: coolDownTime)
run(waitAction) { [weak self] in
// Enable button
}
To expand on this answer I would like to propose the creation of a component, so you don't need to deal with every button press on your GameScene.
First create your button on the file SKButton.swift:
import SpriteKit
class SKButton: SKSpriteNode {
var coolDownTime: TimeInterval = 2.0
var isReady: Bool = true
private var originalColor: UIColor = .red
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if isReady {
print("This button was touched")
originalColor = color
color = .gray
isReady = false
let waitAction = SKAction.wait(forDuration: coolDownTime)
run(waitAction) { [weak self] in
self?.isReady = true
self?.color = self?.originalColor ?? .blue
}
}
}
}
Here is an example of it working in my GameScene:
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let myButton = SKButton()
myButton.position = CGPoint(x: frame.midX, y: frame.midY)
myButton.color = .red
myButton.size = CGSize(width: 200, height: 200)
myButton.isUserInteractionEnabled = true
addChild(myButton)
}
}
Related
I have a SKSpriteNode as a ball, it's been given all the SKPhysicsBody properties move around in all direction. What I want now is to make it unidirectional (only move in that direction it hasn't move to before and not go back in to a path it had move upon). Currently I have following thoughts on this the problem,
make a fieldBitMask, to the path that is iterated by it and repel
the ball to not go back
apply some kind of force/ impulses on the ball from touchesBegan/ touchesMoved method to keep it from going back
something that can be handled in update method
a lifesaver from stackflowoverflow, who is coding even on the weekend :)
Supporting Code snippets for better understanding,
//getting current touch position by using UIEvent methods
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchPoint = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchPoint = nil
}
//ball created
func createPlayer(){
player = SKSpriteNode(imageNamed: "player")
player.position = CGPoint(x: 220, y: 420)
player.zPosition = 1
//physics for ball
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
player.physicsBody?.allowsRotation = false
player.physicsBody?.linearDamping = 0.5
player.physicsBody?.categoryBitMask = collisionTypes.player.rawValue
player.physicsBody?.contactTestBitMask = collisionTypes.finish.rawValue
player.physicsBody?.collisionBitMask = collisionTypes.wall.rawValue
addChild(player)
}
//unwarp the optional property, calclulate the postion between player touch and current ball position
override func update(_ currentTime: TimeInterval) {
guard isGameOver == false else { return }
if let lastTouchPosition = lastTouchPoint {
//this usually gives a large value (related to screen size of the device) so /100 to normalize it
let diff = CGPoint(x: lastTouchPosition.x - player.position.x, y: lastTouchPosition.y - player.position.y)
physicsWorld.gravity = CGVector(dx: diff.x/100, dy: diff.y/100)
}
}
Well it was a combination little hacks in touchesBegan/ touchesMoved and update func,
First you need to catch on which touch occurred, get it's name (in my
case I made nodes which had alpha of 0, but become visible upon
moving over them i.e alpha 1). In touchesBegan, touchesMoved as follow
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name
{
if name == "vortex"
{
touching = false
self.view!.isUserInteractionEnabled = false
print("Touched on the interacted node")
}else{
self.view!.isUserInteractionEnabled = true
touching = true
}
}
}
Second use a BOOL touching to track user interactions, on the screen by using getting a tap recogniser setup, as follow
func setupTapDetection() {
let t = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
view?.addGestureRecognizer(t)
}
#objc func tapped(_ tap: UITapGestureRecognizer) {
touching = true
}
Finally in update put checks as follow,
guard isGameOver == false else { return }
self.view!.isUserInteractionEnabled = true
if(touching ?? true){
if let lastTouchPosition = lastTouchPoint {
//this usually gives a large value (related to screen size of the device) so /100 to normalize it
let diff = CGPoint(x: lastTouchPosition.x - player.position.x, y: lastTouchPosition.y - player.position.y)
physicsWorld.gravity = CGVector(dx: diff.x/100, dy: diff.y/100)
}
}
}
I am trying to make an ARKit app for ios and the nodes in the scene are not responding to touch. The scene is properly displayed but I haven't been able to detect any touch.
fileNamed: "TestScene" refers to a TestScene.sks file in my project which is empty and I add the node in the code as shown below.
let detailPlane = SCNPlane(width: xOffset, height: xOffset * 1.4)
let testScene = SKScene(fileNamed: "TestScene")
testScene?.isUserInteractionEnabled = true
let winner = TouchableNode(fontNamed: "Chalkduster")
winner.text = "You Win!"
winner.fontSize = 65
winner.fontColor = SKColor.green
winner.position = CGPoint(x: 0, y: 0)
testScene?.addChild(winner)
let material = SCNMaterial()
material.diffuse.contents = testScene
material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
detailPlane.materials = [material]
let node = SCNNode(geometry: detailPlane)
rootNode.addChildNode(node)
For TouchableNode I have the following class
class TouchableNode : SKLabelNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch detected")
}
}
I've achieved this affect using gesture recognize
private func registerGestureRecognizers() -> Void {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
sceneView.addGestureRecognizer(tapGestureRecognizer)
}
then have a function to handle the tap gesture
#objc private func handleTap(sender: UITapGestureRecognizer) -> Void {
let sceneViewTappedOn = sender.view as! SCNView
let touchCoordinates = sender.location(in: sceneViewTappedOn)
let hitTest = sceneViewTappedOn.hitTest(touchCoordinates)
if !hitTest.isEmpty {
let hitResults = hitTest.first!
var hitNode = hitResults.node
// do something with the node that has been tapped
}
}
}
You need to do isUserInteractionEnabled = true first.
So, something like:
class TouchableNode : 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("Touch detected")
}
}
So I recently made a swift game with sprite kit and now I am stuck.
I have a character selection screen and I want to show a description for the character if you hold on the character, but if you just touch it you choose it and play with it. I already have the code to play/show the description. I just need to know when to call the corresponding functions and how to differentiate if the node is held or just touched.
Thanks in advance
So, here is how you can do it using SKActions... Also, there is another way using SKAction but I can't really show you all the possibilities :) Anyways, here is the code which does what you want:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let kLongPressDelayActionKey = "longPressDelay"
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func singleTap(onNode:SKNode){
other.alpha = other.alpha == 0.0 ? 1.0 : 0.0
}
private func startLongPress(withDuration duration:TimeInterval){
//How long does it take before long press is fired
//If user moves his finger of the screen within this delay, the single tap will be fired
let delay = SKAction.wait(forDuration: duration)
let completion = SKAction.run({
[unowned self] in
self.desc.removeAction(forKey: self.kLongPressDelayActionKey)
self.desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
})
self.desc.run(SKAction.sequence([delay,completion]), withKey: kLongPressDelayActionKey)
}
private func stopLongPress(){
//Fire single tap and stop long press
if desc.action(forKey: kLongPressDelayActionKey) != nil{
desc.removeAction(forKey: kLongPressDelayActionKey)
self.singleTap(onNode: self.other)
//or just stop the long press
}else{
desc.removeAction(forKey: kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//To stop the long press if we slide a finger of the button
if let touch = touches.first {
let location = touch.location(in: self)
if !button.contains(location) {
stopLongPress()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
if button.contains(location) {
print("Button tapped")
startLongPress( withDuration: 1)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
stopLongPress()
}
}
You can run the code and you will see that if you tap on the purple button, a yellow box will pop up. If you tap it again, it will hide. Also, if you hold your finger on purple box for 1 second, a white box will fade in. When you release the finger, it will fade out. As an addition, I have implemented touchesMoved, so if you slide your finger of the purple button while blue box is out, it will fade out as well.
So in this example, I have fused long press with single tap. Single tap is considered to be everything that is not long press. Eg, if user holds his finger 0.01 sec, or 0.5 sec, or 0.99 sec, it will be considered as single tap and yellow box will pop out. If you hold your finger for >= 1 second, the long press action will be triggered.
Another way would be to use gesture recognizers...And I will probably make an example for that later :)
And here is how you can do it using gesture recognizers:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var longPressGesture:UILongPressGestureRecognizer!
var singleTapGesture:UITapGestureRecognizer!
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(GameScene.longPress(_:)))
singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.singleTap(_:)))
self.view?.addGestureRecognizer(longPressGesture)
self.view?.addGestureRecognizer(singleTapGesture)
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func stopLongPress(){
desc.removeAction(forKey: self.kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
func singleTap(_ sender:UITapGestureRecognizer){
if sender.state == .ended {
let location = convertPoint(fromView: sender.location(in: self.view))
if self.button.contains(location) {
self.other.alpha = self.other.alpha == 0.0 ? 1.0 : 0.0
}
}
}
func longPress(_ sender: UILongPressGestureRecognizer) {
let longPressLocation = convertPoint(fromView: sender.location(in: self.view))
if sender.state == .began {
if button.contains(longPressLocation) {
desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
}
}else if sender.state == .ended {
self.stopLongPress()
}else if sender.state == .changed {
let location = convertPoint(fromView: sender.location(in: self.view))
if !button.contains(location) {
stopLongPress()
}
}
}
}
override func didMove(to view: SKView) {
view.scene?.anchorPoint = CGPoint(x: 0,y : 0)
castle = SKShapeNode(circleOfRadius: ballRadius1)
castle.physicsBody = SKPhysicsBody(circleOfRadius:ballRadius1)
castle.fillColor = .white
castle.name = "castle"
castle.position = CGPoint(x: 0, y: 0)
castle.physicsBody?.isDynamic = false
castle.physicsBody?.affectedByGravity = false;
castle.isUserInteractionEnabled = true
self.addChild(castle)
I have a circle in the middle of the screen, I want to make it disappear when I tap it. Can you please help me? It is an SKShapeNode and has a PhysicsBody with the same radius
You could use touchesBegan and make it look like so
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
let nameOfTappedNode = nodeITapped.name
if nameOfTappedNode == "castle"{
//make it do whatever you want
castle.removeFromParent()
}
}
}
you could also implement it in touchesEnded instead if you want the node to disappear as soon as the user releases the touch.
I'm sure I sound like a completed noob but I'm working on my first iOS game and it includes a shoot button which is an arcade style button that I would like to animate by swapping to the "shootPressed" image while the user holds it down then swaps back when released.
Here I have my touches began, moved, and ended. Keep in my mind I left out any nonconflicting code. I've been stuck on this problem for the last two days.
UPDATE: I've included the function I used for creating my button node
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in:self)
let node = self.atPoint(location)
if(node.name == "Shoot") {
shootButton.texture = SKTexture(imageNamed: "shootPushed")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in:self)
let node = self.atPoint(location)
if (node.name == "Shoot") {
shootBeams()
shootButton.texture = SKTexture(imageNamed: "shootUnpushed" )
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in:self)
let node = self.atPoint(location)
if node.name == ("shoot") {
shootButton.texture = SKTexture(imageNamed: "shootPushed")
}
}
}
func addButtons() {
var buttonT1 = SKTexture(imageNamed: "shootUnpushed")
var buttonT2 = SKTexture(imageNamed: "shootPushed")
var shootButton = SKSpriteNode(texture: buttonT1)
shootButton.name = "Shoot"
shootButton.position = CGPoint(x: self.frame.maxX - 130, y: leftButton.position.y)
shootButton.zPosition = 7
shootButton.size = CGSize(width: shootButton.size.width*0.2, height: shootButton.size.height*0.2)
self.addChild(shootButton)
}
The shootButton you create in addButtons is a local variable.
So, when you do shootButton.texture = SKTexture(imageNamed: "shootPushed") inside the touchesMoved this is certainly not referring to the same node.
As this apparently compiles this would mean that you already have a property shootButton in the class. The problem would most likely be fixed if you change the var shootButton = SKSpriteNode(texture: buttonT1) in you addButtons function.
It should simply initialize the shootButton-property rather than creating a new local variable:
func addButtons() {
shootButton = SKSpriteNode(texture: buttonT1)
shootButton.name = "Shoot"
shootButton.position = CGPoint(x: self.frame.maxX - 130, y: leftButton.position.y)
shootButton.zPosition = 7
shootButton.size = CGSize(width: shootButton.size.width*0.2, height: shootButton.size.height*0.2)
self.addChild(shootButton)
}