UIPanGestureRecognizer not behaving as expected - sprite-kit

The following code is being used to change the colour of sprites as my finger pans over them. It works but the sprites only change colour after the gesture has ended. Eg I start by panning over the sprites and draw a shape - nothing changes as I pan - then I lift my finger and the sprites change colour.
the print statements are reporting the correct sprite names and all the print statements are being triggered... but the sprite colour change isn't... until the end.
Its confusing me so I am hoping someone can explain.
#objc func drawingpan(_ recognizer: UIPanGestureRecognizer) {
recognizer.minimumNumberOfTouches = 1
if recognizer.state == .began {
print("drawingbegan")
var touchLocation = recognizer.location(in: recognizer.view)
var touchLocation2 = self.convertPoint(fromView: touchLocation)
let nn = nodes(at: touchLocation2)
if nn.count > 0 {
if let dotSpriteTapped = nn.first! as? SKSpriteNode {
print("touchebeganpan",dotSpriteTapped.name)
for spritesquare in MyVariables.allsprites {
if dotSpriteTapped.name == spritesquare.name {
print("changecolorpan",dotSpriteTapped.name)
spritesquare.color = UIColor.red
}
}}
}
}
} else if recognizer.state == .changed {
print("touchechangedx")
var touchLocation = recognizer.location(in: recognizer.view)
var touchLocation2 = self.convertPoint(fromView: touchLocation)
let nn = nodes(at: touchLocation2)
if nn.count > 0 {
if let dotSpriteTapped = nn.first! as? SKSpriteNode {
for spritesquare in MyVariables.allsprites {
if dotSpriteTapped.name == spritesquare.name {
print("namesmatch",dotSpriteTapped.name)
spritesquare.color = UIColor.red
}
}}
}
}
} else if recognizer.state == .ended {
//recognizer.cancelsTouchesInView = false
}}

Related

How to use pinch gesture in game scene

I need your help guys. I have game scene and func which allow to move camera using panGesture. Also i need pinchGesture to zoom in and out my SKScene. I found some code here, but it lags. Can plz someone help me to improve this code?
`
#objc private func didPinch(_ sender: UIPinchGestureRecognizer) {
guard let camera = self.camera else {return}
if sender.state == .changed {
previousCameraScale = camera.xScale
}
camera.setScale(previousCameraScale * 1 / sender.scale)
sender.scale = 1.0
}
`
try this pinch code.
//pinch -- simple version
#objc func pinch(_ recognizer:UIPinchGestureRecognizer) {
guard let camera = self.camera else { return } // The camera has a weak reference, so test it
if recognizer.state == .changed {
let deltaScale = (recognizer.scale - 1.0)*2
let convertedScale = recognizer.scale - deltaScale
let newScale = camera.xScale*convertedScale
camera.setScale(newScale)
//reset value for next time
recognizer.scale = 1.0
}
}
although i would recommend this slightly more complicated version which centers the pinch around the touch point. makes for a much nicer pinch in my experience.
//pinch around touch point
#objc func pinch(_ recognizer:UIPinchGestureRecognizer) {
guard let camera = self.camera else { return } // The camera has a weak reference, so test it
//cache location prior to scaling
let locationInView = recognizer.location(in: self.view)
let location = self.convertPoint(fromView: locationInView)
if recognizer.state == .changed {
let deltaScale = (recognizer.scale - 1.0)*2
let convertedScale = recognizer.scale - deltaScale
let newScale = camera.xScale*convertedScale
camera.setScale(newScale)
//zoom around touch point rather than center screen
let locationAfterScale = self.convertPoint(fromView: locationInView)
let locationDelta = location - locationAfterScale
let newPoint = camera.position + locationDelta
camera.position = newPoint
//reset value for next time
recognizer.scale = 1.0
}
}
//also need these extensions to add and subtract CGPoints
extension CGPoint {
static func + (a:CGPoint, b:CGPoint) -> CGPoint {
return CGPoint(x: a.x + b.x, y: a.y + b.y)
}
static func - (a:CGPoint, b:CGPoint) -> CGPoint {
return CGPoint(x: a.x - b.x, y: a.y - b.y)
}
}

How detect collision between a box object and ball

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self) // locates touches on the screen
let objects = nodes(at: location) // shows all objects were screen was touched
if objects.contains(editLabel) { // checking if appropriate array of objects contains the editLabel
editingMode.toggle()
} else {
if editingMode {
let size = CGSize(width: Int.random(in: 16...128), height: 16)
let box = SKSpriteNode(color: UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: 1), size: size)
box.zRotation = CGFloat.random(in: 0...3)
box.position = location // add the box ob ject were the user taps
box.name = "box"
box.physicsBody = SKPhysicsBody(rectangleOf: box.size)
box.physicsBody!.contactTestBitMask = box.physicsBody!.collisionBitMask
box.physicsBody?.isDynamic = false // box object will not move when impact happens
addChild(box) // box object will be added to the screen
} else if usedBalls != 0 {
let ball = SKSpriteNode(imageNamed: allBalls.randomElement() ?? "ballRed.png") // pulls out the red ball from app bundle
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width / 2.0) // animates the ball object
ball.physicsBody!.contactTestBitMask = ball.physicsBody!.collisionBitMask
ball.physicsBody?.restitution = 0.4 // determines the ball object bounciness
ball.position = location // ball will appear where the user tapped on the screen
ball.name = "ball"
// ball.position = CGPoint (x: 550, y: 700)
addChild (ball) // adds the ball object to the screen
usedBalls -= 1
}
}
}
func makeBouncer(at position: CGPoint) {
let bouncer = SKSpriteNode(imageNamed: "bouncer.png")
bouncer.position = position
bouncer.physicsBody = SKPhysicsBody(circleOfRadius: bouncer.size.width / 2.0)
bouncer.physicsBody?.isDynamic = false // bouncer object is fixed to the bottom of the screen
addChild(bouncer)
}
func makeSlot(at position: CGPoint, isGood: Bool) {
var slotBase: SKSpriteNode
var slotGlow: SKSpriteNode
if isGood {
slotBase = SKSpriteNode(imageNamed: "slotBaseGood.png")
slotGlow = SKSpriteNode(imageNamed: "slotGlowGood.png")
slotBase.name = "good"
} else {
slotBase = SKSpriteNode(imageNamed: "slotBaseBad.png")
slotGlow = SKSpriteNode(imageNamed: "slotGlowBad.png")
slotBase.name = "bad"
}
slotBase.position = position
slotGlow.position = position
slotBase.physicsBody = SKPhysicsBody(rectangleOf: slotBase.size)
slotBase.physicsBody?.isDynamic = false
addChild(slotBase)
addChild(slotGlow)
let spin = SKAction.rotate(byAngle: .pi, duration: 10)
let spinForever = SKAction.repeatForever(spin)
slotGlow.run(spinForever)
}
func collisionBetween(ball: SKNode, object: SKNode) {
if object.name == "good" { // green slot
destroy(ball: ball)
score += 1
usedBalls += 1
} else if object.name == "bad" { // red slot
destroy(ball: ball) // the ball will be removed once drops into a green or red slot
score -= 1
}
}
func boxandballCollision(box: SKNode, ball: SKNode) {
if ball.name == "ball" {
destroyObject(box: box)
}
}
func destroyObject(box: SKNode) {
if let fireParticles = SKEmitterNode(fileNamed: "FireParticles") {
fireParticles.position = box.position
addChild(fireParticles)
}
box.removeFromParent() // box should be removed when a ball will hit it
}
func destroy(ball: SKNode) {
if let fireParticles = SKEmitterNode(fileNamed: "FireParticles") {
fireParticles.position = ball.position
addChild(fireParticles)
}
ball.removeFromParent() // ball object is removed from scene when hits a slot
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA.name == "ball" {
collisionBetween(ball: nodeA, object: nodeB)
} else if nodeB.name == "ball" {
collisionBetween(ball: nodeB, object: nodeA)
}
}
func begin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
if nodeA.name == "box" {
boxandballCollision(box: nodeA, ball: nodeB)
} else if nodeB.name == "box" {
boxandballCollision(box: nodeB, ball: nodeA)
}
}
I want to remove any box object when a ball will hit it, is just a simple game for studying purposes, but I'm struggling with it. I have used exactly same methods for the box object and this to get notifications about every collision " box.physicsBody!.contactTestBitMask = box.physicsBody!.collisionBitMask".

SCNNode Plane not being correctly detected after rotation

I have an horizontal plane being detected. Once it's detected, I want that plane, which is a rectangle, to always appears horizontal (width > height).
this is the code I'm using to create this SCNNode:
func createFloorNode(anchor: ARPlaneAnchor) ->SCNNode {
let geometry = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
let floorNode = SCNNode(geometry: geometry)
floorNode.position = SCNVector3(anchor.center.x, 0, anchor.center.z)
floorNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
floorNode.geometry?.firstMaterial?.isDoubleSided = true
floorNode.eulerAngles = SCNVector3(-Double.pi/2, 0, 0)
if geometry.width < geometry.height {
floorNode.eulerAngles = SCNVector3(-Double.pi/2, -Double.pi/2, 0)
}
floorNode.name = floorNodeName
return floorNode
}
After this, I have a tapped gesture funtion added, which I want it to let me know if I have tapped inside my SCNNode:
#objc func tapped(sender: UITapGestureRecognizer) {
guard let sceneView = sender.view as? ARSCNView else { return }
let tapLocation = sender.location(in: sceneView)
let hitTest = sceneView.hitTest(tapLocation, types: .existingPlaneUsingExtent)
guard !hitTest.isEmpty else {
print("Not a plane")
return
}
print("Touched on the plane")
}
Problem is, after I rotated my scnnode, that functions keeps detecting the plane as the old one (before being detected). If I don't rotate it, it works fine.
Any ideas?

Smooth object rotation in ARSCNView

I'm trying to make my pan gesture to be as smooth as the jigspace app when rotating 3d objects in AR. Here's what I have right now:
#objc func rotateObject(sender: UIPanGestureRecognizer) {
let sceneView = sender.view as! ARSCNView
var currentAngleY: Float = 0.0
let translation = sender.translation(in: sceneView)
var newAngleY = Float(translation.x)*Float(Double.pi)/180
sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
if sender.state == .changed {
newAngleY -= currentAngleY
node.eulerAngles.y = newAngleY
} else if sender.state == .ended {
currentAngleY = newAngleY
node.removeAllActions()
}
}
}
There seems to be a delay when I'm using it and I'm trying to figure out how to make the rotation as smooth as possible, again, kinda like jigspace or the Ikea app.
I've also noticed that when I try to rotate the object when it's in a certain angle, it could get quite awkward.
Looking at your rotate object function it seems like some of the logic is not quite right.
Firstly, I believe that the var currentAngleY: Float = 0 should be outside of your function body.
Secondly you should be adding the currentAngleY to the newAngleY variable e.g:
/// Rotates The Models On Their YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateModels(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
DispatchQueue.main.async {
self.sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
node.eulerAngles.y = newAngleY
}
}
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
An example therefore of this in a working context would be like so:
class ViewController: UIViewController {
#IBOutlet var augmentedRealityView: ARSCNView!
var currentAngleY: Float = 0
//-----------------------
// MARK: - View LifeCycle
//-----------------------
override func viewDidLoad() {
super.viewDidLoad()
//1. Generate Our Three Box Nodes
generateBoxNodes()
//2. Create Our Rotation Gesture
let rotateGesture = UIPanGestureRecognizer(target: self, action: #selector(rotateModels(_:)))
self.view.addGestureRecognizer(rotateGesture)
//3. Run The Session
let configuration = ARWorldTrackingConfiguration()
augmentedRealityView.session.run(configuration)
}
//------------------------
// MARK: - Node Generation
//------------------------
/// Generates Three SCNNodes With An SCNBox Geometry
func generateBoxNodes(){
//1. Create An Array Of Colours For Each Face
let colours: [UIColor] = [.red, .green, .blue, .purple, .cyan, .black]
//2. Create An SCNNode Wih An SCNBox Geometry
let boxNode = SCNNode()
let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.01)
boxNode.geometry = boxGeometry
//3. Create A Different Material For Each Face
var materials = [SCNMaterial]()
for i in 0..<5{
let faceMaterial = SCNMaterial()
faceMaterial.diffuse.contents = colours[i]
materials.append(faceMaterial)
}
//4. Set The Geometries Materials
boxNode.geometry?.materials = materials
//5. Create Two More Nodes By Cloning The First One
let secondBox = boxNode.flattenedClone()
let thirdBox = boxNode.flattenedClone()
//6. Position Them In A Line & Add To The Scene
boxNode.position = SCNVector3(-0.2, 0, -1.5)
secondBox.position = SCNVector3(0, 0, -1.5)
thirdBox.position = SCNVector3(0.2, 0, -1.5)
self.augmentedRealityView.scene.rootNode.addChildNode(boxNode)
self.augmentedRealityView.scene.rootNode.addChildNode(secondBox)
self.augmentedRealityView.scene.rootNode.addChildNode(thirdBox)
}
//----------------------
// MARK: - Node Rotation
//----------------------
/// Rotates The Models On Their YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateModels(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
DispatchQueue.main.async {
self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (node, _) in
node.eulerAngles.y = newAngleY
}
}
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
}
Hope it helps...

How to create explosion effect using image frames sprite kit swift

So I created this game where you have to shoot at objects. Now, I have an imageset that replicates an object exploding. I would like to call those images to appear in a sequence so it looks like an explosion after the projectile hits the object. Obviously the images have to be called at the exact location of where the projectile hits the object. Does anyone have any idea on how to make this happen? Here is some code.
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
projectile.removeFromParent()
monster.removeFromParent()
playerScore = playerScore + 1
playerScoreUpdate()
if (playerScore > 100) {
let reveal = SKTransition.crossFadeWithDuration(0.3)
let gameOverScene = GameOverScene(size: self.size, won: true)
self.view?.presentScene(gameOverScene, transition: reveal)
}
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & UInt32(laserCategory)) != 0 && (secondBody.categoryBitMask & UInt32(monsterCategory)) != 0 {
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
}
if playerScore > highScore() {
saveHighScore(playerScore)
println("New Highscore = " + highScore().description)
highScoreLabel.text = "Best score: \(highScore().description)"
} else {
println("HighScore = " + highScore().description ) // "HighScore = 100"
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "muteSound" {
if musicIsPlaying == true {
backgroundMusicPlayer.stop()
musicIsPlaying = false
} else if musicIsPlaying == false {
backgroundMusicPlayer.play()
musicIsPlaying = true
}
} else {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let projectile = SKSpriteNode(imageNamed: "boom")
projectile.setScale(0.6)
projectile.position = player.position
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.dynamic = true
projectile.physicsBody?.categoryBitMask = UInt32(laserCategory)
projectile.physicsBody?.contactTestBitMask = UInt32(monsterCategory)
projectile.physicsBody?.collisionBitMask = 0
projectile.physicsBody?.usesPreciseCollisionDetection = true
// 3 - Determine offset of location to projectile
let offset = touchLocation - projectile.position
// 4 - Bail out if you are shooting down or backwards
if (offset.y < 0) { return }
// 5 - OK to add now - you've double checked position
addChild(projectile)
// 6 - Get the direction of where to shoot
let direction = offset.normalized()
// 7 - Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
// 8 - Add the shoot amount to the current position
let realDest = shootAmount + projectile.position
// 9 - Create the actions
let actionMove = SKAction.moveTo(realDest, duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
if !isStarted {
start()
}else{
projectile.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}
}
}
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 10)
self.addChild(monster)
}
For you explosion you could create an SKSpriteNode that play the frames you mentioned:
1. You're going to need the images as an array of SKTextures. You said you've got you images in an image set so the easiest thing to do may be to create an array using a for loop, for example:
// I don't know how many images you've got, so I'll use 10.
var textures: [SKTexture] = []
for i in 0..<10 {
let imageName = "explosion\(i)"
textures.append(SKTexture(imageNamed: imageName))
}
Alternatively, which is what I would recommend, is to create a Texture Atlas of your images. (For more information on texture atlases see here) To create an atlas, make a folder with the extension .atlas and adding all your explosion images to it. (Then add this to your project). Here's an extension I wrote to get your sprites out of a texture atlas, ready for animation:
extension SKTextureAtlas {
func textureArray() -> [SKTexture] {
var textureNames = self.textureNames as! [String]
// They need to be sorted because there's not guarantee the
// textures will be in the correct order.
textureNames.sort { $0 < $1 }
return textureNames.map { SKTexture(imageNamed: $0) }
}
}
And here's how to use it:
let atlas = SKTextureAtlas(named: "MyAtlas")
let textures = atlas.textureArray()
2. Now you've got your textures you need to create an SKSpriteNode and animate it:
let explosion = SKSpriteNode(texture: textures[0])
let timePerFrame = // this is specific to your animation.
let animationAction = SKAction.animateWithTextures(textures, timePerFrame: timePerFrame)
explosion.runAction(animationAction)
3. Add the sprite to your scene and position it correctly. To add it in the correct place you could use the contactPoint variable on SKPhysicsContact, after checking it was the projectile hitting an object.
func didBeginContact(contact: SKPhysicsContact) {
// Other stuff...
explosion.position = contact.contactPoint
self.addChild(explosion)
}
Hope that helps!