I have two nodes, one "cat" and one "rat", but for some reason I can't get their collision to be detected. I'm using this method for masks:
enum CollisionTypes: UInt32 {
case holder = 1
case chef = 2
case powerups = 4
case ingredients = 8
case utensils = 16
case floor = 32
case bag = 64
case table = 128
case tip = 256
case rat = 512
case cat = 1024
}
Here is where I initialize their physics bodies:
// Cat physics body, the node's name is "cat"
public func initializeAt(position: CGPoint) {
sprite.position = position
sprite.zPosition = 5
sprite.name = "cat"
sprite.alpha = 0.7
scene.sceneContent.addChild(sprite)
sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)
sprite.physicsBody!.isDynamic = false
sprite.physicsBody!.categoryBitMask = CollisionTypes.cat.rawValue
sprite.physicsBody!.contactTestBitMask = CollisionTypes.rat.rawValue
sprite.physicsBody!.collisionBitMask = CollisionTypes.rat.rawValue
// Rat physics body, the nodes name is "rat"
init() {
node.name = "rat"
node.zPosition = 5
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
node.physicsBody!.isDynamic = false
node.physicsBody!.categoryBitMask = CollisionTypes.rat.rawValue
node.physicsBody!.contactTestBitMask = CollisionTypes.cat.rawValue
node.physicsBody!.collisionBitMask = CollisionTypes.cat.rawValue
setupFrames()
}
Here is my didBegin() method. However, neither of the if statements get executed and I don't know why because I am using this method for a number of other things in my project.
func didBegin(_ contact: SKPhysicsContact) {
if let node1 = contact.bodyA.node as? SKSpriteNode,
let node2 = contact.bodyB.node as? SKSpriteNode {
if node1.name == "rat" && node2.name == "cat" {
for rat in rats {
if node1 == rat.node {
rat.die()
}
}
Cat.shared.resetPosition()
return
}
else if node1.name == "cat" && node2.name == "rat" {
for rat in rats {
if node2 == rat.node {
rat.die()
}
}
Cat.shared.resetPosition()
return
}
If I try playing around with the contactTestBitMasks and making them something different like "ingredients", then I can see that the cat and rat are interacting with ingredients but it seems like they just wont interact with eachother.
rats and cats won't trigger contacts with each other because both have isDynamic set to false. At least one of them needs to be dynamic before a contact is triggered.
From https://developer.apple.com/documentation/spritekit/skphysicsbody
The isDynamic property controls whether a volume-based body is
affected by gravity, friction, collisions with other objects, and forces or impulses you directly apply to the object.
Related
I am trying to add SCNNode mutiple time in a loop at different position but I can see same type of node at once with the last position.
Below is the code
let entityArray:[entity] = [.coin, .coin, .coin, .brick, .coin, .coin, .coin, .brick]
func setupworld() {
let scene = SCNScene(named: "art.scnassets/MainScene.scn")!
var zPosition = -10
var count = 0
let delta = -4
for entity in entityArray {
var node = SCNNode()
switch entity {
case .coin:
node = scene.rootNode.childNode(withName: "coin", recursively: true) ?? node
node.position = SCNVector3(0, -5, zPosition)
case .brick:
node = scene.rootNode.childNode(withName: "brick", recursively: true) ?? node
node.position = SCNVector3(0, 0, zPosition)
}
self.sceneView.scene.rootNode.addChildNode(node)
zPosition += delta
count += 1
}
}
It shows one coin and one brick at last positions.
I am new to scenekit so would be doing something wrong, Please help me.
Building on from the other comments and as #rmaddy has said an SCNNode has a clone() function (which is the approach you should take here) and which simply:
Creates a copy of the node and its children.
One thing to be aware of when using this however, is that each cloned Node will share the same geometry and materials.
That's to say that if you wanted at any point to have some bricks with a red colour and some with a green colour you wouldn't be able to do it with this method since:
changes to the objects attached to one node will affect
other nodes that share the same attachments.
To achieve this e.g. to render two copies of a node using different materials, you must copy both the node and its geometry before assigning a new material, which you can read more about here: Apple Discussion
The reason you are only ever seeing one instance of either the coin or brick is because each time you are iterating through your loop you are saying that the newly created node is equal to either the coin or the brick, so naturally the last element in that loop will be the one that references that element from your scene.
Putting this into practice and solving your issue therefor, your setupWorld function should look like something like this:
/// Sets Up The Coins & Bricks
func setupworld(){
//1. Get Our SCNScene
guard let scene = SCNScene(named: "art.scnassets/MainScene.scn") else { return }
//2. Store The ZPosition
var zPosition = -10
//3. Store The Delta
let delta = -4
//4. Get The SCNNodes We Wish To Clone
guard let validCoin = scene.rootNode.childNode(withName: "coin", recursively: true),
let validBrick = scene.rootNode.childNode(withName: "brick", recursively: true) else { return }
//5. Loop Through The Entity Array & Create Our Nodes Dynamically
var count = 0
for entity in entityArray {
var node = SCNNode()
switch entity{
case .coin:
//Clone The Coin Node
node = validCoin.clone()
node.position = SCNVector3(0, -5, zPosition)
case .brick:
//Clone The Brick Node
node = validBrick.clone()
node.position = SCNVector3(0, 0, zPosition)
}
//6. Add It To The Scene
self.sceneView.scene.rootNode.addChildNode(node)
//7. Adjust The zPosition
zPosition += delta
count += 1
}
}
Getting an error on this pice of code.
I Don't want to go through the spriteNode on GameScene.sks manually (in code) - the for loop must work somehow? Any ideas...
let sprites:[SKSpriteNode] = [block1, block2, block3, block4,
block5, block6, block7, block8, block9, block10, block11,
block12, block13, block14, block15, block16, block17,
block18, block19, block20, block21, block22, block23,
block24, block25, block26, block27, block28, block29,
block30, block31, block32, block33, block34, block35,
block36, block37, block38, block39, block40, block41,
block42, block43, block44, block45, block46, block47,
block48, block49, block50, block51, block52, block53,
block54, block55, block56, block57, block58, block59,
block60, block61, block62, block63, block64, block65,
block66, block67, block68, block69, block70, block71,
block72, block73, block74, block75, block76, block77,
block78, block79, block80, block81, block82, block83, block84]
for sprite in sprites {
self.sprite; in sprites = (self.childNode(withName: sprite.name) as? SKSpriteNode)!
sprite.name = "Green"
sprite.zPosition = 3
}
I'm guessing that from your not very descriptive description that you have a bunch of SKSpriteNodes in your .sks file and you want to loop through them in code to setup them up???
something like this might work better for you, if that is indeed what you are looking for.
let spriteNames: [String] = ["block1", "block2", "block3"]
let sprites = [SKSpriteNode]()
for spriteName in spriteNames {
if let sprite = self.childNode(withName: spriteName) as? SKSpriteNode {
sprite.name = "Green"
sprite.zPosition = 3
sprites.append(sprite)
}
}
I have a for loop that runs 10 times, each time creating a node and adding it to the scene. However, I want there to be a delay in between each node being added (add a node, wait a second, add a node, wait a second, etc.)
However, after the first 1 second, all 10 nodes are added at the same time. How can I achieve this desired effect of waiting a second in between each node being added?
Here is my code:
EDIT:
func createText(correct: Bool) {
let text = SKNode()
var line: String!
addChild(text)
if correct {
line = (GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(happyLines) as! [String])[0]
} else {
line = (GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(sadLines) as! [String])[0]
}
var xPos = self.frame.midX + 300
var yPos = self.frame.midY
var spaces = 1
// For each character in sentence, create a node of it
for character in line.characters {
runAction(SKAction.waitForDuration(1.0)) {
if spaces == 4 {
spaces = 0
print("space")
xPos = self.frame.midX + 300
yPos -= 30
}
xPos += 10
if character != " " {
let letter = SKLabelNode(fontNamed: GameScene.fontName)
letter.fontSize = 14 * GameScene.fontScale
letter.position = CGPoint(x: xPos, y: yPos)
letter.text = String(character)
text.addChild(letter)
} else {
spaces += 1
xPos += 10
}
}
}
runAction(SKAction.waitForDuration(2.0)) {
text.removeAllChildren()
text.removeFromParent()
}
}
You can achieve this using actions.
First, create actions for the delay and adding the node to the scene.
let waitAction = SKAction.wait(forDuration: 1)
let addNodeAction = SKAction.run {
let node = SKSpriteNode(imageNamed: "example")
self.addChild(node)
}
Next, create a sequence of actions so that the delay always occurs before the node is added to the scene.
let sequenceAction = SKAction.sequence([waitAction, addNodeAction])
Next, create an action that repeats the sequence 10 times.
let repeatAction = SKAction.repeat(sequenceAction, count: 10)
Finally, run this action and watch the nodes appear!
run(repeatAction)
EDIT:
To solve your second question in the comments about needing access to the current character (making each action where the node is added slightly different), loop through your characters to build a sequence of actions, and then run that action.
var actions = [SKAction]()
let waitAction = SKAction.wait(forDuration: 1)
for character in line.characters {
let addNodeAction = SKAction.run {
let node = SKSpriteNode(imageNamed: "example")
// use the character variable here
self.addChild(node)
}
actions.append(waitAction)
actions.append(addNodeAction)
}
let sequenceAction = SKAction.sequence(actions)
run(sequenceAction)
How about this? (not tested :)
for (i, character) in line.characters.enumerate() {
runAction(SKAction.waitForDuration(Double(i) * 1.0)) {
// rest of your for loop
So you enumerate through the characters, where i is the i-th character, starting at 0. With each character, you just multiply its index position in the string with the 1 second interval to give you the wait duration.
I tried to make contact but it didn't work .
I create an enum:
enum object:UInt32{
case BB = 1
case TR = 2
case GAP = 3
}
Then I have 3 nodes:
balloon.physicsBody?.categoryBitMask = object.BB.rawValue
balloon.physicsBody?.contactTestBitMask = object.TR.rawValue
balloon.physicsBody?.collisionBitMask = object.GAP.rawValue
tree2.physicsBody?.categoryBitMask = object.TR.rawValue
tree2.physicsBody?.contactTestBitMask = object.BB.rawValue
gap.physicsBody?.categoryBitMask = object.GAP.rawValue
gap.physicsBody?.collisionBitMask = object.GAP.rawValue
gap.physicsBody?.contactTestBitMask = object.BB.rawValue
In DidBeginContact:
let result = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask
switch result{
case object.BB.rawValue + object.TR.rawValue:
xoaBB(contact.bodyA.node as! SKSpriteNode, TR: contact.bodyB.node as! SKSpriteNode, toaDo: contact.contactPoint)
case object.BB.rawValue + object.GAP.rawValue:
score++
scorelabel.text = "\(score)"
default:
return
}
The problem is:
"case object.BB.rawValue + object.GAP.rawValue" didn't work
I want the ballon fly through the gap to score but it can't. Balloon just contact to gap and gets stuck there as shown here.
Can anyone help me, I really need "how to use Enum tutorial too"
As mentioned in my comment your bitmask-values doesn't quite make sense. How about something a little like this (I have not checked for typos):
enum MaskType : UInt32 {
case Balloon = 1
case Gap = 2
case Wall = 4
case None = 0
}
Then you need to decide what you are after. The following example does not register collisions, meaning you want have physical interactions happening between the objects.
balloon.physicsBody?.categoryBitMask = MaskType.Balloon.rawValue
balloon.physicsBody?.collisionBitMask = MaskType.None.rawValue
balloon.physicsBody?.contactTestBitMask = MaskType.Gap.rawValue | MaskType.Wall.rawValue
The balloon sprite will now register contacts with objects having either the .Gap or .Wall value as their categoryBitMask.
If you now setup your other spriteNode's physicsBodies appropriately you can do some nice things in the didBeginContact function. Perhaps something like this:
func didBeginContact(contact: SKPhysicsContact) {
let contactA = contact.bodyA
let contactB = contact.bodyB
func ballonHitType() -> Uint32 {
if contactA.categoryBitMask == MaskType.Baloon.rawValue {
return contactB.categoryBitMask
} else {
return contactA.categoryBitMask
}
}
// Then simply perform your wanted actions based on whatever ballonHitType you receive
}
I have 3 SKSpriteNodes in my Scene. One bird, one coin and a border around the scene. I don't want the coin and the bird to collide with each other but withe the border.
I assign a different collisionBitMask and categoryBitMask to every node:
enum CollisionType:UInt32{
case Bird = 1
case Coin = 2
case Border = 3
}
Like so:
bird.physicsBody!.categoryBitMask = CollisionType.Bird.rawValue
bird.physicsBody!.collisionBitMask = CollisionType.Border.rawValue
coin.physicsBody!.categoryBitMask = CollisionType.Coin.rawValue
coin.physicsBody!.collisionBitMask = CollisionType.Border.rawValue
But the coin and the bird still collide with each other.
What am I doing wrong?
The bitmask is on 32 bits. Declaring them like you did corresponds to :
enum CollisionType:UInt32{
case Bird = 1 // 00000000000000000000000000000001
case Coin = 2 // 00000000000000000000000000000010
case Border = 3 // 00000000000000000000000000000011
}
What you want to do is to set your border value to 4. In order to have the following bitmask instead :
enum CollisionType:UInt32{
case Bird = 1 // 00000000000000000000000000000001
case Coin = 2 // 00000000000000000000000000000010
case Border = 4 // 00000000000000000000000000000100
}
Keep in mind that you'll have to follow the same for next bitmask : 8, 16, ... an so on.
Edit :
Also, you might want to use a struct instead of an enum and use another syntax to get it easier (it's not mandatory, just a matter of preference) :
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let Bird : UInt32 = 0b1 // 1
static let Coin : UInt32 = 0b10 // 2
static let Border : UInt32 = 0b100 // 4
}
That you could use like this :
bird.physicsBody!.categoryBitMask = PhysicsCategory.Bird
bird.physicsBody!.collisionBitMask = PhysicsCategory.Border
coin.physicsBody!.categoryBitMask = PhysicsCategory.Coin
coin.physicsBody!.collisionBitMask = PhysicsCategory.Border