How to pass the NSTimer as parameter in function in SpriteKit swift - swift

I have a simple function to move my sprite for predefine time duration. I want that the time duration which i pass in to function as time duration is passing in function parameter as NSTimer. My function is as below!
func moveGooses(){
let path = UIBezierPath()
path.moveToPoint(CGPoint(x:self.parentScene.frame.width*0.99 , y: self.parentScene.frame.height*0.90))
path.addCurveToPoint(CGPoint( x:self.parentScene.frame.width*0.01 , y: self.parentScene.frame.height*0.90), controlPoint1: CGPoint(x: self.parentScene.frame.width*0.01, y: self.parentScene.frame.height*0.90) , controlPoint2: CGPoint(x: self.parentScene.frame.width*0.01 , y: self.parentScene.frame.height*0.90))
let anim = SKAction.followPath(path.CGPath, asOffset: false, orientToPath: false, duration: 3.0)
let seq = SKAction.sequence([anim])
let removeAction = SKAction.runBlock{
let myGameScene = self.parentScene as GameScene
self.removeFromParent()
}
self.runAction(SKAction.sequence([seq,removeAction]))
}

Your question is vague but I believe this is what you're trying to do according to question title:
func timerAsParam(timer: NSTimer) {
//timer is a local nstimer
}

Related

SKEmitterNode: How to start/stop rain at random time?

I added rain to my game as SKEmitterNode particle and I want it to be shown at random time. What kind of function should I use?
Just in case, my code for rain:
func startGame () {
if let rain = SKEmitterNode(fileNamed: "rain.sks") {
rain.position = CGPoint(x: frame.width, y: frame.height)
addChild(rain)
}
....
You can use an action to remove the rain at a random time, for example:
if let rain = SKEmitterNode(fileNamed: "rain.sks") {
rain.position = CGPoint(x: frame.width, y: frame.height)
addChild(rain)
let waitAction = SKAction.wait(forDuration: Double.random(in: 10.0 ... 50.0))
let removeAction = SKAction.removeFromParent()
rain.run(SKAction.sequence([waitAction, removeAction]))
}

How to create realistic spinning wheel in SpriteKit

I am trying to create a spinning fortune wheel action via SKAction. I have a SKNode which used as wheel, this SKNode is a circle that divided to four quarters (each quarter in different color). also I set an SKAction (which is repeating for 10 counts) that spin the SKNode around fixed point (the node's center). The problem is that the action is running well but it stops suddenly and not slowing down - like a real wheel. I don't really have an idea how to set this animation, I mean to slow the spinning down before the action is stop.
Here is my code so far:
class GameScene: SKScene {
let colors = [SKColor.yellow, SKColor.red, SKColor.blue, SKColor.purple]
override func didMove(to view: SKView) {
createWheel()
let sq = CGRect(x: size.width/2, y: size.height/2, width: 300, height: 300)
let sqx = SKShapeNode(rect: sq)
sqx.lineWidth = 2
sqx.fillColor = .clear
sqx.setScale(1.0)
addChild(sqx)
}
func createWheel() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: -200))
path.addArc(withCenter: CGPoint.zero,radius: 200,startAngle: CGFloat(0.0), endAngle: CGFloat(3.0 * Double.pi / 2),clockwise: false)
path.addLine(to: CGPoint(x: 200, y: 0))
let obstacle = obstacleByDuplicatingPath(path, clockwise: true)
obstacle.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(obstacle)
let rotateAction = SKAction.rotate(byAngle: CGFloat((3.0 * CGFloat(Double.pi / 2)) - 90), duration: 0.5)
//obstacle.run(SKAction.repeatForever(rotateAction))
obstacle.run(SKAction.repeat(rotateAction, count: 10))
}
func obstacleByDuplicatingPath(_ path: UIBezierPath, clockwise: Bool) -> SKNode {
let container = SKNode()
var rotationFactor = CGFloat(Double.pi / 2)
if !clockwise {
rotationFactor *= -1
}
for i in 0...3 {
let section = SKShapeNode(path: path.cgPath)
section.fillColor = colors[i]
section.strokeColor = colors[i]
section.zRotation = rotationFactor * CGFloat(i);
let origin = CGPoint(x: 0.0, y: 0.0)
switch i {
case 0:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y - 10))
case 1:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y + 10))
case 2:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y + 10))
case 3:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y - 10))
default:
print("bolbol")
}
container.addChild(section)
}
return container
}
}
edit:
I was thinking about it and I tried to do it via SKAction, I set another action but this time I set their duration to a long one. first it run a action of duration 0.5, then of 2 and at end of 4. I looks pretty good but still not smooth as I want it to be.
here is my code:
let rotateAction = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 0.5)
let rotateAction2 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 2)
let rotateAction3 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 4)
let wait = SKAction.wait(forDuration: 5)
let g1 = SKAction.repeat(rotateAction, count: 10)
let group = SKAction.group([wait, g1, rotateAction2, rotateAction3])
what do you think? there is any way to do it better??
edit 2:
Continued to #Ali Beadle answer, I tried to do it via physics body, the problem now is the when I drag finger on the screen the SKShapeNode (shape) in continue to rotate and never stops. can you detect what is wrong?
class GameScene: SKScene {
var start: CGPoint?
var end:CGPoint?
var startTime: TimeInterval?
let shape = SKShapeNode.init(rectOf: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
let sceneBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
shape.fillColor = SKColor.red
shape.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
shape.physicsBody = SKPhysicsBody.init(rectangleOf: CGSize(width: 50, height: 50))
shape.physicsBody?.affectedByGravity = false
shape.physicsBody?.isDynamic = true
addChild(shape)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.start = touch.location(in: self)
self.startTime = touch.timestamp
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.end = touch.location(in: self)
var dx = ((self.end?.x)! - (self.start?.x)!)
var dy = ((self.end?.y)! - (self.start?.y)!)
let magnitude:CGFloat = sqrt(dx*dx+dy*dy)
if(magnitude >= 25){
let dt:CGFloat = CGFloat(touch.timestamp - self.startTime!)
if dt > 0.1 {
let speed = magnitude / dt
dx = dx / magnitude
dy = dy / magnitude
print("dx: \(dx), dy: \(dy), speed: \(speed) ")
}
}
let touchPosition = touch.location(in: self)
if touchPosition.x < (self.frame.width / 2) {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(-180)
} else {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(180)
}
}}
I have created an open source prize spinning wheel in Spritekit that uses physics for realistic movement and flapper control. It also allows the user to drag the wheel to spin or generates a random spin by pushing the center of the wheel.
https://github.com/hsilived/SpinWheel
You can add realistic movement like this by using the built-in Physics simulation of SpriteKit. This will allow you to give your wheel a mass and friction and then use forces to rotate it. It will then slow down realistically.
In outline see Simulating Physics in the Apple Documentation:
To use physics in your game, you need to:
Attach physics bodies to nodes in the node tree and configure their physical properties. See SKPhysicsBody.
Define global characteristics of the scene’s physics simulation, such as gravity. See SKPhysicsWorld.
Where necessary to support your gameplay, set the velocity of physics bodies in the scene or apply forces or impulses to them. ...
The most appropriate method for your wheel is probably to make the wheel pinned to the scene and then rotate it with applyAngularImpulse.

How to reference the node from within a custom SKAction Swift

I can create a custom SKAction as follows:
extension SKAction {
class func move(from: CGPoint, halfWayTo: CGPoint, duration: TimeInterval) -> SKAction {
let midPoint = CGPoint(x: (from.x + halfWayTo.x)/2, y: (from.y + halfWayTo.y)/2 )
return SKAction.move(to: midPoint, duration: duration)
}
}
which I then use like this:
//Setup
let node = SKNode()
node.position = CGPoint(x: 50, y: 50)
let destination = CGPoint(x: 100, y: 100)
//Move from node's current position half way towards the destination
let action = SKAction.move(from: node.position, halfWayTo: destination, duration: 1)
node.run(action)
but in keeping with the standard:
SKAction.move(to: destination, duration: 1)
I would rather be able to say:
let action = SKAction.move(halfWayTo: destination, duration: 1)
node.run(action)
but I don't know how to refer to 'node' from inside the custom SKAction so that I can get it's position and calculate the halfway point?
You use self.
extension SKAction {
class func move(halfWayTo: CGPoint, duration: TimeInterval) -> SKAction {
let from = self.position
let midPoint = CGPoint(x: (from.x + halfWayTo.x)/2, y: (from.y + halfWayTo.y)/2 )
return SKAction.move(to: midPoint, duration: duration)
}
}
Then call with (as you said)
node.move(halfWayTo: B, duration: 1)
I'm still not sure whether the SKAction has a reference to the node it is applied to - even when it's running - or even if it does I'm not sure whether there's a way to access it. If anyone knows that it does please let me know.
I've thought of one approach as follows:
Instead of extending SKAction, extend SKNode as follows:
extension SKNode {
func move(halfWayTo: CGPoint, duration: TimeInterval) -> SKAction {
let from = self.position
let midPoint = CGPoint(x: (from.x + halfWayTo.x)/2, y: (from.y + halfWayTo.y)/2 )
return SKAction.move(to: midPoint, duration: duration)
}
}
and then use as follows:
//Setup
let node = SKNode()
node.position = CGPoint(x: 50, y: 50)
let destination = CGPoint(x: 100, y: 100)
//Move from node's current position half way towards the destination
let action2 = node.move(halfWayTo: destination, duration: 1)
node.run(action2)

Is it possible to run code inside of an SKAction sequence that isn't an SKAction?

I have this SKAction sequence:
func move(){
let recursive = SKAction.sequence([
SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.runBlock({self.move()})])
doc.runAction(recursive, withKey: "move")
}
When this part below runs, I want to change the texture property of my node, but I can't figure out how to add that into the SKAction sequence.
SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber()))
Can you add another runBlock call?
SKAction.runBlock(
{
//change the texture and whatever else you need here...
doc.texture = someNewTexture;
})

Randomizing node movement duration

I am making a game in SpriteKit and I have a node that is moving back and forth across the screen and repeating using the code:
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: 1.5)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: 1.5)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let moveBackAndForth = SKAction.repeatActionForever(SKAction.sequence([texRight, moveRight, texLeft, moveLeft,]))
Drake1.runAction(moveBackAndForth)
I am trying to figure out what method I can use to randomize the duration. Every time moveBackandForth runs, I want it to rerun using a different duration, (within games, not between). If someone could give me some example code to try I would really appreciate it.
Also arc4Random works fine, but it doesn't randomize within the game, only between games.
When you run actions like from your example and randomize duration parameter with something like arc4Random this is actually happening:
Random duration is set and stored in action.
Then action is reused in a sequence with a given duration.
Because the action is reused as it is, duration parameter remains the same over time and moving speed is not randomized.
One way to solve this (which I prefer personally) would be to create a "recursive action", or it is better to say, to create a method to run desired sequence and to call it recursively like this :
import SpriteKit
class GameScene: SKScene {
let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))
override func didMoveToView(view: SKView) {
shape.position = CGPointMake(CGRectGetMidX(self.frame) , CGRectGetMidY(self.frame)+60 )
self.addChild(shape)
move()
}
func randomNumber() ->UInt32{
var time = arc4random_uniform(3) + 1
println(time)
return time
}
func move(){
let recursive = SKAction.sequence([
SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.runBlock({self.move()})])
shape.runAction(recursive, withKey: "move")
}
}
To stop the action, you remove its key ("move").
I don't have any project where I can try right now.
But you might want to try this :
let action = [SKAction runBlock:^{
double randTime = 1.5; // do your arc4random here instead of fixed value
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: randTime)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: randTime)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let sequence = SKAction.sequence([texRight, moveRight, texLeft, moveLeft])
Drake1.runAction(sequence)
}]; 
let repeatAction = SKAction.repeatActionForever(action)
Drake1.runAction(repeatAction)
Let me know if it helped.