Constraining movement of a SKSpriteNode to a fixed area within a scene - sprite-kit

How can I constrain manual movement of an SKSpriteNode to a fixed rectangular area within a scene? This fixed rectangular area a also a SKSpriteNode which is fixed within the scene. In other words, I want to constrain manual movement of an object (SKSpriteNode) to be completely contained within another SKSpriteNode or at least in the same space that it occupies. I have tried several different approaches (e.g. using an SKShapeNode that has an edged-based physics body), but nothing seems to work. This seems like it should be a fairly simple task to accomplish. Thanks for any help or hints you can offer.

Put an if statement around your moving code - so don't carry out the movement if it will take the object past your boundary. e.g.
//check that a positive movement won't take your node past the right boundary
if(node.position.x + yourXMovementValue < boundaryXRight){
//move your node
}
//same for y

let rangeX = SKRange(lowerLimit: CGFloat, upperLimit: CGFloat)
let contraintX = SKConstraint.positionX(rangeX)
let rangeY = SKRange(lowerLimit: CGFloat, upperLimit: CGFloat)
let contraintY = SKConstraint.positionY(rangeY)
yourObject.constraints = [contraintX, contraintY]

Related

SpriteKit 'pinned' property allows y-axis change in position

I have a SpriteKit game in which balls bounce around, interacting with other objects.
One of those other objects is a spinner that should rotate around its center, but NOT change its x/y position. It should be stationary except for the rotation.
According to Apple's documentation, node.physicsBody.pinned = true should do exactly what I want, making it so that:
"the node’s position is fixed relative to its parent. The node’s position cannot be changed by actions or physics forces. The node can freely rotate around its position in response to collisions or other forces."
However, that's not what's happening. What's happening is that the spinner's y-axis position changes when a ball hits it squarely -- briefly moving down and then popping back into the correct position.
My code for the spinner (please assume all variables are defined):
for i in 0..<spinners.count {
let spinnerNode = SKSpriteNode(texture: texture)
spinnerNode.position = CGPoint(x: spinners[i].minX, y: spinners[i].minY)
spinnerNode.size = CGSize(width: spinners[i].width, height: spinners[i].height)
spinnerNode.physicsBody = SKPhysicsBody(texture: spinnerNode.texture!, size: CGSize(width: spinners[i].width, height: spinners[i].height))
spinnerNode.physicsBody?.isDynamic = true
spinnerNode.physicsBody?.affectedByGravity = false
spinnerNode.physicsBody?.pinned = true
addChild(spinnerNode)
}
Why on earth is my spinner node moving vertically when a ball collides with it? Why isn't .pinned working as advertised?
Thank you for your help!
I solved the problem by setting the spinner node's mass to a value slightly greater than that of the ball nodes.
node.physicsBody?.mass = 6.0

Swift-Setting a physics body velocity by angle

I was wondering if it was at all possible to make an SKNode move forward in a particular direction, but with only one factor. I'm aware of both applying an impulse and setting the velocity of a physics body, but they're both determined by two factors; dx and dy. I also know of rotating to an angle with SKActions. But is it possible to make an object simply "move forward" once it has been set on an angle? Or set its velocity with just one factor?
Thanks in advance.
Yes, is the answer to your question.
What I think you're looking for = THRUST... right?
What you want is for the "ship" to be able to rotate in any direction, and the thrust to be applied correctly, out of the "arse" of the ship, moving it forward, in ship terms.
This is absolutely possible, but does require a little "dummy" trick.
But I'm confusing you.
The local space of a SKPhysicsBody is relative to its parent. I presume.
And there's the speculative part. I'm guessing. I haven't tried this.
But... most physicsBodys are the child of an SKNode that's parented to the scene.
If you parent your ship to a dummy node for the purposes of rotation, and then rotate the dummy node, you should be able to make your spaceship fly in circles without ever changing the thrust vector, by simply rotating the dummy node.
Theoretically.
Something like this horrible pseudo code might help to start... maybe.
let dummy = SKNode()
let ship = SKSPriteNode()
dummy.addchild(ship)
ship.Physicsbody(add how you want here...)
ship.PhysicsBody.applyForce (vector that's only X, for example)
rotate dummy with action over time...
Sure I think what you're talking about is something like this:
Now let's say you have an SKSpriteNode that is called player who eventually has a physicsBody setup.
var player: SKSpriteNode!
You can just set the dx property of their velocity, so lets say you wanted to move them horizontally towards the location where the user tapped on the right hand side of the screen. If you then detect the position of the touch with touchesBegan(_:)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
// Replace with name of node to detect touch
let touchLocation = touch.location(in: <NAME_OF_NODE_PROPERTY>)
// Verify it's in front of the player and not behind
if (touchLocation.x - playerPosition.x) > 0 {
movePlayerVertically(toward: touchLocation)
}
}
func movePlayerVertically(toward location: CGPoint) {
let dx:CGFloat = location.x - player.position.x
player.physicsBody!.velocity.dx = dx
}
EDIT: -
Since you said you just want to be able to move your player horizontally without knowing the destination, you could do something like this, this is just moving the player forward on the x-axis by 50pts every second, and will repeat it forever. Obviously you would want to tweak it to your liking.
let move = SKAction.moveBy(x: 50, y: 0, duration: 1)
let repeatAction = SKAction.repeatForever(move)
player.run(repeatAction)

Increase Touchable Area of an SKSpriteNode

I know a similar question has been asked before about this topic but here is my problem. I am using the following simple code to touch and SKNode.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
I then remove the touches node with the following:
node.removeFromParent()
Now, my problem is that the image of the node I am touching is small. Therefore it is very hard to accurately register a touch. I do not want to make the image bigger. This wouldn't be a problem but the nature of my game is such that I:
1) Cannot use another invisible SKSpriteNode ontop of my image SKSpriteNode to increase the registered touch area because each image has a unique name so I need that specific node to be touched and removed.
2) I cannot give the invisible SKSpriteNode the same name as the image SKSpriteNode because then only the invisible SKSpriteNode would be removed on touching.
3) I have tried to load a new SKSpriteNode into the original image SKSpriteNode to increase the touchable area, but once again, on touching, only the invisible SKSpriteNode gets removed, not the image SKSpriteNode which I want.
So, how can I increase the touchable area? Is there a way to maybe:
1) Remove all nodes associated with the node that is touched? This would then work if I put an invisible SKSpriteNode inside the original node to increase the touchable area. If they had the same name. But I have already tried "node.removeAllChildren()"......didn't work.
2) Detect a touch and the closest possible image near that touch is the image that the code in TouchesBegan gets use on? This would also work for my situation.
Any other suggestions? Cheers :)
-------------------------------UPDATE---------------------------
The following code below works! If I touch the invisible SKNode, then both the invisible and Original SKNodes disappear. However, for whatever reason, the original SKNode always appears infront of the invisible SKNode. I cannot fix this?
let original = SKSpriteNode(imageNamed: "original")
original.size = CGSize(width: 50, height: 50)
original.zPosition = 0
let invisible = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(70, 70))
invisible.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
invisible.alpha = 1.0
invisible.zPosition = 1
invisible.name = original.name
invisible.addChild(original)
self.addChild(invisible)
For zPosition order, put this inside GameViewController
//Put this line
skView.ignoresSiblingOrder = true
//before this
skView.presentScene(scene)
In you code you have two nodes:
original
invisible
Who is the parent, who is the child?
According to your question "If I touch the invisible SKNode, then both the invisible and original SKNodes disappear..however, for whatever reason, the original SKNode always appears infront of the invisible SKNode" seems that you want invisible inside original, in other words original should be the parent and invisible should be the child.
But your code says the exact opposite:
invisible.addChild(original)
self.addChild(invisible)
So , we make an example:
Suppose that your original node is green, this how appear your elements follow your code: invisible node (red) do an addChild to the original (green) , and then you add to self the invisible. Invisible is parent, original is child.
Finally, if you touch the red rectangle to remove it, both red and childs (green) will be removed, if you touch the green rectangle to remove it, only the green rectangle disappear.
Hope you can help you to understand what happen to your code.

Restricting a node within another in SpriteKit

I'm new to SpriteKit, trying to build basic Breakout game. The problem I'm facing is that I can't restrict the paddle within the screen (that's yet another node with blue texture as shown in image). When I move the paddle it goes beyond the screen limits.
I've applied physics to both, screen area and the paddle but no luck.
Your paddle doesn't collide appropriately with the edge because you are moving it by changing its position directly. To participate in the physics simulation, the paddle must be moved by setting its velocity or by applying a force or impulse to its physics body. For example,
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if (location.x < size.width/2.0) {
paddle.physicsBody?.applyImpulse(CGVectorMake(-scale, 0))
}
else {
paddle.physicsBody?.applyImpulse(CGVectorMake(scale, 0))
}
}
where scale determines the amount of momentum that is applied to the body in the x dimension.
EDIT:
Alternatively, you can constrain the paddle's x position to be within a set range by
let range = SKRange(lowerLimit: CGRectGetMinX(view.frame), upperLimit: CGRectGetMaxX(view.frame))
let constraint = SKConstraint.positionX(range)
paddle.constraints = [constraint]
Add the above to the didMoveToView method.

Get a list of nodes in an specific area?

I'm working in a side-scolling game and I need to know what nodes are in an area to implement something like "line of sight". Right now I'm trying using enumerateBodyiesInRect() however it's detecting bodies that are 20px or more from the evaluated rect and I cannot figure out why it's so imprecise.
This is what I'm trying now:
import SpriteKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate
{
var player = SKShapeNode()
var world = SKShapeNode()
var rShape = SKShapeNode()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.scaleMode = SKSceneScaleMode.AspectFit
self.size = view.bounds.size
// Add world
world = SKShapeNode(rectOfSize: view.bounds.size)
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
world.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2) // Move camera
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 25, height: 25))
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.frame.size)
player.physicsBody.dynamic = false
player.strokeColor = SKColor.blueColor()
player.fillColor = SKColor.blueColor()
player.position = CGPointMake(90, -50)
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
// Define rect position and size (area that will be evaluated for bodies)
var r : CGRect = CGRect(x: 200, y: 200, width: 25, height: 25)
// Show rect for debug
rShape.removeFromParent()
rShape = SKShapeNode(rect: r)
rShape.strokeColor = SKColor.redColor()
self.addChild(rShape)
// Evaluate rect
rShape.fillColor = SKColor.clearColor()
self.physicsWorld.enumerateBodiesInRect(r) {
(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
self.rShape.fillColor = SKColor.redColor() // Paint the area blue if it detects a node
}
}
}
This code should show the evaluated rect and ray on the screen (for debugging purposes) and paint them red if they contact the player node. However you can see in the screenshot how it turns red when the player is 25px or more away from it, it's like if the drawing is a little bit off, or smaller than the actual area being evaluated. You can copy paste it to a project to duplicate the problem.
Could this be because this is just beta or am I doing something wrong?
You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.
Think of it like a 'tractor beam' or a 'warp rectangle'.
I believe you want SKPhysicsWorld's enumerateBodyiesInRect() instance method, which will iterate over all nodes in a given rectangle. If you're looking to get at the physics world through your scene, usage could look like this:
self.physicsWorld.enumerateBodiesInRect(CGRect(x: 0, y: 0, width: 50, height: 50)) {(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
// enumerates all nodes in given frame
}
I've experimented quite a bit with enumerateBodiesInRect now, and I've found it to be incredibly inaccurate. It seems to not have any of the claimed functionality, and instead produces random results. I honestly cannot even determine any pattern from its products.
enumerateBodiesAlongRay seems better, but still very buggy. The problem with that function seems to be the conversion between Screen and PhysicsWorld coordinates. I would avoid that one, as well.
I think your solution should simply be to use the existing contact detection system. All of your desired functionality can be written in the didBeginContact() and didEndContact() functions. This has the added benefit of allowing you to specify distinct functionality for both entering and leaving the area. You can also add particle effects, animations, and similar, as well as intentionally ignoring specific types of nodes.
The only thing to ensure success with this method is to clarify that the contact area has a unique category, that the contactTestBitMask contains all desired nodes and the collisionBitMask is set to 0.
The enumerateBodiesInRect method of SKPhysicsWorld expects the rect parameter to be in scene coordinates. This is important. If you have a scene hierarchy of nodes, you need to convert the rect you calculate from a reference node to the scene coordinates.
I faced a lot of issues with this method returning bodies that were off by values like 30px to the left etc. and finally realized the issue was because of the rect parameter not defined in scene coordinate space.
In my case, I had a worldNode inside my scene, and all objects were created in the worldNode. My camera was moving the worldNode about, and applying scaling to it for zooming out and in.
In order to use enumerateBodiesInRect correctly, I had to do something as follows:
// get your world rect based on game logic
let worldRect = getWorldRect()
// calculate the scene rect
let sceneRectOrigin = scene.convertPoint(worldRect.origin, fromNode:scene.worldNode)
let worldScale = scene.worldNode.xScale // assert this is not 0
// now to get the scene rect relative to the world rect, in scene coordinates
let sceneRect = CGRectMake( sceneRectOrigin.x, sceneRectOrigin.y, worldRect.width / worldScale, worldRect.height / worldScale)
world.physicsWorld.enumerateBodiesInRect(sceneRect) {
// your code here
}
Hope this helps.
I am not sure if this is a good practice. Correct me if not. But I am using
let shapeNode = SKShapeNode()
shapeNode.intersects(playerNode)
I checked selected nodes with simple loop if they intersect the player. Additionally I created SKShapeNodes which are drawn in front of nodes representing view sight of other actors in the game. They are moved along those actors.
There is only nodesAtPoint: method.
To achieve what you want you'd better to store all enemies in an array and have an int variable, something like nextEnemyIndex. This approach lets you to easily return the next enemy node, it's much more efficient than trying to find a node on the scene.
yes problem may occur because of your player's image, for example try to use 10px smaller body size:
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGRectMake(self.frame.origin.x, self.frame.origin.y, self.size.width-10, self.size.height-10)));