Swift SpriteKit unwrapping and wrapping - swift

func bombTowerTurnShoot() {
let zombieGreen = self.childNode(withName: "zombie") as! SKSpriteNode
self.enumerateChildNodes(withName: "bomb tower") {
node, stop in
if let bombTower = node as? SKSpriteNode {
let angle = atan2((zombieGreen.position.x) - bombTower.position.x, (zombieGreen.position.y) - bombTower.position.y)
let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
bombTower.run(actionTurn)
}
}
}
My issue is on the let angle line. When I call the function when there is no zombieGreens on the scene I get a Thread 1 problem. How can I change the code to take into account when the zombie is not present?

if there is no ZombiGreens in the scene the error should happen already at the second line:
let zombieGreen = self.childNode(withName: "zombie") as! SKSpriteNode
I think the simplest solution without changing to much of your code would be to use an if let just as you did for the bomb tower. and it would look something like this:
func bombTowerTurnShoot() {
if let zombieGreen = self.childNode(withName: "zombie") as? SKSpriteNode{
self.enumerateChildNodes(withName: "bomb tower") {
node, stop in
if let bombTower = node as? SKSpriteNode {
let angle = atan2((zombieGreen.position.x) - bombTower.position.x, (zombieGreen.position.y) - bombTower.position.y)
let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
bombTower.run(actionTurn)
}
}
}
}
But it could be a good idea to review your code when you have more logic to handle. it might be a better way to do things but this should work :)

Related

How can I handle a touch as a consequence of a previous touch? (Swift, SpriteKit)

This is a chess game in Swift using SpriteKit.
What I would like to happen is for the touched piece to move to the square touched on the next touch. When I run, the piece does not move when I try this. I know for certain that my code works up to and including 'case: "p"', and that the sprite is detecting the first touch.
In the image I've tried to implement this second touch using 'touch2', is this the correct thing to do? Thanks.
This is how I'm detecting the first touch and identifying the piece which has been touched. touchedPiece is the identified piece. moveset is an attribute determining the possible moves.
for touch in touches
{
let location = touch.location(in:self)
let firstTouchedNode = atPoint(location)
let touchedPiece:SKSpriteNode = (firstTouchedNode as? SKSpriteNode)!
switch touchedPiece.moveset {
case "p":
let locationToMove = touch.location(in:self)
let moveto = findpawnmoves(piece: touchedPiece)
spawnMoves(arr: moveto)
And here is the attempt to move the piece after locationToMove has been decided.
if moveto[0].contains(locationToMove){
move(piece: touchedPiece, location: moveto[0].position)
whiteMoved = true
deleteMoves(arr: moveto)
This is an example of how I am creating an array of possible squares to move to. I'll use the knight moves because it's the most concise.
func createMoveNode(piece: SKSpriteNode, up:Int, across:Int) -> SKSpriteNode{
let moveNode = SKSpriteNode(color: .clear, size: CGSize(width: CGFloat(cellsize), height: CGFloat(cellsize)))
moveNode.anchorPoint = CGPoint(x:0.5, y:0.5)
moveNode.position = CGPoint(x: piece.position.x + CGFloat(Double(across)*d), y: piece.position.y + CGFloat(Double(up)*d))
return moveNode
}
func findknightmoves(piece: SKSpriteNode) -> Array<SKSpriteNode>{
let move1 = createMoveNode(piece: piece, up:2, across:-1)
let move2 = createMoveNode(piece: piece, up:2, across:1)
let move3 = createMoveNode(piece: piece, up:-2, across:-1)
let move4 = createMoveNode(piece: piece, up:-2, across:1)
return [move1,move2,move3,move4]
}
func move(piece: SKSpriteNode, location: CGPoint){
piece.position = location
}
func spawnMoves(arr: Array<SKSpriteNode>){
for i in 0...arr.count-1{
arr[i].zPosition = 1
self.addChild(arr[i])
}
}
func deleteMoves(arr: Array<SKSpriteNode>){
for i in 0...arr.count-1{
arr[i].removeFromParent()
}
}

How to know when an SKAction.repeat(action, count) is finished

I'm making a video games with Swift & SpriteKit. I'm trying to make the level system of my game. Each levels has his own specifications (but it's not in the code right now).
However, I would like that when my SKaction.repeat is done, to move to an other scene (such as "Level completed" scene).
Do you know how can I do it ?
Here's my code :
func parametersLevel(){
let spawn = SKAction.run(asteroids)
let waitSpawn = SKAction.wait(forDuration: 0.8)
let sequence = SKAction.sequence([waitSpawn,spawn])
let spawnCount = SKAction.repeat(sequence, count: 750)
self.run(spawnCount)
}
Thanks for you help.
From run(_:completion:) instead of self.run(spawnCount) try with:
self.run(spawnCount, completion: {() -> Void in
println("completed")
})
If you need a key with your action, you can also do:
func parametersLevel(){
let spawn = SKAction.run(asteroids)
let waitSpawn = SKAction.wait(forDuration: 0.8)
let sequence1 = SKAction.sequence([waitSpawn,spawn])
let spawnCount = SKAction.repeat(sequence, count: 750)
let endAction = SKAction.run{} //whatever you need your ending to be
let sequence2 = SKAction.sequence([spawnCount ,endAction])
self.run(sequence2,withKey:”spawn” )
}

How to move and rotate SCNode using ARKit and Gesture Recognizer?

I am working on an AR based iOS app using ARKit(SceneKit). I used the Apple sample code https://developer.apple.com/documentation/arkit/handling_3d_interaction_and_ui_controls_in_augmented_reality as base for this. Using this i am able to move or rotate the whole Virtual Object.
But i want to select and move/rotate a Child Node in Virtual object using user finger, similar to how we move/rotate the whole Virtual Object itself.
I tried the below two links but it is only moving the child node in particular axis but not freely moving anywhere as the user moves the finger.
ARKit - Drag a node along a specific axis (not on a plane)
Dragging SCNNode in ARKit Using SceneKit
Also i tried replacing the Virtual Object which is a SCNReferenceNode with SCNode so that whatever functionality present for existing Virtual Object applies to Child Node as well, it is not working.
Can anyone please help me on how to freely move/rotate not only the Virtual Object but also the child node of a Virtual Object?
Please find the code i am currently using below,
let tapPoint: CGPoint = gesture.location(in: sceneView)
let result = sceneView.hitTest(tapPoint, options: nil)
if result.count == 0 {
return
}
let scnHitResult: SCNHitTestResult? = result.first
movedObject = scnHitResult?.node //.parent?.parent
let hitResults = self.sceneView.hitTest(tapPoint, types: .existingPlane)
if !hitResults.isEmpty{
guard let hitResult = hitResults.last else { return }
movedObject?.position = SCNVector3Make(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
}
To move an object:
Perform a hitTest to check where you have touched, and detect which plane you touched and get a position. Move your SCNNode to that position by changing the node.position value with an SCNVector3.
Code:
#objc func panDetected(recognizer: UIPanGestureRecognizer){
let hitResult = self.arSceneView.hitTest(loc, types: .existingPlane)
if !hitResult.isEmpty{
guard let hitResult = hitResult.last else { return }
self.yourNode.position = SCNVector3Make(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
}
The above code is enough to move your node over a detected plane, anywhere you touch, and not just in a single axis.
Rotating a node according to your gesture is a very difficult task and I have worked on a solution for quite sometime, never reaching a perfect output.
But, I have come across this repository in GitHub which allows you to do just that with a very impressive result.
https://github.com/Xartec/ScreenSpaceRotationAndPan
The Swift version of the code you require to rotate your node using your gesture would be :
var previousLoc: CGPoint?
var touchCount: Int?
#objc func panDetected(recognizer: UIPanGestureRecognizer){
let loc = recognizer.location(in: self.view)
var delta = recognizer.translation(in: self.view)
if recognizer.state == .began {
previousLoc = loc
touchCount = recognizer.numberOfTouches
}
else if gestureRecognizer.state == .changed {
delta = CGPoint.init(x: 2 * (loc.x - previousLoc.x), y: 2 * (loc.y - previousLoc.y))
previousLoc = loc
if touchCount != recognizer.numberOfTouches {
return
}
var rotMatrix: SCNMatrix4!
let rotX = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.y), 1, 0, 0)
let rotY = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0 / 100) * delta.x), 0, 1, 0)
rotMatrix = SCNMatrix4Mult(rotX, rotY)
let transMatrix = SCNMatrix4MakeTranslation(yourNode.position.x, yourNode.position.y, yourNode.position.z)
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, SCNMatrix4Invert(transMatrix))
let parentNoderanslationMatrix = SCNMatrix4MakeTranslation((self.yourNode.parent?.worldPosition.x)!, (self.yourNode.parent?.worldPosition.y)!, (self.yourNode.parent?.worldPosition.z)!)
let parentNodeMatWOTrans = SCNMatrix4Mult((self.yourNode.parent?.worldTransform)!, SCNMatrix4Invert(parentNoderanslationMatrix))
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, parentNodeMatWOTrans)
let camorbitNodeTransMat = SCNMatrix4MakeTranslation((self.arSceneView.pointOfView?.worldPosition.x)!, (self.arSceneView.pointOfView?.worldPosition.y)!, (self.arSceneView.pointOfView?.worldPosition.z)!)
let camorbitNodeMatWOTrans = SCNMatrix4Mult((self.arSceneView.pointOfView?.worldTransform)!, SCNMatrix4Invert(camorbitNodeTransMat))
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, SCNMatrix4Invert(camorbitNodeMatWOTrans))
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, rotMatrix)
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, camorbitNodeMatWOTrans)
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, SCNMatrix4Invert(parentNodeMatWOTrans))
self.yourNode.transform = SCNMatrix4Mult(self.yourNode.transform, transMatrix)
}
}

Move 2 different Spritenodes in the touchesMoved function

i'm trying to make a 2 player game in Swift, so far I can detect multiple touches and move each node individually, however when both players try to move their node at the same time one always takes control and moves both nodes at the same time, the code I have for when both nodes are being touched is the following:
if Touching && Touching2 {
for touch: AnyObject in touches {
let Loc = (touch as! UITouch).location(in: self)
let PrevLoc = (touch as! UITouch).previousLocation(in: self)
let Shell = self.childNode(withName: ShellCategory) as! SKSpriteNode
var NewPos = Shell.position.x + (Loc.x - PrevLoc.x)
NewPos = max(NewPos, Shell.size.width / 2)
NewPos = min(NewPos, self.size.width - Shell.size.width / 2)
Shell.position = CGPoint(x:NewPos, y: Shell.position.y)
let Loc2 = (touch as! UITouch).location(in: self)
let PrevLoc2 = (touch as! UITouch).previousLocation(in: self)
let Shell2 = self.childNode(withName: ShellCategory2) as! SKSpriteNode
var NewPos2 = Shell.position.x + (Loc2.x - PrevLoc2.x)
NewPos2 = max(NewPos2, Shell2.size.width / 2)
NewPos2 = min(NewPos2, self.size.width - Shell2.size.width / 2)
Shell2.position = CGPoint(x:NewPos2, y: Shell2.position.y)
}
}
Thanks beforehand.
Well, after some investigation I finally found what I needed, I'm leaving this here for future developers who might find themselves in similar situations.
Multi-touch gesture in Sprite Kit

Detect if a SKSpriteNode left the screen at the bottom and call a function

I have tried to find a way to detect if a SKSpriteNode left the screen (I would like to call a Game Over function).
I have declared the node within a function (It is not global if I get that right?) and made and SKAction that moves the Node out of the screen and removes it afterwards.
This is what I came up with:
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
Now what I need is to call GameOver() if the node left the screen.
I am very thankful for every answer!
In your scene you have to remember the reference for node you want to check and then in update method you just do the following:
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
Edit:
class MyScene: SKScene {
// ....
//here is the list of nodes which you want to check
var nodesToCheck = [SKSpriteNode]()
//here is your spawn function
func spawnNewNode() {
var node = SKSpriteNode()
let nodeTexture = SKTexture(imageNamed: "node")
nodeTexture.filteringMode = .Nearest
node = SKSpriteNode(texture: nodeTexture)
let nodeFalling = SKAction.moveToY(-70, duration: 1.6)
let nodeRemoving = SKAction.removeFromParent()
node.runAction(SKAction.sequence([nodeFalling, nodeRemoving]))
self.addChild(node)
nodesToCheck.append(node)
}
//and here is the update method
override func update(currentTime: NSTimeInterval) {
super.update(currentTime)
// ... every other update logic
for node in nodesToCheck {
if node.position.y < -node.size.height/2.0 {
node.removeFromParent()
gameOver()
}
}
}
func gameOver() {
println("Damn!")
}
}
Dont forget to remove your node from nodeToCheck array when they are no longer scene members.