I would need help with this while loop - what I'm trying to do is slow down the whole process of removing and adding new circles while radius is changing every time this happens. I'm becoming really desperate, I've tried using both dispatch_after and sleep inside the loop (which I found online) but neither of them is suitable, they basically stop the whole app. If I put them in the while loop, nothing happens. Thanks in advance!
while radius < 100 {
self.removeAllChildren()
addCircle()
radius++
print(radius)
}
Basically you just need to do few simple things:
Wait for a certain duration and add a node to the scene
Repeat this step forever (or certain number of times)
Here is the example of how you can do it. The important part is action sequence. You create the step above, and repeat it forever. Each time you check radius value and based on that you stop the action (remove it by the key). And that's it. You can change spawning speed by changing action's duration parameter.
Using NSTimer might seem like an easier solution, but NSTimer doesn't respect node's (or scene's or view's) paused state. So, imagine this situation:
You start spawning of nodes
User receive phone call and app automatically goes to background
Because NSTimer is not automatically paused, the nodes will continue with spawning. So you have to take an additional step and invalidate/restart timer by your self. When using SKAction, this is done automatically. There are some other flaws of using NSTimer in SpriteKit, search SO about all that, there are some posts which covering all this.
import SpriteKit
class GameScene: SKScene{
var radius:UInt32 = 0
override func didMoveToView(view: SKView) {
startSpawning()
}
func startSpawning(){
let wait = SKAction.waitForDuration(0.5)
// let wait = SKAction.waitForDuration(1, withRange: 0.4) // randomize wait duration
let addNode = SKAction.runBlock({
[unowned self] in //http://stackoverflow.com/a/24320474/3402095 - read about strong reference cycles here
if(self.radius >= 30){
if self.actionForKey("spawning") != nil {
self.removeActionForKey("spawning")
}
}
self.radius++
let sprite = self.spawnNode()
sprite.position = CGPoint(x: Int(arc4random()) % 300, y: Int(arc4random()) % 300) // change this to randomize sprite's position to suit your needs
self.addChild(sprite)
})
//wait & add node
let sequence = SKAction.sequence([wait, addNode])
//repeat forever
runAction(SKAction.repeatActionForever(sequence), withKey: "spawning")
}
func spawnNode()->SKSpriteNode{
let sprite = SKSpriteNode(color: SKColor.purpleColor(), size: CGSize(width: 50, height: 50))
//Do sprite initialization here
return sprite
}
}
The sleep stops the whole app because you are running the loop on the main thread.
You can solve this problem in one of two ways...
1) Use an NSTimer. Set it to the amount of time you want to delay. Put the body of the loop in the timer's trigger function and disable the timer when it has been called enough times. This is the best solution.
2) Wrap the loop in a dispatch async block to a background thread and put a sleep in the loop. If you do this though, you will have to wrap the UI code in another dispatch async block that comes back to the main thread.
Related
I'm writing an application that displays chemical reactions and molecules in 3D. I read in all the values and positions of each atom from a text file and I am creating each atom shape with SCNSpheres. I have all the other values I need read in properly, but I can't figure out how to add keyframe animations to each node object in my scene.
I set up the molecules like this in ViewController.swift
func makeAtom(atomName: String, coords: [Double], scene: SCNScene) {
guard let radius = atomRadii[atomName]?.atomicRadius else { return }
atoms.append(Atom(name: atomName, x: coords[0], y: coords[1], z: coords[2], radius: radius, positions: []))
let atomGeometry = SCNSphere(radius: CGFloat(radius))
let atomNode = SCNNode(geometry: atomGeometry)
atomNode.position = SCNVector3(coords[0], coords[1], coords[2])
scene.rootNode.addChildNode(atomNode)
atomNodes.append(atomNode)
}
I know that the CAKeyframeAnimations are supposed to be set up like this
let animation = CAKeyframeAnimation()
animation.keyPath = "position.y"
animation.values = [0, 300, 0]
animation.keyTimes = [0, 0.5, 1]
animation.duration = 2
animation.isAdditive = true
vw.layer.add(animation, forKey: "move")
I just don't know where I should be declaring these animations and how the layers factor into all this. What layer should I be adding the animations to? And how can I trigger them to play? I've been searching all over the internet for help with this but I can't find anything that just shows a simple implementation.
I can provide more code if need be, I'm pretty new to StackOverflow and want to make sure I'm doing this right.
You can do it different ways, but I like this method: 58001288 (my answer here) as you can pre-build some animations using scenekit and then run them as a sequence.
Per the comment.. needed more room.
A sequence is a fixed thing. You can start, repeat, and stop it. However, it's hard to interact with it during phases.
If you really need to do that, then one way is to break up your sequence into its parts and call the next one yourself after a completion handler of the current one. I keep an array and a counter so that I know where I am. So basically it's just a queue of actions that I manage - if I'm on a certain step and the button is pressed, then I can cancel all current actions, set the desired effect, and restart it.
Edit:
The completion handler calls itself at the end of the function and advances its own array count so that the next one in the list can be called. This is obviously a bit dangerous, so I would use sparingly, but that's how I did it. I started mine on a timer, then don't forget to clean it up. I had a global GAME_ACTIVE switch and within the code I checked for it before calling myself again.
Edit2: This is actually a moveTo, but it's still just a custom set of SCNActions that calls itself when complete based on duration so that it immediately goes to the next one without a delay.
func moveTo()
{
let vPanelName = moves[moveCount]
let vLaneNode = grid.gridPanels[vPanelName]!.laneNodes[lane]
let vAction = SCNAction.move(to: vLaneNode.presentation.position, duration: TimeInterval(data.getAttackSpeed(vGameType: gameType)))
node.runAction(vAction, completionHandler:
{
self.moveCount += 1
if(self.moveCount >= self.moves.count - 1)
{
self.killMe(vRealKill: false)
return
}
else
{
self.moveTo()
}
})
}
I have 2 actions that i put in a sequence. In the first action I am calling a method to calculate the new waiting time for the next action. The next action is just a wait for this duration, but the second action always executes straight away, so the time must be 0. I debugged it and in the method spawnFlowers i get the time returned as 3.5 seconds.
these are my 2 actions
let spawnFlowerAction = SKAction.run {
self.WaitTime = self.calculateWaitingTime()
}
let waitForNewFlower = SKAction.wait(forDuration: self.WaitTime)
I execute it this way:
let spawnSeq = SKAction.sequence([spawnFlowerAction, waitForNewFlower])
let spawnRepeat = SKAction.repeat(spawnSeq, count: 4)
self.run(spawnRepeat)
Result: 4 times spawned without waiting, printing 4 different calculated times in the console from the calculateWaitingTime function (in which the spawning happens)
What is a good way to fix this?
The problem is trying to dynamically change the values used within SKActions after the action has been created. For example when your WaitTime variable changes while running the spawnFlowerAction, the waitForNewFlower Action's wait time won't change dynamically because it doesn't reference WaitTime. Instead its wait value is whatever your variable WaitTime was when you declared let waitForNewFlower = SKAction.wait(forDuration: self.WaitTime) (Which I'm guessing was initially 0). Same concept goes with your other two spawn actions.
I usually use the dispatch Queue for things like these, but to use SKActions here's a function. Just call it once and input the number of times you want it to repeat.
func spawnRepeat(count: Int) {
//Put whatever code to spawn flower here
print("SPAWN FLOWER")
if count > 1 {
//Recalculate WaitTime
WaitTime = calculateWaitingTime()
let waitAction = SKAction.wait(forDuration: WaitTime)
run(waitAction, completion: { self.spawnRepeat(count: count - 1) })
}
}
let randomize = SKAction.runBlock({ [unowned self] in
self.footstepFile = "Content/footstep\(RandomInt(1, max: 4))"
print(self.footstepFile)
})
sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([randomize, SKAction.playSoundFileNamed(footstepFile, waitForCompletion: true)])), withKey: "footsteps")
When this action runs, the footstepFile shows its randomizing when I print it, but in reality it's just playing the same sound file over and over. Why is this?
I play this action whenever the sprite is moving, and pause it whenever he stops. When it pauses and unpauses the footstepFile changes but if I'm continuously running, it just plays the same one over and over. Shouldn't the runblock be randomizing it continuously?
I believe the issue is that the footstepFile that is active when the SKAction.sequence is created is the one that is used repeatedly. This is because the sequence only gets created once, and then used repeatedly.
To solve this, try creating an array of playSoundFileNamed actions all using random file names and pass that to SKAction.sequence:
var actions = [SKAction]()
for _ in 1...16 {
footstepFile = "Content/footstep\(RandomInt(1, max: 4))"
actions.append(SKAction.playSoundFileNamed(footstepFile, waitForCompletion: true))
}
sprite.runAction(SKAction.repeatActionForever(SKAction.sequence(actions)), withKey: "footsteps")
I have a SKAction that runs an action if an area on the screen is touched. However I cannot get the SKanimate to only run through the SKarray once (both actions that is), it seems to run around 4 times. The count parameter doesn't seem to make any difference either. Any help on how to get it to run through the frames in the array just once then stop would be appreciated!
//Touch location check
for touch in touches {
let location = touch.location(in: self)
if myButton.contains(location) {
//run shoot animation.
MainGuy.run(SKAction.repeat(SKAction.animate(with: TextureArrayShoot, timePerFrame: 0.10), count: 1),withKey: "outlaw")
print ("touched")
let witchaction = SKAction.animate(with: TextureArrayWitch, timePerFrame: 0.20)
witch.run(witchaction)
missedLabel1.text = "Good Shot!"
}
}
Apologies - solved it. There were 3 arrays for sprites in a row, and the first one wasn't closed properly and encapsulating the other two, meaning the 'I' in for I in...was being used 3 times!
I have several buttons (SKSpriteNodes) which I am trying to cycle through and animate, with a small delay between each. My code compiles, but when I run it - I only get a white screen and a crash with this error: "Message from debugger: Terminated due to memory issue". Here is my code:
var sequence = SKAction.sequence([animationUp, animationDown])
runAction(SKAction.repeatActionForever(SKAction.sequence([
SKAction.runBlock({
button1.runAction(sequence)
SKAction.waitForDuration(0.5)
button2.runAction(sequence)
SKAction.waitForDuration(0.5)
button3.runAction(sequence)
SKAction.waitForDuration(0.5)
}),
])))
So what I am trying to accomplish is an up/down animation on every button I'm drawing to the screen, with a 0.5 second delay between each button. The animation should run forever until I change the current screen. I had no problem animating these buttons simultaneously but I'd really like to add a uniform delay so that they don't all animate at the same time. Any ideas?
let waitAction = SKAction.waitForDuration(0.5)
let movementAction = SKAction.sequence([animateUp, animateDown])
let button1Block = SKAction.runBlock({
button1.runAction(movementAction)
})
let button2Block = SKAction.runBlock({
button2.runAction(movementAction)
})
let button3Block = SKAction.runBlock({
button3.runAction(movementAction)
})
let sequence = SKAction.sequence([button1Block,waitAction,button2Block, waitAction, button3Block, waitAction])
runAction(SKAction.repeatActionForever(sequence))
I'm not certain about how the SKAction.sequence initiates/destroys, but possibly your loop in .repeatActionForever(..) creates and destroys objects, whereas memory won't be released until the loop is allowed to complete.
Try to run your inner runAction block within an autoreleasepool { .. } block:
autoreleasepool {
runAction ...
}
But there is one situation we do need to autorelease, that is we
create a lot of objects in a method scope and want to release them
sooner. It is extremely useful when these objects turn to a pressure
on memory.
http://en.swifter.tips/autoreleasepool/