I'm trying to put a physics body to a line.
For some reason the physics body ends up materializing on the lower left corner.
How can I control the position of a physics body? I was under the impression that once I had attached the body into a shape, it would automatically take its shape.
Here's the code:
var distance = calculateDistance(wayPoints[wayPoints.count-1], point2: wayPoints[wayPoints.count-2])
var linePhysicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: distance, height: 1.0))
linePhysicsBody.categoryBitMask = PhysicsCategory.Line
linePhysicsBody.contactTestBitMask = PhysicsCategory.Ball
linePhysicsBody.collisionBitMask = PhysicsCategory.Ball
linePhysicsBody.dynamic = false
linePhysicsBody.usesPreciseCollisionDetection = true
lineNode.removeFromParent()
lineNode = SKShapeNode()
lineNode.physicsBody = linePhysicsBody
lineNode.name = "drawingLine"
lineNode.path = pathToDraw
lineNode.lineWidth = 5.0
lineNode.strokeColor = UIColor.redColor()
lineNode.glowWidth = 1.0
self.addChild(lineNode)
You have not positioned your line node. That means per default is't on point(0,0).
As LearnCocos2D mentioned in his comment. You are drawing then a path relative to your line node position. That does not influence your physicsBody. This is still positioned at (0,0)
Related
I have a SceneKit scene in which the camera is stationary, and is positioned like this: cameraNode.position = SCNVector3(0.0, 0.0, 100.0). Other than that, the camera has the default configuration.
In the scene is a single, spherical SCNNode with a physics body.
Below the sphere is a flat plane, with a physics body, on which the sphere rolls around. The plane is positioned in the center of the scene, at SCNVector3(0.0, 0.0, 0.0).
What I need is for the scene to be surrounded by invisible "walls" that are positioned exactly at the edges of the screen. The sphere should bounce off these static physics bodies so it never leaves the screen.
I've tried placing one of these "walls" (an SCNNode with an SCNBox geometry) using the actual screen dimensions, but the positioning is incorrect; the node is apparently off screen. This is presumably because SceneKit coordinates are in meters, not pixels or whatever.
Question: How can I figure out the positioning of the "walls" so that they are fixed to the edges of the screen?
Thanks for your help!
Use the SCNSceneRenderer's unprojectPoint(_:) method to convert left and right edges of the screen to 3D space coordinates and add two planes in those coordinates.
let leftPoint = SCNVector3(scnView.bounds.minX, scnView.bounds.midY, 1.0)
let righPoint = SCNVector3(scnView.bounds.maxX, scnView.bounds.midY, 1.0)
let leftPointCoords = scnView.unprojectPoint(leftPoint)
let rightPointCoords = scnView.unprojectPoint(righPoint)
let rightPlane = SCNPlane(width: 100.0, height: 100.0)
let leftPlane = SCNPlane(width: 100.0, height: 100.0)
let rightPlaneNode = SCNNode(geometry: rightPlane)
rightPlaneNode.eulerAngles = .init([0.0, .pi / 2, 0.0])
rightPlaneNode.physicsBody = .init(type: .static, shape: nil)
let leftPlaneNode = SCNNode(geometry: leftPlane)
leftPlaneNode.physicsBody = .init(type: .static, shape: nil)
leftPlaneNode.eulerAngles = .init([0.0, .pi / 2, 0.0])
rightPlaneNode.position = rightPointCoords
leftPlaneNode.position = leftPointCoords
scene.rootNode.addChildNode(rightPlaneNode)
So, obviously there's a mathematical solution to this problem, and that's the best way to do this. Unfortunately, I'm not very good at math, so I had to come up with another solution.
First, I create the wall to be located at the top edge of the screen. It will begin at the center of the scene:
let topEdge = SCNNode()
let topEdgeGeo = SCNBox(width: MainData.screenWidth, height: 5.0, length: 5.0, chamferRadius: 0.0)
topEdge.geometry = topEdgeGeo
topEdge.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.kinematic, shape: SCNPhysicsShape.init(node: topEdge))
topEdge.physicsBody?.categoryBitMask = CollisionTypes.kinematicObjects.rawValue
topEdge.physicsBody?.collisionBitMask = CollisionTypes.dynamicObjects.rawValue
topEdge.physicsBody?.isAffectedByGravity = false
topEdge.physicsBody?.allowsResting = true
topEdge.physicsBody?.friction = 0.0
topEdge.position = SCNVector3(0.0, 0.0, 2.5)
scnView.scene?.rootNode.addChildNode(topEdge)
I then repeatedly reposition the wall a little bit farther up the y axis until it's no longer within the camera's viewport:
var topWallIsOnScreen: Bool = scnView.isNode(topEdge, insideFrustumOf: scnView.pointOfView!)
while topWallIsOnScreen {
topEdge.position.y += 0.001
topWallIsOnScreen = scnView.isNode(topEdge, insideFrustumOf: scnView.pointOfView!)
}
The end result is that the wall is positioned at the top edge of the screen. I was concerned about performance, but it seems to work just fine.
I have a SceneKit scene in which the camera looks down at a single sphere that rolls around atop a flat plane.
The camera has the default configuration, and is positioned at SCNVector3(0.0, 0.0, 100.0).
The sphere starts out in the center of the screen. At this point, the sphere looks normal, as seen in this screenshot.
But the farther away from the center it moves, the more warped/stretched it appears. As seen in this screenshot, the sphere appears warped (it looks like an egg) when it's near the edge of the screen.
The sphere is configured like this:
let ballNode = SCNNode()
let sphereGeometry = SCNSphere(radius: MainData.screenWidth*0.005)
let targetMaterial = SCNMaterial()
targetMaterial.diffuse.contents = UIColor.red
sphereGeometry.materials = [targetMaterial]
sphereGeometry.firstMaterial?.diffuse.contents = targetMaterial
ballNode.geometry = sphereGeometry
ballNode.geometry?.firstMaterial?.lightingModel = .phong
ballNode.position = SCNVector3(0.0,0.0,20.0)
ballNode.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.dynamic, shape: SCNPhysicsShape.init(node: ballNode))
ballNode.physicsBody?.categoryBitMask = CollisionTypes.dynamicObjects
ballNode.physicsBody?.collisionBitMask = CollisionTypes.staticObjects
ballNode.physicsBody?.contactTestBitMask = CollisionTypes.nothing
ballNode.physicsBody?.isAffectedByGravity = true
ballNode.physicsBody?.allowsResting = false
scnView.scene?.rootNode.addChildNode(ballNode)
The "plane" is actually just an SCNBox node, and is configured like this:
let platform = SCNNode()
let platformGeometry = SCNBox(width: MainData.screenWidth, height: MainData.screenHeight, length: 2.0, chamferRadius: 0.0)
platform.geometry = platformGeometry
platform.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.static, shape: SCNPhysicsShape.init(node: platform))
platform.physicsBody?.categoryBitMask = CollisionTypes.staticObjects
platform.physicsBody?.collisionBitMask = CollisionTypes.dynamicObjects
platform.physicsBody?.contactTestBitMask = CollisionTypes.nothing
platform.physicsBody?.isAffectedByGravity = false
platform.physicsBody?.allowsResting = true
scnView.scene?.rootNode.addChildNode(platform)
Question: Why does the sphere appear warped (like an egg) the farther it travels from the center of the scene/screen?
Thanks for your help!
I don't really understand why, but adding the following solved the problem:
cameraNode.camera?.fieldOfView = 20.0
In my original build, my sprites were static, so to make the collision physics as accurate as I could rather than use rectangleOfSize for SKPhysicsBody passing in the node, I used this tool to define a path SpriteKit's SKPhysicsBody with polygon helper tool.
However now I'm animating the sprites so that they move back and forth during gameplay (obviously my physics path remains static given the above) so my physicsbody no longer matches what the player sees on screen.
The helper tool seemed like a bit of hack that Apple would eventually fix in the API, has there been anything recent in SpriteKit that would help me out here so that I can pass in the node and define a precise physicsbody rather than the hard-coded approach? If not, any other alternatives?
Not sure how performant this is but you can pre-generate the physics bodies for each texture then animate the sprite along with its physicsBody using a custom action.
Here is an example:
func testAnimation() {
var frameTextures = [SKTexture]()
var physicsBodies = [SKPhysicsBody]()
for index in 1...8 {
// The animation has 8 frames
let texture = SKTexture(imageNamed: "guy\(index)")
frameTextures.append(texture)
let physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody.affectedByGravity = false
physicsBodies.append(physicsBody)
}
let sprite = SKSpriteNode(texture: frameTextures[0])
let framesPerSecond = 16.0
let animation = SKAction.customAction(withDuration: 1.0, actionBlock: { node, time in
let index = Int((framesPerSecond * Double(time))) % frameTextures.count
if let spriteNode = node as? SKSpriteNode {
spriteNode.texture = frameTextures[index]
spriteNode.physicsBody = physicsBodies[index]
}
})
sprite.run(SKAction.repeatForever(animation));
sprite.position = CGPoint(x: 0, y: 0)
addChild(sprite)
}
I have a wrapper class to create a label node with some subnodes. This is the init for the class:
let label = SKLabelNode(text: word)
let background = SKShapeNode(rectOfSize: size, cornerRadius: 0.5)
let mainNode = SKSpriteNode(color: color, size: background.frame.size)
label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center
label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center
label.addChild(background)
label.addChild(mainNode)
mainNode.zPosition = 1
background.zPosition = 2
label.zPosition = 5
self.node = label
In my main scene I instantiate the node, set its physics body and set the gravity for the scene:
self.physicsWorld.gravity = CGVectorMake( 0, -10 );
let node = MyNode()
node.node.zPosition = 99
node.node.position.y = endOfScreenTop
if let name = node.node.name as String!, let sprite = node.node.childNodeWithName(name) as? SKSpriteNode {
node.node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
node.node.physicsBody!.mass = 1.0;
node.node.physicsBody!.angularDamping = 0.0;
node.node.physicsBody!.linearDamping = 0.0;
node.node.physicsBody!.friction = 0.0;
node.node.physicsBody!.affectedByGravity = true
node.node.physicsBody!.dynamic = true
node.node.physicsBody!.allowsRotation = false
}
addChild(node.node)
I have 2 problems:
The label appears behind the SKSpriteNode, even though the zPosition should place it at the front
The SKShapeNode (that's just an insets to smooth the corners) doesn't appear at all
None of the nodes are affected by gravity
PS. Even changing the following to true doesn't change anything:
skView.ignoresSiblingOrder = false
PS2. the MyNode class has these properties
var node: SKLabelNode
var speed: CGFloat = 1.0
var word: String
var color: UIColor
var kind: Kind
Assuming that skView.ignoresSiblingOrder = true in the view controller (the default setting), some thoughts...
When ignoresSiblingOrder is set to true, Sprite Kit ignores sibling relationships when determining the order in which to draw nodes. It does not, however, affect the parent-sibling draw order.
In either mode, the parent is drawn first and then the child nodes are rendered. Normally, the children appear on top of the parent (if they're at the same location) regardless of the zPositions of the parent and children! The exception is if a child's zPosition is less than zero. In this case, the child will appear beneath its parent (even if the parent's zPosition is less than the child).
It doesn't appear that you are setting node.node.name nor the name of the sprite node. If the names aren't set, the if condition will fail and the physics body won't be attached to node.node.
The default settings for shapes only draws a white border around its path (in this case, a rectangle). You should see this as an white outline around the sprite node. Setting background.fillColor = SKColor.greenColor() will show the SKShapeNode filled with green.
I simply have put a square shape node onto a scene, and made the physicsBody for the scene be the edge of the screen. I turned on the physics view so you can see all of the physics stuff. The square has a physics body around it, as does the border of the scene. However, when the square runs into the border, nothing happens. The square just passes straight through. Here is how I initialize the square:
let rect1 = CGRect(x: 100, y: 100, width: 50, height: 50)
let square = Square(rect: rect1)
square.fillColor = UIColor.blackColor()
square.zPosition = 200
square.physicsBody = SKPhysicsBody(edgeLoopFromRect: rect1)
square.physicsBody?.allowsRotation = false
square.physicsBody?.affectedByGravity = false
square.physicsBody?.restitution = 0.4
self.addChild(square)
Here is how I initialize the physics body for the scene:
let physicsBody = SKPhysicsBody(edgeLoopFromRect: view.bounds)
self.physicsBody = physicsBody
Very similar code has worked for me on other games, so I am not sure why no collisions happen. Any help is greatly appreciated thanks!
The movement of the balls must be caused by physics rather than just setting the position. The issue was fixed through using impulses to move the balls which then made them collide with the various objects.