Swift, Sprite Kit Game: Have Circle Disappear in Clockwise Manner? on Timer

Swift, sprite kit game: Have circle disappear in clockwise manner? On timer?

By changing the path property of an SKShapeNode at a fixed interval, you can create a frame-by-frame animation sequence. To create the animation, set the path property to a sequence of shapes that starts with a circle and ends with nothing. You can use UIBezierPath, a wrapper for CGPath, to create shapes for the animation using the following steps:

  1. Move path's "pen" to the center of the circle
  2. Add an arc to the path with addArcWithCenter from a startAngle to endAngle
  3. Add a line to the path from the point on the circle corresponding to the ending angle to the center
  4. Change the endAngle by a fixed amount
  5. Repeat steps 1-4

Here's an implementation of the above steps:

override func didMove(to:SKView) {

let circle = SKShapeNode(circleOfRadius: 50)
circle.fillColor = SKColor.blue
circle.strokeColor = SKColor.clear
circle.zRotation = CGFloat.pi / 2
addChild(circle)

countdown(circle: circle, steps: 20, duration: 5) {
print("done")
}
}

// Creates an animated countdown timer
func countdown(circle:SKShapeNode, steps:Int, duration:TimeInterval, completion:@escaping ()->Void) {
guard let path = circle.path else {
return
}
let radius = path.boundingBox.width/2
let timeInterval = duration/TimeInterval(steps)
let incr = 1 / CGFloat(steps)
var percent = CGFloat(1.0)

let animate = SKAction.run {
percent -= incr
circle.path = self.circle(radius: radius, percent:percent)
}
let wait = SKAction.wait(forDuration:timeInterval)
let action = SKAction.sequence([wait, animate])

run(SKAction.repeat(action,count:steps-1)) {
self.run(SKAction.wait(forDuration:timeInterval)) {
circle.path = nil
completion()
}
}
}

// Creates a CGPath in the shape of a pie with slices missing
func circle(radius:CGFloat, percent:CGFloat) -> CGPath {
let start:CGFloat = 0
let end = CGFloat.pi * 2 * percent
let center = CGPoint.zero
let bezierPath = UIBezierPath()
bezierPath.move(to:center)
bezierPath.addArc(withCenter:center, radius: radius, startAngle: start, endAngle: end, clockwise: true)
bezierPath.addLine(to:center)
return bezierPath.cgPath
}

and a video clip:

Sample Image

Swift: how to invalidate timer on animated SKShapenode countdown?

You can stop an action by adding a key to the action and then run

removeActionForKey("actionKey")

To add a key to the action, replace the countdown method with the following:

// Creates an animated countdown timer
func countdown(circle:SKShapeNode, steps:Int, duration:NSTimeInterval, completion:()->Void) {
let radius = CGPathGetBoundingBox(circle.path).width/2
let timeInterval = duration/NSTimeInterval(steps)
let incr = 1 / CGFloat(steps)
var percent = CGFloat(1.0)

let animate = SKAction.runBlock({
percent -= incr
circle.path = self.circle(radius, percent:percent)
})
let wait = SKAction.waitForDuration(timeInterval)
let action = SKAction.sequence([wait, animate])

let completed = SKAction.runBlock{
circle.path = nil
completion()
}

let countDown = SKAction.repeatAction(action,count:steps-1)
let sequence = SKAction.sequence([countDown, SKAction.waitForDuration(timeInterval),completed])

runAction(sequence, withKey: "actionKey")
}

Drawing circle outline with small gap Sprite Kit Swift

You can get something similar using the SKShapeNode in combination with SKEffectNode, like this:

override func didMoveToView(view: SKView) {

let effectNode = SKEffectNode()
let gap = CGFloat(M_PI_4 / 4.0)

for i in 0...3 {

let shape = SKShapeNode(circleOfRadius: 50)

shape.fillColor = .clearColor()
shape.lineWidth = 6.8
shape.strokeColor = .darkGrayColor()

let startAngle:CGFloat = CGFloat(i) * CGFloat(M_PI_2) + gap
let endAngle:CGFloat = startAngle + CGFloat(M_PI_2) - gap * 2

print("Iteration \(i) : start angle (\(startAngle * 180 / CGFloat(M_PI)), end angle (\(endAngle * 180 / CGFloat(M_PI)))")

shape.path = UIBezierPath(arcCenter: CGPointZero, radius: -50, startAngle: startAngle, endAngle: endAngle, clockwise: true).CGPath

effectNode.addChild(shape)
}

effectNode.position = CGPoint(x: frame.midX, y: frame.midY)
effectNode.shouldRasterize = true

addChild(effectNode)
}

The result:

crosshair

Or you could make a mask programatically and apply it to a SKCropNode. The SKCropNode of course will be a parent of a ring (SKShapeNode with fillColor set to .clearColor() and strokeColor set to appropriate value).

Sprite Kit - Draw circle with muilticolored borders

Import the object into your game as a png (from photoshop or whatever) and when a node collides with it, detect the point it contacts and calculate the difference in x and y from the center of the circle. Even if the circle is rotating, this should always tell you what color the colliding object hit, as long as you have the colors mapped to "zones" already. make sense?

Alternatively, you could create a transparent circle (SKShapeNode) and have eight SKShapeNode arcs as children of that transparent circle. Position them deliberately. This adds the benefit that each will be a separate object, can have its own strokeColor, and its own touch methods. They would need to be collision-able while the parent transparent circle would need to ignore collisions for it to work.

As for how to detect which section of the circle was hit, here is how I would do it:

Given a circle that is divided into 8 equally sized pie pieces, you should be able to determine which section received a collision based on the x and y values of the collision point compared to the center of the circle. For example, assuming that the Y axis is a division between two halves of four sections each (see pic), you can first narrow the collision down by quadrant as follows:

Circle with 8 sections

cx, cy = collision x and y values
ox, oy = the x and y values of the very center (origin) of the circle

pseudocode:

if cx > ox && cy > oy, then the collision occurred in quadrant 1
if cx > ox && cy < oy, then the collision occurred in quadrant 2
if cx < ox && cy < oy, then the collision occurred in quadrant 3
if cx < ox && cy > oy, then the collision occurred in quadrant 4

Since there are two sections in each quadrant you can further refine this logic, determining which section of the two the collision occurred in by comparing the difference

pseudocode:

if the difference between cx and ox > the difference between cy and oy, then the collision occurred in Section 2
else the collision occurred in section 1

Combining these logics into one nested if-else statement you'd have something like this:

pseudocode:

if cx > ox && cy > oy, then the collision occurred in quadrant 1
......if the difference between cx and ox > the difference between cy and oy, then the collision occurred in Section 2
......else the collision occurred in section 1
if cx > ox && cy < oy, then the collision occurred in quadrant 2
......if the difference between cx and ox > the difference between cy and oy, then the collision occurred in Section 3
......else the collision occurred in section 4
if cx < ox && cy < oy, then the collision occurred in quadrant 3
......if the difference between cx and ox > the difference between cy and oy, then the collision occurred in Section 6
......else the collision occurred in section 5
if cx < ox && cy > oy, then the collision occurred in quadrant 4
......if the difference between cx and ox > the difference between cy and oy, then the collision occurred in Section 7
......else the collision occurred in section 8

HTH!

Passenger/Apache: Can't set expire headers for versioned resources (rewrite rule not recognized)

I prefer to do this by combining it with using an assets host on a separate subdomain to avoid the whole rewrite issue. That way you can set the expire headers for everything on that subdomain. You can activate this in rails in environments/production.rb.

If you don't want to go with a separate subdomain I think the code below should do it, although I have not tested it myself:

ExpiresActive On
<FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
ExpiresDefault "access plus 1 year"
</FilesMatch>


Related Topics



Leave a reply



Submit