I'm trying to get an SKShapeNode to move and fade from 0 alpha to 1 alpha over a 2 second period and then delete itself. The moving part is fine, but when I try to add the fadeIn it fades in very very quickly (in about 0.2 seconds) and repeats itself, so it ends up looking like the SKShapeNode is flashing. Can anyone help?
Here is my code:
func startMoving(){
alpha = 0
let move = SKAction.moveBy(x: moveX, y: moveY, duration: 2.0)
let fadeIn = SKAction.fadeIn(withDuration: 2.0)
let group = SKAction.group([move, fadeIn])
run(group, completion: {
self.removeFromParent()
})
}
Also, I'm a bit worried that self.removeFromParent() won't actually delete the instance of the node. I'm running this quite a few times, so want to make sure i'm properly taking care of it. I've tried self = nul, but that didn't work. Any suggestions?
the code you posted is working fine. Your bug is elsewhere.
Related
Swift 5, iOS 14
Wrote this code to fade in a sprite node... works ok, but Xcode says this, which makes no sense since I am using timer within the loop.
Initialization of immutable value 'timer' was never used; consider replacing with assignment to '_' or removing it
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
node2Delete?.alpha -= 0.1
ghost.alpha += 0.1
if node2Delete?.alpha == 0 {
node2Delete?.removeFromParent()
timer.invalidate()
}
}
And it doesn't complain about the timer within the loop not being defined? Is this a bug or am I missing something?
Is there a better way to do this? Is the timer I set here actually being cancelled?
You want to use SKAction not Timer here, so you can replace all your code with the below.
Create two variables which hold the fade logic, one for fade in, the other for fade out.
Run fadeIn on your ghost node. The ghost node will gradually appear over the course of 5 seconds.
Run fadeOuton your node2Delete node, this will gradually fade the alpha to 0 over the course of 5 seconds. Once completed, we remove the node from it's parent.
let fadeOut = SKAction.fadeAlpha(to: 0.0, duration: 5.0)
let fadeIn = SKAction.fadeAlpha(to: 1.0, duration: 5.0)
ghost.run(fadeIn)
node2Delete.run(fadeOut){
self.node2Delete.removeFromParent()
}
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()
}
})
}
EDIT: I have solved the problem and will post the solution in the next couple of days.
I'm building 3D achievements similar to Apple's Activity app.
I've already loaded my 3D model (a scene with a single node), can show it, and can tap on it to apply a rotational force:
#objc func objectTapped(_ gesture: UITapGestureRecognizer) {
let tapLocation = gesture.location(in: scnView)
let hitResults = scnView.hitTest(tapLocation, options: [:])
if let tappedNode = (hitResults.first { $0.node === badgeNode })?.node {
let pos = Float(tapLocation.x) - tappedNode.boundingBox.max.x
let tappedVector = SCNVector4(x: 0, y: pos, z: 0, w: 0.1)
tappedNode.physicsBody?.applyTorque(tappedVector,
asImpulse: true)
}
}
This works fine. Now to the tricky part:
I want the node to rotate until it either shows its front or backside (like in the Activity app), where it then should stop. It should stop naturally, which means it can overshoot a bit and then return.
To describe it with pictures - here I am holding the node in this position...
...and if I let go of the node, it will rotate to show the front side, which includes a little bit of overshooting. This is the ending position:
Since I'm quite new to SceneKit, I have troubles figuring out how to achieve this effect. It seems like I can achieve that by using SceneKit objects like gravity fields, without having to calculate a whole lot of stuff by myself, or at least that's what I'm hoping for.
I don't necessarily ask for a full solution, I basically just need a point in the right direction. Thanks in advance!
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 an iOS 10 SpriteKit project where I'm trying to put actions on particles from a basic particle emitter created from the "snow" particle template in Xcode 8:
let snowPath = Bundle.main.path(forResource: "Snow", ofType: "sks")!
snowEmitter = NSKeyedUnarchiver.unarchiveObject(withFile: snowPath) as! SKEmitterNode
snowEmitter.position = CGPoint(x: 0, y: size.height / 2)
snowEmitter.particlePositionRange = CGVector(dx: size.width, dy: 0)
snowEmitter.particleAction = SKAction.scale(to: 3, duration: 3)
effectLayer.addChild(snowEmitter) // effectLayer is a SKNode on the scene
The emitter works as it should, but no matter what kind of SKAction I set particleAction to it gets ignored. Has anyone else experienced this?
Update: Doesn't work with Xcode 7 and iOS 9 either.
I think this still might be a leftover iOS 9 bug, not 100% sure. I just tried myself and I cannot get it to work as well.
SKEmitterNode particleAction not working iOS9 Beta
Can you not achieve the same effect using the particles settings directly in snow.sks in the inspector on the right?
You are probably looking at †hose two settings and its subsettings.
1) Particle life cycle (start, range)
2) Particle scale (start, range, speed)
This article has a nice description of each setting.
http://www.techotopia.com/index.php/An_iOS_8_Sprite_Kit_Particle_Emitter_Tutorial#Particle_Birthrate
As a general tip
Your code is not very safe in the first 2 lines because you force unwrapped the snow particle.
If you ever change the name and forget about it or the file becomes corrupted than you will crash. You should change it to something like this
guard let snowPath = Bundle.main.path(forResource: "Snow", ofType: "sks") else { return } // or if let snowPath = ...
snowEmitter = NSKeyedUnarchiver.unarchiveObject(withFile: snowPath) as? SKEmitterNode
...
You can also simply this code a lot by simple saying this where you define your snowEmitter property
let snowEmitter = SKEmitterNode(fileNamed: "Snow")
This will return an optional as well, just like your old code. Than in your method where you set up the emitter say something like this (dont use !)
if let snowEmitter = snowEmitter {
snowEmitter.position =
...
}
Hope this helps
There's another way to achieve the goal.
Make two or more particle systems.
Create two or more noise fields.
Bitmask match one each of the particle systems to one of the noise fields.
Put the noise fields down the bottom, where you want the wiggles to happen
Adjust the noise fields to taste.
As a 2021 datapoint, whilst adding particle emitters to Touchgram, I spent a couple of days exploring this. I came to the conclusion that they were broken in iOS9 and never fixed.