Why is SKPhysicsBody not picking up alpha?! SpriteKit for a WatchOS game.

So I'm trying to use SpriteKit to make the background of my game. The walls have alpha 1.0, and the safe area alpha 0 and fully transparent. (e.g. a big black square with a smaller transparent square in the middle of it). Yet sprite kit always assume the entire image is either fully opaque or fully transparent. That defies its purpose isn't it? Is there a way to make this work?

Your application must fill the entire screen with content. Consider using a simple SKShapeNode to provide a black background behind your game’s main play area.

My content does fill the entire screen. I am also using SKShapeNode for the game's area and my level background that I need collision on. When using "alphaThreshold: 1.0," no collision happen. When removing the alphaThreshold, collision happen instantly, even in the hollowed/transparent area of my level background asset.

Some context: A rocket spawns on a base. The rocket moves within the transparent area of the background, and collides with its when hitting the opaque area of the background (walls)

import SpriteKit
import SwiftUI

class GameScene: SKScene, SKPhysicsContactDelegate {
    var thrustActive: Binding!
    var rotationDegrees: Binding!

    private var rocket: SKSpriteNode!
    private var base: SKSpriteNode!
    private var background: SKSpriteNode!

    override func sceneDidLoad() {
        super.sceneDidLoad()

        // Remove the default background color
        backgroundColor = .clear

        // Create a black SKShapeNode that fills the entire scene
        let blackBG = SKShapeNode(rect: CGRect(origin: .zero, size: size))
        blackBG.fillColor = .black
        blackBG.strokeColor = .clear
        blackBG.zPosition = -10 // behind everything else
        addChild(blackBG)

        // Center point for alignment
        let centerX = size.width / 2
        let centerY = size.height / 2

        // Add the base (below the rocket, centered horizontally)
        base = SKSpriteNode(imageNamed: "Base")
        base.size = CGSize(width: 60, height: 10)
        base.position = CGPoint(x: centerX, y: centerY - 25)
        base.zPosition = 1
        addChild(base)

        base.physicsBody = SKPhysicsBody(rectangleOf: base.size)
        base.physicsBody?.isDynamic = false
        base.physicsBody?.categoryBitMask = PhysicsCategory.base
        base.physicsBody?.contactTestBitMask = PhysicsCategory.rocket
        base.physicsBody?.collisionBitMask = PhysicsCategory.rocket

        // Add the rocket (centered)
        rocket = SKSpriteNode(imageNamed: "Rocket")
        rocket.size = CGSize(width: 15, height: 25) // Adjusted size
        rocket.position = CGPoint(x: size.width / 2, y: size.height / 2)
        rocket.zRotation = 0
        rocket.zPosition = 1
        addChild(rocket)

        // Rocket physics body
        rocket.physicsBody = SKPhysicsBody(rectangleOf: rocket.size)
        rocket.physicsBody?.isDynamic = true
        rocket.physicsBody?.linearDamping = 0.02
        rocket.physicsBody?.angularDamping = 0.02
        rocket.physicsBody?.allowsRotation = true
        rocket.physicsBody?.categoryBitMask = PhysicsCategory.rocket
        rocket.physicsBody?.contactTestBitMask = PhysicsCategory.base | PhysicsCategory.background
        rocket.physicsBody?.collisionBitMask = PhysicsCategory.base | PhysicsCategory.background

        // Physics World
        physicsWorld.gravity = CGVector(dx: 0, dy: -0.2)
        physicsWorld.contactDelegate = self
    }

    func configureBackground(imageName: String, position: CGPoint, scale: CGFloat = 1.0) {
        let bgTexture = SKTexture(imageNamed: imageName)

        background = SKSpriteNode(texture: bgTexture)
        background.size = CGSize(
            width: background.size.width * scale,
            height: background.size.height * scale
        )
        background.position = position
        background.zPosition = -1
        addChild(background)

        // Create a physics body from the texture, ignoring any pixels with alpha < 1.0
        background.physicsBody = SKPhysicsBody(
            texture: bgTexture,
            alphaThreshold: 1.0,
            size: background.size
        )
        background.physicsBody?.isDynamic = false
        background.physicsBody?.categoryBitMask = PhysicsCategory.background
        background.physicsBody?.contactTestBitMask = PhysicsCategory.rocket
        background.physicsBody?.collisionBitMask = PhysicsCategory.rocket
    }

    override func update(_ currentTime: TimeInterval) {
        // Apply rotation based on gesture
        if rotationDegrees.wrappedValue != 0 {
            rocket.zRotation = (rotationDegrees.wrappedValue + 90) * .pi / 180
        }

        // Apply thrust if active
        if thrustActive.wrappedValue {
            let thrustMagnitude: CGFloat = 4.0
            let radians = rocket.zRotation + .pi / 2
            let dx = cos(radians) * thrustMagnitude
            let dy = sin(radians) * thrustMagnitude
            rocket.physicsBody?.applyForce(CGVector(dx: dx, dy: dy))
        }
    }

    func didBegin(_ contact: SKPhysicsContact) {
        let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask

        if contactMask == PhysicsCategory.rocket | PhysicsCategory.base {
            print("Rocket has landed on the base!")
        }

        if contactMask == PhysicsCategory.rocket | PhysicsCategory.background {
            print("Rocket collided with the background!")
        }
    }
}

struct PhysicsCategory {
    static let rocket: UInt32 = 0x1 << 0
    static let base: UInt32 = 0x1 << 1
    static let background: UInt32 = 0x1 << 2
}

import SwiftUI
import SpriteKit

struct Level1View: View {
    @State private var thrustActive: Bool = false
    @State private var rotationDegrees: CGFloat = 0.0

    var body: some View {
        ZStack {
            SpriteView(scene: createGameScene())
                .edgesIgnoringSafeArea(.all)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            let startLocation = value.startLocation
                            let dx = value.location.x - startLocation.x
                            let dy = value.location.y - startLocation.y
                            let angleRadians = atan2(-dy, dx)
                            rotationDegrees = (angleRadians * 180 / .pi) + 180
                        }
                )
                .onTapGesture {
                    thrustActive = true
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                        thrustActive = false
                    }
                }
        }
        .navigationBarHidden(true)
    }

    private func createGameScene() -> SKScene {
        let screenBounds = WKInterfaceDevice.current().screenBounds
        let screenSize = CGSize(width: screenBounds.width, height: screenBounds.height)

        let scene = GameScene(size: screenSize)
        scene.scaleMode = .aspectFill
        scene.thrustActive = $thrustActive
        scene.rotationDegrees = $rotationDegrees

        // Configure the background with image "Level1", centered, at half scale.
        scene.configureBackground(
            imageName: "Level1",
            position: CGPoint(x: screenSize.width / 2, y: screenSize.height / 2),
            scale: 0.5
        )

        return scene
    }
}

Here is the test level background image I am using, a big white square, with a smaller hollow square in the middle of it:

Why is SKPhysicsBody not picking up alpha?! SpriteKit for a WatchOS game.
 
 
Q