How to rotate a particle with a specific angle programmatically in SceneKit? - ios

I would like to rotate a particle, it is a simple line, emitted once in the center of the screen.
After I touch the screen, the method is called, and the rotation changes all the time. With 10° or 180°, around the x or z axis, the result is the same: the angle is N°, then Y°, then Z° (always a different number, with a random difference between one another : with 10°, it is not offset by 10 each time, but by a random number). Would you know why?
func addParticleSceneKit(str:String){
var fire = SCNParticleSystem(named: str, inDirectory: "art.scnassets/Particles")
fire.orientationMode = .Free
fire.particleAngle = 90
//fire.propertyControllers = [ SCNParticlePropertyRotationAxis : [1,0,0] ] // should it be a SCNParticlePropertyController? I don't know how to use it then. But it would not be for an animation in my case.
emitter.addParticleSystem(fire)
Thanks

The particleAngleVariation property controls the random variation in initial particle angles. Normally that defaults to zero, meaning particle angle isn't randomized, but you're loading a particle system from a file, so you're getting whatever is in that file — setting it to zero should stop the randomization you're seeing. (You can also do that to the particle system in the file you're loading it from by editing that file in Xcode.)
By the way, you're not adding another new particle system to the scene every time you want to emit a single particle, are you? Sooner or later that'll cause problems. Instead, keep a single particle system, and make emit more particles when you click.
Presumably you've already set its emissionDuration, birthRate, and loops properties in the Xcode Particle System Editor so that it emits a single particle when you add it to the scene? Then just call its reset method, and it'll start over, without you needing to add another one to the scene.
Also, regarding your comment...
fire.propertyControllers = [ SCNParticlePropertyRotationAxis : [1,0,0] ]
should it be a SCNParticlePropertyController? I don't know how to use it then. But it would not be for an animation in my case.
Reading the documentation might help with that. But here's the gist of it: propertyControllers should be a dictionary of [String: SCNParticlePropertyController]. I know, it says [NSObject : AnyObject], but that's because this API is imported from ObjC, which doesn't have typed collections. That's why documentation is important — it says "Each key in this dictionary is one of the constants listed in Particle Property Keys, and the value for each key is a SCNParticlePropertyController object..." which is just long-winded English for the same thing.
So, passing a dictionary where the key is a string and the value is an array of integers isn't going to help you.
The docs also say that property controllers are for animating properties, and that you create one from a Core Animation animation. So you'd use a property controller for angle if you wanted each particle to rotate over time:
let angleAnimation = CABasicAnimation()
angleAnimation.fromValue = 0 // degrees
angleAnimation.toValue = 90 // degrees
angleAnimation.duration = 1 // sec
let angleController = SCNParticlePropertyController(animation: angleAnimation)
fire.propertyControllers = [ SCNParticlePropertyAngle: angleController ]
Or for rotation axis if you wanted particles (that were already spinning freely due to orientation mode and angular velocity) to smoothly transition from one axis of rotation to another:
let axisAnimation = CABasicAnimation()
axisAnimation.fromValue = NSValue(SCNVector3: SCNVector3(x: 0, y: 0, z: 1))
axisAnimation.toValue =NSValue(SCNVector3: SCNVector3(x: 0, y: 1, z: 0))
axisAnimation.duration = 1 // sec
let axisController = SCNParticlePropertyController(animation: axisAnimation)
fire.propertyControllers = [ SCNParticlePropertyRotationAxis: axisController ]

Related

SceneKit move object on top of another one

I am new to SceneKit and I am programming a game.
I have loaded two objects into my scene. The first object doesn't move, only the second one. The two objects always have to stick together but the second object can move completely free on the first object's surface depending on user input (basically like two magnets with infinity power but no friction).
My approach is to take the second object's x and y coordinates and look what object one's z coordinate is at given x and y coordinates. Then I move object two to the exact same z-coordinate.
I tried using a SCNDistanceConstraint but it didn't have any effect:
let cnstrnt = SCNDistanceConstraint(target: object1)
cnstrnt.maximumDistance = 1
cnstrnt.minimumDistance = 0.99
object2?.constraints?.append(cnstrnt)
I also tried using a SCNTransformConstraint without any effect either:
let transform = SCNTransformConstraint.positionConstraint(inWorldSpace: true) { (object2, vector) -> SCNVector3 in
let z = object1?.worldPosition.z
return SCNVector3(object2.worldPosition.x, object2.worldPosition.y, z!)
}
object2?.constraints?.append(transform)
Using a hitTest only returns results that are positioned on the bounding box of the object and not its actual surface:
let hitTest = mySceneView.scene?.physicsWorld.rayTestWithSegment(from: SCNVector3((object2?.position.x)!, (object2?.position.y)!, -10), to: (object2?.position)!, options: nil)
So how can I get the z-coordinate of an 3d object's surface from a x and y coordinate? Because then I'd be able to set the new position of object2 manually.
Maybe you have another approach that is more elegant and faster than mine?
Thanks beforehand!

Swift Game Scene alter vertically moving background with time?

I have a moving background which is 1500 x 600 pixels and constantly moves vertically down the screen using this code:
let bgTexture = SKTexture(imageNamed: "bg.png")
let moveBGanimation = SKAction.move(by: CGVector(dx: 0, dy: -bgTexture.size().height), duration: 4)
let shiftBGAnimation = SKAction.move(by: CGVector(dx: 0, dy: bgTexture.size().height), duration: 0)
let moveBGForever = SKAction.repeatForever(SKAction.sequence([moveBGanimation, shiftBGAnimation]))
var i: CGFloat = 0
while i < 3 {
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: self.frame.midX, y: bgTexture.size().height * i)
bg.size.width = self.frame.width
bg.zPosition = -2
bg.run(moveBGForever)
self.addChild(bg)
i += 1
}
I now want a new background to come onto the screen after x amount of time to give the feel the player is moving into a different part of the game.
Could I put this code into a function and trigger it with NSTimer after say 20 seconds but change the start position of the new bg to be off screen?
The trouble with repeatForever actions is you don't know where they are at a certain moment. NSTimers are not as precise as you'd like, so using a timer may miss the right time or jump in too early depending on rendering speeds and frame rate.
I would suggest replacing your moveBGForever with a bgAnimation as a sequence of your move & shift actions. Then, when you run bgAnimation action, you run it with a completion block of { self.cycleComplete = true }. cycleComplete would be a boolean variable that indicates whether the action sequence is done or not. In your scene update method you can check if this variable is true and if it is, you can run the sequence action once again. Don't forget to reset the cycleComplete var to false.
Perhaps it sounds more complex but gives you control of whether you want to run one more cycle or not. If not, then you can change the texture and run the cycle again.
Alternatively you may leave it as it is and only change the texture(s) after making sure the sprite is outside the visible area, e.g. its Y position is > view size height.
In SpriteKit you can use wait actions with completion blocks. This is more straightforward than using a NSTimer.
So, to answer your question - when using actions for moving the sprites on-screen, you should not change the sprite's position at any time - this is what the actions do. You only need to make sure that you update the texture when the position is off-screen. When the time comes, obviously some of the sprites will be displayed, so you can't change the texture of all 3 at the same time. For that you may need a helper variable to check in your update cycle (as I suggested above) and replace the textures when the time is right (sprite Y pos is off-screen).

Keeping Direction of a Vector Constant while Rotating Sprite

I'm trying to make a game where the sprite will always move to the right when hit by an object. However since the Sprite rotates constantly and the zero radians rotates with the Sprite causes my calculated magnitude to go the opposite direction if the sprite is facing left and hits the object. Is there a way to keep the direction of the magnitude always pointing to the right even if the zero is facing left?
// referencePoint = upper right corner of the frame
let rightTriangleFinalPoint:CGPoint = CGPoint(x: referencePoint.x, y: theSprite.position.y)
let theSpriteToReferenceDistance = distanceBetweenCGPoints(theSprite.position, b: referencePoint)
let theSpriteToFinalPointDistance = distanceBetweenCGPoints(theSprite.position, b: rightTriangleFinalPoint)
let arcCosineValue = theSpriteToFinalPointDistance / theSpriteToReferenceDistance
let angle = Double(acos(arcCosineValue))
let xMagnitude = magnitude * cos(angle)
let yMagnitude = (magnitude * sin(angle)) / 1.5
Not sure if this works for you:
I would use an orientation constraint to rotate the sprite. The movement can be done independent from the orientation in that case.
I made an tutorial some time ago: http://stefansdevplayground.blogspot.de/2014/09/howto-implement-targeting-or-follow.html
So I figured out what was going on.
It seems like the angle doesn't rotate with the Sprite like I originally thought and the vector that I am making is working with the code above. THE problem that I had was that I also set the collision bit for the objects which is wrong. If I only set the contact bit for the objects against the sprite the my desired outcome comes true.

SpriteKit: What's up with the coordinate system?

I'm teaching myself how to do SpriteKit programming by coding up a simple game that requires that I lay out a square "game field" on the left side of a landscape-oriented scene. I'm just using the stock 1024x768 view you get when creating a new SpriteKit "Game" project in XCode - nothing fancy. When I set up the game field in didMoveToView(), however, I'm finding the coordinate system to be a little weird. First of all, I expected I would have to place the board at (0, 0) for it to appear in the lower-left. Not so -- it turns out the game board has to be bumped up about 96 pixels in the y direction to work. So I end up with this weird code:
let gameFieldOrigin = CGPoint(x:0, y:96) // ???
let gameFieldSize = CGSize(width:560, height: 560)
let gameField = CGRect(origin: gameFieldOrigin, size: gameFieldSize)
gameBorder = SKShapeNode(rect: gameField)
gameBorder.strokeColor = UIColor.redColor()
gameBorder.lineWidth = 0.1
self.addChild(gameBorder) // "self" is the SKScene subclass GameScene
Furthermore, when I add a child to it (a ball that bounces inside the field), I assumed I would just use relative coordinates to place it in the center. However, I ended up having to use "absolute" coordinate and I had to offset the y-coordinate by 96 again.
Another thing I noticed is when I called touch.locationInNode(gameBorder), the coordinates were again not relative to the border, and start at (0, 96) at the bottom of the border instead of (0, 0) as I would have guessed.
So what am I missing here? Am I misunderstanding something fundamental about how coordinates work?
[PS: I wanted to add the tag "SpriteKit" to this question, but I don't have enough rep. :/]
You want to reference the whole screen as a coordinate system, but you're actually setting all the things on a scene loading from GameScene.sks. The right way to do is modify one line in your GameViewController.swift in order to set your scene size same as the screen size. Initialize scene size like this instead of unarchiving from .sks file:
let scene = GameScene(size: view.bounds.size)
Don't forget to remove the if-statement as well because we don't need it any more. In this way, the (0, 0) is at the lower-left corner.
To put something, e.g. aNode, in the center of the scene, you can set its position like:
aNode.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));

How to make a particle image bigger with a scale effect with SceneKit?

I am looking for the same effect as we have in SpriteKit for the emitter particles, the scale effect that can make a particle image bigger or smaller depending on the time. (a simple red circle for example, getting bigger and disappearing after 1 second.) I cannot find the same scale option as we can find in SpriteKit. The image can be bigger and stay bigger, but it would not change depending on the time then.
Would someone know a good way to do this?
Thanks
EDIT:
None of these attempts worked, would you know why?
func addParticleSceneKit(){
println("add")
var fire = SCNParticleSystem(named: "circle1.scnp", inDirectory: "art.scnassets/Particles")
fire.particleSize = 5
emitter.addParticleSystem(fire) //emitter is a SCNNode
/*
let bigger = SCNAction.runBlock { (node) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
SCNTransaction.setAnimationDuration(1)
fire.propertyControllers = [SCNParticlePropertySize : 10.0]
})
}
emitter.runAction(bigger)
*/
//SCNTransaction.begin()
//SCNTransaction.setAnimationDuration(1)
//fire.propertyControllers = [SCNParticlePropertySize : 10.0]
//SCNTransaction.commit()
}
SCNParticleSystem has properties like
// Specifies the initial size of the particle. Animatable.
#property(nonatomic) CGFloat particleSize;
// Specifies the initial size variation of the particle. Animatable.
#property(nonatomic) CGFloat particleSizeVariation;
if you need more control you can also provide your own particle property controller for the key "SCNParticlePropertySize". For example to specify the how the size should be animated over the particle life duration.
see
// Property controllers.
// The keys for this directionary are listed in the "Particle Properties Name" section.
// The values are instances of SCNParticlePropertyController
#property(nonatomic, copy) NSDictionary *propertyControllers;
Wow. Just wow. You've thrown a lot of code at the wall just to see what sticks, but have you looked in the documentation?
The method description for SCNParticlePropertyController's initializer includes a code example that does almost exactly what you're asking for — it animates particle sizes. Reproduced here:
// 1. Create and configure an animation object.
let animation = CAKeyframeAnimation()
animation.values = [ 0.1, 1.0, 3.0, 0.5 ]
// 2. Create a property controller from the animation object.
let sizeController = SCNParticlePropertyController(animation: animation)
// 3. Assign the controller to a particle system, associating it with a particle property.
particleSystem.propertyControllers = [ SCNParticlePropertySize: sizeController ]
If you only need a from size and a to size instead of keyframes, you can use a CABasicAnimation in step 1.

Resources