Can I have physics bodies collide without pushing each other? - ios

I have a game made of little SKNodes moving around. They can not overlap, so I use physics bodies, so that they move around each other. However, in the case that one sknode is animated to follow another, it pushes the sknode ahead. Setting the collision bitmask to 0 makes them overlap, so that is not an option. But otherwise, they push each other way beyond my desired speed. I need a way to get rid of the 'pushing' without overlapping using skphysics bodies. Is there a property I can set to fix this?
Notes: I use skanimations to move my nodes around. If you need any pertinent code, tell me... I don't know where to start. I am using Swift 3.0, but I will accept answers with 2.2/2.3 syntax.
EDIT: The real solution was to change the node's velocity instead of animating movement with SK Actions.

Change the restitution on your physics body of the moving object to 0 when a contact happens, and set the weight of the object you do not want to move really high

Have you tried setting the dynamic property to false on the body being pushed?

After you have set contactTestBitMask you could set this:
myHero.physicsBody!.collisionBitMask = 0
to prevent collisions but permit contacts.

Updating position of dynamic physics body with SKAction doesn't work well. It's just like saying - Hey, no matter what physics world rules say just move my node like I want.
First solution - to make one node follow another with some particular distance use SKConstraint.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let nodeA = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 50, height: 50))
nodeA.name = "nodeA"
addChild(nodeA)
let nodeB = SKSpriteNode(color: SKColor.redColor(), size: nodeA.size)
nodeB.name = "nodeB"
addChild(nodeB)
let followConstraint = SKConstraint.distance(SKRange.init(lowerLimit: nodeB.size.width * 1.5, upperLimit: nodeB.size.width * 1.5), toNode: nodeA)
nodeB.constraints = [followConstraint]
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else {
return
}
childNodeWithName("nodeA")!.runAction(SKAction.moveTo(touch.locationInNode(self), duration: 2))
}
}
If you still want physics bodies and SKActions...
Set "dynamic" property of every physics body to false.
Check in didBeginContact (SKPhysicsContactDelegate protocol) if they are overlapping. If it's true than remove all the actions that are changing their positions. Tip - make the physics body little bigger than it's node.

Related

Is there a simple way to temporarily disable the physics bodies for a sprite in Swift? (SpriteKit)

I'm making a maze app, that generates a new maze after one is completed. Obviously, I can turn the walls (each having their own physics and sprite stored in their spot on an array) invisible by doing what I did in my code wall[location].isHidden = true ,but I couldn't find a way to do the same with the physics body bound to the wall, creating an invisible wall. Is there a simple way to do that without setting it to nil?
Just to clarify, it's not that I don't want it to be dynamic, I just want the entire thing to not exist while that maze is being solved.
Also the only reason I wouldn't want to set it to nil is because I don't know how to restore its old physics body in an easy way.
The preferred way would be to physically remove your wall from your node tree. This will increase performance since the system does not have to check unnecessary nodes.
Just make sure that your wall is being retained when you remove it so that you can readd it when needed.
wall[location].removeFromParent()
...
scene.addChild(wall[location])
Sure
just set
isDynamic = false
wall.physicsBody?.collisionBitMask = 0
wall.physicsBody?.contactTestBitMask = 0
This pretty much isolates the SKPhysicsBody from the rest of the system. Do you have any SKJoints connected to your nodes with the bodies ? This can be awkward if you do.
or you can set it to nil
wall.physicsBody? = nil
then just make a method to set it again when your ready
func addPhysics() {
wall.physicsBody = SKPhysicsBody(circleOfRadius: 5)
wall.isDynamic = true
wall.physicsBody?.collisionBitMask = whatever
wall.physicsBody?.collisionBitMask = whatever
wall.physicsBody?.contactTestBitMask = whatever
}
If you do set to nil and then restore it make sure you do this at either the start or the end of the gameLoop.
In either of these override methods in the gameScene
override func update(_ currentTime: TimeInterval) {
}
override func didFinishUpdate() {
}
Hope this helps
You already have an answer, but another approach might be to subclass SKSpriteNode with one extra property called inactivePhysicsBody (or similar) which is an 'SKPhysicsBody'.
When you want to 'turn off' the physics for that node, set the inactivePhysicsBody property to physicsBody and then set physicsBody to nil. You can then easily restore the physics body when necessary.
Or you could add methods to the new class to do the same:
wall.activatePhysicsBody()
wall.inactPhysicsBody()
Give the object that owns the sprite an 'extra' reference to the sprite's physics body, e.g.:
init(sprite: SKSpriteNode) {
self.sprite = sprite
self.physicsBody = sprite.physicsBody
super.init()
}
Then you can set self.sprite.physicsBody to either self.physicsBody or nil to turn the object's physics on / off. Personally I'd avoid subclassing here but YMMV.

Unexpected physicsBody in SpriteKit scene

I'm implementing a mass-spring system (many small physics bodies joined together with SKPhysicsJointSpring instances) with SpriteKit. Some of the particles would get snagged while traversing the center of the scene.
There seems to be a small, static body in the middle of the scene and I don't know why it's there.
Here's an easy way to see what I'm talking about:
In XCode 8, create a brand new project with the "Game" template.
In GameViewController.viewDidLoad(), add view.showsPhysics = true
If you run the project, you should see a little dot in the middle, which is the errant body:
Anyone know how to get rid of it?
Edit: I tried to manually create the scene object:
In GameViewController.viewDidLoad(), I replaced this:
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
view.presentScene(scene)
}
with this:
let scene = GameScene(size: view.frame.size)
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
view.presentScene(scene)
but that didn't fix it.
Anyways, I decided to make an answer because comments are not suitable due to lot of info I want to share. Also my answer, sadly, doesn't answer the question but it gives some useful info about this unidentified, obviously capable of flying (physics body) object :)
So this is the code how to grab it (and modify it???):
//you can use self.frame here...I just copied Alessandro's code
self.physicsWorld.enumerateBodies(in:(label?.frame)!) { body, stop in
if let node = body.node {
print("Type of this node: \(type(of:node))")
print("Frame of this node: \(node.frame))")
}else{
print("This body's node property is nil")
body.affectedByGravity = true
body.isDynamic = true
body.applyImpulse(CGVector(dx: 0.003, dy: 0.0003))
}
print("Area covered by this node physicsBody: \(body.area)")
}
So if you put a break point inside of that else statement, you can scan this physics body completely and get all the info about it, like that its node property is set to nil or that its isDynamic property is set to false. But you can change that, and like in my code, set for example isDynamics to true. This makes it moveable. So if you apply some forces to it, it will move.
Still, like I said in comments, I don't have an idea why it is there and what it represents or what is its purpose.
Also, for those who are wondering how it is possible that one physics body doesn't have a node associated with it ( body.node equals nil) but is still visible on screen when showsPhysics is set to true, there is a reasonable explanation. Physics world is separated from the node tree. So we can remove a sprite from a node tree, but that doesn't mean that its physics body will be removed instantly. It may happen that physics engine haven't finished simulation... So you probably wonder, how this might happen?
Let say you have three SKSpriteNode objects intersecting at the same time (say A contacts B and A contacts C at the same time). SpriteKit can process only one contact at time. And say that you are removing A from a parent when it is contacting with B. Then, there is a contact between A and C also, so didBegin:(_ contact) will be called twice. And if you remove A from its parent in first didBegin(_ contact) call, in the next didBegin(_ contact) call, bodyA.node will be nil (bodyA is a physics body of sprite A), but its physics body will remain visible until engine finishes what needed. This is because node tree and a physics world are separated.
About the "hello world" xCode game template , it seems a little physicsBody associated to the GameScene node.
With some code I've found this:
class GameScene: SKScene {
private var label : SKLabelNode?
private var spinnyNode : SKShapeNode?
override func didMove(to view: SKView) {
...
// End part of this function:
if let b = physicsWorld.body(in: (label?.frame)!) {
if let node = b.node {
print("Type of this node: \(type(of:node))")
print("Frame of this node: \(node.frame))")
}
print("Area covered by this node physicsBody: \(b.area)")
}
}
}
With a breakpoint to the last brace, you can see two bodies (maybe an array of bodies), one of them is the physicsBody to the left debugArea (array with index 1) with the same hex address as my body b in my code : 0x79794f90, a little rectangle body with area 4.444
Printing description of ((PKPhysicsBody *)0x79794f90):
<SKPhysicsBody> type:<Rectangle> representedObject:[<SKScene> name:'(null)' frame:{{-375, -667}, {750, 1334}} anchor:{0.5, 0.5}]
(lldb)
I had a similar problem. I have a game with two sprite nodes joined together (SKPhysicsJointFixed.joint) moving around an SKEditor created scene.
As per my design, this node-pair would impact a third sprite node and be propelled smoothly away from the third sprite node, EXCEPT when the impact was in the center of the scene. For the center of the scene impact, the node-pair would compress together while be propelled away from the third sprite node, presenting a poor graphical image.
After significant time debugging my code, I found this post. Kudos for the explanations and code. I can’t answer the “why” question but for the “particles would get snagged while traversing the center of the scene” question my suggested solution is to clear the collisionBitMask instead of moving the body.
BTW categoryBitMask is 0 when loaded.
//moves the problem offscreen unless it hits another node
//body.affectedByGravity = true
//body.isDynamic = true
//body.applyImpulse(CGVector(dx: 0.003, dy: 0.0003))
//
// collisionBitMask loads with 4294967295 = default value to collide with all categories
//
body.collisionBitMask = 0

Draw straight line in Swift with Sprite Kit, creating multiple lines

I'm trying to make a game in iOS, using Swift and Sprite Kit. I have the following bit of codes, but it's creating multiple lines. What I want to do is to create a single line, end of which will follow the tip of the finger of the user as the user drags the finger across the screen until the he or she takes the finger off the screen, then for the line to remain there. I found a similar question on stack overflow at Drawing straight line with spritekit and UITouch creates multiple lines, but it is written in Objective-C. And I don't think I understood the answer entirely to be honest. I'm using Xcode 7 and Swift 2.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
}
let path = CGPathCreateMutable()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let position1 = touch!.locationInNode(self)
CGPathMoveToPoint(path, nil, position1.x, position1.y)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let position2 = touch!.locationInNode(self)
CGPathAddLineToPoint(path, nil, position2.x, position2.y)
CGPathCloseSubpath(path)
let line = SKShapeNode()
line.path = path
line.strokeColor = UIColor.blackColor()
line.lineWidth = 5
self.addChild(line)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Any thoughts or suggestions would be greatly appreciated. Thanks.
Ryan
I may be wrong because I don't know too much about game development with Swift. But it appears you are creating a line every time you more your finger, have you tried creating the line in touchesEnded?
Did you try your code without the line
CGPathCloseSubpath(path)
According to the documentation, this is what it does:
Appends a line from the current point to the starting point of the
current subpath and ends the subpath.
and I don't think you want to have an additional line from your last touch to the start of the line with every new touch. Closing of a CGPath is only needed if you want a line that ends where it started.
I would cheat to do this:
Create a very thin sprite, in the line colour you want, and stretch its end point to where you want it to go.
This is effectively a rectangular box made out of an SKSpriteNode that's stretched from the origin to the current point of touch.
Rotating and then stretching a SKSpriteKit node is best done (for me) by attaching it to an SKNode, and rotating the node as needed, then stretching the nested SKSpriteNode as far as is needed to reach the user's current touch point.
Put the origin of the SKNode and the SKSpriteNode, at the same place, the middle left edge, and rotate around that. Or whatever works for you mathematically... This is makes the most sense to me, but it could be the centre points of any edge of the rectangle and you then stretch accordingly.

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.

SKSpriteNode with color doesn't show

I created a SKSPriteNode without texture but color
let tileNode = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 80.0, height: 120.0))
tileNode.position = CGPointMake(50, 50)
tileNode.name = "rectangle"
addChild(tileNode)
But the node doesn't display on the screen.
However, I can detect it with touch collision
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let location: CGPoint = (touches.anyObject() as UITouch).locationInNode(tilesLayer)
let rect: SKSpriteNode = tilesLayer.childNodeWithName("rectangle") as SKSpriteNode
if rect.containsPoint(location) {
println("TOUCHED") //It works
}
}
EDIT : The SKSpriteNode color is only hidden if there is a background SKSpriteNode texture. Complete code : https://gist.github.com/BabyAzerty/9dca752d9faa7b768bf0
I believe you created your project using the Game project template in Xcode, correct? Your issue is this line in GameViewController.m (roughly line 41).
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = YES;
If you set that to NO, then it will render as you expect. I build my game projects from empty projects, so I never had that property set. Just search your code for ignoresSiblingOrder
Of course if you want that rendering optimization, then you can always use zPosition.
I noticed that the difference between creating a SKSpriteNode with texture and a SKSpriteNode with color is that the texture version doesn't need to have its z-index modified. It will display over the previous addChild(someNodes) automatically.
They both have a default z-index of 0 but for some reasons SKSpriteNode with color is hidden behind your previous addChild(someNodes).
This line should do the trick :
tileNode.zPosition = 1 //Or higher
However, I decided to create an enum that controls the different z-layers (just like the one in Unity Engine) :
enum ZLayer: CGFloat {
case Undefined = 0
case Background
case Foreground
case GUI
}
And use it like this :
tileNode.zPosition = ZLayer.Background.rawValue
The advantage of this enum is that if you need to add a new layer between background and foreground layers, you don't need to bother with the values at all, just an entry in the enum between the 2 cases.

Resources