How can I make an SKPhysicsBody for this image using CGPath? - swift

I am trying to make an SKPhysics body for this SKSpriteNode using a CGPath polygon.
The problem is that when I check for a collision between this node and the player node, the didBeginContact method is executed even though they did not touch each other. I believe their is something wrong with the coordinates but I cannot see the polygon lines, which makes it difficult for me too see the accuracy of the lines.
Here is the code that I am using:
let triangle = SKSpriteNode(imageNamed: "Triangle_ZigZag")
let trianglePath = CGMutablePath()
trianglePath.addLines(between: [CGPoint(x: triangle.size.width,
y: triangle.size.height),
CGPoint(x: triangle.size.width,
y: - triangle.size.height),
CGPoint(x: -triangle.size.width,
y: triangle.size.height / 2)])
trianglePath.closeSubpath()
triangle.physicsBody = SKPhysicsBody(polygonFrom: trianglePath)
Can someone please help me figure out what am I doing wrong ?
Thank You

FYI physics lines are green, so a green sprite probably isn't the best choice you can't see the lines very well.
your sprite has a centre anchorPoint or an anchorPoint of (0, 0) by default. Therefore your physics points need to take that into account. top right corner would be half the width from centre and half the height from centre etc. You have full width from centre and full height from centre, that is the issue.
trianglePath.addLines(between: [CGPoint(x: triangle.size.width / 2, y: triangle.size.height / 2), CGPoint(x: triangle.size.width / 2, y: -triangle.size.height / 2), CGPoint(x: -triangle.size.width / 2, y: 0)])

Related

UIBezierPath Quadratic Curve is a Straight Line

I'm trying to create a curved arrow for displaying in ab ARKit scene, however, the curvature of the arrow staff is just rendering as a straight line on both sides.
func createTurnArrow(_ direction: Direction) -> SCNShape {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.2, y: 0)) // A
path.addLine(to: CGPoint(x: 0, y: 0.2)) // B
path.addLine(to: CGPoint(x: 0, y: 0.1)) // C
path.addQuadCurve(to: CGPoint(x: -0.3, y: -0.3), controlPoint: CGPoint(x: -0.3, y: 0.1)) // Curve 1
path.addLine(to: CGPoint(x: -0.1, y: -0.3)) // D
path.addQuadCurve(to: CGPoint(x: 0, y: -0.1), controlPoint: CGPoint(x: -0.1, y: -0.1)) // Curve 2
path.addLine(to: CGPoint(x: 0, y: -0.2)) // E
path.close()
return direction == .left ?
SCNShape(path: path.reversing(), extrusionDepth: self.defaultDepth) :
SCNShape(path: path, extrusionDepth: self.defaultDepth)
}
My intuition tells me that create a node with this function:
SCNNode(geometry: createTurnArrow(.right))
should produce a shape like this:
but instead renders this without any curves to the tail of the arrow:
I've tried a bunch of other math to get the current control points for the quadratic curves but nothing is worry. Any ideas?
EDIT:
Where is the schematic with plotted points and my assumption of how this should be rendered with the curves.
Read the SCNShape path documentation. It says this:
The path’s flatness (see flatness in NSBezierPath) determines the level of detail SceneKit uses in building a three-dimensional shape from the path—a larger flatness value results in fewer polygons to render, increasing performance.
(Since you're on iOS, substitute UIBezierPath for NSBezierPath.)
What is the default flatness of a UIBezierPath? Here's what the documentation says:
The flatness value measures the largest permissible distance (measured in pixels) between a point on the true curve and a point on the rendered curve. Smaller values result in smoother curves but require more computation time. Larger values result in more jagged curves but are rendered much faster. The default flatness value is 0.6.
Now compare the default flatness (0.6) to the overall size of your shape (0.5 × 0.5). Notice that the flatness is bigger than the size of your shape! So each of your curves is getting flattened to a single straight line.
Change the flatness of your path to something more appropriate for your shape, or change the scale of your shape to something more appropriate for the default flatness.
let path = UIBezierPath()
path.flatness = 0.05 // <----------------------- insert this statement
path.move(to: CGPoint(x: 0.2, y: 0)) // A
path.addLine(to: CGPoint(x: 0, y: 0.2)) // B
// etc.

Spawning sprites on screen edges while keeping anchor point in center

I'm developing a simple game where there's a player sprite in the middle of my screen and monster sprites spawn at random locations on the screen edge and slowly move toward the player sprite. Originally, this worked just fine by setting the GameScene anchor point to the lower left with self.anchorPoint = CGPoint(x: 0.0, y: 0.0), and the positioning the player in the center with player.position = CGPoint(x: size.width * 0.5, y: size.height * 0.5). However, because I changed the anchor point, when I rotate my iPad, the player ends up being off center.
I think it's easier to work with a (0,0) anchor point, because then I can spawn monsters randomly from the edges using lines like randomEdgePosition = CGPoint(x: 0.0, y: randomFloat(min: 0.0, max: 1.0) * size.height). But it's easier for my player's positioning to keep the anchor point centered. How can the screen edges be calculated with a central anchor point? Alternatively, how could I keep my player centered with a (0,0) anchor point?
Keep the anchor point in the center of your screen and spawn your monsters using this
randomEdgePosition = CGPoint(x: 0 - size.width / 2, y: randomFloat(min: 0.0, max: 1.0) * size.height)
The problem with randomizing on an edge is that you will see more enemies spawning in the corners than in the centers of the edges because there is more edge on the corners. To evenly distribute around the entire scene, you want to position based on a circle, and use the longest edge / 2 + the longest edge / 2 of the enemy (I assume you want to spawn off screen) as your radius
let theta = CGFloat(randomFloat(min: 0.0, max: 1.0) * Float.pi * 2)
let radius = max(size.width, size.height)/2 + max(enemy.size.width, enemy.size.height)/2
let randomPosition = CGPoint(x: cos(theta) * radius, y: sin(theta) * radius)
This will also solve your issue with the anchorPoint, and allows you to keep it at the center of the screen (0.5,0.5), making it easier to detect which way the enemies are coming from without having to do additional subtraction. (E.G. enemy at -x is left of the player, no need to check if x < player.x0

SpriteKit CGPath and SpriteMovement

func zombieAI() {
let zombieGreen = SKSpriteNode(imageNamed: "Zombie")
zombieGreen.setScale(0.3)
zombieGreen.zPosition = 3
zombieGreen.physicsBody?.affectedByGravity = false //change this later
zombieGreen.position = CGPoint(x: 200, y: 200)
self.addChild(zombieGreen)
let goToTurret = CGMutablePath()
goToTurret.move(to: CGPoint(x: 0, y: 0))
zombieGreen.run(SKAction.follow(goToTurret, speed: 1.0))
}
I am still learning about CGPaths and my objective of this code was to make the zombie move to the point 0,0. Currently the zombie spawns and just sits where it spawned. I didn't want to use moveTo, which I am more comfortable with, because the zombie may be obstructed by something, so I want the zombie to move at a speed rather than getting to the point in a certain amount of time. Any suggestions on how to use the CGPath correctly or what I could change to achieve my goal? I am very new to this so please respectfully judge my code :)
Try this (not tested):
Replace this line
goToTurret.move(to: CGPoint(x: 0, y: 0))
with
goToTurret.move(to: zombieGreen.position)
goToTurret.addLine(to: CGPoint(x: 0, y: 0))
So you start the path off with the zombie's current position, then add a line from that position to the zombie's intended position.
Hope this helps!

Sprite positioning at wrong points in y axis

so I know this is a dumb question, but maybe it'll help other people like me out too. Basically, I'm making a sprite appear at the top of the screen and then move to the bottom but in a random x position. There's nothing wrong with moving to the random x position, the problem is that it doesn't start out at the top of the screen. Here is my code:
let sprite = SKSpriteNode(imageNamed: "comet")
sprite.size = CGSize(width: 150, height: 100)
sprite.position = CGPoint(x: self.frame.size.width/2, y: 0)
sprite.zPosition = 100
self.addChild(sprite) `
let randomNum = CGPoint(x:Int (arc4random() % 1000), y: 1)
let actionMove = SKAction.moveTo(randomNum, duration: 1)
let actionComet = SKAction(actionMove)
sprite.runAction(actionComet)
Any help is appreciated.
You are placing your sprite at location (0,0) which is left bottom of the screen. If you want to place the sprite at the top use:
sprite.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height + sprite.size.height / 2.0)

SKSpriteNode animation to appear bottom-up

According to the following code, i am displaying a mountain (hill) in the scene at x = 400 and y = self.frame.height / 2
However, I want the hill to appear from bottom-up (from below the scene) like an animation. How can I code this ?
let hill = SKSpriteNode(imageNamed: "hill")
hill.position = CGPoint(x: 400, y: self.frame.height / 2 )
hill.setScale(1)
hill.physicsBody = SKPhysicsBody(rectangleOfSize: hill.size)
hill.physicsBody?.categoryBitMask = 1
hill.physicsBody?.collisionBitMask = 2
self.addChild(hill)
To create an animation that makes the sprite slide up from "under" the scene, do this:
Set its initial position to below the scene:
hill.position = CGPoint(x: 400, y: self.frame.height / CGFloat(2) - self.frame.height)
Note: - self.frame.height is used to place the node 1 full frame height below the desired location
Then start an action that moves it up to its desired position in didMoveToView, or wherever you want the animation to start:
let moveUpAnimation = SKAction.move(to: CGPoint(x: 400, y: self.frame.height / CGFloat(2)), duration: 0.75)
hill.run(moveUpAnimation)
This example creates an action that takes 0.75 seconds to move the sprite up one full frame height. You can set its initial position to anywhere below the visible scene to create this effect, and change the duration to any desired speed.