How to update waitForDuration constant? - ios

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.

Related

How to properly set the CABasicAnimation (begin) time?

I animate the color of CAShapeLayers stored in an Array using CABasicAnimation. The animation displays erratically depending on the animation.duration and I cannot figure out why. I suspect an issue with animation.beginTime = CACurrentMediaTime() + delay
Animation Description
The animation consists in successively flashing shapes to yellow before turning them to black once the animation ends.
Current State of the animation
When the animation duration is above a certain time, it works properly.
For instance with a duration of 2 seconds:
But when I shorten the duration, the result substantially differs.
For instance, with a duration of 1 second:
You will notice that the animation has already cached/ended for the first 10 bars or so, then waits and starts animating the remainder of the shapes.
Likewise, with a duration of 0.5s:
In this case, it seems an even larger number of animation has already ended (shapes are black) before it displays some animation after a certain time. You can also notice that although the shape color animation is supposed to last the same duration (0.5s) some feels quicker than others.
The Code
The animation is called in the viewDidAppear method of the UIViewController class.
I have created a UIView custom class to draw my shapes and I animate them using an extension of the class.
The code to animate the color:
enum ColorAnimation{
case continuousSwap
case continousWithNewColor(color: UIColor)
case randomSwap
case randomWithNewColor(color: UIColor)
case randomFromUsedColors
}
func animateColors(for duration: Double,_ animationType: ColorAnimation, colorChangeDuration swapColorDuration: Double){
guard abs(swapColorDuration) != Double.infinity else {
print("Error in defining the shape color change duration")
return
}
let animDuration = abs(duration)
let swapDuration = abs(swapColorDuration)
let numberOfSwaps = Int(animDuration / min(swapDuration, animDuration))
switch animationType {
case .continousWithNewColor(color: let value):
var fullAnimation = [CABasicAnimation]()
for i in (0...numberOfSwaps) {
let index = i % (self.pLayers.count)
let fromValue = pLayers[index].pattern.color
let delay = Double(i) * swapDuration / 3
let anim = colorAnimation(for: swapDuration, fromColor: value, toColor: fromValue, startAfter: delay)
fullAnimation.append(anim)
}
for i in (0...numberOfSwaps) {
CATransaction.begin()
let index = i % (self.pLayers.count)
CATransaction.setCompletionBlock {
self.pLayers[index].shapeLayer.fillColor = UIColor.black.cgColor
}
pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape")
CATransaction.commit()
}
default:
()
}
}
The segment the whole duration of the animation by the duration of the color change (e.g. if the whole animation is 10s and each shape changes color in 1s, it means 10 shapes will change color).
I then create the CABasicaAnimation objects using the method colorAnimation(for: fromColor, toColor, startAfter:).
func colorAnimation(for duration: TimeInterval, fromColor: UIColor, toColor: UIColor, reverse: Bool = false, startAfter delay: TimeInterval) -> CABasicAnimation {
let anim = CABasicAnimation(keyPath: "fillColor")
anim.fromValue = fromColor.cgColor
anim.toValue = toColor.cgColor
anim.duration = duration
anim.autoreverses = reverse
anim.beginTime = CACurrentMediaTime() + delay
return anim
}
Finally I add the animation to the adequate CAShapeLayer.
The code can obviously be optimized but I chose to proceed by these steps to try to find why it was not working properly.
Attempts so far
So far, I have tried:
with and without setting the animation.beginTime in the colorAnimation method, including with and without CACurrentMediaTime(): if I don't set the animation.beginTime with CACurrentMediaTime, I simply do not see any animation.
with and without pointing animation.delegate = self: it did not change anything.
using DispatchQueue (store the animations in global and run it in main) and as suspected, the shapes did not animate.
I suspect something is not working properly with the beginTime but it might not be the case, or only this because even when the shapes animate, the shape animation duration seems to vary whilst it should not.
Thank very much in advance to have a look to this issue. Any thoughts are welcome even if it seems far-fetched it can open to new ways to address this!
Best,
Actually there is a relationship between duration and swapColorDuration
func animateColors(for duration: Double,_ animationType: ColorAnimation, colorChangeDuration swapColorDuration: Double)
when you call it, you may need to keep this relationship
let colorChangeDuration: TimeInterval = 0.5
animateColors(for: colorChangeDuration * TimeInterval(pLayers.count), .continousWithNewColor(color: UIColor.black), colorChangeDuration: colorChangeDuration)
Also here :
let numberOfSwaps = Int(animDuration / min(swapDuration, animDuration)) - 1
This value maybe a little higher than you need.
or
The problem lies in this let index = i % (self.pLayers.count)
if numberOfSwaps > self.pLayers.count, some bands will be double animations.
let numberOfSwaps1 = Int(animDuration / min(swapDuration, animDuration))
let numberOfSwaps = min(numberOfSwaps1, self.pLayers.count)
in the rest is
for i in (0..<numberOfSwaps) {... }
Now if numberOfSwaps < self.pLayers.count. It's not finished.
if numberOfSwaps is larger, It is fine.
If double animations are required, changes the following:
pLayers[index].shapeLayer.add(fullAnimation[i], forKey: nil)
or pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape" + String(i))

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.

Updating parameter of an SKAction that is repeating forever swift

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.

Need help changing speed, direction and spawn time of SKSpriteNode called in didMoveToView

sorry for the wall of text, I just wanted to give a good explanation of how my game is set up so you can understand my problem better.
I have a few questions about the game that I am currently making using swift.
I am making a game where a player has to dodge enemies. The player is an SKSpriteNode image which gets it’s physics body and is added to the scene here:
override func didMoveToView(view: SKView) {
player.position = CGPoint(x: size.width/2, y: size.height/2)
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.dynamic = true
player.physicsBody?.categoryBitMask = PhysicsCategory.Player
player.physicsBody?.contactTestBitMask = PhysicsCategory.Enemy
player.physicsBody?.collisionBitMask = PhysicsCategory.None
player.physicsBody?.usesPreciseCollisionDetection = true
addChild(player)
}
This implementation seems to work great, I have collision working between the Player and Enemy.
The enemies are initialised as SkSpriteNodes too:
func addEnemy() {
let enemy = SKSpriteNode(imageNamed: “enemy”)
enemy.name = “enemy”
enemy.position = CGPoint(x: size.height/2, y: size.width/2)
self.addChild(enemy)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.width/2.75)
enemy.physicsBody?.dynamic = true
enemy.physicsBody?.categoryBitMask = PhysicsCategory.Enemy
enemy.physicsBody?.contactTestBitMask = PhysicsCategory.Player
enemy.physicsBody?.collisionBitMask = PhysicsCategory.None
// code for making the enemies move here (including speed)
}
enemy.runAction(SKAction.sequence([moveAction,removeAction]))
}
and are constantly spawned every 0.7 seconds in didMoveToView(view: SKView) like this:
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(0.7)
])
))
So my question is as follows. Is this the most appropriate way to do what I am trying to achieve? That is; spawning multiple different types of enemies constantly at different intervals?
I am struggling to figure out how I could (for example), make the enemy start spawning only after 10 seconds has passed, or how I could change the direction of the enemies with the press of a UIButton on screen? It seems since they are all called and created when the view loads, I can’t be making constant changes to things like direction, speed etc.
What is a better way of doing this, or how can I do what I need?
Thanks :)
It sounds like what you want to do is use the update method instead of using a repeating SKAction.
In update you calculate how much time has passed using currentTime and then decided on if you should spawn an enemy or not. This also gives you a chance to spawn an enemy any way you want based on the current state of your game.
As far as the button. It really depends on your game, but most games have some sort of count down or requires some sort of user interaction before game play begins.
EDIT
My swift is a bit rusty but it would be something like this...
var lastUpdateTime = 0.0;
var spawnTimer = 0.0;
var nextSpawnTime = 10.0;
override func update(currentTime: CFTimeInterval) {
if (lastUpdateTime == 0.0)
{
lastUpdateTime = currentTime
return
}
spawnTimer += currentTime-lastUpdateTime
//Do any other update logic here
if (spawnTimer >= nextSpawnTime)
{
spawnTimer = 0.0
spawn()
}
lastUpdateTime = currentTime
}
func spawn ()
{
//spawn and change nextSpawnTime if you wish
println("spawn")
}
Hopefully that helps and makes sense.

spritekit SKAction.resizeToHeight is not repeating

I want to increase the height of in touch events, it's same like stick hero is doing. I am trying following code
func changeHeight(){
let action = SKAction.resizeToHeight(self.leadherNode!.size.height+50, duration: 0.5);
let seq = SKAction.repeatActionForever(action)
self.leadherNode?.runAction(seq, withKey: "height")
}
but unfortunately it just increase the height of node for first time and it never repeats. How can I achieve this?
Changes to an argument of an SKAction after it has started will have no effect on the action. You will need to create a new action with the updated value at each step. Here's one way to do that:
Define and initialize the height and max height properties
var spriteHeight:CGFloat = 50.0;
let maxHeight:CGFloat = 500.0
Call this from didMoveToView
resizeToHeight()
This function creates an SKAction that resizes a sprite to a specific height. After the completion of the action, the function updates the height value and then calls itself.
func resizeToHeight() {
self.leadherNode?.runAction(
SKAction.resizeToHeight(self.spriteHeight, duration: 0.5),
completion:{
// Run only after the previous action has completed
self.spriteHeight += 50.0
if (self.spriteHeight <= self.maxHeight) {
self.resizeToHeight()
}
}
)
}
I dont know swift. But I can write objective-c version.
CGFloat currentSpriteSize;//Create private float
SKSpriteNode *sprite;//Create private sprite
currentSpriteSize = sprite.size.height;//Into your start method
//And your Action
SKAction *seq = [SKAction sequence:#[[SKAction runBlock:^{
currentSpriteSize += 50;
}], [SKAction resizeToHeight:currentSpriteSize duration:0.5]]];
[sprite runAction:[SKAction repeatActionForever:seq]];

Resources