Struggle creating correct physicsbody - swift

In my current project I create some node that I would like to create physics bodies for.
This creates the nodes:
let seg = SKShapeNode(ellipseOf: CGSize(width: 10 / xScale, height: 10 / yScale))
let offset = (seg.frame.height / 1.5) * CGFloat(i + 1)
seg.position = CGPoint(x: anchorPoint.x, y: ((size.height / 2) / yScale) + (10 / yScale) - offset)
segments.append(seg)
addChild(seg)
And this is how I currently try to create the bodies:
seg.physicsBody = SKPhysicsBody(circleOfRadius: 10 / xScale / yScale / 2)
I dont know how I would go about creating the correct one. It should function like a normal physics body(edge based bodies don't collide with stuff).
Currently when the circles roll down it looks like this although they are initially circles:
Also they look very pixelated. Do you have any idea why?
Thanks for your help!

Related

SpriteKit: SKPhysicsJointLimit not respecting 'maxLength'

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.

SpriteKit sprite not following path correctly

I'm currently converting my android game to iOS, and I'm trying to get a bat enemy to 'swoop' the player. I have the curve created in SpriteKit and it looks fine, however the bat does not appear to follow the line correctly, it disappears for a few seconds and then zips past the top right corner going upwards despite running an action.follow on a path that looks drawn properly. The path is attached to a shapeNode which follows the player.
Code:
func createPath(){
//All x values are moved back 1 / 4 of the screens width due to the camera being 1 / 4 of the screens width ahead of the player, and y values are halved due to the player being in the centre of y axis
path.move(to: CGPoint(x: gameScene.frame.width * 3 / 4, y: gameScene.frame.height / 2)) //top right corner
path.addCurve(to: CGPoint(x: -(gameScene.frame.width / 3.5), y: gameScene.frame.height / 6), control1: CGPoint(x: gameScene.frame.width / 2, y: -(gameScene.frame.height / 9)), control2: CGPoint(x: 0, y: -(gameScene.frame.width / 9)))
//to = off screen
//control1 3 / 4 across screen, slighly higher than player
//control2 exactly on player (players node height is screen width / 4.5)
followLine = SKAction.follow(path, asOffset: true, orientToPath: false, duration: 3)
viewLine.path = path
gameScene.addChild(viewLine)
}
func update(dt: Double){
if(GameScene.gV.distanceM == 4 && !run){//DistanceM is just a timer (seconds)
run = true
bat.run(followLine)
}
if run {
/*time += dt//timer to reset bats position
if time > 4 {
run = false
time = 0
}*/
} else {
bat.position = CGPoint(x: gameScene.pigeonCam.position.x + gameScene.frame.width / 2, y: gameScene.pigeonCam.position.x + gameScene.frame.height / 2)//keep bat at top right of screen
}
viewLine.position = gameScene.pigeon.pigeon.position//get node to follow the player
}
My best guess is that while moving the shapeNode appears to move the path it actually doesn't, if so, any other way I could get this path to "follow" the player
After much trial and error I realised the node asOffset is referring to is the one that runs the action, so I put the bat at the top right corner of the screen and adjusted the values, my final values looked as so:
let width = gameScene.frame.width
let height = gameScene.frame.height
path.move(to: CGPoint(x: 0, y: 0)) //top right corner
//0, 0
path.addCurve(to: CGPoint(x: -(width * 1.2), y: -(height / 3.5)), control1: CGPoint(x: -(width / 4), y: -(height / 1.5)), control2: CGPoint(x: -(width * 3 / 4), y: -(height / 2)))
This makes the bat follow the line perfectly, though the visual representation of the line is messed up

Why is this expression "too complex to be solved in reasonable time?"

func createPaddles() -> SKNode {
let container = SKNode()
let colors = [SKColor.green, SKColor.blue, SKColor.red, SKColor.green, SKColor.blue]
let width = self.frame.size.width
let height = self.frame.size.height
for i in 0...4 {
let node = SKShapeNode(rectOf: CGSize(width: self.frame.size.width / 3, height: self.frame.size.height * 0.15))
node.fillColor = colors[i]
node.strokeColor = colors[i]
node.zPosition = 100
node.position = CGPoint(x: -width * 1/6 + width / 3 * i, y: height * 0.075)
container.addChild(node)
}
return container
}
node.position is giving me an error saying it's too complex. Is this because calculating the frame width twice in one equation is too much for the compiler? Seems unlikely and this doesn't seem overly complex to me. Am I missing something here?
EDIT: Great group effort from everyone, thank you! This is what eventually worked (a combination of a few different answers):
node.position = CGPoint(x: -width / 6.0 + width / 3.0 * CGFloat(i), y: height * 0.075)
One of the reasons why this happens is that you have a type mismatch. However, instead of printing the type mismatch error, the compiler tries to find a way to match the types... and fails.
In your specific case, i is an Int while other variables in the expression are CGFloat:
-width * 1/6 + width / 3 * CGFloat(i)
Also note that 1/6 will be probably considered to be integer division, with the result being 0 although I am not sure about the priority right now. You probably want:
-width / 6 + width / 3 * CGFloat(i)
to be sure.
Generally it has to do with the compiler type system inferring the proper types to execute the statement. You can often shortcut the process by providing the types for it.
Note that simplifying your equation results in this:
node.position = CGPoint(x: width * (i - 0.5) / 3.0, y: height * 0.075)
You can also expressly convert types so that the compiler has less to do:
node.position = CGPoint(x: CGFloat(width) * (CGFloat(i) - CGFloat(0.5)) / CGFloat(3.0), y: CGFloat(height) * CGFloat(0.075))
This error occurs when the compiler needs to do a lot of type checking. See this answer for a good explanation: https://stackoverflow.com/a/29931329/6658553
In your specific case, try reducing the number of operators you are using, and split it into multiple calculations.
node.position = CGPoint(x: -width * 1/6 + width / 3 * i, y: height * 0.075)
could be refactored to something like this:
let newXPosition: CGFloat = -width * 1/6 + width / 3 * CGFloat(i)
node.position = CGPoint(x: newXPosition, y: height * 0.075)
It is not a very clear error, but it's because you are trying to multiply CGFloats with Ints
width is a CGFloat
let width = self.frame.size.width
"i" in your for loop is an int
for i in 0...4 {
let node = SKShapeNode(rectOf: CGSize(width: self.frame.size.width / 3, height: self.frame.size.height * 0.15))
node.fillColor = colors[i]
node.strokeColor = colors[i]
node.zPosition = 100
node.position = CGPoint(x: -width * 1/6 + width / 3 * i, y: height * 0.075)
container.addChild(node)
}
You can separate them to sub clocks as others have mentioned (probably preferred) but at the core of it you need to ensure that all of the values in your equation are of the same type
easiest way to make them of the same type would be to cast i to CGFloat
CGFloat(i)

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.

Spawning Nodes randomly between positions

I'm trying to make a matching game, where these circles appear randomly in a rectangular area, without overlapping. Here's the spawning function:
func SpawnRed(){
var Red = SKSpriteNode(imageNamed: "Matched_Red")
Red.size = CGSize(width: 50, height: 50)
Red.zPosition = 1
let MinValueX = self.size.width / 3 + 50
let MaxValueX = self.size.width / 1.5 - 50
let MinValueY = self.size.height / 1.5 + 25
let MaxValueY = self.size.height / 6
let SpawnPointX = UInt32(MaxValueX - MinValueX)
let SpawnPointY = UInt32(MaxValueY - MinValueY)
Red.position = CGPointMake(CGFloat(arc4random_uniform(SpawnPointX)) + MinValueY,CGFloat(arc4random_uniform(SpawnPointY)))
self.addChild(Red)
}
But for some reason, I keep getting
"Thread 1: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP,subcode=0x0)" error.
Can you find the solution? Also, it'll be so helpful if you tell me how to spawn nodes without overlapping.