Node is slowing down in Sprite Kit - swift

I'm trying to create an arcade game, where a ball moves at a constant speed and is unaffected by gravity or friction. So I created the ball as an SKShapeNode and set its linearDamping and friction to 0. I also set the game scene to have no gravity. But when playing, if the ball hits another shape node (a circle) at a low angle, it can slow down. The ball's restitution is 1, and allowsRotation is false.
I am keeping the ball moving by applying one impulse at the beginning of the game, that is a random direction.

This might not be the most ideal fix but you could set the fixed speed of the object every update to a specific value which is your constant speed.
The other alternative way to solve this would be to set the fixed speed of the object under the collision delegate functions.

I had a similar problem that was resolved by setting physicsBody?.isDynamic = false on the node that the ball makes contact with.
For example if you have a ball and a brick:
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.frame.width / 2) // (diameter / 2) = radius
ball.physicsBody?.categoryBitMask = ballCategoryBitMask
// Detect contact with the bottom of the screen or a brick
//
ball.physicsBody?.contactTestBitMask = bottomCategoryBitMask | brickCategoryBitMask
ball.physicsBody?.friction = 0
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1
ball.physicsBody?.applyImpulse(CGVector(dx: 10, dy: -10))
brick.physicsBody = SKPhysicsBody(rectangleOf: brick.frame.size)
brick.physicsBody?.linearDamping = 0
brick.physicsBody?.allowsRotation = false
brick.physicsBody?.isDynamic = false // Prevents the ball slowing down when it gets hit
brick.physicsBody?.affectedByGravity = false
brick.physicsBody?.friction = 0.0

Related

SpriteKit - Bouncing node constant speed/velocity?

Simple setup - I have bouncing ball nodes (blue) and static ball nodes (green). The grey box has Physics body SKPhysicsBody(edgeLoopFrom: box.frame), so it limits the bouncing ball from moving outside. I set the bouncing balls node (blue) velocity to a random CGVector upon creation and it starts moving. It bounces off the static ball node (green), and off the edges of the grey box.
My general SKPhysicsBody setup with all nodes is:
node.physicsBody?.restitution = 1
node.physicsBody?.friction = 0.0
node.physicsBody?.linearDamping = 0.0
node.physicsBody?.angularDamping = 0.0
My question is: How do I make sure the bouncing ball does move at a constant speed? After a couple of bounces, the node is either speeding up, or slowing down.
In order to achieve this, I normalized the vector whenever the physics engine detects a collision. So in func didBegin(_ contact: SKPhysicsContact), the following happens:
let length = hypotf(Float(node.velocity.dx), Float(node.velocity.dy))
let multiplier = (290.0 / length)
firstBody.velocity.dx *= CGFloat(multiplier)
firstBody.velocity.dy *= CGFloat(multiplier)
290.0 is an arbitrary value that determines the speed of ball.
For reference, check this answer.

Node automatically move in random direction

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.

Aligning second sprite to physics body of first sprite in Spritekit/Swift

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

Issue creating SKPhysicsBody

I have a plank, which undergoes constant vertical velocity changes. Therefore setting it's dynamic property to false will not do since physicsBody.velocity will not work.
plank.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(plankWidth, plankHeight))
plank.physicsBody?.affectedByGravity = false
plank.physicsBody?.linearDamping = 0
I also have a ball that rests on the plank:
ball.physicsBody = SKPhysicsBody(circleOfRadius: frame.width*CGFloat(1/Float(NumberOfPlanks+1))*0.2)
ball.physicsBody.affectedByGravity = true
ball.physicsBody.linearDamping = false
This ball must be affected by gravity, rest on the plank, but not affect it's motion. This ball must also be able to undergo applyForce and applyImpulse.
Is there a simple way of accomplishing my goal? Thanks :)

SKNode Changing Direction on Flat Surface Unexpectedly in Swift 2

I am trying to make a SpriteKit game in swift similar to Mario Brothers or Pitfall. In this game, there are zoo keepers that move back and forth in the horizontal direction, i.e. along the x-coordinate axis. The zoo keeper is walking on a base referred to as platform nodes. When the zoo keeper collides with a platform in front of them, for example a crate or a zoo cage, the zoo keeper turn and walk the other way. The zoo keeper is expected to move over the ground. The game uses the built in physics properties. The zoo keeper has a restitution of 1.0. I start the zoo keeper with a small impulse. In the update function, I set the velocity to zero in the y direction and set the x velocity to the current zoo keeper x velocity such that the zoo keeper doesn't bounce in the y direction and continues in whatever x velocity/direction they were moving. I set the y velocity to zero such that if the zoo keeper walk off a higher level platform, I don't don't want them to bounce in the y direction. The zoo keeper starts off in his daily rounds and moves to the right across 8 ground platform units, bounces off of a crate, turns around and starts walking the other direction. Although, on the way back the after walking over 3 ground platform units, the zoo keeper unexpectedly collides with the ground platform that he previously already walked over and turns around. Each time I re-run the game in xCode, the collision may occur in different places although once running, the collision occurs consistently at the same place. If I give the zoo keeper a lot more initial impulse, the issue goes away although then my zoo keeper moves way too fast. The ground nodes are all the same size and placed with a for loop at the same y position. Here is an example of the zoo keeper (CharacterNode) walking (contacting) ground a node just prior to turning around and contacting a ground node (PlatformNode) that turns the node around, but shouldn't.
Node that is walked over (Before):
Printing description of contact._bodyA:
type: representedObject:[ name:'Platform' position:{974.613037109375, 24.98663330078125} scale:{1.00, 1.00} accumulatedFrame:{{962.11297607421875, 12.487000465393066}, {25, 25}}]
Printing description of contact._bodyB:
type: representedObject:[ name:'ZooKeeper' position:{1000.2339477539062, 62.757087707519531} scale:{1.00, 1.00} accumulatedFrame:{{987.7340087890625, 37.756999969482422}, {25, 50}}]
Ground node that causes the unexpected blocking:
Printing description of contact._bodyA:
type: representedObject:[ name:'Platform' position:{774.5714111328125, 24.986606597900391} scale:{1.00, 1.00} accumulatedFrame:{{762.07098388671875, 12.487000465393066}, {25, 25}}]
Printing description of contact._bodyB:
type: representedObject:[ name:'ZooKeeper' position:{749.27301025390625, 62.757034301757812} scale:{1.00, 1.00} accumulatedFrame:{{736.77301025390625, 37.756999969482422}, {25, 50}}]
Here is the code that I believe is relevant. I create the platforms and zoo keepers by reading a file of integer x, y coordinates from excel files, scale the objects and place them in sknodes with skspritenodes. The "pattern" for a platform could be 1 ground block, two, three, ten, a stair pattern, etc. The ground pattern is 10 consecutive boxes. All platforms are exactly the same size (25x25). Although if you look a the print description, precision appears to be slightly off.
Platform logic: The pattern for a ground node is a set of 10 single ground units placed side by side, i.e., same y coordinate and spaced by 1 unit in the x direction.
let kScenePositionScale: CGFloat = 25 // scale all scene positions read from a file by this factor
let kPlatformSizeScale:CGFloat = 0.5
let kCharacterSizeScale:CGFloat = 0.25
for platform in platformArray {
for patternItem in platform.pattern.patternItemArray {
let positionX = platform.xCoordinate + patternItem.xCoordinate
let positionY = platform.yCoordinate + patternItem.yCoordinate
let platformNode = SceneObject.createPlatformAtPosition(CGPoint(x: positionX, y: positionY), ofType: PlatformType(rawValue: patternItem.imageName)!)
foregroundNode.addChild(platformNode)
}
}
class func createPlatformAtPosition(position: CGPoint, ofType type: PlatformType) -> PlatformNode {
let node = PlatformNode()
node.position = position
node.name = kPlatformNodeName
node.platformType = type
var sprite: SKSpriteNode
sprite = SKSpriteNode(imageNamed: type.rawValue)
sprite.setScale(kPlatformSizeScale)
node.addChild(sprite)
node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
node.physicsBody?.dynamic = false
node.physicsBody?.categoryBitMask = PhysicsCategoryBitmask.Platform
node.physicsBody?.collisionBitMask = 0
return node
}
Zoo Keeper: I only use the contact bitmask to be able to print the contact causing the unexpected turn around feature. The pattern is irrelevant at this point for the zoo keeper. The pattern is at 0,0
// for each item in the character array
for character in characterArray {
for patternItem in character.pattern.patternItemArray {
let positionX = character.xCoordinate + patternItem.xCoordinate
let positionY = character.yCoordinate + patternItem.yCoordinate
let characterNode = Character.createCharacterAtPosition(CGPoint(x: positionX, y: positionY), ofType: CharacterType(rawValue: patternItem.imageName)!, ofMotionType: character.motionType)
foregroundNode.addChild(characterNode)
characterNode.physicsBody?.applyImpulse(CGVectorMake(1.0, 0.0))
}
}
class func createCharacterAtPosition(position: CGPoint, ofType type: CharacterType, ofMotionType motionType: CharacterMotionType) -> CharacterNode {
let node = CharacterNode()
node.position = position
node.name = type.rawValue // named after the character type
node.characterMotionType = motionType
node.textureArray = loadPrimaryAtlas(type.rawValue)
var sprite: SKSpriteNode
sprite = SKSpriteNode(texture: node.textureArray[0])
// anchor at the feet
sprite.setScale(kCharacterSizeScale)
node.sprite = sprite
node.addChild(sprite)
node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.frame.size)
node.physicsBody?.dynamic = true
node.physicsBody?.allowsRotation = false
node.physicsBody?.restitution = 1.0 // bounciness
node.physicsBody?.friction = 0.0 // keep the character moving if needed
node.physicsBody?.angularDamping = 0.0
node.physicsBody?.linearDamping = 0.0
node.physicsBody?.categoryBitMask = PhysicsCategoryBitmask.Character
node.physicsBody?.collisionBitMask = PhysicsCategoryBitmask.Platform //Collision will occur when hero hits land
node.physicsBody?.contactTestBitMask = PhysicsCategoryBitmask.Platform //Contact will be detected when make a contact with land and fruit
return node
}