How can I make images that act as buttons in Swift? - swift

I have the code for one button on my scene, but I tried using that same code to make another button in the same scene, and it didn't work. Here's the code I have so far:
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if playGameButton.containsPoint(location) {
let newScene = GameplayScene(size: self.size)
newScene.scaleMode = scaleMode
self.view?.presentScene(newScene)
}
}
Again, that's only one button. When I use that same bit of code for another image, it doesn't work.

A better way to approach this problem would be to use an SKSpriteNode object as your button and set its name property to easily distinguish between multiple buttons. For example, if you have an SKSpriteNode object button1:
button1.name = "first"
Then in your touch method to check which was touched, you can use the name property:
SKNode node = self.nodeAtPoint(location)
if node.name == "first" {
// do something
}

Related

swift SpriteNode and SceneKit multiple touch gestures

I am making a word game. For this I am using SceneKit and adding a SpriteNodes to represent letter tiles.
The idea is that when a user clicks on a letter tile, some extra tiles appear around it with different letter options. My issue is regarding the touch gestures for various interactions.
When a user taps on a letter tile, additional tiles are shown. I have achieved this using the following method in my tile SpriteNode class:
override func touchesBegan(_ touches:Set<UITouch> , with event: UIEvent?) {
guard let touch = touches.first else {
return
}
delegate?.updateLetter(row: row, column: column, x:xcoord, y:ycoord, useCase: 1)
}
This triggers the delegate correctly which shows another sprite node.
What I would like to achieve is for a long press to remove the sprite node from parent. I have found the .removeFromParent() method, however I cannot get this to detect a long press gesture.
My understanding is that this type of gesture must be added using UIGestureRecognizer. I can add the following method to my Scene class:
override func didMove(to view: SKView) {
let longPress = UILongPressGestureRecognizer(target: self,
action: #selector(GameScene.longPress(sender:)))
view.addGestureRecognizer(longPress)
}
#objc func longPress(sender: UILongPressGestureRecognizer) {
print("Long Press")
This will detect a long press anywhere on the scene. However I need to be able to handle the pressed nodes properties before removing it. I have tried adding the below to the longPress function:
let location = sender.location(in: self)
let touchedNodes = nodes(at: location)
let firstTouchedNode = atPoint(location).name
touchedNodes[0].removeFromParent()
but I get the following error: Cannot convert value of type 'GameScene' to expected argument type 'UIView?'
This seems a little bit of a messy way of doing things, as I have touch methods in different places.
So my question is, how can I keep the current touchesBegan method that is in the tile class, and add a long press gesture to be able to reference and delete the spriteNode?
Long press gestures are continuous gestures that may be called multiple times as you are seeing. Have you tried Recognizer.State.began, .changed, .ended? I solved a similar problem doing things this way.
EDIT - I think one way to get there is to get your object on handleTap and hang on to the object. Then when LongPress happens, you already have your node. If something changes before longPress, obviously you need to reset. Sorry, this is some extra code on here, but look at hitTest.
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
let location: CGPoint = recognizer.location(in: gameScene)
if(data.isAirStrikeModeOn == true)
{
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
gameControl.airStrike(position: scenePoint)
}
else
{
let hitResults = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
// May have selected an invalid panel or auto upgrade was on
if(gameControl.selectPanel(vPanel: vHit.node.name!) == false) { return }
return
}
}
}
}
So I am not completely satisfied with this answer, however it is a work around for what I need.
What I have done is added two variables ‘touchesStart’ and ‘touchesEnd’ to my tiles class.
Then in touchesBegan() I add a call to update touchesStart with CACurrentMediaTime() and update touchesEnd via the touchesEnded() function.
Then in the touchesEnded() I subtract touchesStart from touchesEnd. If the difference is more than 1.0 I call the function for long press. If less than 1.0 I call the function for tap.

How to use the SpriteKit method body(at : CGPoint)?

I have game that during the game your finger moves around and should avoid hitting some obstacles. I know how to use collision but I recently heard that there is a funtion called the body(at : point).
I should say that I have masked my obstacles. Because of that I can't use the contains(point) function.
I really want to use the body(at : point) function otherwise I know how to work with collisions.
Some of my code:
play_button = self.childNode(withName: "play_button") as! SKSpriteNode
for t in touches{
let fingerlocation = t.location(in: self)
if physicsworld.body(at : fingerlocation)
{
let so = level6(fileNamed:"level6")
so?.scaleMode = .aspectFill
self.view?.presentScene(so!, transition:
SKTransition.crossFade(withDuration: 1))
}
}
but it does not work.
physicsworld.body returns an SKPhysicsBody if found, so you just need to check if it exists
if let _ = physicsworld.body(at : fingerlocation)
If your goal is to make a button, then I would not do this approach.
I would recommend just subclassing SKSpriteNode and enabling the isUserInteractionEnable`
class Button : SKSpriteNode
{
override init()
{
super.init()
isUserInteractionEnabled = true
}
//then in your touchesBegan/touchesEnded code (wherever you placed it)
....
let so = level6(fileNamed:"level6")
so?.scaleMode = .aspectFill
scene.view?.presentScene(so!, transition:
SKTransition.crossFade(withDuration: 1))
....
}
I've had a similar problem, "if let body = physicsWorld.body(at: touchLocation){...}" was responding wherever I've touched the screen.
I figured out, that by default the SKScene object created its own SKPhysicsBody which was set on top of all other SKPhysicsBodies.
I solved this problem by making a new SKPhysicsBody for the scene, which didn't interfere anymore. Hope this code will help you.
inside of didMove:
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody

Drag and drop children of the same sprite

I'm making a game using Sprite Kit and I want to be able to drag and drop boxes as they travel down the screen.
Here's the gist of the code: I spawn the boxes on a timer and they move down the screen.
override func didMoveToView(view: SKView) {
let timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("spawnBox"), userInfo: nil, repeats: true)
}
func spawnBox() {
/* Set up the box */
addChild(box)
let boxMoveDown = SKAction.moveToY(-100, duration: 5.0)
let actionDone = SKAction.removeFromParent()
box.runAction(SKAction.sequence([boxMoveDown, actionDone]))
}
But the problem is I how can I move a specific child which I am touching without affecting all the other 'children'? I understand that at the moment, every time I spawn a box it's exactly the same so I can't be specific when I set a individual child's position.
Here's what's inside my touchesBegan and touchesMoved functions
if let touch = touches.first {
let location = touch.locationInNode(self)
let objects = nodesAtPoint(location) as [SKNode]
if objects.contains(nodeAtPoint(location)) && nodeAtPoint(location).name == "box" {
box.position = location
box.removeAllActions()
}
}
The - box.position = location is what
needs changing.
Hopefully you understand my idea. I've tried to keep included code to what's necessary. I'm quite new to Sprite Kit which you can probably tell.
If I were you, I would handle it this way:
Create a custom class for your box nodes that extends SKSpriteNode.
In this custom class, override the touch property.
Then set the position inside this function based on location.
Now all you need to worry about is your zPosition, whatever child has the highest zPosition will be the one that gets called on touch.
You do not need to worry about nodesAtPoint or what not anymore, the API will handle all that for you.

How do I call a function from another scene?

My game unlocks a level every-time they beat a level. So the problem im having is when the user beats level one I want it to show a button of level 2 thats in my GameScene. When I call the function unlockLevelTwo() in my Level1.swift file it doesn't show up in my GameScene.swift file. What am I doing wrong?
//GameScene.Swift
func unlockLevelTwo() {
let fadeIn = SKAction.fadeInWithDuration(0.5)
levelTwo.position = CGPointMake(self.size.width / 2.0, self.size.height / 2.2)
levelTwo.zPosition = 20
levelTwo.setScale(0.8)
levelTwo.alpha = 0
levelTwo.runAction(fadeIn)
levelTwo.name = "leveltwo"
addChild(levelTwo)
}
//Level1.swift
if firstBody.categoryBitMask == HeroCategory && sixthBody.categoryBitMask == GoldKeyCategory{
//calling this function from my gameScene to unlock levelTwo button.
var gameScene = GameScene()
gameScene.unlockLevelTwo()
}
This function:
if firstBody.categoryBitMask == HeroCategory && sixthBody.categoryBitMask == GoldKeyCategory{
//calling this function from my gameScene to unlock levelTwo button.
var gameScene = GameScene()
gameScene.unlockLevelTwo()
}
is creating a new GameScene object and adding the childNode "levelTwo" to that gamescene. Instead, you need to call unlockLevelTwo() on the actual GameScene that is currently presented to the user.
I imagine somewhere in Level1.Swift there is a reference to the current GameScene (the one the user is interacting with)? Call the function on that one.
EDIT:
In essence, what you must do is keep a reference to your original GameScene object somewhere in your code, which I will henceforth refer to it as MyScene. That way, when in Level1.swift, you can reference MyScene and add buttons, levels, whatever you like to it without creating a new one like you did here:
var gameScene = GameScene()
gameScene.unlockLevelTwo()
Instead, you would just call
MyScene.unlockLevelTwo().
If Level1 is a view of some sort, or an object that gets created, in the init function you could pass in your GameScene object and set it like so
class Level1 : (Type) {
var myScene: GameScene!
init(myScene: GameScene) {
self.myScene = myScene)
}
}
Something like that, hope it helps!

Detecting scene from viewcontroller

I am working on a game in Xcode with swift/spritekit.
I want to add the background from the GameViewController but I have different scenes with each different backgrounds: GameScene / MenuScene / MapScene.
Now how do I detect in gameviewcontroller which scene is running so that I can add the right background to it(as UIImage)?
And how do I set Z-position(Z-index) of an UIImage?
You'll have to track which scene is present yourself. Something like this:
class GameViewController {
var mapScene: SKScene?
var menuScene: SKScene?
var gameScene: SKScene?
var currentScene: SKScene?
func addBackground() {
if currentScene === mapScene {
// ...
} else if currentScene === menuScene {
// ...
} else if currentScene === gameScene {
// ...
}
}
}
If the UIImage is inside an SKNode, then you can set its zPosition property. If it's just a UIImage in normal UIKit elements, then it's implicit based on when it was added as a subview. If you want it on top, remove it from its parent and re-add it:
view.removeFromSuperview()
self.parentView.addSubview(view)
You don't need to detect which scene is running if you simply add the appropriate background image in the scene's implementation. Also, I recommend you avoid adding a UIImage to your view, since you will need to manually remove it when you transition to a new scene. I suggest you create a sprite node with the background image as its texture and add that to your scene with a negative value for its zPosition. Also, you should consider setting the "background" sprite's anchor point and position to CGPointZero.
For example, in 'didMoveToView'
let backgroundNode = SKSpriteNode(imageNamed:"MenuSceneBackground")
backgroundNode.position = CGPointZero
backgroundNode.anchorPoint = CGPointZero
backgroundNode.zPosition = -1000
addChild(backgroundNode)
EDIT: Add the following to remove the gesture recognizers as needed
override func willMoveFromView(view: SKView) {
view.removeGestureRecognizer(swipeGesture)
}