Well, my aim is to extract the content of a SKScene and then run some actions to their node:
func loadSceneNodes() {
let loadedScene = SKScene(fileNamed: "newScene.sks")
// Reads the content of the "Content" node in the newScene.sks
let content = loadedScene!.childNode(withName: "Content") as! SKSpritenode
// I move the content to self
// (THANKS Knight0fDragon for suggestion)
content.move(toParent: self)
// Forces the unpause
self.isPause = false
// Try to run a test action on the node
content.run(.rotate(byAngle: 90, duration: 2.0)) {
print("DONE")
}
print(content.hasActions()) // DEBUG -> it prints TRUE
}
What happens is that I successful read the content and it shows on the scene, but the actions aren't shown even if hasActions() results true.
I found out something very interesting by playing with breakpoints and the debug console. content.hasActions() returns true because all of the SKActions get into a stack:
They seem like freeze because if I assign more actions, they will get stack in the pile one by one without getting run.
Only once I quit the breakpoint (or whether I pause and then play the application through xCode) everything is executed. Just like the app "needs" a refresh.
Use moveToParent on your sknode instead of addChild on your parent node to avoid the need to create a copy.
Ensure the isPaused state is set to false, you may be copying at a time where it is true
Related
I have a bunch of objects (SKNode) starting from the top of the screen 'falling' to the bottom via SKAction.move(to:duration:) and node.run(moveAction). In addition I have node in the center of the screen with its own physics body that can be dragged side to side with touch input. I can detect collisions fine, but I was wondering if there was a paradigm for 'pausing' all the objects while the center node is contact with any of the objects. Additionally, I want to able to move the center node while the other objects are 'paused' so I can move it out of the way and then let the objects resume their motion. I figured I could probably iterate through all the existing objects and set their isPaused property, but I am unsure how the app would know when the center node is no longer 'colliding' so I can toggle the property back.
To pause things, you will have to detect the contact in didBegin() , tofill some array with nodes that should be paused and finally to pause the nodes. Actual pausing could be done in didSimulatePhysics() for example. To pause all the nodes you could use
self.enumerateChildNodesWithName("aName") {
node, stop in
// do something with node or stop
}
or use children property of a node and loop through it (eg. to loop through the container of your nodes that should be paused).
Also you can pause certain action with:
if let action = square.actionForKey("aKey") {
action.speed = 0
}
and unpause it with action.speed = 1, or make it slow-mo with action.speed = 0.5
To slow down physics simulation, there is a property called physicsWorld.speed (determines the rate at which the simulation runs).
Oh boy, things sure do get complicated. Every thing that Whirlwind says is correct, but to iterate through every node on the scene can become burdensome.
Instead, I recommend creating a special subclass for SKNode. Now this special class is needed due to a bug caused by apples framework
class UserPausableNode : SKNode
{
public var userPaused = false
{
didSet
{
super.isPaused = userPaused
}
}
override var isPaused : Bool
{
get
{
return userPaused
}
set
{
//Yes, do nothing here
}
}
}
The reason why we need to do this is because whenever isPaused is called, it iterates to all children and sets the child isPaused property.
This becomes a problem when scene pauses and unpauses, as it will change the pause state of the children, which we do not want to be doing.
This class prevents the isPaused variable from changing.
Now that this class exists, what we want to do is add it to the scene, and add all moving nodes to this new node.
Since all of the nodes are on this new node, all we need to do is set the userPaused to true, and this will stop all nodes.
Now make sure the node you are moving is not a part of this branch, it should be outside so that you could move it.
Example
class GameScene : SKScene
{
var userBranch = UserPausableNode()
func didMove(to view:SKView)
{
self.addChild(userBranch)
//generetedChildren are nodes that you create that you plan on pausing as a group
for generatedChild in generatedChildren
{
userBranch.addChild(node)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//this will pause and unpause based on opposite of previous state
userBranch.userPaused = !userBranch.userPaused
}
}
When I run my program. The code I put into "override func sceneDidLoad()" runs two times.
E.g.
Note: I have no idea why this picture is not uploading, but it shows "spawn" happening twice.
This code should only run once when "sceneDidLoad()" is called.
Here is the code for the "sceneDidLoad" function, and for the "testSpawn()" function (which is the specific one that gave the duplicated printout).
class GameScene: SKScene {
var mapTerrain: SKTileMapNode!
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
//do zoom by change in scale in pinch. (E.g. if they start out 5 units apart and end up 15 units apart, zoom by a factor of 3
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 100, y: 100)
setupLayers()
loadSceneNodes()
setUpUI()
testSpawn()
//print("\(self.frame.width), \(self.frame.height)")
}
func testSpawn(){
let RedLegion = legion(texture: textureRedLegion, moveTo: nil, tag: 1, health: 2)
RedLegion.position = mapTerrain.centerOfTile(atColumn: 0, row: 0)
RedLegion.team = "Red"
unitsLayer.addChild(RedLegion)
legionList.append(RedLegion)
print("spawn")
}
}
Note: Not all of the code is here (like "setUpLayers()"), if needed I can supply it, I just do not think it is neccessary.
Search your whole document for "print("spawn")" just to make sure that is the only time you call the function. Also check for "testSpawn()" to make sure it is only called once. Additionally, instead of relying on this print to count how many times the sceneDidLoad runs, place a print directly within your sceneDidLoad. Finally, check to make sure you are not creating the scene twice.
I've also seen this and submitted a bug report but apple responded saying that it is intended behavior. Apple said that it creates a dummy scene and then creates the actual scene. Before it runs the second time it gets rid of anything done the first time so you shouldn't get any errors from it. The bug is really hard to reproduce, one of my friends working off the same repository that I was but did not experience the bug.
I changed sceneDidLoad to didMoveToView:(SKView *)view if you are looking for a solution to this. Make sure you xcode is up to date.
I'm trying to temporarily disable touch on the entire screen, despite their being many sprites with touchesBegun onscreen.
I thought, obviously wrongly, turning off touch for the scene would do it:
scene?.isUserInteractionEnabled = false
But that didn't work, so I tried this, which also didn't work:
view?.scene?.isUserInteractionEnabled = false
That also didn't work, so I tried this, also from inside the scene:
self.isUserInteractionEnabled = false
There is no global method to turn off the touch, whatever is at the top of the drawing queue is the first responder.
You need to iterate through all of your nodes from your scene and turn them off:
enumerateChildNodesWithName("//*", usingBlock:
{ (node, stop) -> Void in
node.isUserInteractionEnabled = false
})
Now the problem is turning them back on, if you use this method, you will turn it on for everything, so you may want to adopt a naming convention for all your touchable sprites
enumerateChildNodesWithName("//touchable", usingBlock:
{ (node, stop) -> Void in
node.isUserInteractionEnabled = true
})
This will look for any node that has a name that begins with touchable.
This method involves recursion, so if you have a ton of nodes, it can be slow. Instead you should use an alternative method:
let disableTouchNode = SKSpriteNode(color:SKColor(red:0.0,green:0.0,blue:0.0,alpha:0.1),size:self.size)
disableTouchNode.isUserinteractionEnabled = true
disableTouchNode.zPosition = 99999
self.addChild(disableTouchNode)
What this does is slap on an almost transparent node on top of all elements the size of the scene. This way when a user touches the screen, this node will absorb it instead of anything else.
The following will disable all touches
self.view?.isUserInteractionEnabled = false
So I would like to make a block of code that will run inside the sprite node every frame. The result would be me tapping and creating a bubble there that would then float up using sine. Is that possible to code into the bubble? Sorry if this seems vague, I'm just not sure what to call it. The closest thing I can think of it cloning and each clone runs the same script every frame. Anyways, Here's what I have now.
let bubble = SKSpriteNode(imageNamed:"bubble")
bubble.position.x = CGFloat(rand())*self.frame.width
bubble.position.y = -20
//Code for bubble to run each frame
self.addChild(bubble)
I think that what you are looking for is enumerateChildNodesWithName. When it is called, it will run the code it contains on every node with the name you put in it.
bubble.name = "bubble"
If you name bubble "bubble", all of its child nodes will also be named "bubble".
enumerateChildNodesWithName("bubble"){node, stop in
let bubbleChild:SKSpriteNode = node as! SKSpriteNode
//Run the code telling them what to do here. You should make it so that
//it can figure out how to move the node a slight amount based on its position
}
You could call this by putting it in a function that is called by a timer, and the timer should be called frequently, like every 0.017 seconds (Approximately 60 times a second) so that the motion is smooth. For example:
var timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(0.017), target: self, selector: Selector("moveBubbles:"), userInfo: nil, repeats: true)
Then, to make a function that the timer calls:
func moveBubbles(timer: NSTimer!){
//enumerateChildNodesWithName here
}
There is more information on enumerateChildNodesWithName here, and more information on NSTimer here.
Once a node is removed, I'm needing to respawn it back to it's original position.
I've tried a few things:
1) In the didBeginContact where it gets deleted, I've just added an addChild() after the delete, but that just added it back where it was, making it look like it just paused in place
2) When it dies, I'm setting a variable to 0, and I created a function that adds the node back where it starts out, and then I set up an if statement that looks like :
if playerLife == 0 {
spawnPlayer()
}
and the player life gets set to 0 down in my didBeginContact. Don't know if that even makes sense
3) I tried slapping all of this down in the didBeginContact
The ball is either freezing when I try to addchild directly back right after I delete it in the DidBegin, otherwise it gets deleted and never is respawned.
Try moving your code inside of the update function. also in your spawnPlayer function make sure to add your node if node.parent == nil
override func update(currentTime: NSTimeInterval) {
if playerLife == 0 {
spawnPlayer()
}
}