I have simple gameplay that functions like this:
My goal is to detect when the ball hits the white part (game over scene would load) or when the ball hits the gray part (game continues)
Because the shapes shrink and have a lot of stuff going on, I figured it would be a lot more efficient to just create the SKPhysicsBody when I need it, and then remove it after the collision checks happen. So when I click the screen this happens: (after 5*0.0325 seconds--how long it takes the circle to shrink--has passed, it will add the physicsbody to both the gray and white part so I can detect which the ball is touching)
DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: {
let trackPhysics = SKPhysicsBody(texture: track.texture!, size: track.texture!.size())
trackPhysics.isDynamic = false
trackPhysics.affectedByGravity = false
track.physicsBody = trackPhysics
let goalPhysics = SKPhysicsBody(texture: goal.texture!, size: goal.texture!.size())
goalPhysics.isDynamic = false
goalPhysics.affectedByGravity = false
goal.physicsBody = goalPhysics
})
Which works. The SKPhysicsBody are applied to both the gray and white part perfectly without any loss in framerate and follow the rotation. The problem is... how do I detect which is the ball touching? Since they did not formally collide, it will not call the collision at all (which makes sense since they did not really collide
This is the basic logic, and it works perfectly until I want to check the results:
someone touches the screen
circles shrink
right after they shrink it will create an SKPhysicsBody for both the gray and white part based on its texture
Problem is here... how do I detect which the ball is touching?
I have tried this:
func didBegin(_ contact: SKPhysicsContact) {
print("touching!")
}
func didEnd(_ contact: SKPhysicsContact) {
print("not touching")
}
which has no messages at all, and have tried using allContactedBodies() 1 second after the SKPhysicsBody were applied, but returns a count with 0 for all of them. I have even tried making this 5 seconds and still not working
DispatchQueue.main.asyncAfter(deadline: .now() + seconds + 1, execute: {
print(track.physicsBody!.allContactedBodies().count)
print(goal.physicsBody!.allContactedBodies().count)
})
This is what it looks like with the SKPhysicsBody applied, you can see the physics is applied perfectly
Am I doing something wrong? Any ideas?
The solution (as discovered in the comments above), was:
Set the isDynamic property of nodes to true for collision detection to work properly.
Ensure that didBegin(contact:) is being called, by double-checking that the scene's physicsWorld.contactDelegate is set.
Ensure the masks and physics bodies are set at the proper times and locations.
Related
I'm creating a small game where the ball needs to bounce on the upper, left and right wall, while it should stop as soon as it touches the floor.
I already have a working contact-detection code and delegate that is called in the correct situation and with the correct timing.
Creating a small circle in the collision point found in the func didBegin(_ contact: SKPhysicsContact) creates the node in the correct position.
In the same method I call ball.physicsBody?.velocity = .zero and ball.position = contact.contactPoint and the ball correctly stops. The problem is that sometimes the ball stops a frame too late, after it's already bounced off the floor.
The ball has a restitution of 1 because I would like it to bounce without slowing down, except when touching the floor.
I have already tried to set the position to zero, the velocity to zero, its isDynamic attribute to false. Each of these tries does what it's supposed to do, but sometimes it happens a frame too late.
Both the ball and the floor have the usesPreciseCollisionDetection property set to true.
If I try to add some artificial delay, the contact method is called again (breaking part of the gameplay).
Here is the code of my ball node:
class Ball: SKSpriteNode {
convenience init() {
self.init(imageNamed: "earth")
physicsBody = SKPhysicsBody(circleOfRadius: 12.5)
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.categoryBitMask = Masks.ball
physicsBody?.contactTestBitMask = Masks.floor
physicsBody?.allowsRotation = false
physicsBody?.restitution = 1
physicsBody?.friction = 0
physicsBody?.angularDamping = 0
physicsBody?.linearDamping = 0
}
}
and here is the code for my didBegin method:
func didBegin(_ contact: SKPhysicsContact) {
ball.physicsBody?.velocity = .zero
ball.position = contact.contactPoint
}
I would expect the ball to stop while still touching the floor, and not a frame after it's already bounced off.
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
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.
For a game I'm creating, an SKSpriteNode gradually makes its way down the user's screen. There's another SKSpriteNode (position is static) near the bottom of the screen, leaving only a little bit of space for the original SKSpriteNode to fit in. I need to detect when the first SKSpriteNode is COMPLETELY below the second SKSpriteNode, which I'm having a bit of trouble with. Here's the code I'm currently using:
if (pos.y > barPosY) //pos.y = 1st SKSpriteNode, barPosY = 2nd SKSpriteNode
{
touchedTooEarly = true
}
For some reason, when the first SKSpriteNode goes just a little bit over the 2nd SKSpriteNode (not all the way), it still detects it as being completely over. Is there a coordinate space issue I'm missing?
The logic
A sprite a covers a sprite b if
b.frame is inside a.frame
b.zPosition is below a.zPosition
The extension
Now let's build an extension
extension SKSpriteNode {
func isCoveredBy(otherSprite: SKSpriteNode) -> Bool {
let otherFrame = CGRect(
origin: convertPoint(otherSprite.position, fromNode: otherSprite),
size: otherSprite.frame.size
)
return zPosition < otherSprite.zPosition && CGRectContainsRect(frame, otherFrame)
}
}
Problem #1: transparency
This mechanism totally ignores transparency.
Problem #2: same sprite
If you compare 2 sprites of the same type the function CGRectContainsRect will return true only when they are exactly aligned. Althoug you can solve this problem creating a smaller rectangle when you compare the sprites.
I'm using SpriteKit for a game where there is a ball and a character. The character is moving in a line with SKAction and the user can move the ball by touch. When the ball and character makes contact, I'd like the ball to be attached to the character.
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == charMask && contact.bodyB.categoryBitMask == ballMask{
println("contact")
if contactMade {
let movingBall = contact.bodyB.node!.copy() as! SKSpriteNode
//this line of code doesn't affect anything no matter what coordinates i set it to
movingBall.position = contact.bodyA.node!.position
contact.bodyB.node!.removeFromParent()
contact.bodyA.node!.removeAllActions()
contact.bodyA.node!.addChild(movingBall)
}
}
}
The current issue is that when I do contact.bodyA.node!.addChild(ball) (character node), the ball gets attached to the far left side of the character instead of on the character and it seems setting movingball.position does not affect it. I've also tried doing addChild to the scene and although it adds the ball to the correct place, it doesn't move with the character without another SKAction. What did I do wrong/what can I do to fix it?
Also, instead of using 2 sprites like that, should I use separate sprites so that theres a character sprite without a ball, character sprite with a ball and the ball itself, then just change the node's texture when they contact?
as movingBall is to be a child of bodyA.node, it's position needs to be in bodyA.node's coordinate space. So if you gave it coordinates of (0,0) it'd be in the middle:
Replace:
movingBall.position = contact.bodyA.node!.position
with
movingBall.position = CGPointZero
and see how that looks. the ball should be positioned directly on top of bodyA.node.