Sprite-kit: Moving an element in circular path - swift

I'm trying make an element move by the edge of circle.
I have created and positioned a circle in the middle of the screen:
var base = SKShapeNode(circleOfRadius: 200 ) // Size of Circle
base.position = CGPointMake(frame.midX, frame.midY)
base.strokeColor = SKColor.blackColor()
base.glowWidth = 1.0
base.fillColor = UIColor(hue:1, saturation:0.76, brightness:0.94, alpha:1)
base.zPosition = 0
self.addChild(base)
Then I've created another sprite and added an action on it:
main = SKSpriteNode(imageNamed:"Spaceship")
main.xScale = 0.15
main.yScale = 0.15
main.zPosition = 1
let circle = UIBezierPath(roundedRect: CGRectMake((self.frame.width/2) - 200, CGRectGetMidY(self.frame) - 200,400, 400), cornerRadius: 200)
let circularMove = SKAction.followPath(circle.CGPath, duration: 5.0)
main.runAction(SKAction.repeatAction(circularMove,count: 2))
self.addChild(main)
The first rotation(see image below) of the spaceship follow exactly the edges of the circle but the second iteration changes the position of the spaceship and move it outside the screen bounds.
Is this normal or am I doing something wrong ?
Thanks.

+ follow:duration: produce the same effect as follow:asOffset:orientToPath:duration: with both, asOffset and orientToPath parameters set to true.
About asOffset parameter from the docs:
If true, the points in the path are relative offsets to the node’s
starting position. If false, the points in the node are absolute
coordinate values.
So, you should explicitly set it to false:
let circularMove = SKAction.follow(circle.CGPath, asOffset: false, orientToPath: true, duration: 5)

Related

How to exclude positions in a field in SpriteKit?

I have a function that spawns little balls, randomly positioned, on the screen. The problem I face is that I want to distribute the balls randomly, but when I do so, some balls spawn on top of each other. I want to exclude all the positions that are already taken (and maybe a buffer of a few pixels around the balls), but I don't know how to do so. I worked around this by giving the balls a Physicsbody, so they move off from one another if they happen to spawn on top of each other. But I want them to not spawn on top of each other in the first place. My code for now is the following:
spawnedBalls = [Ball]()
level = Int()
func setupLevel() {
let numberOfBallsToGenerate = level * 2
let boundary: CGFloat = 26
let rightBoundary = scene!.size.width - boundary
let topBoundary = scene!.size.height - boundary
while spawnedBalls.count < numberOfBallsToGenerate {
let randomPosition = CGPoint(x: CGFloat.random(in: boundary...rightBoundary), y: CGFloat.random(in: boundary...topBoundary))
let ball = Ball()
ball.position = randomPosition
ball.size = CGSize(width: 32, height: 32)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width)
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = 1
spawnedBalls.append(ball)
self.addChild(ball)
}
}
I don't know if this problem should be solved by having an array that stores all taken positions, or if I should use some kind of FiledNode, where occupied space can be sort of subtracted, but sadly I am unfamiliar with FieldNodes, so I don't know if that's the right way to face the problem.
Step 1) Replace
let randomPosition = ....
with
let randomPosition = randomPositionInOpenSpace()
Step 2) Write the randomPositionInOpenSpace function:
Idea is:
a) generate a random position
b) is it in open space? if so return that
c) repeat until OK
Then Step 3) write the 'is it in open space' function
For that you need to know if the proposed coordinate is near any of the other balls. For circles, you can test the distance between their centers is greater than (radiuses + margins). Distance between centers is pythagoras: sqrt of the x delta squared plus the y delta squared.

Why does SceneKit spotlight angle affect shadow?

I'm trying to use a spotlight in my scene and add shadows to an object. However, I noticed that when I increase the spotInnerAngle, the shadow of the object changes significantly. Here's an example:
Both of shadows in these images look quite different – does anyone know why increasing the spot angle is causing the shadow to be less apparent?
This is the code I'm using to create a spotlight/add shadows to my scene:
let spotLight = SCNNode()
spotLight.light = SCNLight()
spotLight.light?.type = SCNLight.LightType.spot
spotLight.light?.spotInnerAngle = 120
spotLight.light?.spotOuterAngle = 120
spotLight.light?.color = UIColor.white
spotLight.light?.castsShadow = true
spotLight.light?.automaticallyAdjustsShadowProjection = true
spotLight.light?.shadowSampleCount = 32
spotLight.light?.shadowRadius = 8
spotLight.light?.shadowMode = .deferred
spotLight.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
spotLight.light?.shadowColor = UIColor.black.withAlphaComponent(1)
spotLight.position = SCNVector3(x: 0,y: 5,z: 0)
spotLight.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
SceneKit's engine calculates shadows slightly differently than 3D software apps do, like Maya or 3dsMax. In SceneKit framework the position and scale of your Spotlight as well as its value of cone angle are crucial for shadow generating. The main rule is the following: when area of spotlight's ray in SceneKit becomes greater, the shadow edges become more obscure (aka blurry).
Here's a properties you have to take into consideration when using spotlight:
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .spot
lightNode.rotation = SCNVector4(x: 0, y: 0, z: 0, w: 1)
lightNode.castsShadow = true
/* THESE SEVEN SPOTLIGHT PROPERTIES AFFECT SHADOW'S APPEARANCE */
lightNode.position = SCNVector3(0, 10, 0)
lightNode.scale = SCNVector3(7, 7, 7)
lightNode.light?.spotOuterAngle = 120
lightNode.light?.shadowRadius = 10
lightNode.light?.zNear = 0
lightNode.light?.zFar = 1000000
lightNode.light?.shadowSampleCount = 20
lightNode.light?.shadowColor = UIColor(write: 0, alpha: 0.75)
lightNode.light?.shadowMode = .deferred
scene.rootNode.addChildNode(lightNode)
Also, I recommend you use Ambient Light with very low Intensity for lighting up dark areas on your 3D models.
Hope this helps.
From Apple's documentation:
"[spotInnerAngle] determines the width of the fully illuminated area."
The default value of this property is 0, which means only the center of the area illuminated by the spotlight is lit at full intensity.
Increasing the inner angle will increase the area that is lit at full intensity, thus adding more light to the scene. In your case, this additional light was decreasing the visible shadow from that angle.

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

Smoothing Edges of Unstroked Paths on SKShapeNode

I've been doing some shape drawing using Swift's built in drawing tools to draw simple paths with straight lines. One anomaly I've noticed is that if you want a path that is filled but does not have a line stroke on its edge, it renders without antialiasing and has a rough appearance. In contrast, a line by default always renders smoothly. There is an option to turn antialiasing on but this also makes no difference. I could just make the stroke the same colour as the fill, but this means that the edge expands outwards from the point positions that define the shape.
//creates shape node to contain path
let myTriangle = SKShapeNode()
//declares path object
let myPath = CGPathCreateMutable()
//moves to starting point of shape
CGPathMoveToPoint(myPath, nil, -50, 0)
//draws lines of shape
CGPathAddLineToPoint(myPath, nil, 0, 100 )
CGPathAddLineToPoint(myPath, nil, 50, 0 )
CGPathAddLineToPoint(myPath, nil, -50, 0 )
//closes path
CGPathCloseSubpath(myPath)
myTriangle.antialiased = true
//adds path to shape node
myTriangle.path = myPath
//sets fill colour and zero line width
myTriangle.fillColor = SKColor.blueColor()
myTriangle.lineWidth = 0
//sets antialiasing
myTriangle.antialiased = true
//sets position in parent object and adds node
myTriangle.position = CGPointMake(600, 600)
self.addChild(myTriangle)
Any ideas?
Many thanks,
Kw
About the antialiasing you can take a look here to this document.
I've seen also you have a lineWidth equal to 0, that's not help according with this document..
To improve the appearance of your path you could try also:
yourPath.lineCap = CGLineCap(rawValue: 1)!
where, following the source:
/* Line cap styles. */
public enum CGLineCap : Int32 {
case butt
case round
case square
}
Some code to example written in Swift 3:
let myTriangle = SKShapeNode()
let myPath = CGMutablePath()
myPath.move(to: CGPoint(x:-110,y: 0))
myPath.addLine(to: CGPoint(x:0,y: 290))
myPath.addLine(to: CGPoint(x:260,y: 0))
myPath.addLine(to: CGPoint(x:-110,y: 0))
myPath.closeSubpath()
myTriangle.path = myPath
myTriangle.fillColor = SKColor.white
myTriangle.lineWidth = 2
myTriangle.lineCap = CGLineCap(rawValue: 1)!
myTriangle.strokeColor = SKColor.white
myTriangle.isAntialiased = true
myTriangle.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
self.addChild(myTriangle)
The Output of this code (lineWidth 2 and lineCap valorized..):
Your Output (lineWidth equal to 0):