I am creating a game and i am trying to create a character that has 2 different nodes, the legs and the torso. I tried to link them using a fixed type joint, however when i do that the torso slides off of the legs sometimes while moving around. Now i am trying to create the character using a distance limit between the 2 nodes hoping that they won't slide away from each other now,however it keeps crashing on launch any ideas?
here is my code
func CreateHero (){
soldierLegs.position = CGPoint(x: 405 , y: 139)
soldierLegs.zPosition = 1
soldierLegs.anchorPoint.x = 0.6
soldierLegs.anchorPoint.y = 0.7
let soldierLegsBody:SKPhysicsBody = SKPhysicsBody(rectangleOfSize:
soldierLegs.size)
soldierLegsBody.dynamic = true
soldierLegsBody.affectedByGravity = true
soldierLegsBody.allowsRotation = false
//body.restitution = 0.4
soldierLegsBody.categoryBitMask = BodyType.soldierL.rawValue
soldierLegsBody.contactTestBitMask = BodyType.enemy1.rawValue |
BodyType.enemy2.rawValue | BodyType.enemy3.rawValue |
BodyType.desertForegroundCase.rawValue
soldierLegs.physicsBody = soldierLegsBody
soldierTorso.position = soldierLegs.position
soldierTorso.zPosition = 2
soldierTorso.anchorPoint.x = 0.25
soldierTorso.anchorPoint.y = 0.1
let soldierTorsoBody:SKPhysicsBody = SKPhysicsBody(rectangleOfSize:
soldierTorso.size)
soldierTorsoBody.dynamic = true
soldierTorsoBody.affectedByGravity = true
soldierTorsoBody.allowsRotation = false
soldierTorsoBody.categoryBitMask = BodyType.soldierT.rawValue
soldierTorsoBody.contactTestBitMask = BodyType.enemy1.rawValue |
BodyType.enemy2.rawValue | BodyType.enemy3.rawValue |
BodyType.desertForegroundCase.rawValue
soldierTorso.physicsBody = soldierTorsoBody
let Joint =
SKPhysicsJointLimit.jointWithBodyA(soldierLegs.physicsBody!, bodyB:
soldierTorso.physicsBody!, anchorA: soldierLegs.position, anchorB:
soldierTorso.position)
Joint.maxLength = 0.01
self.addChild(soldierTorso)
self.addChild(soldierLegs)
self.physicsWorld.addJoint(Joint)
}
According to the Apple official document :
Creating a Limit Joint
class func joint(withBodyA: SKPhysicsBody, bodyB: SKPhysicsBody, anchorA: CGPoint, anchorB: CGPoint)
Creates a new limit joint.
anchorA
A connection point on the first body in the scene’s coordinate system.
anchorB
A connection point on the second body in the scene’s coordinate system.
In your case anchorA is equal to anchorB (the same point), there is no limit, this one cause the crash.
Related
Have an SCNNode, add a physics body,
physicsBody = SCNPhysicsBody(type: .dynamic, shape: ..)
physicsBody?.isAffectedByGravity = false
Now add a physics field, for example
physicsField = SCNPhysicsField.noiseField(smoothness: 0.2, animationSpeed: 0.01)
physicsField?.strength = 0.05
It works perfectly. In this case, .noise , the object will jiggle around.
However after a few seconds (often 7 seconds, sometimes a different length of time), the object will simply stop moving.
(The three values, smoothness speed and strength, make no difference if you change them - it will still end after a few seconds.)
What's the solution to this mystery?
Just to be clear, I never used a SCNPhysicsField.noiseField, but I used one of type SCNPhysicsField.linearGravity and another of type SCNPhysicsField.customField and both of them are working correctly and do not stop unexpected as you describe.
here are my examples:
let attractionField = SCNPhysicsField.linearGravity()
attractionField.halfExtent = SCNVector3(250.0, 35.0, 60.0)
attractionField.direction = SCNVector3(-1.0, 0.0, 0.0)
attractionField.strength = 0.2 // 0.15
attractionNode.physicsField = attractionField
and the other one, (which I used to create a tornado):
private func addCustomVortexField() {
// Tornado Particles Field
let worldOrigin = stormNode.presentation.worldPosition
let worldAxis = simd_float3(0.0, 1.0, 0.0)
let customVortexField = SCNPhysicsField.customField(evaluationBlock: { position, velocity, mass, charge, time in
let l = simd_float3(worldOrigin.x - position.x, 1.0, worldOrigin.z - position.z)
let t = simd_cross(worldAxis, l)
let d2: Float = l.x * l.x + l.z * l.z
let vs: Float = 27 / sqrt(d2) // diameter, the bigger the value the wider it becomes
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed)
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
stormNode.physicsField = customVortexField
stormNode.physicsField?.categoryBitMask = BitMasks.BitmaskTornadoField
}
I hope this is gonna help you in some way. You can also provide me your project, and I will have a look at it.
I need to have two physics bodies on my playerSprite. I need one body to be a smaller contact area and to only detect collisions on certain bodies. My code is inside my PlayerNode class is:
let walkingBitMask = PhysicsCategory.Door | PhysicsCategory.Wall | PhysicsCategory.SlideTerminator | PhysicsCategory.Shootable
let contactBitMask = PhysicsCategory.Monster | PhysicsCategory.Door | PhysicsCategory.Item | PhysicsCategory.Slide | PhysicsCategory.Teleport
let mainBody = SKPhysicsBody(rectangleOf: CGSize(width: 40, height: 50))
mainBody.collisionBitMask = walkingBitMask
mainBody.contactTestBitMask = contactBitMask
let pitBody = SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 8))
pitBody.contactTestBitMask = PhysicsCategory.Pit
pitBody.collisionBitMask = 0
self.physicsBody = SKPhysicsBody(bodies: [mainBody,pitBody])
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = PhysicsCategory.Player
self.name = "player"
self.physicsBody?.allowsRotation = false
The Pit category is all I want to interact with the pitBody. My code in setting up the Pit sprites is:
sprite.physicsBody=SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 8))
sprite.physicsBody?.affectedByGravity = false
sprite.physicsBody?.isDynamic = false
sprite.name = "pit"
sprite.physicsBody?.allowsRotation = false
sprite.physicsBody?.categoryBitMask = PhysicsCategory.Pit
sprite.physicsBody?.collisionBitMask = 0
The problem I'm running into is that the player sprite stops moving when it encounters a pit sprite. Everything else works fine.
When you create a physics body using SKPhysicsBody.init(bodies:), the properties on the children are ignored. Only the shapes of the child bodies are used, according to the docs.
So the test bit masks specified on mainBody and pitBody are ignored, and the compound body of the player that is part of the simulation only specifies the categoryBitMask:
self.physicsBody?.categoryBitMask = PhysicsCategory.Player
This means it will collide with other bodies, since it's collisionBitMask default value is 0xFFFFFFFF (all bits set).
Configure the collisionBitMask and contactTestBitMask on the player body.
If you need to use separate collisions, consider using a SKPhysicsJointFixed to fuse the two physics bodies together.
In the SceneKit WWDC 2014, they have an example of a vortex field with this effect:
The particle system looks much like a tornato, as it spins inward with a hollow center.
However, the documentation for vortex fields have no information on how to achieve this effect. Right now, I have this:
// create the particle system
let exp = SCNParticleSystem()
exp.loops = true
exp.particleMass = 5
exp.birthRate = 10000
exp.emissionDuration = 10
exp.emitterShape = SCNTorus(ringRadius: 5, pipeRadius: 1)
exp.particleLifeSpan = 15
exp.particleVelocity = 2
exp.particleColor = UIColor.white
exp.isAffectedByPhysicsFields = true
scene.addParticleSystem(exp, transform: SCNMatrix4MakeRotation(0, 0, 0, 0))
// create the field
let field = SCNPhysicsField.vortex()
field.strength = -5
field.direction = SCNVector3(x: 0, y: 1, z: 0)
let fieldNode = SCNNode()
fieldNode.physicsField = field
scene.rootNode.addChildNode(fieldNode)
This creates this effect:
Where I am looking down at the particles rotating clockwise with a really big radius outwards. It looks nothing like a tornato effect. How can I create this effect?
You say tornato, I say tornado, let’s call the whole thing off...
The SceneKit WWDC 2014 demo/slides is a sample code project, so you can see for yourself how they made any of the effects you see therein. In this case, it looks like the “vortex” demo isn’t actually using the vortexField API, but instead the custom field API that lets you supply your own math in an evaluator block. (See the link for the code in that block.)
You might be able to get similar behavior without a custom field by combining a vortex (causes rotation only) with radial gravity (attracts inward) with linear gravity (attracts downward), or some other combination (possibly something involving electric charge). But you’d probably have to experiment with tweaking the parameters quite a bit.
If anyone is still interested in this topic - here is a Swift 5 implementation of that legendary tornado effect.
Here is an example function that will create your tornado.
func addTornadoPhysicsField() {
// Tornado Particles Field Example
guard let tornadoSystem = SCNParticleSystem(named: "tornado.scnp", inDirectory: nil) else { return }
let emitterGeometry = SCNTorus(ringRadius: 1.0, pipeRadius: 0.2)
emitterGeometry.firstMaterial?.transparency = 0.0
let fieldAndParticleNode = SCNNode(geometry: emitterGeometry)
fieldAndParticleNode.position = SCNVector3(0.0, 0.0, -20.0)
tornadoSystem.emitterShape = emitterGeometry
fieldAndParticleNode.addParticleSystem(tornadoSystem)
yourScene.rootNode.addChildNode(fieldAndParticleNode)
// Tornado
let worldOrigin = SCNVector3Make(fieldAndParticleNode.worldTransform.m41,
fieldAndParticleNode.worldTransform.m42,
fieldAndParticleNode.worldTransform.m43)
let worldAxis = simd_float3(0.0, 1.0, 0.0) // i.Ex. the Y axis
// Custom Field (Tornado)
let customVortexField = SCNPhysicsField.customField(evaluationBlock: { position, velocity, mass, charge, time in
let l = simd_float3(worldOrigin.x - position.x, 1.0, worldOrigin.z - position.z)
let t = simd_cross(worldAxis, l)
let d2: Float = l.x * l.x + l.z * l.z
let vs: Float = 27 / sqrt(d2) // diameter, the bigger the value the wider it becomes (Apple Default = 20)
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed, Apple Default = 15.0))
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
fieldAndParticleNode.physicsField = customVortexField // Attach the Field
}
Additional Configuration Options:
Finally all this can result in something like that:
Note: if you would like to move your static tornado almost like a real tornado, you will have to find a way to re-apply the physics field for each rendererd frame. If you don't, the world origin used in the evaluation block will not move and it will distort your tornado.
Note: You can also split the particle/field node into two different nodes that moves independently from each other. Constrain the field node to the position of the particle node and play around with the influence factor (still need to re-apply the field each frame)
For more information on Custom Fields check out here.
I would like to know how should I detect overlapping nodes while enumerating them? Or how should I make that every random generated position in Y axis is at least some points higher or lower.
This is what I do:
1 - Generate random number between -400 and 400
2 - Add those into array
3 - Enumerate and add nodes to scene with generated positions like this:
var leftPositions = [CGPoint]()
for _ in 0..<randRange(lower: 1, upper: 5){
leftPositions.append(CGPoint(x: -295, y: Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)))
}
leftPositions.enumerated().forEach { (index, point) in
let leftSparkNode = SKNode()
leftSparkNode.position = point
leftSparkNode.name = "LeftSparks"
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
But like this sometimes they overlap each other because the generated CGPoint is too close to the previous one.
I am trying to add some amount of triangles to the wall and those triangles are rotated by 90°
To describe in image what I want to achieve:
And I want to avoid thing like this:
Your approach to this is not the best, i would suggest only storing the Y values in your position array and check against those values to make sure your nodes will not overlap. The following will insure no two sparks are within 100 points of each other. You may want to change that value depending on your node's actual height or use case.
Now, obviously if you end up adding too many sparks within an 800 point range, this just will not work and cause an endless loop.
var leftPositions = [Int]()
var yWouldOverlap = false
for _ in 0..<randRange(lower: 1, upper: 5){
//Moved the random number generator to a function
var newY = Int(randY())
//Start a loop based on the yWouldOverlap Bool
repeat{
yWouldOverlap = false
//Nested loop to range from +- 100 from the randomly generated Y
for p in newY - 100...newY + 100{
//If array already contains one of those values
if leftPosition.contains(p){
//Set the loop Bool to true, get a new random value, and break the nested for.
yWouldOverlap = true
newY = Int(randY())
break
}
}
}while(yWouldOverlap)
//If we're here, the array does not contain the new value +- 100, so add it and move on.
leftPositions.append(newY)
}
func randY() -> CGFloat{
return Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
}
And here is a different version of your following code.
for (index,y) in leftPositions.enumerated() {
let leftSparkNode = SKNode()
leftSparkNode.position = CGPoint(x:-295,y:CGFloat(y))
leftSparkNode.name = "LeftSparks\(index)" //All node names should be unique
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
I want to be able to change a node's physicsBody height when the user swipes downwards, but have not been able to find out how to do this, beside resetting the entire physicsBody.
When I originally load the node, I use the below code:
nodeHero.color = UIColor .grayColor()
nodeHero.size.width = 20
nodeHero.size.height = 45
nodeHero.position.x = -frame.size.width/2 + 45
nodeHero.position.y = pointMainY + 30 + nodeHero.size.height/2
nodeHero.zPosition = 110
nodeHero.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(nodeHero.size.width, nodeHero.size.height))
nodeHero.physicsBody?.mass = 1
nodeHero.physicsBody?.angularVelocity = 0
nodeHero.physicsBody?.allowsRotation = false
nodeHero.physicsBody?.restitution = 0
nodeHero.physicsBody?.categoryBitMask = bitHero
addChild(nodeHero)
And when I swipe down, I want to be able to do something like this (this doesn't work):
nodeHero.size.height = 28
nodeHero.physicsBody?.size.height = 28
But instead I have to use the nodeHero.physicsBody = SKPhysicsBody() again, which resets all the other physicsBody properties, so I have to do this:
nodeHero.size.height = 28
nodeHero.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(nodeHero.size.width, nodeHero.size.height))
nodeHero.physicsBody?.mass = 1
nodeHero.physicsBody?.angularVelocity = 0
nodeHero.physicsBody?.allowsRotation = false
nodeHero.physicsBody?.restitution = 0
nodeHero.physicsBody?.categoryBitMask = bitHero
According to SpriteKit documentation the area of a SKPhysicsBody can't be modified, so you need to create another SKPhysicsBody instance and copy the values you want to keep from the previous instance.