How do you apply one action to multiple Sprites in SpriteKit? - swift

If I've got a dude at the top of my screen, and a dude who comes in later at the bottom, and I want them both to move to the left of the screen using my moveToLeftOfScreen action I've created, how do I do that?
If I've got var moveToLeftOfScreen = SKAction.moveTo(CGPoint(x:blhabhla, y:bhlahbla, duration: 3)) that won't allow something to move to the left in a straight line from where it spawns.
Long story short: Can you make a single action that says "Move to the left until you hit the screen, then move back to the right until you hit the screeN" and apply that to things spawning in at different locations at different times and then just apply that action when needed throughout your code instead of writing a different action for every single height.

Contrary to sangony, I would have thought this is very possible, though I may be missing something.
Could you not create a reference to an SKAction that is a sequence of move to X=0 and move to X=size.width? and repeat if forever.
I'm not near my mac so I cant get the syntax perfect in XCode so apologies for any errors. Also this assumes that the scene will create this reference, hence the self.size.width:
let moveLeft = SKAction.moveToX(0, duration: 3)
let moveRight = SKAction.moveToX(self.size.width, duration: 3)
let moveLeftThenRight = SKAction.sequence([moveLeft, moveRight])
let continousMovement = SKAction.repeatActionForever(moveLeftThenRight)
Apply that final action to any node that spawns and you should be ok if I understand you correctly.
There will be an issue with the first movement being slow as it will always take 3 seconds to move to the left regardless of where the node spawns. This can be fixed by getting the fraction of the distance it needs to move and multiplying it by 3:
let firstMovementDuration = (node.position.x / self.size.width) * 3
let firstMoveLeft = SKAction.moveToX(0, duration: firstMovementDuration)
And from there continue with normal movement. There's also the timingMode to consider for more natural movement such as .EaseInEaseOut.

Related

Which position value changes when you use an SKAction on an SKSpriteNode? And how would I account for the position changes?

I'm trying to use an SKAction to move my sprites. However, I noticed that after moving the sprites, their position values still remained the same. With some research, I learned that (correct me if I'm wrong) the sprites' position values are actually their location relative to the parent view, and that it was the parent view's position value that had actually changed, and not the sprites'. Only when I checked, the parent view's position value also hadn't changed.
So which position value actually changes?? And how would I go about accounting for the change in position when accessing the sprites' position values? (Since a lot of my game's logic is dependent on their position values)
Here's my code:
let fall = SKAction.moveBy(x: 0, y: -85, duration: 0.2)
print("Original position \(gift.position)")
gift.run(fall)
print("New position \(gift.position)") // Both print the same value
Any help would be greatly appreciated!
Your gift.run(fall) is scheduling an action that will happen over the course of 0.2 seconds, not waiting for it to finish. At 60 frames/second, that means the sprite will reach its final position only after 12 frames. If you print the gift's position at each step in update(_:), you'll see that it is indeed changing.
Since the run does not wait for the action to complete, the sprite hasn't had a chance to move at all when the second print is executed.
If you want something to happen only after an action completes, you have various possibilities. A common way is to add a completion block; see Apple's documentation on getting started with actions: https://developer.apple.com/documentation/spritekit/getting_started_with_actions. Or if you want the same node to do multiple things in order, you can make a composite SKAction, like with the sequence or repeat initializers.

How to push dynamic object with same speed using UIPushBehavior?

I'm not a physics expert. However, I want to move UILable which has a dynamic height (depends on content) just like teleprompter. But when I start behaviour with magnitude 10, it starts moving but suddenly its slow down and I want continuous move up at the same speed.
Below is my code :
push = UIPushBehavior(items: [lblText], mode: .instantaneous)
push.setAngle(-.pi/2, magnitude: 10)
animator.addBehavior(push)
lblText size is 375*1500
Josh is right, try adding friction and resistance.
To add friction, you will have to create UIDynamicItemBehavior
let behavior = UIDynamicItemBehavior.init(items: [lblText])
Create this with the items that you need to perform your animations on.
Then you can add friction and resistance to it
behavior.friction = 0
behavior.resistance = 0
And finally add the behavior to the animator
animator.addBehavior(behavior)
Let me know if this works, happy to help.

Random Downwards Motion of SKSpriteNode with Constraints in Swift

I am making a small SpriteKit game in Swift playgrounds and it is an asteroid defence type of game. Basically,the player is defending the spacecraft with a cannon and has to destroy approaching rocks. The image link below is my scene without the gun added yet, but basically I want the rocks to move slowly down from the top towards the spacecraft at the bottom (Sorry, it's too big to add in here).
https://www.dropbox.com/s/pisgazar4jaxra0/Screenshot%202017-03-18%2011.23.17.png?dl=0
The rocks will be SKSpriteNodes and I want to speed them up for each successive rock to increase difficulty over time.
How would I be able to implement this. I have seen some answers elsewhere about using UIBezierPaths but how do I make it random but with the constraint that the rocks only fall from above?
Thanks in advance!
If you only need them to go from top to bottom without any special curvy trajectory then you don't need to deal with splines.
Let's address the gradual acceleration first. You want them to fall slowly in the beginning and speed up during game progress. One approach would be to do track level time and do some duration math based on elapsed level time. An easier one would be to decrease the duration of time taken by the asteroid to reach the bottom. In your scene declaration section, declare those:
fileprivate let startingDuration: TimeInterval = 6.0 // Put here whatever value you feel fit for the level start falling speed
fileprivate let difficultyIncrease: TimeInterval = 0.05 // Put here whatever value you feel fit for how much should each next asteroid accelerate
fileprivate let collisionPosition = CGPoint(x: 300, y: 200) // Put here the onscreen location where the asteroids should be heading to
fileprivate var asteroidCounter = 0 // Increase this counter every time you spawn a new asteroid
Now, let's get down to the actual asteroid movement. There are different ways to approach it. The easiest one and recommended by Apple is to use an action for that. Add this code to your asteroid spawn method:
// Implement here your spawning code ...
// It's best to choose random starting points (X axis) a bit higher than the upper screen boundary
let fallingDuration: TimeInterval = startingDuration - difficultyIncrease * TimeInterval(asteroidCounter)
let fallingAction = SKAction.move(to: collisionPosition, duration: fallingDuration)
asteroid.runAction(fallingAction)
If you need a function for random integer generation, try this:
func RandomInt(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max-(min-1)))) + min
}

SKSprite.addChild at location

In Swift, I have some code like this:
let wand = SKSpriteNode(imageNamed: "Wand")
let item = SKSpriteNode(imageNamed: "Item")
...
func didBeginContact(contact: SKPhysicsContact) {
contact.bodyB.node!.removeAllActions()
var imageToAdd = contact.bodyB.node?.name
wand.addChild(SKSpriteNode(imageNamed: imageToAdd!))
}
What I want is when a certain type of item touches the wand, I want to attach it to the wand. The bitmap masks are set and all of that works perfectly. But, when addChild adds the image, it's in the center of the wand and I want to control where it gets added.
Additional information. I'm using physicsBody to animate the wand and the items. The wand is controlled by the user touching the screen. I tried a few ways to connect the two objects, SKPhysicsJointFixed.jointWithBodyA and SKPhysicsJointSliding.jointWithBodyA but couldn't get it to work the way I wanted. I think joining the two physics bodies is overkill for what I'm doing, but I could be wrong. My real goal is:
1) to add (or join) one sprite (the wand) to another sprite (an item)
2) allow the wand to continue to be controlled (animated) by the user touch
3) have control over the relative position of the item in relation to some point on the wand.
It's the third item that is tough. Any help is appreciated, let me know if you need to see more code.
Your First Question:
The default position of the SKSpriteNode is (0,0) so if you add it to a parent node, the SKSpriteNode will thus be in the middle of the parent node's coordinate system. If you want to add the image a bit higher on the wand, lets say 100 points up, you just say imageToAdd.position = CGPointMake(0,100). The child node will remain attached at that location. Another option of course is to create a joint, which isn't overkill at all if you want the wand and the attachment to have physics properties.
Your Second Question:
As long as you don't remove the physics body from the wand, it will still be there and could be controlled by the user.
Your Third Question:
The attached item will have a certain position relative to the wand since you created it as a child node. If you need to move the item you would create an SKAction. For example, if you needed to move the item down the wand, you could say:
moveDownWand = SKAction.moveByX(0.0, y: pointsToMoveDownBy, duration: someDuration)
attachedItem.runAction(moveDownWand)
Hope this helps, let me know if you have questions.
Thanks #M321K, you put me on the right track! Here is what I did:
var nodeToAdd = SKSpriteNode(imageNamed: imageToAdd!)
nodeToAdd.position = CGPointMake(0, -40)
wand.addChild(nodeToAdd)
I had to create the sprite and set it's position before adding it to the wand. Huge help, thanks much.

Swift (iOS8) SpriteKit waiting for an animation to complete inside a running loop before moving onto the next?

When inside a loop in swift, how can I ensure that an animation completes first before the next iteration and therefore new animation call occurs?
Apologies that I may be covering old ground here with a question similar to another that used Objective-C, rather than swift. I just wanted to make my own query as I've pretty much started off in Swift and have not found a lot of clear help on this issue.
Below is some simple code to help demonstrate my question. The function moveAlong simply takes an array of CGPoints which I've hard coded as below:
let point1 = CGPointMake(100, 100)
let point2 = CGPointMake(100, 400)
let point3 = CGPointMake(400, 400)
array.append(point1)
array.append(point2)
array.append(point3)
moveAlong(array) //calls the function passing in our points
func moveAlong(thesePoints:[CGPoint]) {
var length = thesePoints.count //capture the length of array
//for every point in the array
for i in 0..<length {
//plug point from the array into our move action here
let move = SKAction.moveTo(thesePoints[i], duration: 1)
let spinAction = SKAction.rotateByAngle(CGFloat(2*M_PI), duration:1)
//both above actions are sequenced together
let sequence = SKAction.sequence([move,spinAction])
//run the sequence on sprite
sprite.runAction(sequence)
}
}
The current behaviour of this code is a kind of weird looking hybrid of all three actions moving the sprite to the final position at point3 followed by all 3 of the spin actions (Not too sure how sprite kit is dealing with what appears to be 3 animation calls on the same sprite, at the same time?). Not quite the behaviour I'm after..
What I'm looking for is a way to make the each animation call execute in entirety before moving onto the next. So, hopefully this will make the sprite move to each point, perform a spin and then continue onto the next animation. Obviously the important part with this example is that must happen within a loop.
I clearly have a lot to learn here so any help would be appreciated.
Thanks in advance, Matt
Hint : instead of running your sequence action directly, store it in an array. After the for loop ended, your array should contains 3 actions in that case. You can now create another sequence action to run these 3 actions.