collision between two sprites not working - xcode

I Cannot seem to get the 2 sprites to make contact with one another, as when the app runs one of the sprites does not make contact but just goes pass it. I'm not sure whats wrong. Could someone please help me.
import SpriteKit
import GameplayKit
enum BodyType:UInt32{
case caveMan = 1
case Zombie = 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var caveMan:SKSpriteNode = SKSpriteNode ()
let swipeRightRec = UISwipeGestureRecognizer ()
var Zombie:SKSpriteNode = SKSpriteNode ()
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
swipeRightRec.addTarget(self, action: #selector (GameScene.swipedRight ))
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
if let somePlayer:SKSpriteNode = self.childNode(withName: "caveMan") as? SKSpriteNode {
caveMan = somePlayer
caveMan.physicsBody?.affectedByGravity = true
caveMan.physicsBody?.isDynamic = false
caveMan.physicsBody?.categoryBitMask = BodyType.caveMan.rawValue
caveMan.physicsBody?.collisionBitMask = BodyType.Zombie.rawValue
caveMan.physicsBody?.contactTestBitMask = BodyType.Zombie.rawValue
}
if let somePlayer:SKSpriteNode = self.childNode(withName: "Zombie") as? SKSpriteNode {
Zombie = somePlayer
Zombie.physicsBody?.affectedByGravity = false
Zombie.physicsBody?.isDynamic = false
Zombie.physicsBody?.categoryBitMask = BodyType.Zombie.rawValue
Zombie.physicsBody?.collisionBitMask = BodyType.caveMan.rawValue
Zombie.physicsBody?.contactTestBitMask = BodyType.caveMan.rawValue
}
}
#objc func swipedRight() {
print("went right")
moveDown()
}
func moveDown() {
let walkAnimation:SKAction = SKAction(named: "Running")!
let walk:SKAction = SKAction.moveBy(x: 90, y: 0, duration: 1)
let group:SKAction = SKAction.group([walkAnimation, walk])
caveMan.run(group)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
break
}
}
func didBegin(_ contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == BodyType.caveMan.rawValue && contact.bodyB.categoryBitMask == BodyType.Zombie.rawValue) {
print ("touched a Zombie")
} else if (contact.bodyB.categoryBitMask == BodyType.caveMan.rawValue && contact.bodyA.categoryBitMask == BodyType.Zombie.rawValue) {
print ("touched a Zombie")
}
}
}

See this: caveMan.physicsBody? That means if a body exists then allow a value to be set. It is called optional binding, or short circuiting in other languages.
You need to create the physics body, it is not given to you for free.
As of right now this is what your code looks like:
caveMan = somePlayer
nil.affectedByGravity = true
nil.isDynamic = false
nil.categoryBitMask = BodyType.caveMan.rawValue
nil.collisionBitMask = BodyType.Zombie.rawValue
nil.contactTestBitMask = BodyType.Zombie.rawValue
what you want to do is:
caveMan = somePlayer
caveMan.physicsBody = SKPhysicsBody(rectangleOf: caveMan.size)
caveMan.physicsBody!.affectedByGravity = true
caveMan.physicsBody!.isDynamic = false
caveMan.physicsBody!.categoryBitMask = BodyType.caveMan.rawValue
caveMan.physicsBody!.collisionBitMask = BodyType.Zombie.rawValue
caveMan.physicsBody!.contactTestBitMask = BodyType.Zombie.rawValue

Related

Not able to Rotate my 3D objects

I had created one project using ARKit and SceneKit framework. In which I am working with file extension .dae, the files are locally available in my project as shown in below screenshot.
Here I had applied many gestures on this virtual object such as Tap Gesture(When I tap on camera screen, it places the virtual object there), same way Pinch Gesture and Pan Gesture. All of these gestures are working perfectly fine. Now I wanted to apply rotation gesture, for which I got stuck how to do that, also I am not getting any such available sources to achieve this.
Below is my working code so far,
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
private var movedObject: SCNNode?
private var hud :MBProgressHUD!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.autoenablesDefaultLighting = true
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
registerGestureRecognizers()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
private func registerGestureRecognizers() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(recognizer:)))
tapGestureRecognizer.numberOfTapsRequired = 1
self.sceneView.addGestureRecognizer(tapGestureRecognizer)
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinched(recognizer:)))
self.sceneView.addGestureRecognizer(pinchGestureRecognizer)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveObject(recognizer:)))
panGestureRecognizer.maximumNumberOfTouches = 1
panGestureRecognizer.minimumNumberOfTouches = 1
self.sceneView.addGestureRecognizer(panGestureRecognizer)
let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotateObject(recognizer:)))
self.sceneView.addGestureRecognizer(rotationGestureRecognizer)
}
#objc func pinched(recognizer :UIPinchGestureRecognizer) {
if recognizer.state == .changed {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = self.sceneView.hitTest(touch, options: nil)
if let hitTest = hitTestResults.first {
let chairNode = hitTest.node
let pinchScaleX = Float(recognizer.scale) * chairNode.scale.x
let pinchScaleY = Float(recognizer.scale) * chairNode.scale.y
let pinchScaleZ = Float(recognizer.scale) * chairNode.scale.z
chairNode.scale = SCNVector3(pinchScaleX,pinchScaleY,pinchScaleZ)
recognizer.scale = 1
}
}
}
#objc func moveObject(recognizer: UIPanGestureRecognizer) {
print("Move object")
if recognizer.state == .began {
print("Pan state began")
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let result = sceneView.hitTest(tapPoint ?? CGPoint.zero, options: nil)
if result.count == 0 {
return
}
let hitResult: SCNHitTestResult? = result.first
if (hitResult?.node.name == "free_car_1") {
movedObject = hitResult?.node
} else if (hitResult?.node.parent?.name == "free_car_1") {
movedObject = hitResult?.node.parent
}
if (movedObject != nil) {
print("Holding an Object")
}
}
if recognizer.state == .changed {
print("Pan State Changed")
if (movedObject != nil) {
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(tapPoint ?? CGPoint.zero, types: .featurePoint)
let result: ARHitTestResult? = hitResults.last
let matrix: SCNMatrix4 = SCNMatrix4((result?.worldTransform)!)
//SCNMatrix4FromMat4((result?.worldTransform)!)
let vector: SCNVector3 = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
movedObject?.position = vector
print("Moving object position")
}
}
if recognizer.state == .ended {
print("Done moving object homeie")
movedObject = nil
}
}
#objc func tapped(recognizer :UITapGestureRecognizer) {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = sceneView.hitTest(touch)
guard let hitTest = hitTestResults.first?.node else {
let hitTestResultsWithExistingPlane = sceneView.hitTest(touch, types: .existingPlane)
let chairScene = SCNScene(named: "ShelbyWD.dae")!
guard let chairNode = chairScene.rootNode.childNode(withName: "ShelbyWD", recursively: true) else {
return
}
if let hitTestAvailable = hitTestResultsWithExistingPlane.first {
chairNode.position = SCNVector3(hitTestAvailable.worldTransform.columns.3.x,hitTestAvailable.worldTransform.columns.3.y,hitTestAvailable.worldTransform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(chairNode)
return
}
return
}
hitTest.removeFromParentNode()
}
#objc func rotateObject(recognizer :UIRotationGestureRecognizer)
{
}
}
Can anyone help me out to apply rotation gesture on my object?
Thank you!
In order to rotate an SCNNode, the 1st thing you need to do, is create a variable to store the rotationAngle around the YAxis or any other that you wish to perform the rotation on e.g:
var currentAngleY: Float = 0.0
Then have some way to have detected to node you wish to rotate, which in my example I am calling currentNode e.g.
var currentNode: SCNNode!
In my example I will just rotate around the YAxis.
You can use a UIPanGestureRecognizer like so:
/// Rotates An Object On It's YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateObject(_ gesture: UIPanGestureRecognizer) {
guard let nodeToRotate = currentNode else { return }
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
nodeToRotate.eulerAngles.y = newAngleY
if(gesture.state == .ended) { currentAngleY = newAngleY }
print(nodeToRotate.eulerAngles)
}
Or if you wish to use a UIRotationGesture you can do something like this:
/// Rotates An SCNNode Around It's YAxis
///
/// - Parameter gesture: UIRotationGestureRecognizer
#objc func rotateNode(_ gesture: UIRotationGestureRecognizer){
//1. Get The Current Rotation From The Gesture
let rotation = Float(gesture.rotation)
//2. If The Gesture State Has Changed Set The Nodes EulerAngles.y
if gesture.state == .changed{
isRotating = true
currentNode.eulerAngles.y = currentAngleY + rotation
}
//3. If The Gesture Has Ended Store The Last Angle Of The Cube
if(gesture.state == .ended) {
currentAngleY = currentNode.eulerAngles.y
isRotating = false
}
}
Hope it helps...

Trouble with collision detection swift 2.0 spritekit

I am building a game for iOS for the first time. I am very close, however I got collision detection working early then I changed something and I lost it working. I have tried to get in back but haven't work. This is what I have so far, but nothing working. I am trying different versions of collision detection that I have found online but I am sticking to get this one to work. Just don't understand where I have went wrong.
Thank you in advanced.
import SpriteKit
enum ColliderType: UInt32 {
case Player = 1
case Traffic = 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
createWalls()
self.backgroundColor = SKColor.whiteColor()
self.physicsWorld.gravity = CGVectorMake(0, 0)
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.dynamic = true
player.physicsBody!.categoryBitMask = ColliderType.Player.rawValue
player.physicsBody!.contactTestBitMask = ColliderType.Traffic.rawValue
player.zPosition = 3
player.name = "player"
player.position = CGPoint(x: self.frame.midX, y: (self.frame.midY)/3)
player.setScale(1.0)
self.addChild(player)
_ = NSTimer.scheduledTimerWithTimeInterval(2.5, target: self, selector: #selector(GameScene.makeTraffic), userInfo: nil, repeats: true)
}
func makeTraffic(){
var aNumber = Int(arc4random_uniform(2))
let Pos1 = Int(self.frame.midX/2)+30
let Pos2 = Int((self.frame.size.width)/2)
let Pos3 = Int(self.frame.size.width-300)
let array = [Pos1, Pos2, Pos3]
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
let randomPOS = CGPoint(x:Int(array[randomIndex]), y:Int(self.frame.height))
if aNumber == 0 {
aNumber = aNumber + 1
}
switch aNumber {
case 1:
let car1 = SKSpriteNode(imageNamed: "Car_Green_Front")
car1.position = randomPOS
car1.zPosition = 3
car1.setScale(1.0)
car1.physicsBody = SKPhysicsBody(rectangleOfSize: car1.size)
car1.physicsBody?.dynamic = true
car1.physicsBody!.categoryBitMask = ColliderType.Player.rawValue
car1.physicsBody!.contactTestBitMask = ColliderType.Traffic.rawValue
self.addChild(car1)
case 2:
let car2 = SKSpriteNode(imageNamed: "Car_Purple_Front")
car2.position = randomPOS
car2.zPosition = 3
car2.setScale(1.0)
car2.physicsBody = SKPhysicsBody(rectangleOfSize: car2.size)
car2.physicsBody?.dynamic = true
car2.physicsBody!.categoryBitMask = ColliderType.Traffic.rawValue
car2.physicsBody!.contactTestBitMask = ColliderType.Player.rawValue
self.addChild(car2)
default:
return
}
}
func createWalls(){
let wallSize = CGSize(width: 5, height: self.frame.size.height)
let rightwall = SKShapeNode(rectOfSize: wallSize)
rightwall.physicsBody = SKPhysicsBody(rectangleOfSize: wallSize)
rightwall.physicsBody!.dynamic = false
rightwall.position = CGPoint(x: self.frame.maxX-300, y: self.frame.size.height/2)
rightwall.fillColor = UIColor.clearColor()
self.addChild(rightwall)
let leftwall = SKShapeNode(rectOfSize: wallSize)
leftwall.physicsBody = SKPhysicsBody(rectangleOfSize: wallSize)
leftwall.physicsBody!.dynamic = false
leftwall.position = CGPoint(x: self.frame.minX+300, y: self.frame.size.height/2)
leftwall.fillColor = UIColor.clearColor()
self.addChild(leftwall)
}
func didBeginContact(contact: SKPhysicsContact) {
print("Contact")
if contact.bodyA.categoryBitMask == ColliderType.Traffic.rawValue && contact.bodyB.categoryBitMask == ColliderType.Player.rawValue {
print("Hi")
} else {
print("Hello") }
}
}
Your code seems alrite, although there is some small changes I would make.
1) I would write my collider types like so
struct ColliderType {
static let player: UInt32 = 0x1 << 0
static let traffic: UInt32 = 0x1 << 1
}
because this way you only need to increment the last number by 1.
Your way if you decide to add more categories the next one would have to be 4, than 8, than 16 etc, which is more confusing (you are dealing with 32 bit integers)
Than use it like so
...categoryBitMask = ColliderType.player
2) It is recommended that you give the sprite the position before adding the physicsBody. You are doing it the other way round which could cause unexpected issues.
3) Change your collision method to this
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 == ColliderType.player) && (secondBody.categoryBitMask == ColliderType.traffic) {
// player hit traffic, do something
}
}
4) Finally the most important part is that you need to set the delegate which I couldn't see in your code.
Call this in DidMoveToView
physicsWorld.contactDelegate = self
otherwise the DidBeginContact method will never fire.
Also it is a good idea if you follow apples naming conventions. So only classes, protocols, enums and structs should start with capital letters.
Hope this helps
A cleaner way to code didBeginContact is:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case ColliderType.player | ColliderType.traffic:
// player and traffic have contacted
print("Collision between player and traffic")
default :
//Some other contact has occurred
print("Some other contact")
}
}
You can add as many case ColliderType.object1 | ColliderType.object2: as you like.

How to show route between a MKPointAnnotation and user's current location in swift 2

I am trying to show the route between a MKPointAnnotation and user's current location, but i am fail with it.
My idea is: getting user's current location -> getting the MKPointAnnotation' Coordinate -> line up with MKPolylineRenderer
The problem is that i cannot find the problem. :( I have no idea where i should modify.
class MapInSearch: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var destination: MKMapItem?
var coords: CLLocationCoordinate2D?
let locationManager = CLLocationManager()
var PlaceLat = ""
var PlaceLong = ""// get from previous view controller
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}// step 1
self.mapView.showsUserLocation = true
self.mapView.delegate = self
self.addRoute() // step 2
}
func addRoute() {
var pointsToUse: [CLLocationCoordinate2D] = []
if PlaceLat != "" || PlaceLong != "" {
let coords = "\(PlaceLat), \(PlaceLong)"
let p = CGPointFromString(coords)
pointsToUse += [CLLocationCoordinate2DMake(CLLocationDegrees(p.x), CLLocationDegrees(p.y))]
}
pointsToUse += [CLLocationCoordinate2DMake(CLLocationDegrees(coords!.latitude), CLLocationDegrees(coords!.longitude))]
let myPolyline = MKPolyline(coordinates: &pointsToUse, count: 2)
mapView.addOverlay(myPolyline)
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let lineView = MKPolylineRenderer(overlay: overlay)
lineView.strokeColor = UIColor.greenColor()
return lineView // step 3
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.coords = manager.location!.coordinate
print("locations = \(coords!.latitude) \(coords!.longitude)")
}
My code is very disorderly because i mixed 4-5 tutorials. Also, these tutorials is written with swift 1.2.(i have tried to edit it to swift 2, but i am fail)
Did you ever resolve your problem? Using the latest iteration of Swift 2 in XCode 7.3, in your view (we will call it MyViewController):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.mapView.delegate = self
var coordinates : [CLLocationCoordinate2D] = [];
addRoute(coordinates);
}
func addRoute(coordinates: [CLLocationCoordinate2D]) {
// insert your code to populate coordinates array with your coordinates
polyLine = MKPolyline(coordinates: &coordinates, count: coordinates.count)
self.mapView.addOverlay(polyLine, level: MKOverlayLevel.AboveRoads)
}
Then in the same file:
extension MyViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let pr = MKPolylineRenderer(overlay: overlay);
pr.strokeColor = UIColor.blueColor().colorWithAlphaComponent(0.5);
pr.lineWidth = 5;
return pr;
}
}
You may find the important part was the extension. I haven't tested this code, so feel free to correct any issues that crept in.
in your CLLocationManagerDelegate delegate function didUpdateLocations you can update your location by setting
self.myLocation = locations[0] as CLLocation
Then call MakeRoute() - This is a function i wrote to either make a route by car or by walking (hence the self.driveIsSet)
func makeRoute() {
let startPlaceMark = MKPlacemark(coordinate: myLocation.coordinate)
let endPlaceMark = MKPlacemark(coordinate: restLocation.coordinate)
let startMapItem = MKMapItem(placemark: startPlaceMark)
let endMapItem = MKMapItem(placemark: endPlaceMark)
let directionRequest = MKDirectionsRequest()
directionRequest.source = startMapItem
directionRequest.destination = endMapItem
if self.driveIsSet {
directionRequest.transportType = .automobile
} else {
directionRequest.transportType = .walking
}
let directions = MKDirections(request: directionRequest)
directions.calculate { (routeResponse, routeError) in
guard let routeResponse = routeResponse else {
if let routeError = routeError {
print(routeError)
}
return
}
self.mapView.removeOverlays(self.mapView.overlays)
let route = routeResponse.routes[0]
self.mapView.add(route.polyline, level: .aboveRoads)
}
}

building project from a tutorial in xcode 7 swift 2, encountering unresolved identifier

I am following a tutorial written in Swift 2 for Xcode 7, part 1 of which (you can navigate to part IV, where my issue has come up) is here: http://www.mav3r1ck.io/spritekit-with-swift/
I am using my own sprites in place of those in the tutorial. When I run my code, an error appears on the first line of the following
let spawnRandomHead = SKAction.runBlock(spawnHead)
let waitTime = SKAction.waitForDuration(1.0)
let sequence = SKAction.sequence([spawnRandomHead,waitTime])
runAction(SKAction.repeatActionForever(sequence))
The full code is here:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
enum bitMask: UInt32 {
case defender = 1
case head = 2
case frame = 4
}
let defender = SKSpriteNode(imageNamed: "Ivanovic is a boss")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = UIColor.blueColor()
defender.position = CGPoint(x: frame.size.width / 2, y:frame.size.height / 2)
defender.physicsBody = SKPhysicsBody(texture: defender.texture!, size: defender.frame.size)
defender.physicsBody?.dynamic = false
defender.physicsBody?.affectedByGravity = false
defender.physicsBody?.allowsRotation = false
defender.physicsBody?.categoryBitMask = bitMask.head.rawValue
defender.physicsBody?.contactTestBitMask = bitMask.head.rawValue
defender.physicsBody?.collisionBitMask = 0
addChild(defender)
let spawnRandomHead = SKAction.runBlock(spawnHead)
let waitTime = SKAction.waitForDuration(1.0)
let sequence = SKAction.sequence([spawnRandomHead,waitTime])
runAction(SKAction.repeatActionForever(sequence))
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVectorMake(0.0, -0.9)
defender.physicsBody?.contactTestBitMask = bitMask.frame.rawValue
defender.physicsBody?.collisionBitMask = bitMask.frame.rawValue
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
//print(touchLocation)
let moveTo = SKAction.moveTo(touchLocation, duration: 1.0)
defender.runAction(moveTo)
func randomNumber(min min: CGFloat, max: CGFloat) -> CGFloat {
let random = CGFloat(Float(arc4random()) / 0xFFFFFFFF)
return random * (max - min) + min
}
func spawnHead() {
let head = SKSpriteNode(imageNamed: "The Biter Strikes")
head.position = CGPoint(x: frame.size.width * randomNumber(min: 0, max: 1), y: frame.size.height + head.size.height)
head.physicsBody = SKPhysicsBody(texture: head.texture!, size: head.frame.size)
head.physicsBody?.categoryBitMask = bitMask.head.rawValue
head.physicsBody?.contactTestBitMask = bitMask.defender.rawValue
addChild(head)
}
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(contactMask) {
case bitMask.defender.rawValue | bitMask.head.rawValue:
let secondNode = contact.bodyB.node
secondNode?.physicsBody?.allowsRotation = true
let firstNode = contact.bodyA.node
firstNode?.physicsBody?.allowsRotation = true
firstNode?.removeFromParent()
default:
return
}
}
}
}
I have tried cleaning & rebuilding, restarting Xcode, and moving sections of the code around, but the error does not go away. I appreciate your support!
Hmm. Tried both. Now on the second line this
let spawnRandomHead = SKAction.runBlock({ [unowned self] () -> Void in
self.spawnHead()
})
let waitTime = SKAction.waitForDuration(1.0)
let sequence = SKAction.sequence([spawnRandomHead,waitTime])
runAction(SKAction.repeatActionForever(sequence))
a new error pops up saying " Value of type 'GameScene' has no member 'spawnHead' ".
The runBlock requires closure as an argument, so replace
let spawnRandomHead = SKAction.runBlock(spawnHead)
with
let spawnRandomHead = SKAction.runBlock({ [unowned self] () -> Void in
self.spawnHead()
})
or simply
let spawnRandomHead = SKAction.runBlock { [unowned self] in
self.spawnHead()
}

removeFromParent() does not remove a node, but makes it invisible

I have a restartButton that must appear when two bodies collide and when it happens for the first time, all goes great - bodies collide--> restartButton appears--> I restart level by touching restartButton.
It was a "good, right restart" And here problem starts...
After level been restarted, if I touch at center of the screen(where restartButton must appear when is called) game crashes, saying following:
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent: name:'(null)' particleTexture: 'enemyPart.png' (100 x 100) position:{721.33929, 175.39999} accumulatedFrame:{{inf, inf}, {inf, inf}}'
** First throw call stack:
(0x2a0fa137 etc.)
libc++abi.dylib: terminating with uncaught exception of type NSException"
but restartButton is invisible and it couldn't even be there because no bodies have collided.
If after that "good restart" some enemy collide with player, restartButton appears for a moment and player, enemy1, enemy2, enemy3 are fadingOut from scene.
I would appreciate if someone can help
Here is code where you can see all that stuff:
import SpriteKit
import UIKit
let player = SKEmitterNode(fileNamed: "playerPart.sks")
let enemy1 = SKEmitterNode(fileNamed: "ePart.sks")
let enemy2 = SKEmitterNode(fileNamed: "ePart.sks")
let enemy3 = SKEmitterNode(fileNamed: "ePart.sks")
let restartButton = SKSpriteNode(imageNamed: "restartButton")
let playerCat: UInt32 = 0x1 << 0
let enemyCat: UInt32 = 0x1 << 1
class Level2: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
initWorld()
movements()
player.physicsBody = SKPhysicsBody(circleOfRadius: 50)
player.position = CGPointMake(819.2 , 693.8)
player.zPosition = 1
player.physicsBody?.categoryBitMask = playerCat
player.physicsBody?.contactTestBitMask = enemyCat
player.targetNode = self
self.addChild(player)
enemy1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
enemy1.position = CGPointMake(819.2, 175.4)
enemy1.zPosition = 1
enemy1.physicsBody?.affectedByGravity = false
enemy1.physicsBody?.dynamic = true
enemy1.physicsBody?.allowsRotation = false
enemy1.physicsBody?.categoryBitMask = enemyCat
enemy1.physicsBody?.contactTestBitMask = playerCat
enemy1.physicsBody?.collisionBitMask = 0x0
enemy1.targetNode = self
enemy1.particleBirthRate = 150
enemy1.particleLifetime = 10
enemy1.particleLifetimeRange = 20
enemy1.particlePositionRange = CGVectorMake(50, 60)
enemy1.emissionAngle = 0
enemy1.emissionAngleRange = 0
enemy1.particleSpeed = 0
enemy1.particleSpeedRange = 0
enemy2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
enemy2.position = CGPointMake(614.4, 386.6)
enemy2.zPosition = 1
enemy2.physicsBody?.affectedByGravity = false
enemy2.physicsBody?.dynamic = true
enemy2.physicsBody?.allowsRotation = false
enemy2.physicsBody?.categoryBitMask = enemyCat
enemy2.physicsBody?.contactTestBitMask = playerCat
enemy2.physicsBody?.collisionBitMask = 0x0
enemy2.targetNode = self
enemy2.particleBirthRate = 150
enemy2.particleLifetime = 10
enemy2.particleLifetimeRange = 20
enemy2.particlePositionRange = CGVectorMake(50, 60)
enemy2.emissionAngle = 0
enemy2.emissionAngleRange = 0
enemy2.particleSpeed = 0
enemy2.particleSpeedRange = 0
enemy3.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
enemy3.position = CGPointMake(409.6, 181.8)
enemy3.zPosition = 1
enemy3.physicsBody?.affectedByGravity = false
enemy3.physicsBody?.dynamic = true
enemy3.physicsBody?.allowsRotation = false
enemy3.physicsBody?.categoryBitMask = enemyCat
enemy3.physicsBody?.contactTestBitMask = playerCat
enemy3.physicsBody?.collisionBitMask = 0x0
enemy3.targetNode = self
enemy3.particleBirthRate = 150
enemy3.particleLifetime = 10
enemy3.particleLifetimeRange = 20
enemy3.particlePositionRange = CGVectorMake(50, 60)
enemy3.emissionAngle = 0
enemy3.emissionAngleRange = 0
enemy3.particleSpeed = 0
enemy3.particleSpeedRange = 0
func initWorld() {
self.addChild(enemy1)
self.addChild(enemy2)
self.addChild(enemy3)
}
func movements() {
let move11 = SKAction.moveTo(CGPointMake(819.2, 386.6), duration: 1.5)
let move12 = SKAction.moveTo(CGPointMake(614.4, 386.6), duration: 1.5)
let move13 = SKAction.moveTo(CGPointMake(614.4, 175.4), duration: 1.5)
let move14 = SKAction.moveTo(CGPointMake(819.2, 175.4), duration: 1.5)
let enemy1m = SKAction.sequence([move11, move12, move13, move14])
let enemy1move = SKAction.repeatActionForever(enemy1m)
let move21 = SKAction.moveTo(CGPointMake(614.4, 591.4), duration: 1.5)
let move22 = SKAction.moveTo(CGPointMake(409.6, 591.4), duration: 1.5)
let move23 = SKAction.moveTo(CGPointMake(409.6, 386.6), duration: 1.5)
let move24 = SKAction.moveTo(CGPointMake(614.4, 386.6), duration: 1.5)
let enemy2m = SKAction.sequence([move21, move22, move23, move24])
let enemy2move = SKAction.repeatActionForever(enemy2m)
let move31 = SKAction.moveTo(CGPointMake(409.6, 386.6), duration: 1.5)
let move32 = SKAction.moveTo(CGPointMake(204.8, 386.6), duration: 1.5)
let move33 = SKAction.moveTo(CGPointMake(204.8, 181.8), duration: 1.5)
let move34 = SKAction.moveTo(CGPointMake(409.6, 181.8), duration: 1.5)
let enemy3m = SKAction.sequence([move31, move32, move33, move34])
let enemy3move = SKAction.repeatActionForever(enemy3m)
enemy1.runAction(enemy1move)
enemy2.runAction(enemy2move)
enemy3.runAction(enemy3move)
}
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (playerCat | enemyCat) {
self.removeAllActions()
self.runAction(SKAction.waitForDuration(0.1), completion: {
self.runAction(SKAction.waitForDuration(0.2), completion:
{self.removeAllActions()
self.removeChildrenInArray([enemy1, enemy2, enemy3, player])})
restartButton.size = CGSizeMake(200, 200)
restartButton.position = CGPointMake(512, 384)
restartButton.zPosition = 1
self.addChild(restartButton)
})
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (restartButton .containsPoint(location)) {
restartButton.runAction(fadeAway)
restartButton.removeFromParent()
println(1)
self.runAction(SKAction.waitForDuration(1.5), completion: {
let repeatLevel = SKTransition.fadeWithDuration(2)
let level2 = Level2(fileNamed: "Level2")
self.view?.presentScene(level2, transition: repeatLevel)
})
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Your restartButton is having its size set for the first time when your playerCat and enemyCat first collide, which is fine.
Since the restartButton now has a size. You can check if your touch is within the bounds of the restartButton.
if (restartButton .containsPoint(location)) {
Like you have done so, but at no point do you check wether the restartButton is added to the scene.
A quick fix could possibly be:
if (restartButton.parent != nil && restartButton .containsPoint(location)) {
If dont specifically need to check if it with the bounds of the node. You could directly check using this instead. Which will eliminate the need to check for a parent.
if (self.nodeAtPoint(location) == restartButton) {
Another thing i noticed, in your collision detection, you never check if it has already collided. So you might run the same code multiple times, where you just keep removing all actions and then adding a new one.
You could add a simple have variable to prevent redudancy
var detectionMade = false
and reset at
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
initWorld()
movements()
detectionMade = false
...
and set to true when first colliding and checking
if collision == (playerCat | enemyCat) && !detectionMade {
detectionMade = true
...
#martinmeincke I've done it! By doing THIS:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (restartButton .containsPoint(location) && restartButton.parent == nil) {
restartButton.runAction(fadeAway)
println(1)
}
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (restartButton .containsPoint(location)) {
restartButton.removeFromParent()
self.runAction(SKAction.waitForDuration(1.5), completion: {
let repeatLevel = SKTransition.fadeWithDuration(2)
let level2 = Level2(fileNamed: "Level2")
self.view?.presentScene(level2, transition: repeatLevel)
})
}
}
}

Resources