Making a node's sprite change as an animation (spriteKit) - swift

im very new to swift, i have made a sprite kit game with a coin sprite. I want to make it spin so ive made 6 sprites in total. Im trying to get a continuous loop of spinning by quickly changing the sprites. I have tried to do this with the code below.
//This will hold all of the coin spinning sprites
let coinTextures : NSMutableArray = []
//There are 6 in total, so loop through and add them
for i in 0..<6 {
let texture : SKTexture = SKTexture(imageNamed: "Coin\(i + 1)")
coinTextures.insert(texture, at: i)
}
//When printing coinTextures, they have all been added
//Define the animation with a wait time of 0.1 seconds, also repeat forever
let coinAnimation = SKAction.repeatForever(SKAction.animate(with: coinTextures as! [SKTexture], timePerFrame: 0.1))
//Get the coin i want to spin and run the action!
SKAction.run(coinAnimation, onChildWithName: "coin1")
As i said im very new so im not sure what ive done wrong here.
Also the name of the coin i want to spin is "coin1" and the sprites so from coin1 to coin 6

You are almost there.
The problem is that your final line creates an action, but not running it on anything...
You got two alternatives:
1) Run your action on your scene
// Create an action that will run on a child
let action = SKAction.run(coinAnimation, onChildWithName: "coin1")
scene?.run(action)
or
2) Run the action directly on the child
// Assuming that you have a reference to coin1
coin1.run(coinAnimation)
As a sidenote, your array could be declared as var coinTextures: [SKTexture] = [], you can use append to add items to it and avoid the casting when you pass the textures to the action.
Or you can use a more compact form to construct your textures array:
let coinTextures = (1...6).map { SKTexture(imageNamed: "Coin\($0)") }
I hope that this makes sense

Related

Animating SCN Objects with CAKeyframeAnimation in Swift SceneKit

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()
}
})
}

SKTextureAtlas gets overridden by declaration of second SKTextureAtlas

Whenever I create and define two variables as type SKTextureAtlas they rewrite each other. Here is my initial creation of my two SKTextureAtlas
class GameScene: SKScene {
var idle = true
var TextureAtlas = SKTextureAtlas()
var TextureAtlasIdle = SKTextureAtlas()
Later on in my code I assign a folder of images to each atlas. If I comment out the second atlas my animation works, but when I define the two as shown below my animation overlaps and plays frames from cat_walk even though it is told to only play cat_idle
TextureAtlasIdle = SKTextureAtlas(named: "cat_idle")
for i in 1...TextureAtlasIdle.textureNames.count{
let Name = "\(i).png"
TextureArrayIdle.append(SKTexture (imageNamed: Name))
}
TextureAtlas = SKTextureAtlas(named: "cat_walk")
This is how I start my cat_idle animation. I don't start the cat_walk animation
aN.run(SKAction.repeatForever(SKAction.animate(with:self.TextureArrayIdle, timePerFrame: 0.1)))
I'm trying to find out why this line of code is causing my two different animations to overlap.
TextureAtlas = SKTextureAtlas(named: "cat_walk")
Texture Atlas is kind of a sprite sheet for your game. Your approach is not right for accessing atlas images for the game. modify your for loop like below:
for i in 0..< 8 {
let texture:SKTexture = TextureAtlasIdle.textureNamed(String(format: "%i", i+1))
TextureArrayIdle.insert(texture, at:i)
}
Every time you access your atlas folder, use this for loop procedure. i have used static 8 number in for loop, it would be your sprites count. There is a simple game called Desert Run in github. please check this out for more clarification.
NB: your cat's images naming must start with 1.png
if you want to run the for loop

Copying SCNParticleSystem doesn't seem to work well

I'm trying to use an SCNParticleSystem as a "template" for others. I basically want the exact same properties except for the color animation for the particles. Here's what I've got so far:
if let node = self.findNodeWithName(nodeName),
let copiedParticleSystem: SCNParticleSystem = particleSystemToCopy.copy() as? SCNParticleSystem,
let colorController = copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color],
let animation: CAKeyframeAnimation = colorController.animation as? CAKeyframeAnimation {
guard animation.values?.count == animationColors.count else {
return nil
}
// Need to copy both the animations and the controllers
let copiedAnimation: CAKeyframeAnimation = animation.copy() as! CAKeyframeAnimation
copiedAnimation.values = animationColors
let copiedController: SCNParticlePropertyController = colorController.copy() as! SCNParticlePropertyController
copiedController.animation = copiedAnimation
// Finally set the new copied controller
copiedParticleSystem.propertyControllers?[SCNParticleSystem.ParticleProperty.color] = copiedController
// Add the particle system to the desired node
node.addParticleSystem(copiedParticleSystem)
// Some other work ...
}
I copy not only the SCNParticleSystem, but also SCNParticlePropertyController and CAKeyframeAnimation just to be safe. I've found that I've had to manually do these "deep" copies "manually" since a .copy() on SCNParticleSystem doesn't copy the animation, etc.
When I turn on the copied particle system on the node it was added to (by setting the birthRate to a positive number), nothing happens.
I don't think the problem is with the node that I've added it to, since I've tried adding the particleSystemToCopy to that node and turning that on, and the original particle system becomes visible in that case. This seems to indicate to me that the the node I've added the copied particle system to is OK in terms of its geometry, rendering order, etc.
Something else perhaps worth mentioning: the scene is loaded from a .scn file and not created programmatically in code. In theory that shouldn't affect anything, but who knows ...
Any ideas on why this copied particle system doesn't do anything when I turn it on?
Do not use copy() method for particle systems !
copy() method does not allow copy particles' color (copied particles will be default white).
You can test it with the following code:
let particleSystem01 = SCNParticleSystem()
particleSystem01.birthRate = 2
particleSystem01.particleSize = 0.5
particleSystem01.particleColor = .systemIndigo // INDIGO
particleSystem01.emitterShape = .some(SCNSphere(radius: 2.0))
let particlesNode01 = SCNNode()
particlesNode01.addParticleSystem(particleSystem01)
particlesNode01.position.y = -3
sceneView.scene.rootNode.addChildNode(particlesNode01)
let particleSystem02 = particleSystem01.copy() // WHITE
let particlesNode02 = SCNNode()
particlesNode02.addParticleSystem(particleSystem02 as! SCNParticleSystem)
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)
Use clone() method for nodes instead !
clone() method works more consistently for 3d objects and particle systems and it can help you save particles' color but, of course, doesn't allow save a position for each individual particle.
let particlesNode02 = particlesNode01.clone() // INDIGO
particlesNode02.position.y = 3
sceneView.scene.rootNode.addChildNode(particlesNode02)

SK.action runs through action several times

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!

Create Array of SKShapeNodes. Swift > Xcode 6

In swift, the code below is called at a certain rate (2 times per second) from didMoveToView(). Every time this function is called, a new circlenode should appear on the screen. But instead of continually adding them, when it tries to add the second one it throws an error : attempted to add sknode which already has a parent. I figured out that you can't make a duplicate node on the same view. With that figured out, I came to the conclusion that I would need to make an array of SKShapeNodes so whenever the function is called, it would take one from the array and add it to the view. When a node has reached the bottom (y = -20) in my case, then it would need to remove the node, and make it available again to use.
So, my question: How to I make an array of SKShapeNodes, so when my function below is called it will take a new node and display it to the view? Also, when nodes exit the view, it will need to make the node available again to use.
let circlenode = SKShapeNode(circleOfRadius: 25) //GLOBAL
func thecircle() {
circlenode.strokeColor = UIColor.whiteColor()
circlenode.fillColor = UIColor.redColor()
let initialx = CGFloat(20)
let initialy = CGFloat(1015)
let initialposition = CGPoint(x: initialx, y: initialy)
circlenode.position = initialposition
self.addChild(circlenode)
let action1 = SKAction.moveTo(CGPoint(x: initialx, y: -20), duration: NSTimeInterval(5))
let action2 = SKAction.removeFromParent()
circlenode.runAction(SKAction.sequence([action1, action2]))
}
So you're right in that the problem the code above is that an SKNode can only have one parent.
You have 2 approaches you could take.
Create an array of your SKShapeNodes
Create a new SKShapeNode as needed
The former has the constraint that you need to keep tabs on your total amount of circles, else you'll exceed your bounds. If you remove items, this also means accounting for it. The latter has the overhead of generating the SKShapeNode when you need it. More than likely this will not be an issue.
To create the array you'd do something like (option 1):
var circleArray:[SKShapeNode] = [SKShapeNode]() // Property in your class
// Code in your init or wherever else you want, depending on what class this is.
// 10 is just an arbitrary number
for var i=0;i<10;i++ {
circleArray.append(SKShapeNode(circleOfRadius: 25))
}
When you want to add it in your thecircle, where you were calling self.addChild(circlenode) something like:
if numCircles < circleArray.count {
SKShapeNode *circlenode = circleArray[numCircles]
// Do other initialization here
self.addChild(circlenode)
numCircles++
}
Or you can do (option 2):
SKShapeNode *circlenode = SKShapeNode(circleOfRadius: 25)
// Do other initialization here
self.addChild(circle node)
I'd probably do option 2 in order to not have to deal with any of the management of number of outstanding circles in the array. This is particularly important if you ever remove circles and want to recycle them.