Dragging and dropping sprites without hardcoding names using swift [closed] - swift

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I'm just trying to have the user drag and drop certain sprites around the screen. The answers I saw for this are checking the name of the sprite (something like below). There must be a better way to do this. Thanks for any input.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
var nodeTouched = SKNode()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
nodeTouched = self.nodeAtPoint(location)
if(nodeTouched.name? == "character") {
character.position=location
currentNodeTouched = nodeTouched
} else {
currentNodeTouched.name = ""
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
var nodeTouched = SKNode()
nodeTouched = self.nodeAtPoint(location)
if(currentNodeTouched.name == "character") {
character.position = location
}
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
var nodeTouched = SKNode()
nodeTouched = self.nodeAtPoint(location)
if(currentNodeTouched.name == "character") {
character.position = location
}
}
}

Since you want to drag and drop only specific sprites, there has to be a way of identifying them. This can be anything that can be used to distinguish the nodes. The following are a few suggestions on what can be used in this context.
The code in the answer is in Objective-C, hope you can translate it to Swift.
The name property
As you said, this is the most commonly used way of determining whether the sprite can be dragged or not.
The userData property
This is a property associated with SKNode which lets you set any custom data you want. You can read about it here.
[node.userData setValue:#(YES) forKey:#"draggable"]; //Setting the value
if ([[node.userData valueForKey:#"draggable"] boolValue]) //Checking the value
Subclassing
You can also create a custom class which can either handle the deag-drop functionality on it's own or implement a BOOL as a property which can be checked in the touch delegates.
Class name
In some cases, you might want only the nodes of a specific class to be dragged.
if ([node isKindOfClass:[CustomNode class]])
if ([(NSStringFromClass([node class]) isEqualToString:#"CustomNode"])

Related

Cant drag ARKit SCNNode?

Ok, like everyone else I am having trouble dragging/translating an SCNNode in ARKit/world space. Ive looked at Dragging SCNNode in ARKit Using SceneKit and all the popular questions, as well as the code from Apple https://github.com/gao0122/ARKit-Example-by-Apple/blob/master/ARKitExample/VirtualObject.swift
Ive tried to simplify as much as possible and just did what I would in a normal scene kit game - http://dayoftheindie.com/tutorials/3d-games-graphics/t-scenekit-3d-picking-dragging/
I can get the tapped object and store the current finger pos no problem with:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = gameView.hitTest(touch.location(in: gameView), types: [ARHitTestResult.ResultType.featurePoint])
//TAP Test
let hits = gameView.hitTest(touch.location(in: gameView), options: nil)
currentTapPos = getARPos(hitFeature: hitFeature) //TAP POSITION
if let tappedNode = hits.first?.node {
My issue is, however, doing this in update - there is no animation. The object just appears wherever I tap, and overall not working -
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let results = gameView.hitTest(touch.location(in: gameView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return }
testNode.position = currentTapPos
I convert to SCNVector3 with this func:
func getARPos(hitFeature: ARHitTestResult)->SCNVector3
{
let hitTransform = SCNMatrix4.init(hitFeature.worldTransform)
let hitPosition = SCNVector3Make(hitTransform.m41,
hitTransform.m42,
hitTransform.m43)
return hitPosition
}
I have tried:
-translate by vector
-pan gesture (this screwed up other functions)
-SCNAction.moveTo
What can I do here? Whats wrong?
I agree with #Rickster that the Apple Code provides a much more robust example, however, I have this and it seems to work smoothly (much more so when you are using PlaneDetection (since feature points are much more sporadic):
I don't make use of touchesBegan but simply do the work in touchesMoved instead:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Point
guard let currentTouchPoint = touches.first?.location(in: self.augmentedRealityView),
//2. Get The Next Feature Point Etc
let hitTest = augmentedRealityView.hitTest(currentTouchPoint, types: .existingPlane).first else { return }
//3. Convert To World Coordinates
let worldTransform = hitTest.worldTransform
//4. Set The New Position
let newPosition = SCNVector3(worldTransform.columns.3.x, worldTransform.columns.3.y, worldTransform.columns.3.z)
//5. Apply To The Node
nodeToDrag.simdPosition = float3(newPosition.x, newPosition.y, newPosition.z)
}
I might be going about this completely the wrong way (and if I am I would love to get feedback).
Anyway, I hope it might help... You can see a quick video of it in action here: Dragging SCNNode

How to access var of an extension in another class?

I´m working with Sprite Kit. I want to change the button picture of the main class with the settings class. How can I make the variable in the extension (from the setting class) available for the main class?
Here is the extension:
extension ChangingDots {
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
for touch in touches{
let locationUser = touch.location(in: self)
if atPoint(locationUser) == DCButton {
var blackdot = SKSpriteNode(imageNamed: "AppIcon") //<--var I want to use
}
}
}
}
Here is the use in the main class:
blackdot.setScale(0.65)
blackdot.position = CGPoint(x: CGFloat(randomX), y: CGFloat(randomY))
blackdot.zPosition = 1
self.addChild(blackdot)
Does anyone have a better idea of changing button pictures of one class from another?
If you want to use a variable in the main class, it needs to be created in the main class. Extensions are designed to extend functionality, this means functions and computed properties.
To find out more, see this documentation:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
Just to add on here, there are ways to "workaround" adding stored properties in an extension. Here is an article that describes how to do it: https://medium.com/#ttikitu/swift-extensions-can-add-stored-properties-92db66bce6cd
However, if it were me, I would add the property to your main class. Extensions are meant to extend behavior of a class. It doesn't seem to be the right design approach to make a main class DEPENDENT on your extension.
If you want to make it private, you can use fileprivate so your extension can access it but maintain it's "private" access. For example:
fileprivate var value: Object? = nil
Sorry but you make me realize that you can't add stored properties in extensions. You can only add computed properties. You can add your blackdot var as a computed property or declare it in your main class instead of your extension. If you want to try the computed way, use this :
extension ChangingDots {
var blackdot:Type? { // Replace Type with SKSpriteNode type returned
return SKSpriteNode(imageNamed: "AppIcon")
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
for touch in touches {
let locationUser = touch.location(in: self)
if atPoint(locationUser) == DCButton {
blackdot = SKSpriteNode(imageNamed: "AppIcon") //<--var I want to use
}
}
}
}
This way your blackdot var is only gettable and NOT settable. If you want to add the possibility to set it you need to add a setter like this :
var blackdot:Type? { // Replace Type with SKSpriteNode type returned
get {
return SKSpriteNode(imageNamed: "AppIcon")
}
set(newImage) {
...
}
}

Nodes not moving

(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.

Swift / SpriteKit Button Drag Off Cancel Not Working?

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.

SpriteKit remembering which sprite was touched

I have a 'game' with 3 sprites.
If a user clicks 'Sprite1', and thereafter 'Sprite2'.
Is there some way to remember which sprites were pressed?
Its only for 2 sprites if there are more i will need to empty the 'memory' and start over with the last one 'touched', i didn't want to work with booleans.
All my sprites have the .name attribute so i can acces them there.
would an IF statement be sufficient like so
var spriteArr:[String] = []
let location = (touch as UITouch).locationInNode(self)
for touch in touches{
if spriteArr.count < 2 {
spriteArr.append(self.nodeAtPoint(location))
}else{
spriteArr.removeAll
spriteArr.append(self.nodeAtPoint(location))
}
}
You can check the Sprites by accessing the .name of the Node and storing the names in an array like this.
var nodeNames:[String] = []
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
var touchedNode:SKNode = nodeAtPoint(touch.locationInNode(self))
if(nodeNames.count > 2){
nodeNames = []
}
if(touchedNode.name != nil){
if(!contains(nodeNames, touchedNode.name)){
nodeNames.append(touchedNode.name)
}
}
}