Swift 3 (SpriteKit): Locking the x axis of a SKSpriteNode and its physicsBody
Asked Answered
H

2

6

I really need to know how to lock the x axis of an SKSpriteNode and its physicsBody. I need to keep the SKSpriteNode dynamic and affectedByGravity. The node is on a slope, so this is why it's x axis is moved due to gravity. However, I don't want the x axis of this SKSpriteNode to move due to gravity. Is there a way to lock the x axis in order to achieve this? Thanks for any help :D

Edit: I have tried to apply a constraint to the x value like this:

let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints?.append(xConstraint)

However this doesn't work and I'm not sure why and how to fix it. Anyone know of a solution?

Edit 2: SKPhysicsJointPin is actually looking more promising. In the comments of the first response/answer to this question, I have been trying to figure how to properly use it in my situation.

An example of my code:

let node = SKSpriteNode(imageNamed: "node")

enum collisionType: UInt32 {
    case node = 1
    case ground = 2
    case other = 4 //the other node is unrelated to the ground and node
}

class GameScene: SKScene, SKPhysicsContactDelegate {

override func didMove(to view: SKView) {
    //Setup node physicsBody
    node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
    node.physicsBody?.categoryBitMask = collisionType.node.rawValue
    node.physicsBody?.collisionBitMask = //[other node that isn't the ground or the node]
    node.physicsBody?.contactTestBitMask = //[other node that isn't the ground or the node]
    node.physicsBody?.isDynamic = true
    node.physicsBody?.affectedByGravity = true
    addChild(node)

    //Physics Setup
    physicsWorld.contactDelegate = self
}

The node is on top of the ground, and the ground is composed of various SKSpriteNode lines that have a volumeBased physicsBody. The ground keeps adding new lines at the front, and removing the ones at the back, and changing the x value of each line by a negative (so the ground appears to moving - this process is performed in an SKAction). These lines (the parts of the ground) are on an angle which is why the node's x axis moves. I want the node to always be at the front of the ground (e.g. always on top of the newly created line). Currently, setting the position of the node like this locks the x axis (solving my issue):

override func didSimulatePhysics() {
    //Manage node position
    node.position.x = 195
    node.position.y = CGFloat([yPosition of the first line of the ground - the yPosition keeps changing]) 
}

Note: ^This^ function is inside the GameScene class

The x axis actually stays the same like this. However, the issue is that now the physicsBody of the node is lower than the centre of the node (which didn't happen before).

Hale answered 2/1, 2017 at 6:51 Comment(15)
You say the constraint isn't working. Is that that it's not working at all, like no effect, at all?Scandent
What do you mean you don't want the x axis to move due to gravity? Gravity does not affect a sprite on the x axis, perhaps you need to explain the problem betterBeale
@Scandent Yes, thats what i mean, but the didSimulatePhysics() fund is working much better but now have a new problem - read my edit.Hale
@Beale I have edited my post with more details.Hale
@Hale the Constraint is the "right" way to achieve what you're after. The joint that could be used for this is not pin, but an SCNPhysicsSliderJoint in which you spoof the second body (no mass, collision, contacts, etc)Scandent
2D physics in SpriteKit (and UIKit Dynamics) is just Bullet Physics with the 3rd axis (Z) constrained out of the 'game'. Excuse the pun. If, as some people think, SpriteKit physics was actually Box2D (under the hood) you'd be able to use the prismatic joint (kind of like a piston) to achieve this, too.Scandent
@Beale gravity is only a word for a "universal" force. It doesn't necessarily mean "down". If, for example, tilting the device rotates gravity to match the angle of the device, the OP might want some objects to only react in the Y axis to these changes in gravity.Scandent
@Hale I suspect there's something else not quite complete or right in your code that's preventing the constraint from providing the effect you want, as this really should be the best 'right' way to achieve your goal. And should work. Can you start a new project, use the positions constraint example on Apple's page, here: developer.apple.com/reference/spritekit/skconstraint (first one) and make it behave as you like, with the noise field impacting your objects only in the y-axis? If not, there might be a bug in SpriteKit. Which wouldn't be the first time, sadly.Scandent
@Scandent why are you wasting my time with a ridiculous comment, the default value for gravity is 0,-9.8, which means a downward force. Had the OP change this, I don't think he would be asking how to solve his problem.Beale
I could ask you the same question. Even (assuming) if he keeps the gravity as at the default (and there's no indication he is) then he might have an object colliding with this object he wants constrained, and want it to be constrained in reaction to those impacts. Etc. We don't know anything about his simulation, game design, game world, etc. So it's ridiculous to assume his gravity (or anything else) about the simulation. And the question does, without assumptions, make perfect sense. I can think of dozens of reasons to want to restrict axis response of a dynamic object. Sure you can, too.Scandent
it is not ridiculous to assume that his gravity has changed, because like I said, had he have changed it, he would know where his problem is. This is why I asked the OP to further explain his problem, because the force being applied is a Y only force. This means tilting, rotating, and sliding are happening due to other forces, not gravity. So you explaining what "gravity" is is a waste of time.Beale
@Hale ok I get it, due to the slants in the ground, your sprite is sliding down the slant, the constraint 0x141E said should solve this for you. In your method, you are probably not adjusting the y axis the right way, and even though it "looks" like the body is off, it really isn't. I see you are using default anchor point, so your y should be the y of your ground + 1/2 the height of your ground + 1/2 the height of your node. Are you doing this?Beale
Actually, should you even be changing your y? Because your physics are not doing you any good now since you are just manually setting the position to the groundBeale
@Hale this might, initially, seem completely off topic, but consider how using a camera might advantage you in ground generation/movement: developer.apple.com/reference/spritekit/skcameranodeScandent
@Scandent Actually, I think the physicsBody being off centre is just a visual glitch when I do view.showPhysics = true because the physics of the node works exactly how it should if the physicsBody was on the centre. Thanks for suggestions though.Hale
R
5

A node's constraints property is nil by default. You'll need to create an array of one or more constraints and assign it to the property. For example

let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints = [xConstraint]

Update

You may want to use a camera node instead of moving the ground in the scene. With a camera node, you move the main character and the camera instead of the ground.

enter image description here

Rutkowski answered 2/1, 2017 at 19:49 Comment(5)
For some reason this doesn't help but thanks anyway.Hale
This should behave very similarly to the code you added to didSimulatePhysics. What happens when you add this to your didMove method?Rutkowski
The x position still moves and it isn't constrained.Hale
@Hale see the update about the camera. This is what I was getting at with regards my comment on the 3rd, about using a camera. It completely changes how you can think about this, and gives you a level (excuse pun) of freedom in how you generate and drive across ground. You can either do as is done in this answer, or you can move the geometry underneath the camera, or a blend of both.Scandent
Thanks so much. That looks brilliant, although I have already worked out a solution for my specific issue by using some other people's comments. Although I definitely could use this in the future and this is a great solution for any other people with the same problem. Thanks!Hale
N
3

I think you could set the linearDamping property to 0.0

The linearDamping is a property that reduces the body’s linear velocity.

This property is used to simulate fluid or air friction forces on the body. The property must be a value between 0.0 and 1.0. The default value is 0.1. If the value is 0.0, no linear damping is applied to the object.

You should pay attention also to the other forces applied to your SKSpriteNode. The gravitational force applied by the physics world for example where dx value, as you request , should be setted to 0.0:

CGVector(dx:0.0, dy:-4.9)

Remember also that when you apply other forces vectors like velocity you should maintain the dx property to 0.0 as constant if you want to block the x axis.

You could find more details to the official docs

Update (after your details to the comments below):

You could also anchored your sprite to the ground with an SKPhysicsJoint (I don't know your project so this is only for example):

var myJoint = SKPhysicsJointPin.joint(withBodyA: yourSprite.physicsBody!, bodyB: yourGround.physicsBody!, anchor: CGPoint(x: yourSprite.frame.minX, y: yourGround.frame.minY))

self.physicsWorld.add(myJoint)

You can work with the anchor property to create a good joint as you wish or adding more joints.

Needham answered 2/1, 2017 at 8:6 Comment(23)
Thanks for that. Changing the linearDamping to 1.0 made the gravity act slower, but it still hasn't locked the x axis. There are no gravitational forces applied to the node. However, the reason its x axis is moving is because it is on a slope (a diagonal straight line SKSpriteNode). This is why it continues to roll down the slope.Hale
what about physicsBody?.allowsRotation ? have you try to set it to false?Needham
I actually just tried that then and it nearly fixed the problem, but when it is on the edge of the slope it falls off.Hale
If you have a diagonal straight there are also vertical forces involved to your sprite (gravity for example..), not only horizontal..Needham
I realise that. But if the x axis is locked and never changes and there is always a surface underneath it, it will never fall off the screen. I'm OK with changes to the y axis, but those changes shouldn't be affecting the x axis IF it is locked and never changes.Hale
This is not the normal physic nature. In the real life you should increase the mass of an object or think to use SKPhysicsJoint to anchor your sprite to the ground for example. You could block your sprite using SKConstraint. There are various methods.Needham
Take a look at the positionX(_:) of SKConstraint - "Creating Position Constraints" at developer.apple.com/reference/spritekit/skconstraint discusses this.Brasilin
Thank you for the help. When I add the constraint by appending it to the node's constraints array, nothing changes though. Also, if this worked would I be able to stop the constraint midway through the program?Hale
@Hale Yes , you can change the var enabled: Bool propertyNeedham
How do I make the constraint actually work because it doesn't change anything right now?Hale
@SimonGladman & Alessandro Ornano I have edited my original post with what I have currently (the code that isn't working).Hale
@AlessandroOrnano Your SKPhysicsJointPin actually works the best, but do you know how to delete one mid way through the program and replace it with another because I want to continuously change the anchor of the joint? EDIT: Do you know how to change the anchorPoint of the joint without deleting it?Hale
@Hale You can remove a joint from a SKPhysicsWorld by using the removeJoint method for example: scene.physicsWorld.removeJoint(myJoint)Needham
In my program, the ground is made up of several SKSpriteNode lines, and each time I add a new joint to a new SKSpriteNode line and remove the old joint, it seems as if each line of the ground is joined together because they fall with gravity in response to the movement of the node.Hale
@Hale You should respect the order : 1) remove the old joint 2) add a new joint. Where do you make these changes (in what method)?Needham
In a function that runs forever by an SKAction. The joint is a global variable, and in the function I have: self.physicsWorld.remove(Joint) manJoint = SKPhysicsJointPin.joint(withBodyA: nodeA.physicsBody!, bodyB: nodeB.physicsBody, anchor: CGPoint(x: 200, y: [new y each time])) self.physicsWorld.add(Joint)Hale
@Hale You shouldn't use SKActions with physics or you have these unexpected results, try to take a look and override this method with the appropriate condition (if sprite exist do..)Needham
Thanks so much! That works really well, but the only problem is that now the physicsBody doesn't match up with the node like it used to.Hale
Try to give an example above to your question so we can understand better your physicsBody codeNeedham
@Hale According to your new updates seeme the joint distorced the physicsBody, give a look to shouldEnableLimits to restrict the physicsBody moveNeedham
Could you give me the code for that please because I'm a bit unsure about what you mean.Hale
@AlessandroOrnano I have deleted everything related to the physicsJoint and the constraint, and this hasn't changed much.Hale
@Hale As you can easily understand, for me and the other guys is very hard to re-build your situation, we don't know your code and I don't understand your graphic situation about this dynamic floor and your node. If you want other help please try to publish a simple gif that show exactly what happen, builded by only polygons without textures if you don't want to show your stuff but do it.Needham

© 2022 - 2024 — McMap. All rights reserved.