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
Related
I would like to animate the appearance of a NSSplitViewItem using .setPosition() using Swift, Cocoa and storyboards. My app allows a student to enter a natural deduction proof. When it is not correct, an 'advice view' appears on the right. When it is correct, this advice view will disappear.
The code I'm using is the below, where the first function makes the 'advice' appear, and the second makes it disappear:
func showAdviceView() {
// Our window
let windowSize = view.window?.frame.size.width
// A CGFloat proportion currently held as a constant
let adviceViewProportion = BKPrefConstants.adviceWindowSize
// Position is window size minus the proportion, since
// origin is top left
let newPosition = windowSize! - (windowSize! * adviceViewProportion)
NSAnimationContext.runAnimationGroup { context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
func hideAdviceView() {
let windowSize = view.window?.frame.size.width
let newPosition = windowSize!
NSAnimationContext.runAnimationGroup{ context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
My problem is that the animation action itself is causing the text in the views to stretch, as you can see in this example: Current behaviour
What I really want is the text itself to maintain all proportions and slide gracefully in the same manner that we see when the user themselves moves the separator: Ideal behaviour (but to be achieved programmatically, not manually)
Thus far in my troubleshooting process, I've tried to animate this outside of NSAnimationContext; played with concurrent drawing and autoresizing of subviews in XCode; and looked generally into Cocoa's animation system (though much of what I've read doesn't seem to have direct application here, but I might well be misunderstanding it). I suspect what's going on is that the .animator() proxy object allows only alpha changes and stretches---redrawing so that text alignment is honoured during the animation might be too non-standard. My feeling is that I need to 'trick' the app into treating the animation as though it's being performed by the user, but I'm not sure how to go about that.
Any tips greatly appreciated...
Cheers
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
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
}
}
In my program I have a method called addObstacle, which creates a rectangular SKShapeNode with an SKPhysicsBody, and a leftward velocity.
func addObstacle(bottom: CGFloat, top: CGFloat, width: CGFloat){
let obstacleRect = CGRectMake(self.size.width + 100, bottom, width, (top - bottom))
let obstacle = SKShapeNode(rect: obstacleRect)
obstacle.name = "obstacleNode"
obstacle.fillColor = UIColor.grayColor()
obstacle.physicsBody = SKPhysicsBody(edgeLoopFromPath: obstacle.path!)
obstacle.physicsBody?.dynamic = false
obstacle.physicsBody?.affectedByGravity = false
obstacle.physicsBody?.contactTestBitMask = PhysicsCatagory.Ball
obstacle.physicsBody?.categoryBitMask = PhysicsCatagory.Obstacle
obstacle.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(obstacle)
obstacle.runAction(SKAction.moveBy(obstacleVector, duration: obstacleSpeed))
}
In a separate method, called endGame, I want to fade out all the obstacles currently in existence on the screen. All the obstacle objects are private, which makes accessing their properties difficult. If there is only one on the screen, I can usually access it by its name. However, when I say childNodeWithName("obstacleNode")?.runAction(SKAction.fadeAlphaBy(-1.0, duration: 1.0)), only one of the "obstacles" fades away; the rest remain completely opaque. Is there a good way of doing this? Thanks in advance (:
You could probably go with:
self.enumerateChildNodesWithName("obstacleNode", usingBlock: {
node, stop in
//do your stuff
})
More about this method can be found here.
In this example I assumed that you've added obstacles to the scene. If not, then instead of scene, run this method on obstacle's parent node.
And one side note...SKShapeNode is not performant solution in many cases because it requires at least one draw pass to be rendered by the scene (it can't be drawn in batches like SKSpriteNode). If using a SKShapeNode is not "a must" in your app, and you can switch them with SKSpriteNode, I would warmly suggest you to do that because of performance.
SpriteKit can render hundreds of nodes in a single draw pass if you are using same atlas and same blending mode for all sprites. This is not the case with SKShapeNodes. More about this here. Search SO about this topic, there are some useful posts about all this.
What would be the best way to make a Swift SKSpriteNode auto correct its orientation after it has had a couple of physicsBody knocks and is now not in the default orientation?
For example I have have a 2D stick-man who gets knocked to the side by another object and would like the stick-man to tilt and reorient itself to the default standing position.
The way I have done this in the past is to wait until the physics body comes to rest, turn off the physics engine for the stickman, reorient the stickman, turn the physics engine back on again.
Write yourself an action called standupAction which animates the stickman's reorientation, maybe just a rotate or a jump up. Then do something like the following.
Sorry this Objective-C not Swift, don't know swift, but should be easy to translate.
if (stickman.physicsBody.resting) {
stickman.physicsBody.dynamic = NO;
[stickman runAction:[SKAction sequence:#[
standupAction,
[SKAction runBlock:^{ stickman.physicsBody.dynamic = YES;}]
]]];
}
The only downside of this approach is that sometimes it can take a full second until the stickman comes comes to rest. Box2d takes quite a long time to settle down. But it looks very realistic if you wait.
Alternatively, you could just check the orientation of stickman and if he is >45degrees off balance then reorient him with impulses. I find impulses to be very difficult to know how much force to apply when you need precise movement like this.
Just to update, I couldn't afford to set the physicsBody to not be dynamic.
My scene updates each node, similarly to Apple's Adventure game, as such I implemented the rotation in the subclassed SKSpriteNode's update method as such:
if (isTouchingGround) {
if self.zRotation != 0 && !rotating {
let actions = [
SKAction.runBlock {
self.rotating = true
},
SKAction.rotateToAngle(0, duration: 1.0),
SKAction.runBlock {
self.rotating = false
}]
runAction(SKAction.sequence(actions))
}
}