Sprite-Kit: Moving an Element in Circular Path

Sprite-kit: Moving an element in circular path

+ 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)

Sprite-kit: How to position sprites on a circular path

Like I said, you need to know where is the center of the path (in your case that is CGPoint(x: frame.midX, y:frame.midY) which is "center" of the screen) and you have to know the radius (you have it already calculated when you was creating the path) and you need an angle that the ray from center (frame.midX,frame.midY) to the point on the circumference (coinX,coinY) makes with positive x axis:

import SpriteKit

class GameScene: SKScene {

let player = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 30, height: 30))

override func didMoveToView(view: SKView) {
/* Setup your scene here */

// Adding the big circle
let runway = SKSpriteNode(color: .orangeColor(), size: CGSize(width: 150, height: 150))
runway.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
addChild(runway)
// Adding the player

player.position = CGPointMake( CGRectGetMidX(frame) , (CGRectGetMidY(frame) + runway.size.width/2) )
addChild(player)
// Calculating the initial position of the player and creating a circular path around it
let dx = player.position.x - frame.width / 2
let dy = player.position.y - frame.height / 2

let radius = (runway.frame.size.width / 2) - 20.0

let radian = atan2(dy, dx)
let playerPath = UIBezierPath(
arcCenter: CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame)),
radius: radius,
startAngle: radian,
endAngle: radian + CGFloat(M_PI * 4.0),
clockwise: true)

let follow = SKAction.followPath(playerPath.CGPath, asOffset: false, orientToPath: true, speed: 200)
player.runAction(SKAction.repeatActionForever(follow))

let numberOfCoins = 8
for i in 0...numberOfCoins {

let coin = SKSpriteNode(color: .yellowColor(), size: CGSize(width: 10, height: 10))

let angle = 2 * M_PI / Double(numberOfCoins) * Double(i)

let coinX = radius * cos(CGFloat(angle))
let coinY = radius * sin(CGFloat(angle))

coin.position = CGPoint(x:coinX + frame.midX, y:coinY + frame.midY)
addChild(coin)
}
}
}

Just a sidenote : In Sprite-Kit the angle of 0 radians specifies the positive x axis. And the positive angle is in the counterclockwise direction. The source - Building Your Scene.

How to rotate a sprite along a circular path?

Create an SKNode and place it at the center of your circle.

Then add your sprite to it (addChild) at a position of CGPoint(x:radius,y:0). you can set the zRotation of the SKNode to chose your starting position on the circle.

When you rotate the SKNode, your sprite will move in a perfect circle. For example, to have the spites go around in 5 seconds, you can use SKAction.rotate(by: 2*pi, duration:5) and repeat that forever (on the SKNode containing the sprite (not on the sprite itself).

How to make a sprite that follow a circular path while its size changes?

I think you could make some corrections that can help you for the rest of the project.

This below works well with Swift 2.x, don't try it with Swift 3.x:

First of all I want to introduce this extension that can be useful :

extension SKNode
{
func runAction( action: SKAction!, withKey: String!, optionalCompletion: dispatch_block_t? )
{
if let completion = optionalCompletion
{
let completionAction = SKAction.runBlock( completion )
let compositeAction = SKAction.sequence([ action, completionAction ])
runAction( compositeAction, withKey: withKey )
}
else
{
runAction( action, withKey: withKey )
}
}

func actionForKeyIsRunning(key: String) -> Bool {
if self.actionForKey(key) != nil {
return true
} else {
return false
}
}
}

Usage:

self.runAction(myAction,withKey: "myActionName",optionalCompletion: {
// do whatever you want when action is finished..
})

if self.actionForKeyIsRunning("myActionName") {
// this action is up
}

Where to place this extension:

You can place this extension also after the last brace of your class, like this example:

class MyClass: SKScene {
...
} // end of MyClass

extension SKNode
{
...
}

This line could become:

    circuloPrincipal.runAction(SKAction.repeatActionForever(follow),withKey: "followTrackForever"))

and the scaleTo lines could be:

    if !circuloFondo.actionForKeyIsRunning("scaleTo") {
let scale = SKAction.scaleTo(0.5, duration: 5)
circuloFondo.runAction(scale,withKey:"scaleTo",optionalCompletion: {
// scaleTo is terminated
// stop followTrackPath
self.circuloPrincipal.removeActionForKey("followTrackForever")
// re-run in the new path
let follow = SKAction.followPath(playerPath.CGPath, asOffset: false, orientToPath: true, speed: 200)
self.circuloPrincipal.runAction(SKAction.repeatActionForever(follow),withKey: "followTrackForever"))
})
}

Moving a SKSpriteNode in a downward loop, using Swift

I've put together some sample code which you can adapt for your purposes. I've based the code off the equation for an Archimedean Spiral:

r = a + bθ

Where a is the starting radius; b is the radius the spiral will increase by per revolution and θ is the current angle.

A spiral is basically a glorified circle (IMO), so to move your node in a spiral you need to be able to calculate point on a circle using an angle, radius and center point:

func pointOnCircle(#angle: CGFloat, #radius: CGFloat, #center: CGPoint) -> CGPoint {
return CGPoint(x: center.x + radius * cos(angle),
y: center.y + radius * sin(angle))
}

Next, extend SKAction so you can easily create a spiral action:

extension SKAction {
static func spiral(#startRadius: CGFloat, endRadius: CGFloat, angle
totalAngle: CGFloat, centerPoint: CGPoint, duration: NSTimeInterval) -> SKAction {

// The distance the node will travel away from/towards the
// center point, per revolution.
let radiusPerRevolution = (endRadius - startRadius) / totalAngle

let action = SKAction.customActionWithDuration(duration) { node, time in
// The current angle the node is at.
let θ = totalAngle * time / CGFloat(duration)

// The equation, r = a + bθ
let radius = startRadius + radiusPerRevolution * θ

node.position = pointOnCircle(angle: θ, radius: radius, center: centerPoint)
}

return action
}
}

Finally, an example of use. In didMoveToView:

let node = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 10, height: 10))
node.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(node)

let spiral = SKAction.spiral(startRadius: size.width / 2,
endRadius: 0,
angle: CGFloat(M_PI) * 2,
centerPoint: node.position,
duration: 5.0)

node.runAction(spiral)

Moving SpriteNodes outwards in a circular pattern

if the bullets are moving faster from the bottom left then the top right, that means your anchor points are (0,0) not (0.5,0.5)

Zig zac path in sprite kit

Try the following code to get a zig zag path using bezier curves.

var cgpath: CGMutablePathRef = CGPathCreateMutable();

var xStart: CGFloat = CGFloat(200.0);
var xEnd: CGFloat = CGFloat(200.0);

var yStart : CGFloat = 0
var yEnd : CGFloat = self.size.height

let numberOfIntermediatePoints = 10
let xOffset : CGFloat = 400
let intervalY = (yEnd - yStart)/CGFloat(numberOfIntermediatePoints)

var s: CGPoint = CGPointMake(xStart,yStart);
CGPathMoveToPoint(cgpath,nil, s.x, s.y);

for i in stride(from: 0, through: numberOfIntermediatePoints - 1, by: 1) {

let yOff = intervalY * CGFloat(i) + yStart
let controlPointInterval = intervalY/3

var cp1X: CGFloat = CGFloat(xStart + xOffset);
var cp1Y: CGFloat = CGFloat(yStart + yOff + controlPointInterval);

var cp2X: CGFloat = CGFloat(xStart - xOffset);
var cp2Y: CGFloat = CGFloat(yStart + yOff + controlPointInterval * 2);

var e: CGPoint = CGPointMake(xEnd, yStart + yOff + intervalY);
var cp1: CGPoint = CGPointMake(cp1X, cp1Y);
var cp2: CGPoint = CGPointMake(cp2X, cp2Y);

CGPathAddCurveToPoint(cgpath, nil, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);

}

var ball:SKSpriteNode = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(40, 40));

var followTrack = SKAction.followPath(cgpath, asOffset: false, orientToPath: true, duration: 5.0);
var forever: SKAction = SKAction.repeatActionForever(followTrack);
ball.runAction(forever);
self.addChild(ball);

let shapeNode = SKShapeNode(path: cgpath)
self.addChild(shapeNode);

Change numberOfIntermediatePoints and xOffset values to change the number of zig zags and the amount of distance moved in each zig zag.

Crop/Mask circular image node in sprite kit gives jagged edges

UPDATE:

Ok, so here's one solution I came up. Not super ideal but it works perfectly. Essentially, I have a to size/scale, and cut the image exactly the way it would go on the SKSpriteNode so I would not have to use SKCropNode or some variation of SKShapeNode.

  1. I used these UIImage extensions by Leo Dabus to resize/shape the image exactly as needed. Cut a UIImage into a circle Swift(iOS)

    var circle: UIImage? {
    let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
    let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
    imageView.contentMode = .ScaleAspectFill
    imageView.image = self
    imageView.layer.cornerRadius = square.width/2
    imageView.layer.masksToBounds = true
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
    guard let context = UIGraphicsGetCurrentContext() else { return nil }
    imageView.layer.renderInContext(context)
    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return result
    }

    func resizedImageWithinRect(rectSize: CGSize) -> UIImage {
    let widthFactor = size.width / rectSize.width
    let heightFactor = size.height / rectSize.height

    var resizeFactor = widthFactor
    if size.height > size.width {
    resizeFactor = heightFactor
    }

    let newSize = CGSizeMake(size.width/resizeFactor, size.height/resizeFactor)
    let resized = resizedImage(newSize)
    return resized
    }
  2. The final codes look like this:

    //create/shape image
    let image = UIImage(named: "TestImage")
    let scaledImage = image?.resizedImageWithinRect(CGSize(width: 100, height: 100))
    let circleImage = scaledImage?.circle

    //create sprite
    let sprite = SKSpriteNode(texture: SKTexture(image: circleImage!))
    sprite.position = CGPoint(x: view.frame.width/2, y: view.frame.height/2)

    //set texture/image
    sprite.texture = SKTexture(image: circleImage!)
    sprite.physicsBody = SKPhysicsBody(texture: SKTexture(image: circleImage!), size: CGSizeMake(100, 100))
    if let physics = sprite.physicsBody {
    //add the physic properties
    }

    //scale node
    sprite.setScale(1.0)
    addChild(sprite)

So if you have a perfectly scaled asset/image, then you probably dont need to do all this work, but I'm getting images from the backend that could come in any sizes.

sprite follow another sprite's path - sprite kit

You don't provide any code to go with your question... You should try to post something, to see how we can do things with what you've got. Anyways...

You should store the first sprite's X and Y position values, many of them in a set of time (a buffer, if you will). Register the sprite's XY positions in an array, where the older positions get deleted and the new positions take their place.

Then assign those positions to the second sprite, with a delay.

Edit:

I'm guessing the x and y positions of the first sprite are the

self.player.position.x, self.player.position.y

so you would store these at regular time intervals in an array, and delete the older positions as you assign them to the second sprite.

This should be very easy to do, provided that you understand the code that you're using.



Related Topics



Leave a reply



Submit