Couldn't make a particle follow a path in spriteKit - objective-c

This is the animation in XCode SKEmitter editor (I want to achieve this on the iPhone) :
This is the animation on the iPhone (I don't want this animation):
Using this code:
let sparkEmmiter = SKEmitterNode(fileNamed: "fireflies.sks")
self.addChild(sparkEmmiter) // self is a SKScene
var circle: CGPathRef? = nil
circle = CGPathCreateWithEllipseInRect(CGRectMake(400, 200, 200, 200), nil)
let followTrack = SKAction.followPath(circle!, asOffset: false, orientToPath: true, duration: 3.0)
let followTrackForever = SKAction.repeatActionForever(followTrack)
//sparkEmmiter.runAction(followTrackForever)
sparkEmmiter.particleAction = followTrackForever;
This is the emitter settings:
I tried both runAction and particleAction by referring to this question, but it doesn't work as I wanted it to...
-----------------Update----------------------------------------------
Tried the solution mentioned by hamobi (still doesn't work) :
//If I were you:
// 1) I'd make a sprite and
let texture = SKTexture(imageNamed: "spark")
let mySprite = SKSpriteNode(texture: texture)
self.addChild(mySprite)
// 2) add the emitter in your first example as a child.
let sparkEmmiter = SKEmitterNode(fileNamed: "fireflies.sks")
mySprite.addChild(sparkEmmiter)
// 3) I'd set the emitters targetNode to the scene.
sparkEmmiter.targetNode = self
// 4) Then I'd just animate the sprite itself in an arc.
var circle: CGPathRef? = nil
circle = CGPathCreateWithEllipseInRect(CGRectMake(400, 200, 200, 200), nil)
let followTrack = SKAction.followPath(circle!, asOffset: false, orientToPath: true, duration: 3.0)
let followTrackForever = SKAction.repeatActionForever(followTrack)
//sparkEmmiter.runAction(followTrackForever)
sparkEmmiter.particleAction = followTrackForever;
-----------------Update 2----------------------------------------------
Got it! Thx to hamobi :D this is the result :D:D

If I were you I'd make a sprite and add the emitter in your first example as a child. I'd set the emitters targetNode to the scene. Then I'd just animate the sprite itself in an arc.
EDIT:
okay so the main thing you were missing is that you should forget about using particleAction. Make mySprite run the followTrackForever action.
heres my code
//If I were you:
// 1) I'd make a sprite and
let texture = SKTexture(imageNamed: "spark")
let mySprite = SKSpriteNode(texture: texture)
mySprite.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(mySprite)
// 2) add the emitter in your first example as a child.
let sparkEmmiter = SKEmitterNode(fileNamed: "fireflies.sks")
mySprite.addChild(sparkEmmiter)
// 3) I'd set the emitters targetNode to the scene.
sparkEmmiter.targetNode = self
// 4) Then I'd just animate the sprite itself in an arc.
var circle: CGPathRef? = nil
circle = CGPathCreateWithEllipseInRect(CGRectMake(100, 200, 200, 200), nil)
let followTrack = SKAction.followPath(circle!, asOffset: false, orientToPath: true, duration: 3.0)
let followTrackForever = SKAction.repeatActionForever(followTrack)
mySprite.runAction(followTrackForever)
screenshot of my particle
my particle in action

Related

AGSMAPView callout not showing on touch point

I am new to ARCGIS. Any help will be appreciated.
I am showing callout on didtap delegate Like this
func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
isFromSearch = false
MBProgressHUD.showAdded(to: self.view, animated: true)
self.mapView.identifyLayers(atScreenPoint: screenPoint, tolerance: 12, returnPopupsOnly: false, maximumResultsPerLayer: 10) { (identifyLayerResults: [AGSIdentifyLayerResult]?, error: Error?) in
//check for errors and ensure identifyLayerResults is not nil
MBProgressHUD.hide(for: self.view, animated: true)
if let error = error {
print(error)
return
}
guard let identifyLayerResults = identifyLayerResults else { return }
// iterate the identify layer results
guard identifyLayerResults.count > 0 else {return}
guard identifyLayerResults[0].sublayerResults.count > 0 else {return}
guard identifyLayerResults[0].sublayerResults[0].geoElements.count > 0 else {return}
let result = identifyLayerResults[0].sublayerResults[0].geoElements[0].attributes
self.identifyLayerResult = identifyLayerResults[0]
var title: String? = nil
var subtitle: String? = nil
if ((result["SiteCode"] as? String) != nil) && ((result["SiteName"] as? String) != nil){
title = (result["SiteCode"] as? String)
subtitle = (result["SiteName"] as? String)
}
else {
title = (result["company"] as? String)
subtitle = (result["identifier"] as? String)
}
self.mapView.callout.title = title
self.mapView.callout.detail = subtitle
self.mapView.callout.show(at: mapPoint, screenOffset: .zero, rotateOffsetWithMap: false, animated: true)
}
}
Everything is Working fine first time . But User can also search for places using REST API
and then mapview is moves to that point and show callout
https://******/arcgis/rest/services/Google/MobileiOS3/MapServer/find?
It returns Site and I create ViewPoint using Latitude and Longitude and show callout with zoom out and zoom in animation Code is given below
let pointView = AGSViewpoint(latitude: center.latitude, longitude: center.longitude, scale: 12E7)
self.mapView.setViewpoint(pointView, duration: 2) { (value) in
let pointView1 = AGSViewpoint(latitude: center.latitude, longitude: center.longitude, scale: 12E4)
self.mapView.setViewpoint(pointView1, duration: 2) { (true) in
let wgs84 = AGSSpatialReference(wkid: 4236)
let point = AGSPoint(x: center.latitude, y: center.longitude, spatialReference: wgs84)
let marker = AGSPictureMarkerSymbol(image: UIImage(named: "BluePushpin.png")!)
marker.leaderOffsetX = 9
marker.leaderOffsetY = -16
let graphics = AGSGraphic(geometry: point, symbol: marker, attributes: nil)
self.mGraphicOverlay.graphics.add(graphics)
let cgPoint = CGPoint(x: self.mapView.center.x, y: self.mapView.center.y - (self.mapView.callout.frame.height + 33))
print(cgPoint)
self.mapView.callout.show(at: graphics.geometry as! AGSPoint, screenOffset: cgPoint, rotateOffsetWithMap: false, animated: true)
}
}
After that when I tap on map any point Callout always shows to top Left Corner While first time didtap delegate was working fine
When I debug code and print callout frame it always shows zero x and zero y
There are a few things going on here:
Firstly, I think you've got the wrong spatial reference. You're using 4236 but WGS84 is 4326.
Note, you can avoid this type of typo by just referencing AGSSpatialReference.wgs84(), so you could say this:
let point = AGSPoint(x: center.latitude, y: center.longitude, spatialReference: .wgs84())
But look closely at that: you're also using latitude as X and longitude as Y. It's unfortunately confusing, but when you create an x,y point you need to specify longitude,latitude, not latitude,longitude:
let point = AGSPoint(x: center.longitude, y: center.latitude, spatialReference: .wgs84())
It's a common mistake. We have a helper function to simplify working with lat/lon source data:
AGSPointMakeWGS84(center.latitude, center.longitude)
You're also doing a bit more work than you need to (especially in forcing spatial reference conversions) and are introducing a few potential areas where errors could creep in.
So, assuming you actually need to set the map scale twice (I guess you are zooming to the location, and then zooming in a bit to focus attention), you could try something like this, which seems much less prone to errors:
let centerPoint = AGSPointMakeWGS84(center.latitude, center.longitude)
self.mapView.setViewpoint(AGSViewpoint(center: centerPoint, scale: 12E7), duration: 2) { _ in
self.mapView.setViewpoint(AGSViewpoint(center: centerPoint, scale: 12E4), duration: 2) { _ in
let marker = AGSPictureMarkerSymbol(image: UIImage(named: "BluePushpin.png")!)
marker.leaderOffsetX = 9
marker.leaderOffsetY = -16
let graphics = AGSGraphic(geometry: centerPoint, symbol: marker, attributes: nil)
self.mGraphicOverlay.graphics.add(graphics)
let cgPoint = CGPoint(x: self.mapView.center.x, y: self.mapView.center.y - (self.mapView.callout.frame.height + 33))
print(cgPoint)
self.mapView.callout.show(at: centerPoint, screenOffset: cgPoint, rotateOffsetWithMap: false, animated: true)
}
}
I confess I'm not entirely sure what your callout screenOffset calculation is doing, but I've left that as is.
I might also suggest adding the graphic before you animate the view, or after the first animation and before the second one (i.e. you're looking at the right location but have yet to zoom in a bit), but that's up to you.
Also, could I suggest that you post questions like this over at the ArcGIS Runtime SDK for iOS forum? We do monitor this space if you use the right tags, but you'll generally get more eyes on your questions over there.

ARKit – Spatial Audio barely changes the volume over distance

I created a SCNNode and added an Audio to it.
It is a Mono audio. Everything is set up correctly.
It is working as Spatial Audio, that's not the problem.
The problem is that as i get closer or far away it barely changes the volume. I know it changes if i get very very far away, but it's nothing like Apple demonstrated here:
https://youtu.be/d9kb1LfNNU4?t=23
Some other games i see the audio volume really changing from one step distance.
With mine, with one step you can't even tell the volume changed. You need at least 4 steps.
Anyone has any clue why?
Code bellow:
SCNNode *audioNode = [[SCNNode alloc] init];
SCNAudioSource *audioSource = [[SCNAudioSource alloc] initWithFileNamed:audioFileName];
audioSource.loops = YES;
[audioSource load];
audioSource.volume = 0.05; // <-- i used different values. won't change much either
audioSource.positional = YES;
//audioSource.shouldStream = NO; // <-- makes no difference
[audioNode addAudioPlayer:[SCNAudioPlayer audioPlayerWithSource:audioSource]];
[audioNode runAction:[SCNAction playAudioSource:audioSource waitForCompletion:NO] completionHandler:nil];
[massNode addChildNode:audioNode];
Maybe scale of the nodes?
The whole scene is the size of around 4 feet.
When i add an object i usually scale it to 0.005 (otherwise it gets way too big).
But i also tried with one that was already in the right size from .scn file.
It shouldn't affect anything tho, since the result is a coffee table size scene and i can see the objects alright.
Updated.
Here's a working code for controlling sound's decay (works in iOS and macOS):
import AVFoundation
import ARKit
class ViewController: UIViewController, AVAudioMixing {
#IBOutlet var sceneView: SCNView!
// #IBOutlet var sceneView: ARSCNView!
func destination(forMixer mixer: AVAudioNode,
bus: AVAudioNodeBus) -> AVAudioMixingDestination? {
return nil
}
var volume: Float = 0.0
var pan: Float = 0.0
var sourceMode: AVAudio3DMixingSourceMode = .bypass
var pointSourceInHeadMode: AVAudio3DMixingPointSourceInHeadMode = .bypass
var renderingAlgorithm = AVAudio3DMixingRenderingAlgorithm.sphericalHead
var rate: Float = 1.2
var reverbBlend: Float = 40.0
var obstruction: Float = -100.0
var occlusion: Float = -100.0
var position = AVAudio3DPoint(x: 0, y: 0, z: 10)
let audioNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let myScene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 0)
myScene.rootNode.addChildNode(cameraNode)
// let sceneView = view as! SCNView
sceneView.scene = myScene
sceneView.backgroundColor = UIColor.orange
let myPath = Bundle.main.path(forResource: "Mono_Audio", ofType: "mp3")
let myURL = URL(fileURLWithPath: myPath!)
let mySource = SCNAudioSource(url: myURL)!
mySource.loops = true
mySource.isPositional = true // Positional Audio
mySource.shouldStream = false // FALSE for Positional Audio
mySource.volume = volume
mySource.reverbBlend = reverbBlend
mySource.rate = rate
mySource.load()
let player = SCNAudioPlayer(source: mySource)
let sphere: SCNGeometry = SCNSphere(radius: 0.1)
let sphereNode = SCNNode(geometry: sphere)
sphereNode.addChildNode(audioNode)
myScene.rootNode.addChildNode(sphereNode)
audioNode.addAudioPlayer(player)
sceneView.audioEnvironmentNode.distanceAttenuationParameters.maximumDistance = 2
sceneView.audioEnvironmentNode.distanceAttenuationParameters.referenceDistance = 0.1
sceneView.audioEnvironmentNode.renderingAlgorithm = .auto
// sceneView.audioEnvironmentNode.reverbParameters.enable = true
// sceneView.audioEnvironmentNode.reverbParameters.loadFactoryReverbPreset(.plate)
let hither = SCNAction.moveBy(x: 0, y: 0, z: 1, duration: 2)
let thither = SCNAction.moveBy(x: 0, y: 0, z: -1, duration: 2)
let sequence = SCNAction.sequence([hither, thither])
let loop = SCNAction.repeatForever(sequence)
sphereNode.runAction(loop)
}
}
And, yes, you're absolutely right – there are some obligatory settings.
But there are 7 of them:
use AVAudioMixing protocol with its stubs (properties and methods).
use MONO audio file.
use source.isPositional = true.
use source.shouldStream = false.
assign maximumDistance value to distanceAttenuationParameters property.
assign referenceDistance value to distanceAttenuationParameters property.
and location of mySource.load() is very important in your code.
P.S. If the aforementioned tips didn't help you, then use additional instance properties to make your sound even quieter using a graph, obstacles and orientation of implicit listener:
var rolloffFactor: Float { get set } // attenuation's graph, default = 1
var obstruction: Float { get set } // default = 0.0
var occlusion: Float { get set } // default = 0.0
var listenerAngularOrientation: AVAudio3DAngularOrientation { get set } //(0,0,0)
It definitely works if you'll write it in Objective-C.
In this example the distance of audioNode is 1 meter away from a listener.
If none of the above answers seem to work, try the following code:
sceneView.audioEnvironmentNode.reverbParameters.enable = true
And if even these seem to barely work, or if you wanna optimal performance, there is a property called level where you can set the level of how spatial the code can be.
sceneView.audioEnvironmentNode.reverbParameters.level = 40
(the level of the reverbParameters ranges between -40 to 40 parameters)

SceneKit avoid lighting on specific node

In SceneKit I'm building a node made of lines to draw the XYZ axes at the center of the scene, like in Cinema4D.
I would like these 3 nodes not to participate to the global lighting and be viewable even if the light is dark / inexistent / too strong. In the picture below you can see that the Z axis appears too heavily lighten and can't be seen.
Is there a way to stop a node participating to the scene's lighting, like with category masks for physics?
In this case, how would the node be lighten in order for it to appear anyway?
SCNLight has a categoryBitMask property. This lets you choose which nodes are affected by the light (Although this is ignored for ambient lights). You could have 2 light source categories, one for your main scene, and another that only affects your lines.
Here is a simple example with 2 nodes, each lit with a different colour light:
struct LightType {
static let light1:Int = 0x1 << 1
static let light2:Int = 0x1 << 2
}
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene(named: "art.scnassets/scene.scn")!
let lightNode1 = SCNNode()
lightNode1.light = SCNLight()
lightNode1.light!.type = .omni
lightNode1.light!.color = UIColor.yellow
lightNode1.position = SCNVector3(x: 0, y: 10, z: 10)
lightNode1.light!.categoryBitMask = LightType.light1
scene.rootNode.addChildNode(lightNode1)
let lightNode2 = SCNNode()
lightNode2.light = SCNLight()
lightNode2.light!.type = .omni
lightNode2.light!.color = UIColor.red
lightNode2.position = SCNVector3(x: 0, y: 10, z: 10)
lightNode2.light!.categoryBitMask = LightType.light2
scene.rootNode.addChildNode(lightNode2)
let sphere1 = scene.rootNode.childNode(withName: "sphere1", recursively: true)!
sphere1.categoryBitMask = LightType.light1
let sphere2 = scene.rootNode.childNode(withName: "sphere2", recursively: true)!
sphere2.categoryBitMask = LightType.light2
let scnView = self.view as! SCNView
scnView.scene = scene
}
}
I think it would be much easier to set the material's lightning model to constant.
yourNode.geometry?.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant

SpriteKit make two force on a body with joint

The problem: I want balloons rise up in the air. They can collide with one another and they can rotate. With a gravitiy in upright direction that works fine.
But now I want to connect a basket to each balloon. Doing so will result in the basket "flying" up like the balloon. But in real life the basket should be "heavier" than the balloon, so it would always point downwards to earth.
How would I achieve that ?
One way to simulate a balloon with a basket is to set gravity to be in the downward direction to pull the basket toward the bottom of the scene and apply a buoyant force to the balloon to create lift. Here's an example of how to do that:
Define the constants
#define kRandMax 0x7fffffff
#define kNumBalloons 5
Create the balloons and baskets
self.physicsWorld.gravity = CGVectorMake(0, -3);
for (int i=0;i<kNumBalloons;i++) {
// Create a balloon near the bottom of the scene with a random x position
SKShapeNode *balloon = [SKShapeNode shapeNodeWithCircleOfRadius:16];
balloon.fillColor = [SKColor redColor];
balloon.name = #"balloon";
balloon.physicsBody.allowsRotation = NO;
CGFloat rand1 = arc4random_uniform(kRandMax) / (double)kRandMax;
// A value in (-view.frame.size.width/2, view.frame.size.width/2)
CGFloat xOffset = (rand1 - 0.5) * view.frame.size.width / 2;
balloon.position = CGPointMake (CGRectGetMidX(view.frame)+xOffset,
CGRectGetMinY(view.frame)+50);
balloon.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:16];
[self addChild:balloon];
// Create the basket
SKSpriteNode *basket = [SKSpriteNode spriteNodeWithColor:[SKColor brownColor] size:CGSizeMake(8, 8)];
basket.position = CGPointMake(balloon.position.x, balloon.position.y-24);
basket.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:basket.size.width/2];
[self addChild:basket];
// Connect the balloon and basket with an SKPhysicsJointPin
SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:balloon.physicsBody
bodyB:basket.physicsBody anchor:balloon.position];
[self.physicsWorld addJoint:joint];
}
In the update method, apply a force to each balloon to create lift
-(void)update:(CFTimeInterval)currentTime {
// Apply buoyant force to all balloons in the scene
[self enumerateChildNodesWithName:#"//balloon" usingBlock:^(SKNode *node, BOOL *stop){
// Adjust force as needed
[node.physicsBody applyForce:CGVectorMake(0, 17.2)];
}];
}
Look into SKPhysicsBody documentations (can be found here).
Look into Defining a Body’s Physical Properties section.
Every physics body has properties (such as mass) that you want to play with, in order to get a more 'realistic' behaviour.
If you want a less general answer,
Post some code examples.
Good luck mate.
I have now a solution like this:
I create a forcefield going up
func createForceField(strength:Float) {
// add force field
for i in 1...9 {
let field = SKFieldNode.electricField()
field.physicsBody?.categoryBitMask = PhysikcsCategory.ForceField
field.position = CGPointMake(frame.size.width*0.1*CGFloat(i), -1000)
field.strength = strength
addChild(field)
}
}
Before starting the game I turn on gravity and forcefield
override init(size: CGSize) {
super.init(size: size)
// Set-Up forcefield
createForceField(0.1)
self.physicsWorld.gravity = CGVectorMake(0, -0.09)
startGame()
}
here I create the balloons with a rope and tag, the balloon is charged so it will react to the forcefield
func createBalloon(vokabel:Vokabel) {
let x = self.frame.width*0.2
let y = self.frame.height*0.2
// Balloon
var balloon = BalloonNode()
balloon.size = CGSizeMake(balloon.size.width*0.7, balloon.size.height*0.7)
balloon.position = CGPointMake(x-6,y)
balloon.name = "SPRITE"
balloon.vokabel = vokabel
balloon.score = 50
balloonLayer.addChild(balloon)
// Special Start
var specialStar = SKSpriteNode(imageNamed: "Star")
specialStar.size = CGSizeMake(balloon.size.width*0.7, balloon.size.height*0.7)
specialStar.hidden = true
specialStar.name = "STAR"
balloon.addChild(specialStar)
// Rope
let rope = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 2, height: 15))
rope.position = CGPointMake(x, y-balloon.size.height/2-rope.size.height/2)
rope.alpha = 0.2
balloonLayer.addChild(rope)
// Tag
var labelText = getLabelText(vokabel)
let tag = createTagWithText(labelText)
tag.position = CGPointMake(x, y-balloon.size.height/2-rope.size.height)
balloonLayer.addChild(tag)
self.physicsWorld.gravity=CGVectorMake(0.0, -0.3)
// physical bodies
balloon.physicsBody = SKPhysicsBody(rectangleOfSize: balloon.size)
rope.physicsBody = SKPhysicsBody(rectangleOfSize: rope.size)
tag.physicsBody = SKPhysicsBody(rectangleOfSize: tag.size)
// physical forces
balloon.physicsBody?.allowsRotation = false
balloon.physicsBody?.charge = 0.5
tag.physicsBody?.allowsRotation = false
tag.physicsBody?.linearDamping = 1.0
rope.physicsBody?.linearDamping = 1.0
// anchor Points
let anchorBalloonRope = CGPointMake(x, y-balloon.size.height/2)
let anchorRopeTag = CGPointMake(x, y-balloon.size.height/2-rope.size.height)
// create joints
let joint = SKPhysicsJointPin.jointWithBodyA(balloon.physicsBody, bodyB: rope.physicsBody, anchor: anchorBalloonRope)
let joint_rope_tag = SKPhysicsJointPin.jointWithBodyA(rope.physicsBody, bodyB: tag.physicsBody, anchor: anchorRopeTag)
// add joints to physic
self.physicsWorld.addJoint(joint)
self.physicsWorld.addJoint(joint_rope_tag)
// give it a horizontal push
balloon.physicsBody?.applyImpulse(CGVectorMake(10.0, 0.0))
// BalloonNode ( is just a layer, has no special position so it is the same coordinate system as the gamelayer)
let node = BalloonNode()
node.vokabel = vokabel
node.score = SCORE_STANDARD
node.name = "BALLOON"
node.rope = rope
node.tag = tag
}

Swift/Objective c - rotate/spin CAShapeLayer on the spot

Im trying to animate/spin a circle around it self.
did is what i have tried,but it just rotating on the entire screen.. i dont need it to move, i just need to to spin on the spot.
let circleLayer: CAShapeLayer!\outside the method
vlet rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.fromValue = Float(0.0)
rotate.toValue = Float(2.0 * M_PI)
rotate.duration = 1.0
rotate.cumulative = true
rotate.repeatCount = 1
rotate.removedOnCompletion = false
rotate.fillMode = kCAFillModeForward
circleLayer.addAnimation(rotate, forKey: "transform.rotation.z")
any idea why it's moving and to spinning/rotating on the spot?
found the solution, i'll just rotate the view itself with diffrent animation :
UIView.animateWithDuration(0.95, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.circleView.transform = CGAffineTransformMakeRotation(self.sumer)
}, completion: nil)
enjoy