I have two nodes node1 and node2. node1 is at position 0. when I click on the screen it run its action until its in the middle of the screen or the user removes their finger from the screen. Once node1 reaches the middle of screen it will stop, and node2 will run its action. My code does this so far.
However, in order for node2 to run its action the user must remove their finger from the screen and touch it again. I do not want this to happen. I want the action for node2 to run after node1 finishes without the user removing their finger from screen.
Basically, the user is holding down the screen node1 runs its action finishes and then node2 runs its action.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let Node1Move=SKAction.moveBy(x:50,y:0,duration:0.6)
let node1rep=SKAction.repeatForever(Node1Move)
node1.run(node1rep)
if modeNode2==1{
let node2move=SKAction.moveBy(x:-10,y:0,duration:0.9)
let node2Rep=SKAction.repeatForever(node2move)
node2.run(node2Rep)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
node1.removeAllActions()
node2.removeAllActions()
}
override func update(_ currentTime: TimeInterval) {
if node1.position.x+node1.frame.width > self.frame.width/2{
node1.removeAllActions()
moveNode2=1
}
}
touchesBegin will only fire once, so your moveNode2 == 1 will not do anything. Instead of using moveBy, user moveTo, and have node1 moveTo the center of the screen - node1 width. Then just add a completion block to the node1.run.
Note: you may need to change the duration, not sure where these numbers come from. If you plan on the user to constantly be tapping the screen, then just do totalDuration = 3 * (node1.x - 'node1 start x')/((self.frame.width/2 - node1.frame.width) - 'node1 start x')
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let Node1Move=SKAction.moveTo(x:self.frame.width/2 - node1.frame.width,y:0,duration:3.0)
node1.run(Node1Move)
{
let node2move=SKAction.moveBy(x:-10,y:0,duration:0.9)
let node2Rep=SKAction.repeatForever(node2move)
node2.run(node2Rep)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
node1.removeAllActions()
node2.removeAllActions()
}
Related
I am currently working on an app where I need to implement more drop down buttons. I have found a guide on how to make a drop down button, and it works like a charm. The problem is that I want the "drop down part" to hide if you press another drop down button (or somewhere else on the screen if it is possible, but that part is not necessary).
Here is a link to a part of the project with the drop down buttons.
https://github.com/Rawchris/More-drop-downs
If you want the code in here you can say so, but I thought it was better this way. If you don't understand what I want, please say so and I will try to explain it better.
add closure into dropDownBtn (btw this is not a correct naming please check the link)
class dropDownBtn: UIButton, dropDownProtocol {
var didTapDropDown: ((Bool) -> Void)?
.
.
.
add this closure to the end of the override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
.
.
.
didTapDropDown?(isOpen)
}
then add these configuration to viewDidLoad
button1.didTapDropDown = { [unowned self] isOpened in
if isOpened {
self.button2.dismissDropDown()
}
}
button2.didTapDropDown = { [unowned self] isOpened in
if isOpened {
self.button1.dismissDropDown()
}
}
//Add Button to the View Controller
self.view.addSubview(button1)
self.view.addSubview(button2)
I'm trying to make a game where the player at certain times draw a pattern on the screen. My solution for this is to add multiple nodes on the screen that are touchable via an extension of SKSpriteNode.
When the player touches a node, I want to call touchesmoved, and add all nodes touched to an array.
Then, when the player stops touching the screen, I want to match that array to another array, and something happens.
I've been playing around with the updates function and try to run a function each update loop, but it didn't work very well. I've also tried making the gameScene class a delegate of my touchableShapeNode Class, but I struggled to make it work.
class TouchableShapeNode: SKShapeNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if (name != nil) {
print("\(name ?? "node") touched")
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if (name != nil) {
print("\(name ?? "node") touched")
}
}
}
My problem is that the only node that gets selected right now is the first node I touch, not the ones the player's finger move over. Right now I'm just printing the name of the touched node.
I'm not exactly sure what you're after, but here's a little program that does the following:
Puts 15 red blocks on screen
As you drag around the screen, any nodes you touch are added to a set.
When you stop touching, all touched nodes have their colour changed to green.
When you start a new touch, the set of touched nodes is emptied and all nodes return to their starting colour (red).
To use, simply start a new, empty SpriteKit project and replace gameScene.swift with this code.
import SpriteKit
import UIKit
class GameScene: SKScene {
let shipSize = CGSize(width: 25, height: 25)
let normalColour = UIColor.red
let touchedColour = UIColor.green
var touchedNodes = Set<SKSpriteNode>()
override func didMove(to view: SKView) {
let sceneWidth = self.scene?.size.width
let sceneHeight = self.scene?.size.height
// Add 15 colour sprites to the screen in random places.
for _ in 1...15 {
let ship = SKSpriteNode(color: normalColour, size: shipSize)
ship.position.x = CGFloat.random(in: -sceneWidth!/2...sceneWidth!/2) * 0.7
ship.position.y = CGFloat.random(in: -sceneHeight!/2...sceneHeight!/2) * 0.7
ship.name = "ship"
addChild(ship)
}
}
// When the screen is toucheed, empty the 'touchedNodes' set and rest all nodes back to their normal colour.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
resetTouchedNodes()
}
// As the touch moves, if we touch a node then add it to our 'touchedNodes' set.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let location = touch!.location(in: self)
// If there is a node at the touch location, add it to our 'touchedSprites' set.
if let touchedNode = selectNodeForTouch(location) {
touchedNodes.insert(touchedNode)
}
}
// When the touch ends, make all nodes that were touched change colour.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for node in touchedNodes {
node.color = touchedColour
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
resetTouchedNodes()
}
// Return the first sprite where the user touched the screen, else nil
func selectNodeForTouch(_ touchLocation: CGPoint) -> SKSpriteNode? {
let nodes = self.nodes(at: touchLocation)
for node in nodes {
if node is SKSpriteNode {
return (node as! SKSpriteNode)
}
}
return nil
}
// Clear the touchedSprites set and return all nodes on screen to their normal colour
func resetTouchedNodes() {
touchedNodes.removeAll()
enumerateChildNodes(withName: "//ship") { node, _ in
let shipNode = node as! SKSpriteNode
shipNode.color = self.normalColour
}
}
}
You could amend this in various ways. For example, you could change the colour of the sprite immediately you touch it in touchesMoved etc.
I want to implement UIView subclass that will catch all touch events (especially touchesMoved) and pass them through. Is it possible?
class PassthroughView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesBegan")
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesMoved") //not called
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesEnded")
super.touchesEnded(touches, with: event)
}
}
You can’t really do this. If your view receives touches it can’t pass it on to the view that would have received if your view isn’t there. To do this you’d basically have to reimplement the whole event routing. This would be really hard, or maybe even impossible without access to Apple internal API. But even if you got this right it could break with every iOS update.
But there is a different way to get all touch events while still having them delivered to the appropriate views. For this you create a subclass of UIApplication and override the sendEvent(_:) method. This is called for each event and dispatches it to the appropriate views. From there you can first do your special processing and then call super.sendEvent() to send it to your windows as usual.
Of course there you don’t get different calls for touches began/moved/ended, but the information is there in your event object. First you need to check the event.type property to see if you actually got a touch event. If so you can get all the touches from the event.allTouches property. For each touch you can get the phase which describes whether this touch began, moved, ended and so on.
Of course subclassing is not enough, you also have to tell the system to use your new subclass. To do this you annotate it as #objc(MyApplication) where MyApplication is the name of the class. This makes sure the Objective-C runtime can find the class by the name you gave it. Then you edit your Info.plist file and set this name for the "Principal class" key. (If you are not using the Xcode editor the key is also called NSPrincipalClass).
(Swift 3)
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var object = SKSpriteNode()
override func didMove(to view: SKView) {
object = self.childNode(withName: "dot") as! SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
object.run(SKAction.move(to: location, duration: 0.3))
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
object.run(SKAction.move(to: location, duration: 0.3))
}
}
override func update(_ currentTime: TimeInterval) {
}
}
This code is designed to allow me to drag a "dot" around the scene, however... I just cant! It won't budge! I've looked online, but I can't find anything. I tried pasting this code into a fresh project, and it worked there, but even deleting and replacing my "dot" node doesn't seem to help on my main project.
So... any ideas?
Confused has already said everything you must know about your issue.
I try to add details that help you with the code. First of all, you should use optional binding when you search a node with self.childNode(withName so if this node does not exist your project does not crash.
Then, as explained by Confused, you should add controls before you call your actions to be sure that your action is not already running.
I make an example here below:
import SpriteKit
class GameScene: SKScene {
var object = SKSpriteNode.init(color: .red, size: CGSize(width:100,height:100))
override func didMove(to view: SKView) {
if let sprite = self.childNode(withName: "//dot") as? SKSpriteNode {
object = sprite
} else { //dot not exist so I draw a red square..
addChild(object)
}
object.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
func makeMoves(_ destination: CGPoint) {
if object.action(forKey: "objectMove") == nil {
object.run(SKAction.move(to: destination, duration: 0.3),withKey:"objectMove")
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
makeMoves(location)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
makeMoves(location)
}
}
}
Output:
The fact it works in a new, barebones project, and not in your fuller project indicates the problem is in the fuller project. Without a glimpse at that, it's pretty hard to tell what's actually going wrong.
Peculiar, to me, is the use of the SKAction to do the move in touchesMoved(). If you think about this in terms of how SKActions works, you're continually adding new move actions, at whatever rate the touchscreen is picking up changes in position. This is (almost always) much faster than 0.3 seconds. More like 0.016 seconds. So this should be causing all manner of problems, in its own right.
Further, you need something in the GameScene's touchesMoved() to ascertain what's being touched, and then, based on touching the dot, move it, specifically. This, I assume, is your "object".
A simpler way of doing this might be to use touchesMoved in a subclass of SKSpriteNode that represents and creates your dot instance.
Hello I need your help I have been looking a while now to find the Answer to this.
I also have simplified the code to get rid of allot of the information/junk you don't need to know.
I have a main menu scene in for a IOS game using SpriteKit And Swift. There Is a main menu play button. What I want is When the button is pressed the button get bigger by a little. When the my finger is release the button gets smaller. This works fine using override func touchesBegan / touchesEnded. my problem is when my finger gets dragged off the button like to cancel. The button does not return to the original size. I am pretty sure I need to use
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {}
But upon many attempts I am not getting the the desired result of returning the button to original size when my finger is dragged off the button. Thanks for any help.
import Foundation
import SpriteKit
import UIKit
class StartScene: SKScene {
var playButton: SKNode! = nill
override func didMoveToView(view: SKView) {
// Create PlayButton image
playButton = SKSpriteNode(imageNamed: "playButtonStatic")
// location of Button
playButton.position = CGPoint(x:CGRectGetMidX(self.frame), y:520);
self.addChild(playButton)
playButton.setScale(0.5)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if playButton.containsPoint(location) {
playButton.setScale(0.6)}}}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if playButton.containsPoint(location) {
playButton.setScale(0.5)
}}}
// Problem Area
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
for touch: AnyObject in touches! {
let location = touch.locationInNode(self)
if playButton.containsPoint(location) {
playButton.setScale(0.5)
}}}
}
Thanks for any response
Found the solution use a Else statement in TouchesEnded
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if playButton.containsPoint(location) {
playButton.setScale(0.5)
}
else
{
playButton.setScale(0.5)
}}
You say you found a solution but here's an easier solution: use a custom class I created designed specifically for buttons for SpriteKit. Link to the GitHub that has the class file. With this class, you don't have to worry about it at all, and it removes unnecessary code from your touches functions. JKButtonNodes have 3 states: normal, highlighted, and disabled. You also don't have to worry about setting it to nil if you have removed it from the parent.
To declare a variable, do this. Ignore the playButtonAction error for now.
var playButton = JKButtonNode(background: SKTexture(imageNamed: "playButtonStatic"), action: playButtonAction)
The action is the function to call when the button is pressed and let go. Create a function like this anywhere in your class.
func playButtonAction(button: JKButtonNode) {
//Whatever the button does when pressed goes here
}
Then you can set its properties in didMoveToView.
playButton.position = CGPoint(x:CGRectGetMidX(self.frame), y:520)
//These are the 3 images you want to be used when pressed/disabled
//You can create a bigger image for when pressed as you want.
playButton.setBackgroundsForState(normal: "playButtonStatic", highlighted: "playButtonHighlighted", disabled: "")
addChild(playButton)
And that's it! The JKButtonNode class itself already cancels touches if the user moves their finger off, it also doesn't call the function unless the user has successfully pressed the button and released their finger on it. You can also do other things like disable it from playing sounds, etc.
A good pro of using JKButtonNode is that you don't have to have code all over the place anymore since it doesn't require ANY code in the touches functions.