CCActionSequence/CCActionDelay not delaying each action? - ios

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.

Related

How to trigger a function when spriteSceneNode reach at a point in ios SpriteKit

I have a spritekit scene and added some sequence of actions to one of it's spriteSceneNode.
Say for example,a spriteSceneNode called node1 moving to point1 then it will move to point2 then point3 etc. Its implemented by using "SKAction.moveTo" function.
My question is,
Is it possible to call a custom function when it reaches on each points (point1 or point2 or point3).?
Adding some code here.
func MoveObjectToAnotherPosition(arrayOfPoints : [CGPoint],object: SKSpriteNode ) {
let from = object.position
var curPoint1 = object.position
let move = SKAction.move(to: from ,duration: 0.5)
var arrayOfMove : [SKAction] = []
arrayOfMove.append(move)
for point in (arrayOfPoints ) {
let move2 = SKAction.move(to: point,duration: 2.5)
let deltaX = point.x - curPoint1.x
let deltaY = point.y - curPoint1.y
let angle = atan2(deltaY, deltaX)
// let graph = childNode(withName: "Graph1")?
print(angle)
let ang = point.angle(to: curPoint1)
print(ang)
let codeToRunWhenReachingPointX = SKAction.run {
let anglevalue = angle + 90 * self.DegreesToRadians
self.movObject.zPosition = anglevalue
print(anglevalue)
}
//print(anglevalue)
let rotate = SKAction.rotate(byAngle: -angle, duration: 0.0)
arrayOfMove.append(codeToRunWhenReachingPointX)
arrayOfMove.append(move2)
curPoint1 = point
}
let moveToSequence = SKAction.sequence(arrayOfMove)
object.run(moveToSequence)
}
Any help?
Yes, you can call any code when an SKAction completes. To do this, you can create an SKAction that runs any code by using run(_:).
Since you want to call some code when reaching a point X, I would add something like this for each point X:
let moveToPointX = SKAction.move(to: pointX, duration: someDuration)
arrayOfMove.append(moveToPointX)
let codeToRunWhenReachingPointX = SKAction.run {
// add whatever code you need, e.g. a closure passed to this function
}
arrayOfMove.append(codeToRunWhenReachingPointX)
Then you can run the sequence of actions as you are now by calling
let moveToSequence = SKAction.sequence(arrayOfMove)
object.run(moveToSequence)
It is worth noting that running the sequence of actions will not wait for codeToRunWhenReachingPointX to complete before moving to the next point. Say you have the array of SKActions [m1, c1, m2, c2], where the m's are your move actions, and the c's are your "run code" actions. When you run this sequence, this will happen: m1 will run and when it completes c1 will run, and then without waiting for c1 to complete, m2 will run, and when m2 completes c2 will run.
Also have a look at the run(_:completion:) method, which works similarly.
Hope this helps!

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.

Smooth switching between texture atlas animations

How can I make smooth transitions between multiple animations on the same character without delay?
I'm developing a game in iOS Swift with a jumping character. This character has a few animations, like the basic animation, the jump animation and a landing animation.
I set the texture atlas for every animation as follows:
// Basic
let surferBasicAnimatedAtlas = SKTextureAtlas(named: "surfer_basic.atlas")
let numImages = surferBasicAnimatedAtlas.textureNames.count
for var i=0; i<numImages; i++ {
let surferBasicTextureName = "surfer_basic_000\(i)"
self.surferBasicFrames.append(surferBasicAnimatedAtlas.textureNamed(surferBasicTextureName))
}
// Jump
let surferJumpAnimatedAtlas = SKTextureAtlas(named: "surfer_jump.atlas")
let numJumpImages = surferJumpAnimatedAtlas.textureNames.count
for var i=0; i<numJumpImages; i++ {
let surferJumpTextureName = "surfer_jump_000\(i)"
self.surferJumpFrames.append(surferJumpAnimatedAtlas.textureNamed(surferJumpTextureName))
}
// Landing
let surferLandingAnimatedAtlas = SKTextureAtlas(named: "surfer_landing.atlas")
let numLandingImages = surferLandingAnimatedAtlas.textureNames.count
for var i=12; i<numLandingImages; i++ {
let surferLandingTextureName = "surfer_landing_000\(i)"
self.surferLandingFrames.append(surferLandingAnimatedAtlas.textureNamed(surferLandingTextureName))
}
// Pick textures
self.surferWalkingFrames = surferBasicFrames
let firstFrame = surferWalkingFrames[0]
self.character = SKSpriteNode(texture: firstFrame)
self.character.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(surferWalkingFrames, timePerFrame: (1 / 30), resize: false, restore: true)), withKey:"surferBasic")
When I touch the screen the basic animation should stop and the jump animation should run, but it causes a delay. About one second after the touch the jump animation starts.
I've tried this:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.character.removeActionForKey("surferBasic")
self.surferWalkingFrames = surferJumpFrames
self.character.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(surferWalkingFrames, timePerFrame: (1 / 30), resize: false, restore: true)), withKey:"surferJump")
}
How can I make smooth transitions between multiple animations on the same character without delay?
I could be wrong, but it looks like (1 / 30) is your offender for a couple of reasons.
1/30 will return an int and it will be 0
if you do 1.0/30.0 you will get a nice float like you want but it will be 0.03333333333333 and you are asking it two switch the texture 30 times a second. That is rather fast and you may not even see it change.
I would try .2 first and see if that changes anything if it does then it is just a matter of finding how fast you need to change the texture.
Hopefully that helps.

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.

How can I make a sprite object set it's moving direction at the point of a touch?

So I'm playing around and slowly making my first iOS game. I'm trying to get my sprite object to set out a straight path angled towards the location of the player (which is where the user last touched).
The code I have makes the bee move towards the player and stop when it gets to it, instead of going from one side of the screen, through the player and off the other side of the screen.
var beeSpeed = 2.0
var moveAccross = SKAction.moveTo(CGPointMake(player.position.x,player.position.y), duration:beeSpeed)
badBee.runAction(moveAccross)
I will be having many objects constantly spawning that should all set their path once to the player location at the time they spawn.
Any help would be great,
I think I figured it out, if anyone could check my code and make sure it's doing the right thing that would be great :)
let actionMove = SKAction.moveTo(CGPoint(x: player.position.x-player.position.x-bee.size.width, y: player.position.y), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bee.runAction(SKAction.sequence([actionMove, actionMoveDone]))
You have to calculate the slope from the spawning point to the player and then interpolate to somewhere outside the screen.
func add(location:CGPoint)
{
let bee = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(10, 10))
bee.name = "Bee"
bee.position = location
self.addChild(bee)
let slopeToPlayer = (bee.position.y - player.position.y) / (bee.position.x - player.position.x)
let constant = bee.position.y - slopeToPlayer * bee.position.x
let finalX : CGFloat = bee.position.x < player.position.x ? 500.0 : -500.0 // Set it to somewhere outside screen size
let finalY = constant + slopeToPlayer * finalX
let distance = (bee.position.y - finalY) * (bee.position.y - finalY) + (bee.position.x - finalX) * (bee.position.x - finalX)
let beeSpeed : CGFloat = 100.0
let timeToCoverDistance = sqrt(distance) / beeSpeed
let moveAction = SKAction.moveTo(CGPointMake(finalX, finalY), duration: NSTimeInterval(timeToCoverDistance))
let removeAction = SKAction.runBlock { () -> Void in
bee.removeFromParent()
println("removed")
}
bee.runAction(SKAction.sequence([moveAction,removeAction]))
}

Resources