I'm trying to create a chain-like structure in SpriteKit and I'm having trouble understanding the behavior of SKPhysicsJointLimit's maxLength property. It seems not to do anything at all.
This question didn't solve my problem.
According to the documentation, maxLength is The maximum distance allowed between the two physics bodies connected by the limit joint.
However, my two nodes become oriented much farther apart than their maxLength value. It's true that I'm setting their initial positions to be farther apart than maxLength -- but I would expect the nodes to pull together during the simulation, as if tied together by a stretchy rope. Instead, the nodes remain far apart.
So, here's some code that sets a joint between two SKSpriteNodes.
let screen = UIScreen.main.bounds
let bodyA = SKSpriteNode(imageNamed: "box.png")
let bodyB = SKSpriteNode(imageNamed: "box.png")
bodyA.size = CGSize(width: 20, height: 20)
bodyB.size = CGSize(width: 20, height: 20)
bodyA.position = CGPoint(x: screen.width*0.4, y: screen.height*0.8)
bodyB.position = CGPoint(x: screen.width*0.6, y: screen.height*0.8)
bodyA.physicsBody = SKPhysicsBody(circleOfRadius: 20)
bodyB.physicsBody = SKPhysicsBody(circleOfRadius: 20)
addChild(bodyA)
addChild(bodyB)
let pinJoint = SKPhysicsJointLimit.joint(withBodyA: bodyA.physicsBody!, bodyB: bodyB.physicsBody!, anchorA: CGPoint(x: 0.5, y: 0.5), anchorB: CGPoint(x: 0.5, y: 0.5))
//This doesn't seem to do anything:
pinJoint.maxLength = 5.0
scene?.physicsWorld.add(pinJoint)
In the simulation, it's clear that there is a physics joint connecting the two nodes -- it's just that the nodes are much farther apart than they should be.
Why doesn't my maxLength value change the behavior of my two nodes, and how do I fix the problem? What am I not understanding?
Thanks for your input!
Be sure that the anchor points are in scene coordinates, as described in the documentation. The (0.5, 0.5) is likely intended to be "center of the sprite" or something like that, but that's not correct for a joint.
Related
I have two questions:
How can I make an SKSpriteNode move at a constant speed and direction?
I'm currently using item.physicsBody?.velocity, but I want just a constant speed, in the direction to the left.
let item = SKSpriteNode(color: .black, size: CGSize(width: 32, height: 16))
item.position = CGPoint(x: 600, y: self.heights[random])
item.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:32, height: 16))
addChild(item)
item.physicsBody?.velocity = CGVector(dx: -360, dy: 0)
item.physicsBody!.affectedByGravity = false
I have the above code in touchesBegan(). I want to add some code to update(), to detect when one of these created SKSpriteNodes does a certain thing (e.g. reaches a specified x position), and then make a change to that SKSpriteNode (e.g. delete it). How do I do that, so that I get that particular SKSpriteNode?
Just manually update your sprite's position. Define a speed in points per second, use a timer in update() to see how much time has passed since it was last called and with a bi of maths, work out its new position. I've done this and it works.
I'm not sure what the relevance of the touchesBegan() code is to this point (are you creating a sprite and setting it moving when you touch the screen?), but in general you could iterate over the array of child nodes in the scene using enumerateChildNodes(). When you have a node that matches your criteria (specific x position etc.), perform the action on the node.
So let's say I have this rectangular SKNode. Can I add SKPhysicsBody to only one side of the rectangle i.e. only the top side, so that only the top side could detect collision and not the other sides (then the physics indicator blue line would only appear on the top and not anywhere else)?
I wanted to create a game, but I don't know whether something like this could work.
So if I could, how?
If I can't, is there a way around this issue?
I wanted to add a normal rectangle with physicsBody on only one side.
See the rectangle image here, the blue part is the place where I want the physicsBody
So I've tried adding an edgeBased physicsBody, but it doesn't seem to work (either the physicsBody didn't get created or it is in the wrong position).
let rectangle = SKSpriteNode(imageNamed: "Rectangle")
rectangle.size = CGSize(width: 128, height: 128)
rectangle.position = CGPoint(x: frame.midX, y: frame.midY)
rectangle.physicsBody = SKPhysicsBody(edgeFrom: CGPoint(x: rectangle.position.x - rectangle.size.width/2, y: rectangle.position.y + rectangle.size.width/2), to: CGPoint(x: rectangle.position.x + rectangle.size.width/2, y: rectangle.position.y + rectangle.size.width/2))
rectangle.physicsBody!.restitution = 0.0
rectangle.physicsBody!.categoryBitMask = physicsCategories.groundCategory
rectangle.physicsBody!.collisionBitMask = physicsCategories.squareCategory
addChild(rectangle)
Thanks!
You could, but it would also detect collision coming from the 'wrong' side i.e. with an object that has traveled through the node.
Physics bodies can be volume or edge based, but even an edge-based one that was only a pixel thick still has 4 sides.
If you could supply a drawing of exactly what you want, we could help better as there are probably several different approaches.
Blocks just crumble apart.
How can this problem be solved?
Initializing blocks:
var boxNode = SCNNode(geometry: SCNBox(width: 0.75, height: 0.15, length: 0.25, chamferRadius: 0))
boxNode.position = SCNVector3(x: x1, y: y, z: z1)
boxNode.geometry?.firstMaterial = SCNMaterial()
boxNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "wood.jpg")
boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
boxNode.eulerAngles.y = Float(Double.pi / 2) * rotation
boxNode.physicsBody?.friction = 1
boxNode.physicsBody?.mass = 0.5
boxNode.physicsBody?.angularDamping = 1.0
boxNode.physicsBody?.damping = 1
picture
video
full code
I won't be able to tell you how to fix it as I have the exact same problem which I wasn't able to solve. However, as I played around I figured out a couple of things (which you may find useful):
The same problem hasn't happened to me in pure SceneKit, hence I think it's a bug in ARKit
Node with physics has to be added to the rootNode of the scene, otherwise odd stuff happens (elements passing through each other, gravity behaving in an inconsistent way)
If you pass nil as shape parameter, SceneKit will figure bounding box based on the geometry of the node. This hasn't worked properly for me so what I've done (using SceneKit editor) was to duplicate the geometry and then set it as a custom shape for the bounding box (have a look at the attached image)
Overall I've found physics simulation in SceneKit when used with ARKit to be extremely buggy and I spent a lot of time "tricking" it into working more-or-less how I wanted it to work.
I'm trying to create a ball that automatically moves forward in a random direction once it is created. I've tried creating a random angle from 0-360 and having the node rotate and then having an impulse applied to the node, but the node simply stays there once it is created, so I can't tell if it is just the impulse that is not working or if the rotate isn't even working.
enemy.size = CGSize(width: 20, height:20)
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.color = UIColor(red:255.0,green:0.0,blue:0.0,alpha:1.0)
enemy.colorBlendFactor = 1.0
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.linearDamping = 0.0
self.addChild(enemy)
this is just to create the enemy, but i dont know how to apply the random direction and forward movement.
enemy.size = CGSize(width: 20, height:20)
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.color = UIColor(red:255.0,green:0.0,blue:0.0,alpha:1.0)
enemy.colorBlendFactor = 1.0
enemy.physicsBody = SKPhysicsBody(circleOfRadius:10)
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.linearDamping = 0.0
self.addChild(enemy)
let vec = CGVector(dx: CGFloat(arc4random_uniform(100)) / 50.0, dy: CGFloat(arc4random_uniform(100)) / 50.0)
enemy.physicsBody!.applyForce(vec)
this is my edited code.The enemy ball now moves when it is hit by another ball, but I want the enemy ball to just automatically move on its own. Right now it stays in the place in the middle of the screen until hit.
Like Gary already pointed out in the comments you wanna be sure that you have created an added a physicsBody to your SKNode. If you haven't, some neat documentation can be found here.
Now for applying a random force. You basically want to generate a random CGVector and use applyForce on your physics body.
To generate a random vector:
let vec = CGVector(dx: CGFloat(arc4random_uniform(100)) / 50.0, dy: CGFloat(arc4random_uniform(100)) / 50.0)
arc4random_uniform creates a random number between 0 and your upper bound which is passed as a parameter. Then I divide it a bit in order to get a number between 0 and 2. You should tweak that to your needs.
Then just use enemy.physicsBody!.applyForce(vec). Note my use of ! mark. Be sure that you have created and applied a physics body otherwise this will crash.
I have a sprite with a physics body.
To move this sprite, in the update() function I continually set the velocity of the sprite to 150 units to go right, and -150 to go left. (See attached code.)
I have a second physics body that I'd like to have follow the first sprite. The coordinates of this second physics body are at the bottom of the first one, and 20 points to the right.
I'd like the second physics body to always follow the first, with the offset.
My code mostly works but I've noticed that the distance between the first and second bodies varies slightly, and I want there to be no variation. When moving right, the distance between them is compressed a little. When moving left, the distance between them increases a little. See a video HERE: https://www.youtube.com/watch?v=K9FhIdMwp7k
Here is the code I'm using:
override func update(_ currentTime: TimeInterval) {
switch playerDirection {
case "stopped":
playerSpeed = 0.0
case "right":
playerSpeed = 150.0
case "left":
playerSpeed = -150.0
default:
print("default")
}
// ball is the first sprite and footBall is the second sprite:
ball.physicsBody?.velocity = CGVector(dx: playerSpeed, dy: ball.physicsBody!.velocity.dy)
footBall.position = CGPoint(x: (ball.position.x + 20), y: ball.position.y - (ball.size.height / 2) + 4)
myCam.position = CGPoint(x: round(ball.position.x), y: self.size.height / 2)
...
I've been playing around with using override func didSimulatePhysics() without success, also.
I did a different test that just does away with using velocity to move the player, and instead directly increment the ball.position.x (ball = player) and footBall.position.x and when I do this everything is perfectly aligned. But if I take this route I'll have to change how my game physics work elsewhere in the game, which I'd prefer to ignore.
Thanks for having a look.
Would it meet your requirements if you add the second node as a child of the first? That is, instead of having both at the same level:
scene.addChild(ball)
scene.addChild(footBall)
replace with:
scene.addChild(ball)
ball.addChild(footBall)
and just set the footBall position as the offset you need:
footBall.position = CGPoint(x: 20, y: - (ball.size.height / 2) + 4)
then any time the ball moves the footBall will also move as it is a child node, so you can remove the manual position update code for the footBall.
I used SKPhysicsJoint to get around the problem. Here's my working code (testing at this point, so still kinda sloppy, but it works in principle) :
//add a nice player
ball = SKSpriteNode(texture:spriteArray2[0])
ball.size = CGSize(width: 150, height: 48)
//ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height / 2)
ball.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 16, height: 48))
ball.zPosition = 1
ball.physicsBody?.affectedByGravity = true
ball.position = CGPoint(x: 80.0, y: 200.0)
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = UInt32(2 | 32 | 256) //2 = floors, 32 and 256= doors.
ball.physicsBody?.contactTestBitMask = 0
ball.physicsBody?.isDynamic = true
ball.physicsBody?.restitution = 0.0
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.mass = 60.0
ball.physicsBody?.friction = 0.00
addChild(ball)
//add a nice ball for when jumping
footBall = SKSpriteNode(color: SKColor.blue, size: CGSize(width: 24, height: 16))
footBall.position = ball.position
footBall.name = "footBall"
footBall.zPosition = 1
footBall.physicsBody = SKPhysicsBody(circleOfRadius: 4)
footBall.physicsBody?.affectedByGravity = false
footBall.physicsBody?.categoryBitMask = 16
footBall.physicsBody?.collisionBitMask = 2 // floors
footBall.physicsBody?.contactTestBitMask = UInt32(32 | 64 | 128 | 256) // the doors bear snake
footBall.physicsBody?.isDynamic = true
footBall.physicsBody?.restitution = 0.0
//footBall.physicsBody?.allowsRotation = false
//footBall.physicsBody?.mass = 60.0
//footBall.physicsBody?.friction = 0.00
addChild(footBall)
let myCGPoint = ball.position // sets joint position
let myJoint = SKPhysicsJointFixed.joint(withBodyA: ball.physicsBody!, bodyB: footBall.physicsBody!, anchor: myCGPoint)
scene?.physicsWorld.add(myJoint)
...
then down in the update loop I set velocity for the player as so:
ball.physicsBody?.velocity = CGVector(dx: playerSpeed, dy: ball.physicsBody!.velocity.dy)
In my original code, in my update loop I also adjusted the position of the footBall to match the ball (player) position as needed, but in the revised code, because of the joint being used, by moving the ball the footBall moves along with it without any need to otherwise apply any force or velocity or change the x position of the footBall. The footBall just tags along for the ride, which works.
At first, this new method didn't work correctly, and I discovered through trial and error that the reason why is that "footBall.physicsBody?.allowsRotation = false" cannot be specified as a property on footBall. By uncommenting this line it works fine, however.
The point of this exercise is to use the footBall as a jumping test point for when the player's feet are stretched out during a jump. So the footBall is thus slightly forward of the main player's body. Then I'll dynamically turn on or off this footBall as needed for jumps.
Somewhat related are the following pages on "foot sensor":
http://gamedevwithoutacause.com/?p=1076
http://www.iforce2d.net/b2dtut/jumpability