SKSpriteNode physics body created from texture results in nil value - swift

I'm currently working on a small iOS game. In its current iteration, 20 targets spawn and move across the screen space-invaders style, and you control a little ship to shoot and destroy them. The code for my targets, the player ship's bullets, and a simple collision detection function I've written in the interim are as follows:
class Red_Target: SKSpriteNode{
var game_scene: GameScene!
private var ship_texture: SKTexture!
convenience init(scale: CGFloat, game_world: GameScene){
self.init(texture: SKTexture(imageNamed: "Proto Target"))
self.ship_texture = SKTexture(imageNamed: "Proto Target")
self.setScale(scale)
game_scene = game_world
game_scene.addChild(self)
self.position = CGPoint(x: game_scene.view!.bounds.width/10, y: 9 * game_scene.view!.bounds.height/10)
//self.physicsBody = SKPhysicsBody(texture: ship_texture, size: self.size)
self.physicsBody = SKPhysicsBody(circleOfRadius: 13)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.categoryBitMask = CollisionType.Enemy.rawValue
self.physicsBody!.contactTestBitMask = CollisionType.Player_Bullet.rawValue
}
func move() {
self.run(space_invaders(scene: game_scene))
}
}
class PC_Bullet: SKSpriteNode{
convenience init(scale: CGFloat){
self.init(imageNamed: "Goodbullet")
self.setScale(scale)
self.physicsBody = SKPhysicsBody(circleOfRadius: 3)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.categoryBitMask = CollisionType.Player_Bullet.rawValue
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.contactTestBitMask = CollisionType.Enemy.rawValue
}
}
func didBegin(_ contact: SKPhysicsContact) {
contact.bodyA.node!.removeFromParent()
contact.bodyB.node!.removeFromParent()
}
}
This code, in its current iteration, works just fine. However, if the line defining the target's physicsbody as its texture is uncommented and the line defining physicsbody as circleOfRadius is removed, the game will consistently crash after the 5th target is destroyed, claiming that didBegin unwraps a nil value. Why does this only happen from physics bodies with textures? Is there any way I could change the code for this to work? I would love to be able to use the physics body from texture function later on, when working with more irregular shapes.

You are pulling a classic nooby mistake. You are removing your bodies too early. If you browse Stackoverflow you will find a plethera of ways to solve it.
The basic idea is do not remove your sprites until the end of the physics phase because your 1 sprite could have multiple contact points to handle. So come up with a way to flag sprites that need to be deleted, and remove them during the didSimulatePhysics function.

Related

Swift: SKEmitterNode affected by PhysicsWorld gravity?

I'm trying to have a rain particles which are affected by wind aka physicsWorld gravity.
I can see that the gravity does has an affect on my SKSpriteNodes but I can't achieve the same affect on an SKEmitterNode.
I'm just wondering if it's possible.
Here's what I've been trying...
override func didMove(to view: SKView) {
if let rainParticles = SKEmitterNode(fileNamed: "Rain.sks") {
rainParticles.position = CGPoint(x: size.width/2, y: size.height)
rainParticles.name = "rainParticle"
rainParticles.targetNode = scene
rainParticles.particlePositionRange =
CGVector(dx: frame.size.width, dy: frame.size.height)
rainParticles.zPosition = -1
// I don't think this is right
rainParticles.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
rainParticles.physicsBody?.affectedByGravity = true
addChild(rainParticles)
}
physicsWorld.contactDelegate = self
// gravity is pushing to the right here
physicsWorld.gravity = CGVector(dx: 20, dy: 0)
physicsWorld.speed = 0.85
}
Yes I have added SKPhysicsContactDelegate.
Obviously I want to ignore collisions so I haven't set collisionBitMask, also I don't want to have rain bouncing off anything with contactTestBitMask. I don't believe I need to set a categoryBitMask.
Particles are not represented by objects in SpriteKit. This means you cannot perform node-related tasks on particles, nor can you associate physics bodies with particles to make them interact with other content. Although there is no visible class representing particles added by the emitter node, you can think of a particle as having properties like any other object.
This is straight from SKEmitterNode documentation. Particles won't get any gravity acceleration from the physicsWorld of the scene.
Also rainParticles.physicsBody refers to the SKEmitterNode physicsBody, not its particles.
If you simply want the particles to simulate the current physicsWorld's gravity:
rainParticles.xAcceleration = self.physicsWorld.gravity.dx
rainParticles.yAcceleration = self.physicsWorld.gravity.dy

Stop sprite from leaving screen using physics bodies

I have a sprite that the user is able to move side to side by pressing on the left or right side of the screen. If you hold down on either side, the player sprite will leave the screen. I want to stop that from happening using physic bodies, but I can't seem to make it work.
To start, here are my categories.
//Categories for physics bodies
let sceneCategory:UInt32 = 0x1 << 0 // Equal to 1
let playerCategory:UInt32 = 0x1 << 1 // Equal to 2
Here is where I set the physics body of the scene itself.
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody?.categoryBitMask = sceneCategory
self.physicsBody?.contactTestBitMask = playerCategory
self.physicsBody?.collisionBitMask = 0
self.physicsBody?.isDynamic = false
This is how I have the physics set for the player itself. I'm trying to set the physics body around the car itself. Is the way I have it setup the same as setting an alpha mask physics body?
let texture = SKTexture(imageNamed: "PorscheBlue")
player.physicsBody = SKPhysicsBody(texture: texture, size: player.size)
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = sceneCategory
player.physicsBody?.collisionBitMask = 0
Then, in the didBegin method, I just wanted it to print showing that the method was called. This isn't happening though for some reason.
func didBegin(_ contact: SKPhysicsContact)
{
print("called")
}
Why isn't the didBegin method being called? Do I have the physics set up properly? How can I make it so the player isn't allowed to leave the screen when moving?
Thank you
EDIT: So when having the physics boundaries visible, it looks like the boundaries for the scene are only being drawn on the top and bottom. I can't see any lines being drawn on the sides. That may be the issue, but I can't get it to show on the sides.
I resolved my issue by changing the dynamic value. I had both set to false, which causes the didBegin function to not be called. At least one of the nodes needs to be dynamic.

Using a fixed SKPhysicsJoint to attach a platform detection hitbox as a child to a player sprite changes player's collision and detection bit masks

I'm trying to add a hitbox to the bottom of my player to only detect platforms. After a lot of trial and error I figured out to have an additional physicsbody attached as a child in a fixed position to another physicsbody I need to use an SKPhysicsJoint. I did this and it seems to have an unforeseen consequence. when I add the hitbox to the player in this way, it alters the players collision / contact bit masks slightly.
In my game you tap the screen to jump and it works seamlessly with no lag like so:
Player jumping
But when I add this hitbox with the joint I have to hold the screen with my finger and eventually the player jumps, but most of the time the input is ignored. Almost as if there's some sort of severe lag when reading the input.In this gif I'm tapping the screen constantly trying to make my player jump and a lot of the inputs are being ignored or blocked:
Player hitbox not behaving
This function setups up the player in my gamescene.swift and adds the hitbox to the player via a fixed SKPhysicsJoint to the scene:
func playerSetup(){
//setups player
addChild(player)
player.addChild(playerPlatformHitbox)
let myCGPoint = player.position // sets joint position
let myJoint = SKPhysicsJointFixed.joint(withBodyA: playerPlatformHitbox.physicsBody!, bodyB: player.physicsBody!, anchor: myCGPoint)
scene?.physicsWorld.add(myJoint)
}
And this is the hitbox class I made to only detect platforms for the player. you'll see it should ignore everything expect platforms from the bit masks. For some reason it's not allowing the player to accept contact with it's bit masks even though this hitbox and player arent touching:
import Foundation
import SpriteKit
class PlayerPlatformHitbox: SKSpriteNode{
init() {
super.init(texture: nil, color: SKColor.blue, size: CGSize(width: playerTexture.size().width, height: 10))
physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.size.width, height: self.size.height))
position = CGPoint(x:0, y: (-playerTexture.size().height * 0.6))
physicsBody?.categoryBitMask = CollisionTypes.jumpHitBox.rawValue
physicsBody?.contactTestBitMask = CollisionTypes.platform.rawValue
physicsBody?.collisionBitMask = CollisionTypes.platform.rawValue
physicsBody?.restitution = 0.0
physicsBody?.friction = 0.0
zPosition = 20
physicsBody?.linearDamping = 0.0
physicsBody?.angularDamping = 0.0
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)}
}
And this is my player class
class Player: SKSpriteNode {
init() {
super.init(texture: nil, color: SKColor.clear, size: playerTexture.size())
//starts accelerameter
motionManager = CMMotionManager()
motionManager.startAccelerometerUpdates()
physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: playerTexture.size().width,
height: playerTexture.size().height))
physicsBody?.categoryBitMask = CollisionTypes.player.rawValue
physicsBody?.contactTestBitMask = CollisionTypes.memoryModule.rawValue | CollisionTypes.spikes.rawValue | CollisionTypes.finish.rawValue | CollisionTypes.enemy.rawValue | CollisionTypes.ground.rawValue
physicsBody?.collisionBitMask = CollisionTypes.ground.rawValue | CollisionTypes.sceneEdge.rawValue
physicsBody?.affectedByGravity = true
physicsBody?.restitution = 0.0
physicsBody?.friction = 0.3
physicsBody?.isDynamic = true
//physicsBody?.friction = 0.0
physicsBody?.allowsRotation = false
setScale(0.65)
zPosition = 1
physicsBody?.linearDamping = 0.0
physicsBody?.angularDamping = 0.0
animateWalk()
}
The goal of all this is to use the hitbox to detect platforms instead of the player in this function I have in my update method:
//jump through platform check
if let body = player.physicsBody {
let dy = body.velocity.dy
if dy > 0{
// Prevent collisions if the hero is jumping
body.collisionBitMask &= ~CollisionTypes.platform.rawValue
body.contactTestBitMask &= ~CollisionTypes.platform.rawValue
}
else {
// Allow collisions if the hero is falling
body.collisionBitMask |= CollisionTypes.platform.rawValue
body.contactTestBitMask |= CollisionTypes.platform.rawValue
}
}
and the only reason I need to do this is because when the player is falling, if he falls and hits the side of the platform he will stop and slide down it because collisions are back on and the players physicsbody is a square.
If i can use the platform detection hitbox to detect the platforms instead of the players bulky hitbox it will get rid of this issue.
Thanks for any and all advice.
EDIT: this is the childs behavior with a physics body and no joint:
Odd behavior
I found the solution.
It seems there's some bugs with fixed joints and the answer was to use a "pin" joint instead. This StackO question goes into detail about a rotation bug with fixed joints. On a whim I decided to change my fixed joint to a pin and now the child behaves as you would expect, positionally speaking anyway.
So it appears fixed joints have a few bugs that need to be worked out.

Swift Sprite Collision. Objects passing Through walls

I'm making a simple game(using Swift & SpriteKit), where I have a circle that can be dragged around. but the circle is not allowed to go through walls.
My collision BitMask works perfectly, but when I drag fast enough, the circle ends up going through the walls.
The Initialisation of the Player Sprite goes Like this:
func initPlayerSprite(){
let playerTexture = SKTexture(imageNamed: "player.png")
let originX = CGRectGetMidX(self.frame)
let originY = CGRectGetMidY(self.frame)
player = SKSpriteNode(texture: playerTexture, size: CGSize(width: 26, height: 26))
player.position = CGPoint(x: originX , y: originY)
player.physicsBody = SKPhysicsBody(circleOfRadius: playerTexture.size().height/2)
player.physicsBody!.dynamic = true
player.physicsBody?.allowsRotation = false
player.physicsBody!.categoryBitMask = ColliderType.Player.rawValue
player.physicsBody!.contactTestBitMask = ColliderType.Floor.rawValue + ColliderType.Gap.rawValue
player.physicsBody!.collisionBitMask = ColliderType.Wall.rawValue + ColliderType.Floor.rawValue
self.addChild(player)
}
My code for moving the Sprite goes like this:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?){
var nodeTouched = SKNode()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let dx = -player.position.x + location.x
let dy = -player.position.y + location.y
let movePlayer = SKAction.moveBy(CGVector(dx: dx, dy: dy), duration: 0.02)
}
}
Any Idea how to make sure the collision detection work even at high velocities?
From your code its hard to judge.
Why are you calling the collisions like
ColliderType.Wall.rawValue + ColliderType....
I haven't seen this before with a "+" as a separator, usually you use a "|"
to separate them.
ColliderType.Wall.rawValue | ColliderType....
Using a plus should add the two values together and will not treat it as 2 collision types, as far as I understand. I might be wrong here.
Also did you try using precise collision detection like so?
...physicsBody.usesPreciseCollisionDetection = true.
Apple describes this bool as follows
The default value is NO. If two bodies in a collision do not perform precise collision detection, and one passes completely through the other in a single frame, no collision is detected. If this property is set to YES on either body, the simulation performs a more precise and more expensive calculation to detect these collisions. This property should be set to YES on small, fast moving bodies.
Not sure this is helping

SKSpriteNode won't position after didBeginContact

I'm working with collision detection with SpriteKit and Swift. I have a SKScene that responds to a collision with the didBeginContact function:
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == ColliderType.Food && contact.bodyB.categoryBitMask == ColliderType.Head) {
placeFoodInRandomGridLocation()
}
}
func placeFoodInRandomGridLocation() {
let randomX = arc4random_uniform(UInt32(myGrid.columnCount))
let randomY = arc4random_uniform(UInt32(myGrid.rowCount))
foodSpriteHolder.position = CGPoint(x: colLines[Int(randomX)], y: rowLines[Int(randomY)])
}
The problem is that I can easily adjust the position of the foodSpriteHolder before this didBeginContact function fires. It simply will not move the foodSpriteHolder when the placeFoodInRandomGridLocation is called.
It seems like a scope issue. I'm just not sure how to isolate why the position won't update. I can even make the foodSpriteHolder visibility hidden in this flow...So, I know i can access it.
For reference here is how the physics body is setup for the Food class item that is within the foodSpriteHolder:
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size, center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Food
Lastly the placeFoodInRandomGridLocation function definitely gets called...The position just won't update.
Thanks for any ideas,
Josh
The answer in this case was to remove the node when the collision is detected:
contact.bodyA.node?.removeFromParent()
Now I can create a new node in a new position. Since affectedByGravity is set to false the node wouldn't update its position.