I have a spritekit scene with various objects on it. I want to be able to remove these objects in a wide circle around a user's touch event. In my touchesBegan function I have the following code which creates a physicsbody and checks if it contacts any objects:
var bigNode = SKShapeNode(circleOfRadius: 100)
bigNode.position = touchLocation!
var bigCircle = SKPhysicsBody(circleOfRadius: 100, center: touchLocation!)
bigNode.physicsBody = bigCircle
bigNode.strokeColor = SKColor.yellowColor()
bigNode.zPosition = CGFloat.max
bigNode.physicsBody?.dynamic = false
bigNode.physicsBody?.categoryBitMask = ColliderType.Beam.rawValue
bigNode.physicsBody?.contactTestBitMask = ColliderType.Target.rawValue
bigNode.physicsBody?.collisionBitMask = ColliderType.Target.rawValue
bigNode.name = "temp"
gameLayer.addChild(bigNode)
if let targets = bigNode.physicsBody?.allContactedBodies(){
for target in targets{
removeTarget(target as? SKPhysicsBody, location: target.position)
}
}
I only draw the circle so I can explicitly see what should be contacted in the simulator. The call bigNode.physicsBody?.allContactedBodies() always returns an empty array, even when I can see clearly that there are objects in the circle.
Does spritekit need a tick of update or something like that in order for that array to be populated? What am I doing wrong here?
For more info, here's the Bitmasks for the targets
target.physicsBody?.categoryBitMask = ColliderType.Target.rawValue
target.physicsBody?.contactTestBitMask = ColliderType.Target.rawValue | ColliderType.Wall.rawValue | ColliderType.Beam.rawValue
target.physicsBody?.collisionBitMask = ColliderType.Wall.rawValue | ColliderType.Target.rawValue | ColliderType.Beam.rawValue
And here's my collider enum:
enum ColliderType: UInt32 {
case Target = 1
case Beam = 2
case Wall = 4
}
From what I understand, when you touch somewhere : you create an area (bigNode), from which each node (target) inside will be removed.
The allContactedBodies will return an array of SKPhysicsBody objects that this body is in contact with. However, this as to run in the update method apparently. Which is very costly in processing time.
That said, you might want to try it. But you could also try to stick with the didBeginContact method.
Related
I have a spaceship object which has a complex geometry, and since SceneKit's physics doesn't work with complex bodies, I have adopted a workaround: I'm using some basic shapes like cylinders and cubes so simulate the whole spaceship's body. In Blender I created a set of objects that approximate the shape of the spaceship:
Then when I load the scene I remove these objects, but use their geometry to construct a SCNPhysicsShape to be used as the physics body of the spaceship:
// First I retrieve all of these bodies, which I named "Body1" up to 9:
let bodies = _scene.rootNode.childNodes(passingTest: { (node:SCNNode, stop:UnsafeMutablePointer<ObjCBool>) -> Bool in
if let name = node.name
{
return name.contains("Body")
}
return false
})
// Then I create an array of SCNPhysicsShape objects, and an array
// containing the transformation associated to each shape
var shapes = [SCNPhysicsShape]()
var transforms = [NSValue]()
for body in bodies
{
shapes.append(SCNPhysicsShape(geometry: body.geometry!, options: nil))
transforms.append(NSValue(scnMatrix4:body.transform))
// I remove it from the scene because it shouldn't be visible, as it has
// the sole goal is simulating the spaceship's physics
body.removeFromParentNode()
}
// Finally I create a SCNPhysicsShape that contains all of the shapes
let shape = SCNPhysicsShape(shapes: shapes, transforms: transforms)
let body = SCNPhysicsBody(type: .dynamic, shape: shape)
body.isAffectedByGravity = false
body.categoryBitMask = SpaceshipCategory
body.collisionBitMask = 0
body.contactTestBitMask = RockCategory
self.spaceship.physicsBody = body
The SCNPhysicsShape object should contain all the shapes that I created in the Blender file. But when I test the program, the spaceship just behaves like an empty body, and collisions are not detected.
PS: my goal is only to detect collisions. I don't want the physics engine to simulate physics.
You want to use a concave shape for your ship. Example below. In order to use concave your body must be static or kinematic. Kinematic is ideal for animated objects.
No need for:
body.collisionBitMask = 0
A contact bit mask is all you will need for contact only.
The code below should work for what you are looking for. This is swift 3 code fyi.
let body = SCNPhysicsBodyType.kinematic
let shape = SCNPhysicsShape(node: spaceship, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
spaceship.physicsBody = SCNPhysicsBody(type: body, shape: shape)
Without seeing your bit mask, I am unable to tell if you are handling those properly. You should have something like:
spaceship.physicsBody.categoryBitMask = 1 << 1
Anything you want to contact your spaceship should have:
otherObject.physicsBody.contactTestBitMask = 1 << 1
This will only register a contact of the "otherObject" to the "spaceship" and not the "spaceship" to the "otherObject". So make sure your physics world contact delegate is handled that way or vise versa. No need for both objects to look for each other. That would be less efficient.
Example of delegate below:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
var tempOtherObject: SCNNode!
var tempSpaceship: SCNNode!
// This will assign the tempOtherObject and tempSpaceship to the proper contact nodes
if contact.nodeA.node == otherObject {
tempOtherObject = contact.nodeA
tempSpaceship = contact.nodeB
}else{
tempOtherObject = contact.nodeB
tempSpaceship = contact.nodeA
}
print("tempOtherObject = ", tempOtherObject)
print("tempSpaceship = ", tempSpaceship)
}
I am creating a space shooter game. I have created an enemy node. What I want to do is have 4 of those nodes in each corner of the screen. In other words, I want to spawn multiple copies of the same node at the same time. When the game loads, I want there to be four nodes, that are all the same. How can I do this?
Thanks
Here's a section of code I use to generate a row of 4 space invaders. I reuse the same SKSpriteNode() variable as once the node has been added to the scene, the variable is no longer required:
for invaderPosition in 100.stride(to: 500, by: 120) {
invader = SKSpriteNode(texture: texturesA[0])
invader.position = CGPoint(x: CGFloat(invaderPosition), y: 200)
invader.xScale = 8
invader.yScale = 6
invader.color = SKColor.redColor()
invader.colorBlendFactor = 1.0
invader.name = "InvaderA"
invader.texture = texturesA[1]
invader.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(texturesA, timePerFrame: self.timePerMove)))
addChild(invader)
}
invader is defined as an SKSpriteNode property. texturesA is just an array of SKTextures; timePerMove is just a time interval after which the invader should move (in Update) and change shape(texture).
var invader = SKSpriteNode()
var texturesA:[SKTexture] = []
let timePerMove: CFTimeInterval = 1.0
So you could do something similar except the invader's positions would be the 4 corners rather than a row.
Just use the copy command,
let newNode = originalNode.copy() as! SKSpriteNode;
Situation: I have two or more ships on my iOS screen. Both have different attributes like name, size, hitpoints and score points. They are displayed as SKSpriteNodes and each one has added a physicsBody.
At the moment those extra attributes are variables of an extended SKSpriteNode class.
import SpriteKit
class ship: SKSpriteNode {
var hitpoints: Int = nil?
var score: Int = nil?
func createPhysicsBody(){
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.dynamic = true
...
}
}
In this 'game' you can shoot at those ships and as soon as a bullet hits a ship, you get points. 'Hits a ship' is detected by collision.
func didBeginContact(contact: SKPhysicsContact){
switch(contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask){
case shipCategory + bulletCategory:
contactShipBullet(contact.bodyA, bodyB: contact.bodyB)
break;
default:
break;
}
}
Problem: Collision detection just returns a physicsBody and I do not know how to get my extended SKSpriteNode class just by this physicsBody.
Thoughts: Is it a correct way to extend SKSpriteNode to get my objects like a ship to life? When I add a ship to my screen it looks like:
var ship = Ship(ship(hitpoints: 1, score: 100), position: <CGPosition>)
self.addChild(ship)
Or is this just a wrong approach and there is a much better way to find out which object with stats so and so is hit by a bullet thru collision detection?
This question is similar to my other question - I just want ask this in broader sense.
The SKPhysicsBody has a property node which is the SKNode associated to the body. You just need to perform a conditional cast to your Ship class.
if let ship = contact.bodyA.node as? Ship {
// here you have your ship object of type Ship
print("Score of this ship is: \(ship.score)!!!")
}
Please note that the Ship node could be the one associated with bodyB so.
if let ship = contact.bodyA.node as? Ship {
// here you have your ship...
} else if let ship = contact.bodyB.node as? Ship {
// here you have your ship...
}
Hope this helps.
I am fairly new to swift and Sprite Kit. I am trying create a simple game using SKSpriteNodes. My game needs to keep track the sequence of the "clicked" nodes as it needs to be processed based on its sequence. I am thinking of creating a list, as the nodes are clicked it will be added to the list. Is this the easiest way? I am thinking that it may consume more memory. My nodes are of the same names. Below is the method I used in adding the nodes in my scene. I need to know the sequence of colored balls clicked.
for x in listNodes{
var ballNode = SKSpriteNode(imageNamed: String(color))
ballNode.name = "ball"
var point: CGPoint = CGPointMake(0,0)
var done: Bool = false
let randomX = randomRange(CGRectGetMinX(self.frame), max: CGRectGetMaxX(self.frame)-(ballNode.size.width))
let randomY = randomRange(CGRectGetMinY(self.frame) + self.border.size.height, max: CGRectGetMaxY(self.frame)-ballNode.size.height)
point = CGPointMake(randomX, randomY)
ballNode.anchorPoint = CGPointMake(0,0)
ballNode.position = point
self.addChild(ballNode)
}
I'm playing around with some of the new SpriteKit tools and I've run into a frustrating problem.
As you know, iOS 8 introduced the SKFieldNode class which enables the user to create custom "force fields" which affect other SKNodes. I have gotten great results using the springField and the radialGravityFields, however I have yet to figure out how to use the magneticField and electricField types.
Let me explain.
The following code produces absolutely no effects on node4, the SKSpriteNode which I would like to be affected by the SKFieldNode.
SKSpriteNode *node4 = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"Red"] size:CGSizeMake(25.0, 25.0)];
node4.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:12.5];
node4.position = CGPointMake(300.0, 250.0);
node4.physicsBody.dynamic = YES;
node4.physicsBody.charge = 30;
node4.physicsBody.linearDamping = 3;
node4.physicsBody.affectedByGravity = NO;
node4.physicsBody.collisionBitMask = 0;
node4.physicsBody.mass = 1;
SKFieldNode *centerNode = [SKFieldNode magneticField];
centerNode.position = CGPointMake(150.0, 200.0);
centerNode.strength = 100000000000;
centerNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:25];
centerNode.physicsBody.charge = -30;
centerNode.physicsBody.dynamic = NO;
centerNode.falloff = 0;
The node is drawn on screen, yet stays completely static. As you can see, this happens even with a falloff of 0 and a strength of a 10000000000.
Changing the line:
SKFieldNode *centerNode = [SKFieldNode magneticField];
Into:
SKFieldNode *centerNode = [SKFieldNode springField];
Causes the centerNode to act as a springField, and sends the node4 all over the place.
Using an electricField, rather than a magneticField, yields no results either.
Before someone asks, yes, I am adding both nodes to the scene, I just didn't include those lines.
Can anyone see where I'm going wrong?
There's not enough code here for me to test it fully, but I see two likely problems up front:
If node4 isn't moving to start with, a magnetic field has no effect on it. SpriteKit's magnetic field models the second half of the Lorentz force equation (F = qv тип B), where the force on a particle relates to its charge and velocity.
You're setting node4's linear damping to greater than the maximum. The linearDamping property takes a value between 0 and 1, where 1.0 fully arrest's a body's motion. Values greater than 1 are probably clamped to 1.0, so your body shouldn't be moving even if hit with tremendous force.
Probably unrelated: you don't need to add a charged physics body to the field node unless you want that node to be affected by other electric/magnetic fields.
I have the same problem only when I create nodes by writing code. If you work with SpriteKit Editor, it works well!
You can add a new sks file and then unarchive it to SKScene by this code:
extension SKScene {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
scene.size = GameKitHelper.sharedGameKitHelper().getRootViewController().view.frame.size
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
Open the sks file and drag sprites on the scene, it really works well.