I'm working on a game in Swift with SpriteKit. I have a parent node with an SKPhysicsBody and several subnodes with their own physics bodies. All of the subnodes have some velocity relative to the parent node. How do I keep that relative velocity when giving the parent node a velocity? Here's pseudocode to show what I'm trying to do:
let parent:SKNode = SKNode()
let child:SKNode = SKNode()
parent.physicsBody = SKPhysicsBody()
child.physicsBody = SKPhysicsBody()
parent.addChild(child)
child.physicsBody!.velocity = CGVectorMake(dx: 5, dy: 5)
// child now has velocity
parent.physicsBody!.velocity = CGVectorMake(dx: 10, dy:10)
// parent has velocity, but child velocity is still (5,5)
How would I get the child velocity to be set such that it maintains a velocity relative to the parent? (e.g. the child's absolute velocity should become (15,15) once the parent is given a velocity of (10,10) so that it maintains (5,5) relative to the parent).
I tried using SKPhysicsJoint but that seems to fix the node and not allow velocity. Any solutions? (I'm sure I'm overlooking something obvious, but I'm new to SpriteKit)
Thanks!
If you want the child's velocity to be relative to its parent, add the parent's velocity to the child's constant velocity in the update method. For example,
override func update(currentTime: CFTimeInterval) {
child.physicsBody?.velocity = CGVectorMake(parentNode.physicsBody!.velocity.dx + 5.0, parentNode.physicsBody!.velocity.dy + 5.0)
}
BTW, you shouldn't use parent as a variable name in Sprite Kit, since it's a property of SKNode.
Update
You can loop over all children of the parent node with the following.
override func update(currentTime: CFTimeInterval) {
for (i, child) in parentNode.children.enumerate() {
child.physicsBody?.velocity = CGVectorMake(parentNode.physicsBody!.velocity.dx + velocity[i].dx, parentNode.physicsBody!.velocity.dy + velocity[i].dy)
}
}
velocity is an array of CGVectors that contains the velocity of each child.
Related
Near as I can tell, there isn’t a way to add physics joints in the scene editor. Is that right?
Consider a simple person object with a body, child legs and arms w/ pin joints. If I want to design this person in the scene editor and then programmatically add him to a scene, I’m not getting very far. I’m able to find the nodes in the scene, remove them from their parent and add them as a child at a new position in my scene, but I still have to specify all their joints manually.
Thoughts?
Here is my solution. I'm still hoping there is a way to create physics joints with the scene editor but I haven't found it so...
Step 1) Add all the child nodes to the scene, ensuring that objects are grouped by parent.
Step 2) Define a swift class for your complex node.
class MyNode : SKSpriteNode {
func spawn(parentNode: SKNode, position: CGPoint) {
parentNode.addChild(self) // before physics joints
let arm = self.childNode(withName:"arm")
// note if you didn't add physics bodies in scene file
// do that first
let shoulders = SKPhysicsJointPin.joint(withBodyA:self.physicsBody!, bodyB: arm.physicsBody!, anchor: CGPoint(x:position.x,y:position.y-1))
scene!.physicsWorld.add(shoulders)
// feature of pulling a child from a scene, it's always paused by default.
self.isPaused = false
}
}
Set the class for your body node in your scene.
Step 3) Transfer your node to your game scene at init time.
let tmpScene = SKScene.init(fileNamed: "MyNode.sks")
var myNode = tmpScene.childNamed(withName:"myNode") as! MyNode
myNode.removeFromParent()
myNode.spawn(world, position) // or your own parent and position as needed for your scene
This question and others discuss how to track a node in SpriteKit using a SKCameraNode.
However, our needs vary.
Other solutions, such as updating the camera's position in update(_ currentTime: CFTimeInterval) of the SKScene, do not work because we only want to adjust the camera position after the node has moved Y pixels down the screen.
In other words, if the node moves 10 pixels up, the camera should remain still. If the node moves left or right, the camera should remain still.
We tried animating the camera's position over time instead of instantly, but running a SKAction against the camera inside of update(_ currentTime: CFTimeInterval) fails to do anything.
I just quickly made this. I believe this is what you are looking for?
(the actual animation is smooth, just i had to compress the GIF)
This is update Code:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
SKShapeNode *ball = (SKShapeNode*)[self childNodeWithName:#"ball"];
if (ball.position.y>100) camera.position = ball.position;
if (fabs(ball.position.x-newLoc.x)>10) {
// move x
ball.position = CGPointMake(ball.position.x+stepX, ball.position.y);
}
if (fabs(ball.position.y-newLoc.y)>10) {
// move y
ball.position = CGPointMake(ball.position.x, ball.position.y+stepY);
}
}
I would not put this in the update code, try to keep your update section clutter free, remember you only have 16ms to work with.
Instead create a sub class for your character node, and override the position property. What we are basically saying is if your camera is 10 pixels away from your character, move towards your character. We use a key on our action so that we do not get multiple actions stacking up and a timing mode to allow for the camera to smoothly move to your point, instead of being instant.
class MyCharacter : SKSpriteNode
{
override var position : CGPoint
{
didSet
{
if let scene = self.scene, let camera = scene.camera,(abs(position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
}
}
Edit: #Epsilon reminded me that SKActions and SKPhysics access the variable directly instead of going through the stored property, so this will not work. In this case, do it at the didFinishUpdate method:
override func didFinishUpdate()
{
//character should be a known property to the class, calling find everytime is too slow
if let character = self.character, let camera = self.camera,(abs(character.position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: character.position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
I'm trying to make an air hockey game using SpriteKit. I trying to make the pucks draggable but I can't make them continue to move after the touch has ended. Right now I am binding the touch and the puck and setting it's position when the touch moves.
using the physics system:
override func update(currentTime: NSTimeInterval) {
for (touch, node) in draggingNodes {
let targetPosition = touch.locationInNode(self)
let distance = hypot(node.position.x - targetPosition.x, node.position.y - targetPosition.y)
var damping = sqrt(distance * 100)
if (damping < 0) {
damping = 0.0
}
node.physicsBody!.linearDamping = damping
node.physicsBody!.angularDamping = damping
let translation = CGPointMake(targetPosition.x - node.position.x, targetPosition.y - node.position.y)
node.physicsBody!.velocity = CGVectorMake(translation.x * 100, translation.y * 100);
}
}
You're likely going to need to do a lot more reading. Either you'll use the physics system:
In which case you'll impart an impulse onto the puck on the touch end event, having calculated the speed based on a delta in position and delta in time from last frame to current frame (or some type of average over more than 1 frame).
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Physics/Physics.html
[OR]
You'll manually set velocity on the puck (not using the physics system), and then manually update the puck's position per frame based on that velocity, then recalculate its vector when it comes into contact with another object based on angle of of incidence.
What's the right way to convert a point into absolute coordinates if the containing scene has an anchorPoint of (0.5, 0.5)? Conceptually, we set the anchorPoint to (0.5, 0.5) and then add a child with position (0, 0). Assuming the device is a 5S, the conversion should yield (160, 240).
We're using Swift.
Add a SKNode on your Scene. Then set its position rely on the scene's frame. Sample Code here:
var node = SKNode()
node.anchorPoint = CGPointMake(0,0)
node.position = CGPointMake(-scene.frame.width/2, -scene.frame.height/2)
scene.addChild(node)
Add your sprite on this node, you now can get the absolute position~
You can make use of the following methods provided with SKNode to get relative points in the node tree.
convertPoint:toNode:
convertPoint:fromNode:
Using Carrl's method above to add an SKNode, you can get absolute points as follows:
var relativeNode = SKNode()
relativeNode.anchorPoint = CGPointMake(0,0)
relativeNode.position = CGPointMake(-scene.frame.width/2, -scene.frame.height/2)
scene.addChild(relativeNode)
var absolutePoint = scene.convertPoint(someNode.position, toNode: relativeNode)
//This will result in {160, 240} on a 5S.
These methods take into consideration the various anchorPoints of the nodes.
I have a child node added to another node. I want to get the child nodes position with respect to the views coordinates and not the parent nodes coordinates
Get the child node's position with respect to its parent, then use the convertPoint:fromNode: or convertPoint:toNode: method to convert from the parent node's coordinate system to the scene's coordinate system. (Remember, SKScene inherits from SKNode and is the root of the node hierarchy, so you can use it with either of those methods.) If you then need to work in the UIKit view coordinate space, use the scene's convertPointToView: method.
If by 'view coordinates' you mean the scene coordinates, then check out this swift solution I use:
extension SKNode {
var positionInScene:CGPoint? {
if let scene = scene, let parent = parent {
return parent.convert(position, to:scene)
} else {
return nil
}
}
}
Then you can get the scene position in the same manner you get the regular position. Here is an example:
let positionInParent = childNode.position
let positionInScene = childNode.positionInScene? //optional return type
Note that the positionInScene property is optional for the same reason that the scene property of SKNode is optional, the node might not be added to a scene. In this case, you get nil. You could even reverse the process and add a setter, so you could position every node in the scene coordinates no matter how deeply it was buried.