Updating parameter of an SKAction that is repeating forever swift - ios

I have a function that animates a ball in a game that I am designing right now. However, I want the animation speed to change with the balls actual velocity, which I have achieved but only after each iteration of the animation. That makes it come out a little choppy, I am looking for a more elegant solution that could update the timePerFrame argument mid-action. I originally had it set to repeatforever but realized that timePerFrame wouldn't update once the action had started. Is there a way to make this animation speed change more smoothly?
func animBall() {
//This is the general runAction method to make our ball change color
var animSpeedNow = self.updateAnimSpeed()
println(animSpeedNow)
var animBallAction = SKAction.animateWithTextures(ballColorFrames, timePerFrame: animSpeedNow,resize: false, restore: true)
self.runAction(animBallAction, completion: {() -> Void in
self.animBall()
})
}
func updateAnimSpeed() -> NSTimeInterval{
// Update the animation speed based on the velocity to syncrhonize animation with ball velocity
var velocX = self.physicsBody!.velocity.dx
var velocY = self.physicsBody!.velocity.dy
if abs(velocX) > 0 || abs(velocY) > 0 {
var veloc = sqrt(velocX*velocX + velocY*velocY)
var animSpeedNow: NSTimeInterval = NSTimeInterval(35/veloc)
let minAS = NSTimeInterval(0.017)
let maxAS = NSTimeInterval(0.190)
if animSpeedNow < minAS {
return maxAS
}
else if animSpeedNow > maxAS {
return minAS
}
else {
return animSpeedNow
}
}
else {
return NSTimeInterval(0.15)
}
}
If it isn't possible to directly manipulate a parameter of an SKAction that is running forever, I supposed I

In your case, a simpler way would be to remove the old animation action and replace it with a new one.

Related

How to properly get a gesture recognizer to fluidly spin a sprite according to velocity

I've a Sprite that animates via texture array, with spinLeft being increasing the array index, and spinRight decreasing the index, as shown in the answer to my other question here:
https://stackoverflow.com/a/44792902/6593818
The animation is working fine, but now I am trying to control the animation via a gesture recognizer or some other form of input.
In the referenced question, the spin function has an input for speed (the TimeInterval), so I just need an output from a gesture recognizer:
I want to figure out what to do in terms of getting the object to react to my finger in the same way as shown in this video:
https://youtu.be/qjzeewpVN9o
The video is beyond just a basic UIPanGesture reading velocity, I think. At least, I'm not sure how to implement it.
This project is to showcase a friend's sculptures via photography.
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)
}

CCActionSequence/CCActionDelay not delaying each action?

I have a function which creates a CCSprite and moves it across the screen:
func fireWeapon(target: CGPoint) {
let projectile = CCBReader.load("Projectile") as! CCSprite
projectile.position = player.position;
self.addChild(projectile);
let moveAction = CCActionMoveTo(duration: 1, position: target);
let delayAction = CCActionDelay(duration: 1);
let removeAction = CCActionCallBlock(projectile.removeFromParentAndCleanup(true));
projectile.runAction(CCActionSequence(array: [moveAction, delayAction, removeAction]));
}
I'm trying to clean up the sprites after they finish their movement action by running removeFromParentAndCleanup() in sequence with the move action. However, each action is firing instantly after each other in the sequence with no delays. The sprites are cleaned up instantly after being created. Why aren't the delays working? I've tried with and without the CCDelay action and I get the same result.
Solved my own problem. Turns out that I was using the wrong syntax for CCActionCallBlock(), you have to actually encase your block of code within a void function, like so:
func fireWeapon(target: CGPoint) {
let projectile = CCBReader.load("Projectile") as CCNode
projectile.position = player.position;
self.addChild(projectile);
let moveAction = CCActionMoveTo(duration: 1, position: target);
let delayAction = CCActionDelay(duration: 3);
let removeAction = CCActionCallBlock { () -> Void in
projectile.removeFromParentAndCleanup(true);
}
projectile.runAction(CCActionSequence(array: [moveAction, delayAction, removeAction]));
}
Hopefully this helps out a lot of people, because I saw lots of with this problem and they were never presented with a solution.

How to update waitForDuration constant?

I have a flashing light that uses a SKAction sequence that hides and unhides a circle node. I want to be able to change the intervals at which it flashes based on two buttons. I declared a variable stdTime and I change it in the touchesBegan method but it's not working. What am I missing?
my didMoveToView:
let blink = SKAction.sequence([
SKAction.waitForDuration(stdTime),
SKAction.hide(),
SKAction.waitForDuration(stdTime),
SKAction.unhide()])
let blinkForever = SKAction.repeatActionForever(blink)
metronome!.runAction(blinkForever)
and my touchesBegan:
if upArrow!.containsPoint(location) {
stdTime = stdTime + 0.1
println("here: \(stdTime)")
}
waitForDuration takes in a NSTimeInterval and not a variable. So it takes whatever time that variable was set to at creation and does not refer back to the variable you used.
Depending on the result you are looking for this might help.
func startBlink(){
let blink = SKAction.sequence([
SKAction.waitForDuration(stdTime),
SKAction.hide(),
SKAction.waitForDuration(stdTime),
SKAction.unhide()])
let blinkForever = SKAction.repeatActionForever(blink)
metronome!.removeActionForKey("blink")
metronome!.runAction(blinkForever, withKey: "blink")
}
if upArrow!.containsPoint(location) {
stdTime = stdTime + 0.1
startBlink()
println("here: \(stdTime)")
}
Another option without interrupting the sequence is to do something like this
func startBlink(){
let blink = SKAction.sequence([
SKAction.waitForDuration(stdTime),
SKAction.hide(),
SKAction.waitForDuration(stdTime),
SKAction.unhide(),
SKAction.runBlock( {
self.startBlink()
})])
metronome?.runAction(blink, withKey: "blink")
}
if upArrow!.containsPoint(location) {
stdTime = stdTime + 0.1
println("here: \(stdTime)")
}
And recursively call the method on your own. That way each time it hits the end it will take the updated stdTime.

Action not working correctly in SpriteKit

I'm new to iOS programing and I'm experimenting to learn trying to create a game in swift using Sprite Kit.
What I'm trying to achieve is having a constant flow of blocks being created and moving rightwards on the screen.
I start by creating a set which contains all the initial blocks, then an action "constant movement" is added to each one, which makes them move slowly to the right. What I'm having trouble is adding new blocks to the screen.
The last column of blocks has an "isLast" boolean set to true, when it passes a certain threshold it is supposed to switch to false and add a new column of blocks to the set which now have "isLast" set to true.
Each block in the set has the "constantMovement" action added which makes them move slowly to the right, the new blocks have it added as well, but they don't work as the original ones.
Not all of the move, even tho if I print "hasActions()" it says they do, and the ones that do move stop doing so when they get to the middle of the screen. I have no idea why this happens, can somebody experienced give me a hint please?
This is the update function:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let constantMovement = SKAction.moveByX(-1, y: 0, duration: 10);
background.runAction(SKAction.repeatActionForever(constantMovement));
let removeBlock = SKAction.removeFromParent();
let frame = self.frame;
var currentBlockSprite:SKSpriteNode;
var newBlock: Block;
for block in blocks {
currentBlockSprite = block.sprite!;
currentBlockSprite.runAction(constantMovement);
if(block.column == NumColumns - 1) {
block.isLast = true;
}
if(block.isNew) {
println("position \(currentBlockSprite.position.x) has actions \(currentBlockSprite.hasActions())");
}
if(block.isLast && currentBlockSprite.position.x < frame.maxX - 50) {
println("the block that hits is " + block.description);
println("HITS AT \(currentBlockSprite.position.x)");
block.isLast = false;
for row in 0..<NumRows {
newBlock = Block(column: NumColumns - 1, row: row, blockType: BlockType.random(), isLast: true, isNew: true);
blocks.addElement(newBlock);
addBlockSprite(newBlock);
println("new block: " + newBlock.description + "position \(newBlock.sprite?.position.x)");
}
}
if(currentBlockSprite.position.x < frame.minX) {
currentBlockSprite.runAction(removeBlock);
blocks.removeElement(block);
}
}
}
My whole project is in here: https://github.com/thanniaB/JumpingGame/tree/master/Experimenting
but keep in mind that since I'm new to this it might be full of cringeworthy bad practices.
I would remove any SKAction code from the update function as that's kind of a bad idea. Instead I would just apply the SKAction when you add your block sprite to the scene, like this.
func addBlockSprite(block: Block) {
let blockSprite = SKSpriteNode(imageNamed: "block");
blockSprite.position = pointForColumn(block.column, row:block.row);
if(block.blockType != BlockType.Empty) {
addChild(blockSprite);
let constantMovement = SKAction.moveByX(-10, y: 0, duration: 1)
var currentBlockSprite:SKSpriteNode
let checkPosition = SKAction.runBlock({ () -> Void in
if(blockSprite.position.x < -512){
blockSprite.removeAllActions()
blockSprite.removeFromParent()
}
})
let movementSequence = SKAction.sequence([constantMovement, checkPosition])
let constantlyCheckPosition = SKAction.repeatActionForever(movementSequence)
blockSprite.runAction(constantlyCheckPosition)
}
block.sprite = blockSprite;
}
That would then allow you to simply add a new block whenever you see fit and it will have the appropriate action when it's added.
I've used 512 as thats the size of the iPhone 5 screen but you could swap this out for another screen size or what would be better would be a variable that dynamically reflects the screen size.

Modify a repeating task sequence while running

I am trying to make a repeating task where I can change the delay in which it repeats. Here is the current code I am using:
var actionwait = SKAction.waitForDuration(self.wait)
var actionrun = SKAction.runBlock({
self.count+=1
if (self.count % 2 == 0 && self.wait > 0.2){
self.wait -= 0.1
actionwait.duration = self.wait
}
for node in self.children{
if (node.isMemberOfClass(Dot)){
node.removeFromParent()
}
}
var radius = CGFloat(arc4random_uniform(100) + 30)
var newNode = Dot(circleOfRadius: radius)
var color = self.getRandomColor()
newNode.fillColor = color
newNode.strokeColor = color
newNode.yScale = 1.0
newNode.xScale = 2.0
newNode.userInteractionEnabled = true
newNode.setScene(self)
newNode.position = newNode.randomPos(self.view!)
self.addChild(newNode)
})
self.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
However, it appears that because the sequence is already repeating, changing the duration of the delay does not effect anything.
when you create actionwait it's going to save the values inside that closure and keep using them in your repeatActionForever
When you're tracking changes it's best to do that sort of thing in the update method. Using actions in this case might not be the best approach.
I'm not sure about when you (for your game) need to be checking for changes. For most things using the update method is adequate
heres one way i implement a timer in update
// MY TIMER PROPERTIES IN MY CLASS
var missleTimer:NSTimeInterval = NSTimeInterval(2)
var missleInterval:NSTimeInterval = NSTimeInterval(2)
// IN MY UPDATE METHOD
self.missleTimer -= self.delta
if self.missleTimer <= 0 { // TIMER HIT ZERO, DO SOMETHING!
self.launchMissle() // LAUNCH MY MISSLE
self.missleTimer = self.missleInterval // OKAY LETS RESET THE TIMER
}
delta is the difference of time between this frame and the last frame. It's used create fluid motion, and track time. Things like that. This is pretty standard among spritekit projects. you usually need to use delta projectwide
declare these two properties:
// time values
var delta:NSTimeInterval = NSTimeInterval(0)
var last_update_time:NSTimeInterval = NSTimeInterval(0)
This should be how the top of your update method looks
func update(currentTime: NSTimeInterval) {
if self.last_update_time == 0.0
self.delta = 0
} else {
self.delta = currentTime - self.last_update_time
}
self.last_update_time = currentTime

Resources