How to remove a SCNTransformConstraint from node - swift

Hey im simply trying to figure out how to remove a SCNTransformConstraint from its node
I can't find anything and Apple's Developer website doesn't help is it possible to remove a constraint so the now physics and other forces/Impulse/Gravity can act on the node as normal.
Instead of the node being stuck to the constraint in definite
let constraint = SCNTransformConstraint(inWorldSpace: true, withBlock: {
node, matrix in
let newPosition = User.transform
return newPosition
})
Ball.constraints = [constraint]
// Error here
Ball.removeAllConstraints()

Related

How to get a SCNNode to face the current camera position?

I'm currently programming an AR-App, which starts with placing a SCNNode (table). First the app searches for a horizontal plane and whenever a surface was found, the object is shown, but not placed yet. While it is not placed, I want the object to face the camera at all times, but I'm having trouble to find the current position of the camera and also updating the object every frame.
Currently, the object is facing the world coordinates. So when I start the app and a horizontal plane was found - the object appears and faces to the starting point of the world coordinates (wherever I started the app)
Can anybody help me, how to get the vector from the camera position and make the object updates it's direction every frame?
var tableNode : SCNNode! // Node of the actual table
var trackingPosition = SCNVector3Make(0.0, 0.0, 0.0)
let trans = SCNMatrix4(hitTest.worldTransform)
self.trackingPosition = SCNVector3Make(trans.m41, trans.m42, trans.m43)
self.tableNode.position = self.trackingPosition
The functionality you are trying to implement is called billboarding. To achieve this effect you need to set the SCNLookAtConstraint using the scene's .pointOfView as the target.
you can remove
self.trackingPosition = SCNVector3Make(trans.m41, trans.m42, trans.m43)
self.tableNode.position = self.trackingPosition
and use:
// set constraint to force the node to always face camera
let constraint = SCNLookAtConstraint(target:sceneView.pointOfView)
constraint.isGimbalLockEnabled = true
self.tableNode.constraints = [constraint] // constraint can cause node appear flipped over X axis
You should implement ARSCNViewDelegate and use the renderer method renderer(_:updateAtTime:) where you every frame get the camera position from sceneView.pointOfView. This allows you to then use the SCNNode method look(at:) to transform the nodes coordinate system towards the camera.
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if let camera = sceneView.pointOfView {
tableNode.look(at: camera.worldPosition)
}
}

Occasional stuttering with SceneKit when touching the screen

When I interact with the screen the objects in my game start to stutter. My FPS is at 60 and doesn't drop but the stuttering is still prevalent. I believe my problem is how I'm animating the objects on screen(code below).If anybody could help I would appreciate it.
I have an x amount of nodes inside an array called _activePool. In the Update function I am moving the nodes x position inside _activePool, adding new nodes when the last node in _activePool position is <= 25 and removing the first node in _activePool if it's position is <= -25.
if _cycleIsActive{
for obj in _activePool{
//move the obj in _activePool
obj.position.x += Float(dt * self.speedConstant);
}
let lastObj = _activePool.last;
if (lastObj?.position.x)! + getWidthOfNode(node: lastObj!) + Float(random(min: 15, max: 20)) <= 25{
// get new obj(pattern) and add to _activePool
self.getPatternData(sequencePassedIn: selectedSeq, level: self._currentLevel, randomPattern: randomPattern());
}
let firstObj = _activePool.first;
if (firstObj?.position.x)! + getWidthOfNode(node: firstObj!) <= -25{
// remove object and return to specific pool
firstObj?.removeFromParentNode();
returnItems(item: firstObj!);
_activePool.removeFirst();
}
}
I create several arrays and add them to a dictionary
func activatePools(){
temp1Pool = ObjectPool(tag: 1, data: []);
dictPool[(temp1Pool?.tag)!] = temp1Pool;
temp2Pool = ObjectPool(tag: 2, data: []);
dictPool[(temp2Pool?.tag)!] = temp2Pool;
for i in 0... dictPool.count {
obstacleCreationFactory(factorySwitch: i);
}
}
Creating my obstacles(enemies)
func obstacleCreationFactory(factorySwitch: Int){
Enemies = Enemy();
switch factorySwitch {
case 0:
for _ in 0...100{
let blueEnemy = Enemies?.makeCopy() as! Enemy
blueEnemy.geometry = (Enemies?.geometry?.copy() as! SCNGeometry);
blueEnemy.geometry?.firstMaterial?.diffuse.contents = UIColor.blue;
blueEnemy.tag = 1;
temp1Pool?.addItemToPool(item: blueEnemy);
}
case 1:
for _ in 0...100{
let redEnemy = Enemies?.makeCopy() as! Enemy
redEnemy.geometry = (Enemies?.geometry?.copy() as! SCNGeometry);
redEnemy.geometry?.firstMaterial?.diffuse.contents = UIColor.red;
redEnemy.tag = 2;
temp2Pool?.addItemToPool(item: redEnemy);
}
default:
print("factory error");
}
}
Without being able to look at the rest of your code base it’s really difficult to guess what would be causing your issue.
If somewhere you are creating a ton of temporary objects in a loop somewhere, you might consider creating a local autorelease pool to prevent memory spikes. Here is a good article that describes why in some situations it’s a good idea.
You could also be calling some particularly expensive functions on a timer or something. It’s difficult to say.
In short, you should consider using Xcode’s Profiling tools (called Instruments). Specifically I would recommend using Time Profiler to examine what functions are taking the most time and causing those spikes.
Here is a great WWDC session video that shows how you can use the time profiler, I’d recommend regularly profiling your app, especially when you have an issue like this.

Detect other Spritenode within range of Spritenode?

I have a (moving) sprite node.
I'd like to detect other (moving) sprite nodes within a certain range of this node. Once one is detected, it should execute an action.
The playing an action part is no problem for me but I can't seem to figure out the within-range detection. Does have any ideas how to go about this?
A simple, but effective way to do this is comparing the position's in your scene's didEvaluateActions method. didEvaluateActions gets called once a frame (after actions have been evaluated but before physics simulation calculations are run). Any new actions you trigger will start evaluating on the next frame.
Since calculating the true distance requires a square root operation (this can be costly), we can write our own squaredDistance and skip that step. As long as our range/radius of detect is also squared, our comparisons will work out as expected. This example shows detect with a "true range" of 25.
// calculated the squared distance to avoid costly sqrt operation
func squaredDistance(p1: CGPoint, p2: CGPoint) -> CGFloat {
return pow(p2.x - p1.x, 2) + pow(p2.x - p1.x, 2)
}
// override the didEvaluateActions function of your scene
public override func didEvaluateActions() {
// assumes main node is called nodeToTest and
// all the nodes to check are in the array nodesToDetect
let squaredRadius: CGFloat = 25 * 25
for node in nodesToDetect {
if squareDistance(nodeToTest.position, p2: node.position) < squaredRadius {
// trigger action
}
}
}
If the action should only trigger once, you'll need to break out of the loop after the first detection and add some sort of check so it does not get triggered again on the next update without the proper cool down period. You may also need to convert the positions to the correct coordinate system.
Also, take a look at the documentation for SKScene. Depending on your setup, didEvaluateActions might not be the best choice for you. For example, if your game also relies on physics to move your nodes, it might be best to move this logic to didFinishUpdate (final callback before scene is rendered, called after all actions, physics simulations and constraints are applied for the frame).
Easiest way I can think of without killing performance is to add a child SKNode with an SKPhysicsBody for the range you want to hit, and use this new nodes contactBitMask to determine if they are in the range.
Something like this (pseudo code):
//Somewhere inside of setup of node
let node = SKNode()
node.physicsBody = SKPhysicsBody(circleOfRadius: 100)
node.categoryBitMask = DistanceCategory
node.contactBitMask = EnemyCategory
sprite.addNode(node)
//GameScene
func didBeginContact(...)
{
if body1 contactacted body2
{
do something with body1.node.parent
//we want parent because the contact is going to test the bigger node
}
}

Apply SCNAction to a node with constraints in SceneKit

I have a node that have a constraint; to be more specific, it has an SCNLookAtConstraint :
let constraint = SCNLookAtConstraint(target: scene.retriveNodeFromScene(cameraNodeName))
constraint.gimbalLockEnabled = false
self.constraints = [constraint]
But when I want to move node to other coordinates it just doesn't move. I don't receive any error, just nothing happens. I have made some tests, and if I remove constraint, everything is ok, node moves with no problem. So, what should I do? Is there any solution for my problem or should I find another way, equivalent with my constraint, but also compatible with SCNAction?
code for action:
let timeForAction = calculateTimerForSecoundAction()
let action = SCNAction.moveTo(calculateEndingLocation(), duration: timeForAction)
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC * timeForAction))
self.runAction(action)
explodeAfterReachingFinalPosition(timeForAction)
you could create a new intermediate node that you'll use for the action, and have your current node as a child node with the lookAt constraint.
Same issue as the original poster.
I solved it with MNAUGES suggestion.
The 'LookAt' constraint breaks/prevents the SCNActions from happening in the view. Interestingly, the camera node WAS moving - according to the console - just not updating in the view. I could also move the camera node in the view with a change to cameraNode.position = newPosition - but without the nice animated transition.
Parent your cameraNode to an emptyNode. Assign the lookAt constraint to the cameraNode and the moveTo SCNAction to the emptyNode.

How do run an SKAction on a group of SKSpriteNodes with the same name?

I'm making a game in Xcode 6 using spriteKit and swift. I have a plane on scene, and to make it look like its moving, I'm create clouds off the scene to the left of the screen. I then us an SKAction to move the cloud to the right side of the screen. This works great. You then click to jump off the plane, and the plane moves up off the scene. I then have it start making the clouds on the bottom of the scene, then they move up off the top of the scene, but the problem is, the already existing clouds still have to move to the right side of the screen. My question is, how do I make it so all of the existing clouds stop their action that moves them to the right, then begin to move up exactly where they are? How do I access the group of existing clouds all at the same time once they have been created? I also want the clouds to slow down after you have jumped when you tap the screen to open your parachute, but this should be able to be done by ending the SKAction that moves the clouds, then using another SKAction on them that moves them up slower, but I don't know how to access a group of SKSpriteNodes.
Here is the code that I have to make the clouds:
//This is how the cloud is first declared at the top of the .swift file
var cloud = SKSpriteNode()
//This is the function that runs every certain interval through an NSTimer
func createCloud()
{
cloud = SKSpriteNode(imageNamed: "cloud")
cloud.xScale = 1.25
cloud.yScale = cloud.xScale/2
cloud.zPosition = -1
self.addChild(cloud)
if personDidJump == false
{
let moveRight = SKAction.moveToX(CGRectGetWidth(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(-CGRectGetWidth(cloud.frame), randomNumber)
cloud.runAction(moveRight)
}
if personDidJump == true
{
let moveUp = SKAction.moveToY(CGRectGetHeight(self.frame) + CGRectGetWidth(cloud.frame), duration: cloudSpeed)
var apple = CGRectGetWidth(self.frame)
var randomNumber:CGFloat = CGFloat(arc4random_uniform(UInt32(apple)))
cloud.position = CGPointMake(randomNumber, -CGRectGetHeight(cloud.frame))
cloud.runAction(moveUp)
}
}
Also, should I be worried about deleting the clouds when they move off the scene? Or can I just leave them there because you can't see them, and from what I've seen, they don't lag you.
Any help would be greatly appreciated. Thank you.
-Callum-
Put your clouds in an array. When your player jumps (or whenever you need to move them) run through the array and run your action on each cloud.
stackoverflow.com/a/24213529/3402095
^ That is where I found my answere ^
When you make a node give it a name:
myNode.name = "A Node Name"
self.addChild(myNode)
If there are a lot of these nodes, later you can change properties or preform SKAcions on these nodes or do whatever you want to do by using enumerateChildNodesWithName:
self.enumerateChildNodesWithName("A Node Name")
{
myNode, stop in
myNode.runAction(SKAction.moveToY(CGRectGetHeight(self.Frame), duration: 1)
}
I hope this is useful to whoever may need it.