Swift SpriteKit - Gradually increase rotation - ios

I'm experimenting round with #nickfalk's answer on How to rotate sprite in sprite kit with swift on how to rotate a sprite in sprite kit.
How would I adjust this to gradually increase rotation speed up to a maximum, then when the screen is clicked, it gradually slows down and goes in the reverse direction for x amount of time?
Thanks!
Toby.

Ok, the following (slightly messy proof of concept) spins a sprite at a constant speed. Upon tap+hold it gradually slows the rotation to a halt. Ending the touch immediately returns the rotation to full speed.
I've set up a scene with the following properties: var sprite : SKSpriteNode? and var shouldDecelerate = false:
The sprite is set up with the preferred details and have a repeactActionForever-action running a 360 degrees rotation. From here its fairly straightforward:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
shouldDecelerate = true
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
shouldDecelerate = false
sprite?.speed = 1
sprite!.runAction(SKAction.speedTo(sprite!.speed, duration: 1/60))
}
override func update(currentTime: CFTimeInterval) {
if let sprite = sprite {
if sprite.speed > 0 && shouldDecelerate {
let newSpeed = max(sprite.speed - 0.1, 0) // we don't want a negative speed as it will reverse the rotation
sprite.runAction(SKAction.speedTo(newSpeed, duration: 1/60))
}
}
}
If you want a gradual increase in speed you basically just need an if with opposite logic of the one I've included in update() above, oh and you should also remove the sprite?.speed = 1 line in touchesEnded().
If you need to have other move-actions where the speed is not effected by the rotation-speed I suggest you hook the sprite up to an SKNode and let this handle the other actions.

Related

360 degrees spinnable object from a photographed real object

I want to create the ability to spin a photographed object 360 degrees.
It spins endlessly based on the speed you "flick" .
You spin it left or right by flicking the object left or right .
You stop the spin when you touch to stop it if it's spinning.
Similar to the app The Elements by Theodore Grey.
Here's a video of the part of the app I'm trying to recreate. (i.e. the 3D spinner)
https://youtu.be/6T0hE0jGiYY
Here's a video of my finger interacting with it.
https://youtu.be/qjzeewpVN9o
I'm looking to use Swift and likely SpriteKit.
How can I get from a real life object to something high quality and
functional? I'm armed with a Mac , Nikon D810 and a green screen.
I.e I'm guessing that a series of stop motion pictures is the way to
go... but I'm feel that might not be fluid enough.
For the purposes of this question I want to figure out what would make the most sense to program with. E.g. a video I'm rewinding and fast forwarding on
command or a texture atlas of stop motion frames , etc.
Note: Capturing software and photography techniques would be helpful
information as I'm clueless in that department. But, I understand I
can ask that on https://photo.stackexchange.com/ .
What would the basic logic of my code be like for this object? In terms of:
A. The function setting up the object's animation or video or whatever is the best way to have the images prepared for use in my code.
B. The spin() function and
C. The stopSpin() function.
A whole project sample isn't needed (though I guess it'd be nice). But, those 3 functions would be enough to get me going.
Is SpriteKit the wisest choice?
Here is the second draft of my answer that shows off the basic functionality of a simple sprite animation:
class GameScene: SKScene {
// Left spin is ascending indices, right spin is descending indices.
var initialTextures = [SKTexture]()
// Reset then reload this from 0-6 with the correct image sequences from initialTextures:
var nextTextures = [SKTexture]()
var sprite = SKSpriteNode()
// Use gesture recognizer or other means to set how fast the spin should be.
var velocity = TimeInterval(0.1)
enum Direction { case left, right }
func spin(direction: Direction, timePerFrame: TimeInterval) {
nextTextures = []
for _ in 0...6 {
var index = initialTextures.index(of: sprite.texture!)
// Left is ascending, right is descending:
switch direction {
case .left:
if index == (initialTextures.count - 1) { index = 0 } else { index! += 1 }
case .right:
if index == 0 { index = (initialTextures.count - 1) } else { index! -= 1 }
}
let nextTexture = initialTextures[index!]
nextTextures.append(nextTexture)
sprite.texture = nextTexture
}
let action = SKAction.repeatForever(.animate(with: nextTextures, timePerFrame: timePerFrame))
sprite.run(action)
}
override func didMove(to view: SKView) {
removeAllChildren()
// Make our textures for spinning:
for i in 0...6 {
initialTextures.append(SKTexture(imageNamed: "img_\(i)"))
}
nextTextures = initialTextures
sprite.texture = nextTextures.first!
sprite.size = nextTextures.first!.size()
addChild(sprite)
spin(direction: .left, timePerFrame: 0.10)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
spin(direction: .right, timePerFrame: velocity)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
spin(direction: .left, timePerFrame: velocity)
}
}
Right now you just click / release to alternate right left.
Todo for next draft:
- Implement gesture recognizer for velocity
- Implement decay if wanted (so it will slow down over time)
(Old video, new code does not reset frame to 0):
Image assets are found here for the animation:
https://drive.google.com/open?id=0B3OoSBYuhlkgaGRtbERfbHVWb28

Changing SKPhysicsBody while changing SKSpriteNode position

I've got quite simple game. It's like the classic Labyrinth game which uses accelereometer but user is also able to change the size of the rectangle using 3D Touch.
The player's node is rectangle which moves thanks to the world's physics which gets data from the accelerometer (nothing special here):
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data, error) in
if let motionData = data{
self.physicsWorld.gravity = CGVector(dx: motionData.acceleration.x * 7, dy: motionData.acceleration.y * 7)
}
}
and touchesMoved :
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let maxForce = touch.maximumPossibleForce
let force = touch.force
let forceValue = force/maxForce
player.size = CGSize(width: 26-forceValue*16, height: 26-forceValue*16)
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
resetSize()
}
func resetSize(){
player.size = CGSize(width: 26, height: 26)
}
So when I was changing player's size it worked fine: the node was moving around the screen based on device's orientation and while pressing harder it was getting smaller. The problem was that the physicsBody (I've set view.showsPhysics = true in GameViewController.swift) was always the same so I've added player.physicsBody = SKPhysicsBody(rectangleOf: player.size) but now when I'm pressing the screen the player's node resizes but it also stops moving. How to change the node's size and physicsBody simultaneously and keep it in motion?
When you change the physicsBody you lost all physics parameters that invoved your previous physicsBody: you can copy velocity or angularVelocity or other available parameters but it's not enough, you will never have a continuity because the SpriteKit framework (on the actual version) don't have this option: dynamically change the physicsBody during the game.
You can also change the scaling of you node to modify the physicsBody but you always lost physics variables as for example the accumulated speed due to gravity : this happened because SpriteKit change automatically the physicsBody during the scaling to create others bodies without preserve the accumulated properties.
So my advice is to create a kind of "freezing time" when you touch your ball, where you can change you physicsBody when the ball is stopped.

How to create a more natural jump with ease in and ease out?

I'm designing a game where the 'player' has to jump. I have a working jump function, but the jump is very 'linear'. I want the jump to have 'ease in' and 'ease out'.
This means that the first part of the jump should go up faster, while it decreases in speed near the 'top' and should increase in speed while it moves down.
I've tried a few things with:
var jumpStartTime: CGFloat = 0.0
var jumpCurrentTime: CGFloat = 0.0
var currentDuration = CGFloat(currentTime) - jumpStartTime
However, I don't know where and how to start.
This is the functions I have for the linear jump.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if onGround && !gameOver {
if self.initialJumpY == nil {
self.initialJumpY = self.character.position.y
}
if self.character.position.y - self.initialJumpY! < 100 {
self.character.physicsBody?.applyImpulse(CGVectorMake(0, 75))
} else {
self.character.physicsBody?.velocity.dy = 0.0
}
}
self.onGround = false
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
self.character.physicsBody?.velocity.dy = 0.0
self.initialJumpY = nil
}
How can I make a more 'natural', linear jump?
Thanks, guys!
Nick.
First of all a 'linear' jump will never be natural.
You need to apply a negative acceleration to the velocity. The change of the velocity would be linear.
the velocity formula would look like this:
v = v0 - factor * t.
t being the time v0 would be the initial velocity and the factor being something that you try out for a nice effect.
With velocity in meters per second and time in seconds the factor on Earth would be 9.81 for the gravity of earth.

Sprite kit touch and hold with swift

i'm try to make my first game in sprite kit using swift
everything work fine for now but i couldn't know how could handle touch and hold in the screen
i'm try to make a jump power when the player hold the touch but i can't find event for this
thanks ;)
you can try this :
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
// action when the user touches the screen
// you can know where did he touch the screen like this
let touchLocation = touches?.anyObject().locationInView(self/* or your view */)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
// your code here
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
// your code here
}
those method will help cary touches events in your screen
As I understand you need to handle 2 situations
1 - player taps on the screen/node - e.g. to jump
2 - player taps and holds - e.g. to change jump power
I think you need to handle both "touchesBegan" and "touchesEnded".
In "touchesBegan" start special timer and after some delay (e.g. 0.5 secs) start playing special animation for it (jump power indicator that shows current jump power)
on "touchesEnded" - stop timer, stop animation and calculate result jump power (based on timer's value).
If you also need to handle direction (angle) of jump - in this case you should also handle "touchesMoved" and calculate current angle based on touch position.

Move a node to finger using Swift + SpriteKit

UPDATE: I have solved the problem, and figured out a more simplified way to do this then the answer provided. My solution was to make the velocity of the SPACESHIP equal the distance it was from my finger touch. For faster movement, you can multiply this velocity by a constant. In this case, I used 16. I also got rid of setting lastTouch to nil in the touchesEnd event. That way, the ship will still stop even when I release my finger.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if let touch = lastTouch {
myShip.physicsBody.velocity = CGVector(dx: (lastTouch!.x - myShip.position.x) * 16, dy: 0)
}
}
===============================
I have a SPACESHIP node with movement restricted to the X-Axis. When the user PRESSES and HOLDS somewhere on the screen, I want the SPACESHIP to be able to move to the finger's x-coordinate, and not stop moving toward the finger until the finger is RELEASED. If the SPACESHIP is close to the users finger and the users finger is still pressed down, I want it to gradually slow down and stop. I also want this smooth motion to be applied when the SPACESHIP changes direction, starts, and stops.
I am trying to figure out the best way to do this.
So far, I have created the node and it moves correctly, but there is a problem: If I press on the screen and hold down, the ship will eventually cross over my finger and keep moving. This is because the logic to change direction of the ship is only triggered if I move my finger. So essentially, moving my finger over the ship to change the ships' direction works, but if the ship crosses over my still finger, it does't change direction
I need the SPACESHIP node to recognize when it has crossed over my still finger, and either change its direction or stop based on how close it is to my finger.
Here is the relevant code:
Part 1: When the user presses down, find out where the touch is coming from and move myShip (SPACESHIP) accordingly using velocity
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
if (touchLocation.x < myShip.position.x) {
myShip.xVelocity = -200
} else {
myShip.xVelocity = 200
}
}
Part 2 When the user moves their finger, trigger an event that checks to see if the finger has now moved to the other side of the ship. If so, change direction of the ship.
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
//distanceToShip value will eventually be used to figure out when to stop the ship
let xDist: CGFloat = (touchLocation.x - myShip.position.x)
let yDist: CGFloat = (touchLocation.y - myShip.position.y)
let distanceToShip: CGFloat = sqrt((xDist * xDist) + (yDist * yDist))
if (myShip.position.x < touchLocation.x) && (shipLeft == false) {
shipLeft = true
myShip.xVelocity = 200
}
if (myShip.position.x > touchLocation.x) && (shipLeft == true) {
shipLeft = false
myShip.xVelocity = -200
}
}
Part 3 When the user releases their finger from the screen, I want the ship to stop moving.
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
myShip.xVelocity = 0
}
Part 4 Update event that changes the Ship's position
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let rate: CGFloat = 0.5; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
let relativeVelocity: CGVector = CGVector(dx:myShip.xVelocity - myShip.physicsBody.velocity.dx, dy:0);
myShip.physicsBody.velocity = CGVector(dx:myShip.physicsBody.velocity.dx + relativeVelocity.dx*rate, dy:0);
Thanks for reading, and looking forward to a response!
You can save yourself a lot of trouble by using: myShip.physicsBody.applyImpluse(vector). It works by acting as if you gave myShip a push in the direction vector points. If you calculate vector as the x distance from your last touch location to myShip, then it'll accelerate, decelerate, change direction, etc. pretty close to the way you're describing because it'll be giving it little pushes in the right direction on each update.
Basically you store the last touch location then, in your update function, you calculate the CGVector pointing from myShip to lastTouch and apply that as an impulse to your physics body.
Something like:
var lastTouch: CGPoint? = nil
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
// Be sure to clear lastTouch when touches end so that the impulses stop being applies
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
lastTouch = nil
}
override func update(currentTime: CFTimeInterval) {
// Only add an impulse if there's a lastTouch stored
if let touch = lastTouch {
let impulseVector = CGVector(touch.x - myShip.position.x, 0)
// If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
myShip.physicsBody.applyImpluse(impulseVector)
}
}
You'll also probably want to play with the linearDamping and angularDamping values on myShip.physicsBody. They'll help determine how fast myShip accelerates and decelerates.
I maxed out the values at 1.0 in my app:
myShip.physicsBody.linearDamping = 1.0
myShip.physicsBody.angularDamping = 1.0
If myShip doesn't stop fast enough for you, you can also try applying some breaking in your update function:
override func update(currentTime: CFTimeInterval) {
// Only add an impulse if there's a lastTouch stored
if let touch = lastTouch {
let impulseVector = CGVector(touch.x - myShip.position.x, 0)
// If myShip starts moving too fast or too slow, you can multiply impulseVector by a constant or clamp its range
myShip.physicsBody.applyImpluse(impulseVector)
} else if !myShip.physicsBody.resting {
// Adjust the -0.5 constant accordingly
let impulseVector = CGVector(myShip.physicsBody.velocity.dx * -0.5, 0)
myShip.physicsBody.applyImpulse(impulseVector)
}
}
For 2017 here's the easy way to do what is explained in the correct answer here.
There's no need to store the previous position, it is given to you...
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let t: UITouch = touches.first! as UITouch
let l = t.location(in: parent!)
let prev = t.previousLocation(in: parent!)
let delta = (l - prev).vector
physicsBody!.applyImpulse(delta)
}
That's it.
Two notes. (A) properly you should divide the delta distance by the deltaTime to get the correct impulse. If you're a hobbyist really just multiply by "about 100" and you'll be fine. (B) note that of course you will need an extension or function to convert CGPoint to CGVector, it's impossible to do anything without that.
In your thuchesBegan and touchesMoved store the touch location as the "target". In the update then check the position of your ship and reset the xVelocity to 0 if the ship has reached/passed the target.
Since you are only interested in the x coordinate you could also store just touchLocation.x. You can also reverse the velocity but I think that would look strange. Note that if the user moves the finger again, your ship will start moving again because the touchMoved will be triggered again.
On a side note, within touchesMoved you are also setting the shipLeft property but this is not set in your touchesBegan. If this property is used elsewhere you should sync its use.

Resources