Eve
Eve

Reputation: 61

How to implement a raycasting inside Spritekit

I want to check if a laser beam (a line) goes through objects and at which points. Currently I'm recreating the Asteroids project by grapefrukt. For more details you check this video (https://www.youtube.com/watch?v=MeJ76z2Ncyg&t=622s).

There is a method called hitTestWithSegmentFromPoint.. inside SceneKit but I want to achieve the effect only using Spritekit.

enter image description here Edit 1: I need to know the position of the black circles (the points of intersection). The asteroid is with red color, the beam is green.

Edit 2: I was hopeful that the @Knight0fDragon solution will work.. but it didn't. Since the line appears through the box it doesn't detect the first contact correctly.. nor the second (when leaving the box)

The white circle is the contact point.. the purple arrows point to where the contact points should be. The blue circle is the ship :)

I also made when the collision is detected to make the boxes physics bodies not dynamic (stoping them from moving)

enter image description here

Edit 3: Closer image to see where physics bodies are using showsPhysics

enter image description here

Edit 4: How I make the white circle

let firstBody = contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
 let secondBody = contact.bodyA == firstBody ? contact.bodyB : contact.bodyA

if firstBody.categoryBitMask == UInt32(1){

    secondBody.isDynamic = false;

    // run only once 
    print(contact.contactPoint);
    createCircleDebug(x: contact.contactPoint.x, y: contact.contactPoint.y, radius: 5)

}


func createCircleDebug (x:CGFloat, y:CGFloat, radius:CGFloat){

    let shape = SKShapeNode.init(circleOfRadius: radius);
    shape.strokeColor = SKColor.white;
    shape.position = CGPoint(x: x, y: y);

    self.addChild(shape);

}

Edit 5: Relative line path. The circle is still off.

enter image description here

Edit 6: Line is now a rectangle using the new code for creating it

enter image description here

Edit 7: Using Spritenode instead of circle

enter image description here

Upvotes: 1

Views: 908

Answers (2)

ford
ford

Reputation: 11866

The Apple documentation includes a guide on ray casting with SKPhysics:

Searching the World for Physics Bodies

To find the nearest physics body that the ray hits, you can use body(alongRayStart:end:). This is a method on SKPhysicsWorld.

There is also enumerateBodies(alongRayStart:end:using:) if you want to get all bodies that are hit by the ray and where they were hit. This method calls your block for each physics body that the ray touches, with the following arguments:

  • body: The physics body that the ray intersected.
  • point: The point in scene coordinates where the ray contacted the physics body.
  • normal: The normal vector for the physics body at the point of contact.
  • stop: A pointer to a Boolean variable. Your block can set this to true to terminate the enumeration.

Upvotes: 1

Knight0fDragon
Knight0fDragon

Reputation: 16837

Normally I would do what ford said (It was the answer I was going to provide after you answered my question), but if you need to know points of intersection I would create an SKNode with an SKPhysicsBody(polygonFrom:) and use contactBegins to get the intersection point

func rayCast(start:CGPoint,end:CGPoint){

        let mid = CGPoint((end.x + start.x) / 2,(end.y + start.y) / 2)
        let relativeStart1 = CGPoint(start.x - mid.x - 1,start.y - mid.y)
        let relativeEnd1 = CGPoint(end.x - mid.x - 1,end.y - mid.y-1)
        let relativeStart2 = CGPoint(start.x - mid.x + 1,start.y - mid.y)
        let relativeEnd2 = CGPoint(end.x - mid.x + 1,end.y - mid.y)

        let node = SKNode()
        let linePath = UIBezierPath()
        linePath.move(to: relativeStart1)
        linePath.addLine(to: relativeEnd1)
        linePath.addLine(to: relativeEnd2)
        linePath.addLine(to: relativeStart2)
        linePath.closeSubPath()    
        if let physicsBody = SKPhysicsBody(polygonFrom:linePath.cgPath){
            physicsBody.categoryBitMask = 0x80000000  //whatever you want it to be
            physicsBody.contactBitMask = ~physicsBody.categoryBitMask //touch everything but the line
            physicsBody.collisionBitMask = 0
            physicsBody.affectedByGravity = false
            node.physicsBody = physicsBody
        }
        scene.addChild(node)
        node.position = mid
}



func didBegin(_ contact: SKPhysicsContact) {

    let firstBody = contact.bodyA.categeroyBitMask > contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
    let secondBody = contact.bodyA = firstBody ? contact.bodyB : contact.bodyA

    if firstBody.categoryBitMask == 0x80000000{
       //use contact at this point to check intersection points
       //https://developer.apple.com/documentation/spritekit/skphysicscontact
    }

}

Upvotes: 0

Related Questions