Track the position of SKSpriteNode while doing a SKAction moveTo - ios

I'm trying to figure a way to track a postions for multiple nodes, that spwan randomly on the screen so i can make a changes to them while moving when the reach random postion.
the nodes just move along the x axis and i want to be able to generate random number from 0 to postion.x of the ball, and change the color when it reachs the postion
override func update(currentTime: CFTimeInterval)
i tried tacking changes in update method but as soon as new node appers i lost track of the previos one
i also tried
let changecolor = SKAction.runBlock{
let wait = SKAction.waitForDuration(2, withRange: 6)
let changecoloratpoint = SKAction.runBlock { self.changecolorfunc(self.ball)}
let sequence1 = SKAction.sequence([wait, changecoloratpoint])
self.runAction(SKAction.repeatAction(sequence1, count: 3))
}
but it doesn't give me any control over the random postion

You have already all you needed.
Suppose you have a reference for your node:
var sprite1: SKSpriteNode!
And you want to spawn it to a random position (an example method..):
self.spawnToRandomPos(sprite1)
And suppose you want to moveTo your sprite1:
self.sprite1.runAction( SKAction.moveToY(height, duration: 0))
Everytime you check his position you know where is it:
print(sprite1.position)
So to know always your sprite1 position you could do this code and you see all it's movements:
override func update(currentTime: CFTimeInterval) {
print(sprite1.position)
}
P.S.:
In case you dont have references about your object spawned you can also give a name to a generic object based for example by a word followed to a counter (this is just an example):
for i in 0..<10 {
var spriteTexture = SKTexture(imageNamed: "sprite.png")
let sprite = SKSpriteNode(texture: spriteTexture, size: spriteTexture.size)
sprite.name = "sprite\(i)"
self.addChild(sprite)
}
After this to retrieve/re-obtain your sprite do:
let sprite1 = self.childNodeWithName("sprite1")

There are probably a hundred different ways of doing this. Here is how I would do it.
in your update func
checkForCollisions()
this will just scroll through "obstacles" that you randomly generate, and place whoever you want the color trigger to happen. They can be anything you want just change the name to match. if they overlap your "ball" then you can color one or the other.
func checkForCollisions() {
self.enumerateChildNodesWithName("obstacles") { node, stop in
if let obstacle: Obstacle = node as? Obstacle {
if self.ball.intersectsNode(obstacle) {
//color node or ball whichever you want
}
}
}
}

Related

Can't detect collision between rootNode and pointOfView child nodes in SceneKit / ARKit

In an AR app, I want to detect collisions between the user walking around and the walls of an AR node that I construct. In order to do that, I create an invisible cylinder right in front of the user and set it all up to detect collisions.
The walls are all part of a node which is a child of sceneView.scene.rootNode.
The cylinder, I want it to be a child of sceneView.pointOfView so that it would always follow the camera.
However, when I do so, no collisions are detected.
I know that I set it all up correctly, because if instead I set the cylinder node as a child of sceneView.scene.rootNode as well, I do get collisions correctly. In that case, I continuously move that cylinder node to always be in front of the camera in a renderer(updateAtTime ...) function. So I do have a workaround, but I'd prefer it to be a child of pointOfView.
Is it impossible to detect collisions if nodes are children of different root nodes?
Or maybe I'm missing something in my code?
The contactDelegate is set like that:
sceneView.scene.physicsWorld.contactDelegate = self so maybe this only includes sceneView.scene, but will exclude sceneView.pointOfView???
Is that the issue?
Here's what I do:
I have a separate file to create and configure my cylinder node which I call pov:
import Foundation
import SceneKit
func createPOV() -> SCNNode {
let pov = SCNNode()
pov.geometry = SCNCylinder(radius: 0.1, height: 4)
pov.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
pov.opacity = 0.3 // will be set to 0 when it'll work correctly
pov.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
pov.physicsBody?.isAffectedByGravity = false
pov.physicsBody?.mass = 1
pov.physicsBody?.categoryBitMask = BodyType.cameraCategory.rawValue
pov.physicsBody?.collisionBitMask = BodyType.wallsCategory.rawValue
pov.physicsBody?.contactTestBitMask = BodyType.wallsCategory.rawValue
pov.simdPosition = simd_float3(0, -1.5, -0.3) // this position only makes sense when setting as a child of pointOfView, otherwise the position will always be changed by renderer
return pov
}
Now in my viewController.swift file I call this function and set is as a child of either root nodes:
pov = createPOV()
sceneView.pointOfView?.addChildNode(pov!)
(Don't worry right now about not checking and unwrapping).
The above does not detect collisions.
But if instead I add it like so:
sceneView.scene.rootNode.addChildNode(pov!)
then collisions are detected just fine.
But then I need to always move this cylinder to be in front of the camera and I do it like that:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let currentPosition = pointOfView.simdPosition
let currentTransform = pointOfView.simdTransform
let orientation = SCNVector3(-currentTransform.columns.2.x, -currentTransform.columns.2.y, -currentTransform.columns.2.z)
let currentPositionOfCamera = orientation + SCNVector3(currentPosition)
DispatchQueue.main.async {
self.pov?.position = currentPositionOfCamera
}
}
For completeness, here's the code I use to configure the node of walls in ViewController (they're built elsewhere in another function):
node?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: node!, options: nil))
node?.physicsBody?.isAffectedByGravity = false
node?.physicsBody?.mass = 1
node?.physicsBody?.damping = 1.0 // remove linear velocity, needed to stop moving after collision
node?.physicsBody?.angularDamping = 1.0 // remove angular velocity, needed to stop rotating after collision
node?.physicsBody?.velocityFactor = SCNVector3(1.0, 0.0, 1.0) // will allow movement only in X and Z coordinates
node?.physicsBody?.angularVelocityFactor = SCNVector3(0.0, 1.0, 0.0) // will allow rotation only around Y axis
node?.physicsBody?.categoryBitMask = BodyType.wallsCategory.rawValue
node?.physicsBody?.collisionBitMask = BodyType.cameraCategory.rawValue
node?.physicsBody?.contactTestBitMask = BodyType.cameraCategory.rawValue
And here's my physycsWorld(didBegin contact) code:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue || contact.nodeB.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue {
print("Begin COLLISION")
contactBeginLabel.isHidden = false
}
}
So I print something to the console and I also turn on a label on the view so I'll see that collision was detected (and the walls indeed move as a whole when it works).
So Again, it all works fine when the pov node is a child of sceneView.scene.rootNode, but not if it's a child of sceneView.pointOfView.
Am I doing something wrong or is this a limitation of collision detection?
Is there something else I can do to make this work, besides the workaround I already implemented?
Thanks!
Regarding the positioning of your cyliner:
instead to use the render update at time, you better use a position constraint for your cylinder node to move with the point of view. the result will be the same, as if it were a child of the point of view, but collisions will be detected, because you add it to the main rootnode scenegraph.
let constraint = SCNReplicatorConstraint(target: pointOfView) // must be a node
constraint.positionOffset = positionOffset // some SCNVector3
constraint.replicatesOrientation = false
constraint.replicatesScale = false
constraint.replicatesPosition = true
cylinder.constraints = [constraint]
There is also an influence factor you can configure. By default the influence is 100%, the position will immediatly follow.

SpriteKit: how to smoothly animate SKCameraNode while tracking node but only after node moves Y pixels?

This question and others discuss how to track a node in SpriteKit using a SKCameraNode.
However, our needs vary.
Other solutions, such as updating the camera's position in update(_ currentTime: CFTimeInterval) of the SKScene, do not work because we only want to adjust the camera position after the node has moved Y pixels down the screen.
In other words, if the node moves 10 pixels up, the camera should remain still. If the node moves left or right, the camera should remain still.
We tried animating the camera's position over time instead of instantly, but running a SKAction against the camera inside of update(_ currentTime: CFTimeInterval) fails to do anything.
I just quickly made this. I believe this is what you are looking for?
(the actual animation is smooth, just i had to compress the GIF)
This is update Code:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
SKShapeNode *ball = (SKShapeNode*)[self childNodeWithName:#"ball"];
if (ball.position.y>100) camera.position = ball.position;
if (fabs(ball.position.x-newLoc.x)>10) {
// move x
ball.position = CGPointMake(ball.position.x+stepX, ball.position.y);
}
if (fabs(ball.position.y-newLoc.y)>10) {
// move y
ball.position = CGPointMake(ball.position.x, ball.position.y+stepY);
}
}
I would not put this in the update code, try to keep your update section clutter free, remember you only have 16ms to work with.
Instead create a sub class for your character node, and override the position property. What we are basically saying is if your camera is 10 pixels away from your character, move towards your character. We use a key on our action so that we do not get multiple actions stacking up and a timing mode to allow for the camera to smoothly move to your point, instead of being instant.
class MyCharacter : SKSpriteNode
{
override var position : CGPoint
{
didSet
{
if let scene = self.scene, let camera = scene.camera,(abs(position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}
}
}
Edit: #Epsilon reminded me that SKActions and SKPhysics access the variable directly instead of going through the stored property, so this will not work. In this case, do it at the didFinishUpdate method:
override func didFinishUpdate()
{
//character should be a known property to the class, calling find everytime is too slow
if let character = self.character, let camera = self.camera,(abs(character.position.y - camera.position.y) > 10)
{
let move = SKAction.move(to: character.position, duration:0.1)
move.timingMode = .easeInEaseOut
camera.run(move,withKey:"moving")
}
}

Swift Collision Not Working After SKReferenceNode Scale

I have a weird problem where my SKReferenceNode does not collide properly after being scaled up. It collides well in the center, but it ignores collisions and contacts on the edges.
Here is the first photo of the scene. The SKReferenceNode was scaled up significantly, and as seen in this photo, does not collide correctly on the edges. The PhysicsBody appears to be correct (with showPhysics on), yet the ball refuses to collide.
The SKReferenceNode is using an alpha collision mask, because I need to change it to a larger sprite in the future to do animations and such. Additionally, non-scaled objects work completely fine. Finally, after the ball collides with the center, which does work, and the block is reset, collisions start working as expected again. The code to fix it is probably in the reset function, but I reset everything before the level is loaded, so this wouldn't make sense. Here is a part of my reset code:
func reset() {
physicsWorld.gravity = CGVectorMake(0, -9.8)
for grav in gravityBlock {
grav.reset()
}
gravity = -9.8
//Resets blocks
for blocks in destroyedBlocks { //the blocks are stored in destroyedBlocks when collided with
blocks.reset()
}
destroyedBlocks = []
/*Irrelevant code removed*/
}
Here's my blocks.reset() function:
override func reset() {
super.reset()
self.physicsBody?.categoryBitMask = 1
removeAllActions()
self.texture = self.text
shadow.hidden = false
self.alpha = 0
shadow.alpha = 0
let appear = SKAction(named: "Appear")!
self.runAction(appear)
shadow.runAction(SKAction.fadeInWithDuration(0.25))
}
Here is super.reset()
func reset() {
self.hidden = false
state = .NotBroken
}
Thanks so much!
When you scale the sprite you are just changing it's visual representation. The Physics Body stays the same. That's why you are getting the "strange" during the collisions.
You can see that showing the physics uses in your scene, just open GameViewController and add this line
skView.showsPhysics = true
inside viewDidLoad, just after this line
skView.showsNodeCount = true
Then run again your game, the physics boundaries now will be highlighted.

SKEmitterNode isn't removing itself from parent?

I added SKEmitterNode as a childNode of my mainScene and than expected that It would be removed when particleLifetime ends, described like apple docs.
Added emitters like this;
var emitterPath : String = NSBundle.mainBundle().pathForResource("ShipFire", ofType: "sks")!
var emitter : SKEmitterNode = NSKeyedUnarchiver.unarchiveObjectWithFile(emitterPath) as! SKEmitterNode
emitter.position = position
emitter.particleLifetime = 0.1;
self.addChild(emitter)
My SKEmitterNode properties like image below
When I run it emitters aren't removed from screen.
I don't know what to add more if you need more information please ask any help would be appreciated thanks.
particleLifetime determines the average lifetime of a particle, in seconds. That doesn't affect on removal of SKEmitterNode from parent.
numOfParticlesToEmit which refers to Maximum field in Particles area of Particle Editor determines the number of particles that emitter should emit before stopping. That doesn't affect on removal of SKEmitterNode from parent too. Also note that you've set 0 in this field which will enable infinitely emitting.
So, if you want to remove node from parent when emitter is done with emitting, you can set the number of particles to emit (field called Maximum in Particles area inside editor) and run an SKAction sequence which will:
start an emitter
wait for some duration of time
and remove the emitter from parent (at this point emitter should finish with emitting)
Here is an simple example to show you how to do this with SKAction sequence:
class GameScene: SKScene {
let emitter : SKEmitterNode = NSKeyedUnarchiver.unarchiveObjectWithFile(NSBundle.mainBundle().pathForResource("MyParticle", ofType: "sks")!) as SKEmitterNode
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.blackColor()
}
func addEmitter(position:CGPoint){
var emitterToAdd = emitter.copy() as SKEmitterNode
emitterToAdd.position = position
let addEmitterAction = SKAction.runBlock({self.addChild(emitterToAdd)})
var emitterDuration = CGFloat(emitter.numParticlesToEmit) * emitter.particleLifetime
let wait = SKAction.waitForDuration(NSTimeInterval(emitterDuration))
let remove = SKAction.runBlock({emitterToAdd.removeFromParent(); println("Emitter removed")})
let sequence = SKAction.sequence([addEmitterAction, wait, remove])
self.runAction(sequence)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch: AnyObject? = touches.anyObject()
let location = touch?.locationInNode(self)
self.addEmitter(location!)
}
}
And here is the result (note how node's count is changing after emitting is done) :
Hope this helps
EDIT:
For those interested in how to make similar effect like from the video above, try with something like this:
The point is to use Color Ramp, and to choose Add for a blend mode.
Here is the dropbox link to the .sks file : Effect.sks
Set your Particle "BirthRate" and "Maximum" both to 20. Setting the max to 0 will repeat the birth.

SpriteKit Node gives me nil when subclassed (maybe?) to another node | Swift

Okay so i am trying to learn how to create a game... I want a node to be the camera, so that i can move it and center the view to my player node. When i subclass (i don't know if it's right to say that i subclass it, maybe not...) the player node to the world node, the application crashes. When player is simply a node, a "subclass" of GameScene (and not of the "world" node), it goes "fine", i mean i can move my player (yeah but the camera doesn't work).
so here is my code (few // lines in italian but not relevant)
import SpriteKit
class GameScene: SKScene {
var world: SKNode? //root node! ogni altro nodo del world dev'essere sottoclasse di questo
var overlay: SKNode? //node per l'HUD e altre cose che devono stare sopra al world
var camera: SKNode? //camera node. muovo questo per cambiare le zone visibili
//world sprites
var player: SKSpriteNode!
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPoint(x: 0, y: 0) //center the scene's anchor point at the center of the screen
//world setup
self.world = SKNode()
self.world!.name = "world"
self.addChild(world!)
//camera setup
self.camera = SKNode()
self.camera!.name = "camera"
self.world!.addChild(self.camera!)
//UI setup
self.overlay = SKNode()
self.overlay?.zPosition = 10
self.overlay?.name = "overlay"
addChild(self.overlay!)
player = world!.childNodeWithName("player") as SKSpriteNode!
}
var directionToMove = CGVectorMake(0, 0)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
directionToMove = CGVectorMake((directionToMove.dx + (location.x - player!.position.x)), (directionToMove.dy + (location.y - player!.position.y)))
}
}
override func update(currentTime: CFTimeInterval) {
//*these both print the string*
if player == nil {
println("player is nil")
}
if player?.physicsBody == nil {
println("player.physicsBody is nil")
}
//*here it crashes*
player!.physicsBody!.applyForce(directionToMove)
}
override func didFinishUpdate() {
if self.camera != nil {
self.centerOnNode(self.camera!)
}
}
func centerOnNode(node: SKNode) {
let cameraPositionInTheScene: CGPoint = node.scene!.convertPoint(node.position, fromNode: node.parent!)
node.parent!.position = CGPoint(x: node.parent!.position.x - cameraPositionInTheScene.x, y: node.parent!.position.y - cameraPositionInTheScene.y)
}
}
thanks in advance : )
Your player is nil because you are accessing it from the world node, which is empty. By default, the player is a child of scene. You can change this in the scene editor by setting the node's parent property (see Figure 1).
Figure 1. SpriteKit Scene Editor's Property Inspector
I suggest you make the following changes:
Create the world, overlay, and camera nodes in the scene editor and access them with childNodeWithName
Add the player to the scene, not the world, and have it fixed in middle of the scene
Move the camera node not the player. The code in didFinishUpdate will automatically adjust the world to center the camera in the scene. You can add a physics body to the camera and move it by applying a force/impulse or by setting its velocity.
Add the other sprites to the world (instead of to the scene)
If you add the other sprites to the world, they will move appropriately when you adjust the world's position to center the camera. You will then need to move the other sprites relative to the world. The scene is just a window to view a portion of the world at a time.
I'm not sure why you are using optionals everywhere. You only need to use an optional when a variable might be nil. Are your world, overlay, and camera ever going to be nil? I would guess probably not.
To answer your question:
Youre trying to get player from world. I dont see that youve added any player sprite to the world node. It doesnt find it, and youre unwrapping an optional that isnt there. So you get an error.
You're going to have a lot more luck if you only use optionals when you need them. If you use them for everything youre going to add unnecessary complexity to your code.
Optionals should be the exception, not the rule. I could try to fix your code for you, but I'm not sure what you intended the player sprite to be?
Follow simple tutorials and start small. Things will make more and more sense over time.

Resources