Okay, so I have a really specific problem and I hope I can help you. I will attach an image to clarify what I'm talking about.
redball.physicsBody is spawning above boss.physicsBody - falling (through) the boss. Colliding with whitebar.physicsBody - bouncing back up and once again colliding with boss.physicsBody. THIS time, i want to fire an event that notices when the ball is bouncing back up and hitting the boss.
Currently. Boss & Ball shares the same collisionBitMask, so that they can pass through each other. When the ball collides with the bar, I'm trying to add ball.physicsBody?.contactTestBitMask = PhysicsCategory.boss.rawValue to the ball. So that i can notice the collision on the way back up between ball & boss. However this does not seem to work.
Does anyone have a solution to this weird problem? Super thankful for some help.
Sidenote: If someone can figure out a clever title, let me know and ill edit it!
EDIT: Adding code
enum PhysicsCategory : UInt32 {
case bar = 1
case ball = 2
case boss = 4
case noCollision = 8
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var score = Int()
var background = SKSpriteNode(imageNamed: "background.png")
var bar = SKSpriteNode(imageNamed: "bar.png")
var boss1 = SKSpriteNode(imageNamed: "boss1.png")
override func didMoveToView(view: SKView) {
self.scene?.size = CGSize(width: 640, height:1136)
physicsWorld.contactDelegate = self
background.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
background.zPosition = -20
self.addChild(background)
bar.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 8)
bar.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: bar.size.width, height: bar.size.height))
bar.physicsBody?.categoryBitMask = PhysicsCategory.bar.rawValue
bar.physicsBody?.contactTestBitMask = PhysicsCategory.ball.rawValue
bar.physicsBody?.collisionBitMask = PhysicsCategory.bar.rawValue
bar.physicsBody?.affectedByGravity = false
bar.physicsBody?.dynamic = false
self.addChild(bar)
boss1.position = CGPoint(x: self.frame.width, y: self.frame.height - boss1.size.height / 2)
boss1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: boss1.size.width, height: boss1.size.height))
boss1.physicsBody?.categoryBitMask = PhysicsCategory.boss.rawValue
boss1.physicsBody?.contactTestBitMask = PhysicsCategory.ball.rawValue
boss1.physicsBody?.collisionBitMask = PhysicsCategory.noCollision.rawValue
boss1.physicsBody?.affectedByGravity = false
boss1.physicsBody?.dynamic = false
boss1.zPosition = 5
self.addChild(boss1)
boss1.runAction(SKAction.moveToX(self.frame.width / 2, duration: 1))
let spawnBallsAction = SKAction.sequence([SKAction.waitForDuration(2), SKAction.runBlock(spawnBalls)])
self.runAction(SKAction.repeatActionForever(spawnBallsAction))
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if (firstBody.categoryBitMask & PhysicsCategory.bar.rawValue == PhysicsCategory.bar.rawValue &&
secondBody.categoryBitMask & PhysicsCategory.ball.rawValue == PhysicsCategory.ball.rawValue) {
CollisionWithBar(firstBody.node as! SKSpriteNode, ball: secondBody.node as! SKSpriteNode)
}
if (firstBody.categoryBitMask & PhysicsCategory.ball.rawValue == PhysicsCategory.ball.rawValue &&
secondBody.categoryBitMask & PhysicsCategory.boss.rawValue == PhysicsCategory.boss.rawValue) {
CollisionWithBoss(firstBody.node as! SKSpriteNode, boss: secondBody.node as! SKSpriteNode)
}
}
func CollisionWithBar(bar: SKSpriteNode, ball: SKSpriteNode) {
ball.physicsBody?.applyImpulse(CGVectorMake(0, 500))
ball.physicsBody?.contactTestBitMask = PhysicsCategory.boss.rawValue //Trying to solve the problem, aint working
}
func CollisionWithBoss(ball: SKSpriteNode, boss: SKSpriteNode) {
NSLog("Ball hit the boss")
}
func spawnBalls(){
let ball = SKSpriteNode(imageNamed: "ball.png")
let MinValue = self.frame.width / 8
let MaxValue = self.frame.width - 20
let SpawnPoint = UInt32(MaxValue - MinValue)
ball.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.frame.height - 128)
ball.zPosition = 50
//Physics
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.frame.height / 2)
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball.rawValue
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bar.rawValue
ball.physicsBody?.collisionBitMask = PhysicsCategory.noCollision.rawValue
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.dynamic = true
self.addChild(ball)
}
This is how contacts should be done:
Upon creating a ball set the contact category to this:(which you have)
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bar.rawValue
Make sure the boss contacts nobody:
boss1.physicsBody?.contactTestBitMask = PhysicsCategory.noCollision.rawValue
Then on Contact, you want to do this:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = (contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask) ? contact.bodyA : contact.bodyB
let secondBody : SKPhysicsBody = (contact.bodyA.categoryBitMask >= contact.bodyB.categoryBitMask) ? contact.bodyA : contact.bodyB
if (firstBody.categoryBitMask & PhysicsCategory.bar.rawValue == PhysicsCategory.bar.rawValue &&
secondBody.categoryBitMask & PhysicsCategory.ball.rawValue == PhysicsCategory.ball.rawValue) {
//This should be inside CollisionWithBar
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bar.rawValue | PhysicsCategory.boss.rawValue
//the ball has hit the bar, so lets enable hitting on the boss
//normally we would only want to do this once, but since this is
//tiny, it would be more time to wrap it in ifs and putting guards on it
boss1.physicsBody?.contactTestBitMask = PhysicsCategory.ball.rawValue
CollisionWithBar(firstBody.node as! SKSpriteNode, ball: secondBody.node as! SKSpriteNode)
}
if (firstBody.categoryBitMask & PhysicsCategory.ball.rawValue == PhysicsCategory.ball.rawValue &&
secondBody.categoryBitMask & PhysicsCategory.boss.rawValue == PhysicsCategory.boss.rawValue) {
//This should be inside CollisionWithBoss
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bar.rawValue
CollisionWithBoss(firstBody.node as! SKSpriteNode, boss: secondBody.node as! SKSpriteNode)
}
}
Which basically says, if the ball hits the bar, enable boss hitting, if the ball hits the boss, disable boss hitting
Edit: Upon the realization of having multiple balls, we have to change some things
Make sure the boss contacts ball upon creation again:
boss1.physicsBody?.contactTestBitMask = PhysicsCategory.ball.rawValue
Now we need to create a new category
enum PhysicsCategory : UInt32 {
case bar = 1
case ball = 2
case boss = 4
case noCollision = 8
case initialBall = 16
}
In the beginning, assign the category of ball to this:
ball.physicsBody?.categoryBitMask = initialBall
Then your CollissionWithBar will look like this:
func CollisionWithBar(bar: SKSpriteNode, ball: SKSpriteNode) {
ball.physicsBody?.applyImpulse(CGVectorMake(0, 500))
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bar.rawValue | PhysicsCategory.boss.rawValue
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball.rawValue
}
And your CollissionWithBolls will look like this:
func CollisionWithBoss(ball: SKSpriteNode, boss: SKSpriteNode) {
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bar.rawValue
}
Finally, on Contact, you want to do this:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = (contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask) ? contact.bodyA : contact.bodyB
let secondBody : SKPhysicsBody = (contact.bodyA.categoryBitMask >= contact.bodyB.categoryBitMask) ? contact.bodyA : contact.bodyB
if (firstBody.categoryBitMask & PhysicsCategory.bar.rawValue == PhysicsCategory.bar.rawValue &&
secondBody.categoryBitMask & PhysicsCategory.ball.rawValue == PhysicsCategory.ball.rawValue) {
CollisionWithBar(firstBody.node as! SKSpriteNode, ball: secondBody.node as! SKSpriteNode)
}
if (firstBody.categoryBitMask & PhysicsCategory.ball.rawValue == PhysicsCategory.ball.rawValue &&
secondBody.categoryBitMask & PhysicsCategory.boss.rawValue == PhysicsCategory.boss.rawValue) {
CollisionWithBoss(firstBody.node as! SKSpriteNode, boss: secondBody.node as! SKSpriteNode)
}
}
Related
I would like to have one piece of code in my app where I can track the textures of two nodes at a time. If the textures of the two nodes don't match I would like them reset back to their original state before the user touched them. On top of that i would like another piece of code for tracking if the particular node's textures were matched under a particular time frame like 90 seconds. Any tips on how I can do something like this for my app? Thanks.
Here is my current code:
import Foundation
import SpriteKit
class EasyScreen: SKScene {
override func didMove(to view: SKView) {
var background = SKSpriteNode(imageNamed: "Easy Screen Background")
let timerText = SKLabelNode(fontNamed: "Arial")
timerText.fontSize = 40
timerText.fontColor = SKColor.white
timerText.position = CGPoint(x: 20, y: 400)
timerText.zPosition = 1
var counter:Int = 90
timerText.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
counter-=1
timerText.text = " Time: \(counter)"
print("\(counter)")
if counter <= 0{
let newScene = TryAgainScreen(fileNamed: "Try Again Screen")
newScene?.scaleMode = .aspectFill
self.view?.presentScene(newScene)
}
},SKAction.wait(forDuration: 1)])))
background.position = CGPoint(x: 0, y: 0)
background.size.width = self.size.width
background.size.height = self.size.height
background.anchorPoint = CGPoint(x: 0.5,y: 0.5)
let matchCardOne = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardTwo = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardThree = SKSpriteNode(imageNamed: "Fruit Match Card")
let matchCardFour = SKSpriteNode(imageNamed: "Fruit Match Card")
matchCardOne.name = "FruitMatchCard1"
matchCardTwo.name = "FruitMatchCard2"
matchCardThree.name = "FruitMatchCard3"
matchCardFour.name = "FruitMatchCard4"
matchCardOne.size = CGSize(width: 150, height: 300)
matchCardTwo.size = CGSize(width: 150, height: 300)
matchCardThree.size = CGSize(width: 150, height: 300)
matchCardFour.size = CGSize(width: 150, height: 300)
matchCardOne.zPosition = 1
matchCardTwo.zPosition = 1
matchCardThree.zPosition = 1
matchCardFour.zPosition = 1
matchCardOne.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardTwo.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardThree.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardFour.anchorPoint = CGPoint(x: 0.5, y: 0.5)
matchCardOne.position = CGPoint(x: -125, y: 60)
matchCardTwo.position = CGPoint(x: -125, y: -260)
matchCardThree.position = CGPoint(x: 70, y: 60)
matchCardFour.position = CGPoint(x: 70 , y: -260)
addChild(background)
addChild(matchCardOne)
addChild(matchCardTwo)
addChild(matchCardThree)
addChild(matchCardFour)
addChild(timerText)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view?.isMultipleTouchEnabled = false
let touch = touches.first
let positionInSceneOne = touch!.location(in: self)
let tappedNodes = nodes(at: positionInSceneOne)
for node in tappedNodes{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard1" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchTwo = touches.first
let positionInSceneTwo = touch!.location(in: self)
let tappedNodesTwo = nodes(at: positionInSceneTwo)
for node in tappedNodesTwo{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard2" {
tappedCard.texture = SKTexture(imageNamed: "Apple")
}
}
let touchThree = touches.first
let positionInSceneThree = touch!.location(in: self)
let tappedNodesThree = nodes(at: positionInSceneThree)
for node in tappedNodesThree{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard3" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
let touchFour = touches.first
let positionInSceneFour = touch!.location(in: self)
let tappedNodesFour = nodes(at: positionInSceneFour)
for node in tappedNodesFour{
if let tappedCard = node as? SKSpriteNode {
if tappedCard.name == "FruitMatchCard4" {
tappedCard.texture = SKTexture(imageNamed: "Grapes")
}
}
}
}
}
}
}
}
i have a code where i two spaceships floating around the screen however they can't break through the top of the screen and the motto. however they can fly through i sides. i've look at endless about of sites and done some many different types of skphysicsbodys but none of them help.
code example.
class GameScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode(imageNamed: "spaceship.png")
var player2 = SKSpriteNode(imageNamed: "spaceship.png")
var timer = NSTimer()
var tapsValid:Bool?
var playerRight:Bool?
var playerChange:Bool?
override func didMoveToView(view: SKView) {
let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
tapsValid = true
playerRight = true
self.scene?.backgroundColor = UIColor.whiteColor()
player.position = CGPointMake(self.size.width / 2, self.size.height / 1.8 + 280)
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.dynamic = false
player.physicsBody?.affectedByGravity = false
player.physicsBody?.restitution = 1
player.physicsBody?.friction = 0
player.physicsBody?.linearDamping = 0
player.physicsBody?.angularDamping = 0
player2.position = CGPointMake(self.size.width / 2, self.size.height / 14)
player2.physicsBody = SKPhysicsBody(rectangleOfSize: player2.size)
player2.physicsBody?.affectedByGravity = false
player2.physicsBody?.dynamic = false
player2.physicsBody?.restitution = 1
player2.physicsBody?.friction = 0
player.physicsBody?.linearDamping = 0
player.physicsBody?.angularDamping = 0
self.addChild(player)
self.addChild(player2)
can anyone help me out here. i just want to add physics to the edges of the screen. Thanks,
You should define your colliderTypes and categories for your nodes. That way you can check to see what hits what
enum ColliderType: UInt32 {
case Spaceship = 0
case CornerCategory = 1
}
self.physicsBody!.categoryBitMask = ColliderType.CornerCategory.rawValue
player.physicsBody?.contactTestBitMask = ColliderType.CornerCategory.rawValue
player2.physicsBody?.contactTestBitMask = ColliderType.CornerCategory.rawValue
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
//If it is true. Means that bodyA is the spaceship. Because it's 0
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
//Either way. firstBody will always be the spaceship
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == ColliderType. Spaceship.rawValue && secondBody.categoryBitMask == ColliderType.CornerCategory.rawValue{
//means that Spaceship and wall touches
print("Spaceship hits wall")
}
}
I am making a game using Sprite Kit in swift that has objects falling down the screen that need to be caught by the player(Labeled Person) at the bottom. I am trying do remove the falling objects from the screen when they contact the player but I cant call on the falling object (labeled Ice) inside of the function did begin contact. I am able to call on other objects like the person or the score label but not the object falling down the screen (Labeled Ice). Why is this I will post my full code below. This has really been bugging me. (ps. I am given the error "use of unresolved identifier")
import SpriteKit
struct physicsCatagory {
static let person : UInt32 = 0x1 << 1
static let Ice : UInt32 = 0x1 << 2
static let IceTwo : UInt32 = 0x1 << 3
static let IceThree : UInt32 = 0x1 << 4
static let Score : UInt32 = 0x1 << 5
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var scorenumber = Int()
var person = SKSpriteNode(imageNamed: "Person")
let Score = SKSpriteNode()
var ScoreLable = SKLabelNode()
override func didMoveToView(view: SKView) {
self.scene?.backgroundColor = UIColor.purpleColor()
physicsWorld.contactDelegate = self
self.scene?.size = CGSize(width: 640, height: 1136)
Score.size = CGSize(width: 648, height: 1)
Score.position = CGPoint(x: 320, y: -90)
Score.physicsBody = SKPhysicsBody(rectangleOfSize: Score.size)
Score.physicsBody?.affectedByGravity = false
Score.physicsBody?.dynamic = false
Score.physicsBody?.categoryBitMask = physicsCatagory.Score
Score.physicsBody?.collisionBitMask = 0
Score.physicsBody?.contactTestBitMask = physicsCatagory.IceThree
Score.color = SKColor.blueColor()
self.addChild(Score)
person.position = CGPointMake(self.size.width/2, self.size.height/12)
person.setScale(0.4)
person.physicsBody = SKPhysicsBody(rectangleOfSize: person.size)
person.physicsBody?.affectedByGravity = false
person.physicsBody?.categoryBitMask = physicsCatagory.person
person.physicsBody?.contactTestBitMask = physicsCatagory.Ice
person.physicsBody?.collisionBitMask = physicsCatagory.Ice
person.physicsBody?.dynamic = false
ScoreLable.position = CGPoint(x: self.frame.width / 2, y: 1000)
ScoreLable.text = "\(scorenumber)"
ScoreLable.fontColor = UIColor.yellowColor()
ScoreLable.fontSize = 100
ScoreLable.fontName = "Zapfino "
self.addChild(ScoreLable)
var IceThreeTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: ("spawnThirdIce"), userInfo: nil, repeats: true)
self.addChild(person)
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCatagory.person && secondBody.categoryBitMask == physicsCatagory.IceThree || firstBody.categoryBitMask == physicsCatagory.IceThree && secondBody.categoryBitMask == physicsCatagory.person{
scorenumber++
ScoreLable.text = "\(scorenumber)"
CollisionWithPerson(firstBody.node as! SKSpriteNode, Person: secondBody.node as! SKSpriteNode)
}
if firstBody.categoryBitMask == physicsCatagory.Score && secondBody.categoryBitMask == physicsCatagory.IceThree ||
firstBody.categoryBitMask == physicsCatagory.IceThree && secondBody.categoryBitMask == physicsCatagory.Score{
self.view?.presentScene(EndScene())
}
}
func CollisionWithPerson (Ice: SKSpriteNode, Person: SKSpriteNode){
Person.removeFromParent()
}
func spawnThirdIce(){
var Ice = SKSpriteNode(imageNamed: "Ice")
Ice.setScale(0.9)
Ice.physicsBody = SKPhysicsBody(rectangleOfSize: Ice.size)
Ice.physicsBody?.categoryBitMask = physicsCatagory.IceThree
Ice.physicsBody?.contactTestBitMask = physicsCatagory.person | physicsCatagory.Score
Ice.physicsBody?.affectedByGravity = false
Ice.physicsBody?.dynamic = true
let MinValue = self.size.width / 8
let MaxValue = self.size.width - 20
let SpawnPoint = UInt32(MaxValue - MinValue)
Ice.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
self.addChild(Ice)
let action = SKAction.moveToY(-85, duration: 2.5)
let actionDone = SKAction.removeFromParent()
Ice.runAction(SKAction.sequence([action,actionDone]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
I have a very simple app on sprite kit for mac os. (BTW, this code for iOS is working correctly).
AppDelegate code:
import Cocoa
import SpriteKit
extension SKNode {
class func unarchiveFromFile(file : String) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var skView: SKView!
func applicationDidFinishLaunching(aNotification: NSNotification) {
/* Pick a size for the scene */
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
self.skView!.presentScene(scene)
/* Sprite Kit applies additional optimizations to improve rendering performance */
self.skView!.ignoresSiblingOrder = true
self.skView!.showsFPS = true
self.skView!.showsNodeCount = true
}
}
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool {
return true
}
}
And the scene code:
import Foundation
import SpriteKit
struct Detection {
static var no : UInt32 = 0
static var all : UInt32 = UInt32.max
static var monster : UInt32 = 0b1
static var suric : UInt32 = 0b10
static var ninja : UInt32 = 0b100
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x * scalar, y: point.y * scalar)
}
func / (point: CGPoint, scalar: CGFloat) -> CGPoint {
return CGPoint(x: point.x / scalar, y: point.y / scalar)
}
#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
return CGFloat(sqrtf(Float(a)))
}
#endif
extension CGPoint {
func length() -> CGFloat {
return sqrt(x*x + y*y)
}
func normalized() -> CGPoint {
return self / length()
}
}
class GameScene: SKScene, SKPhysicsContactDelegate {
let player = SKSpriteNode(imageNamed: "player.png")
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
physicsWorld.gravity = CGVectorMake(0.0, 0.0)
physicsWorld.contactDelegate = self
player.position = CGPoint(x: self.size.width * 0.1, y: self.size.height / 2)
addChild(player)
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(createMonster), SKAction.waitForDuration(1)])))
}
override func mouseDown(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
let suric = SKSpriteNode(imageNamed: "projectile.png")
suric.position = player.position
suric.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
suric.physicsBody?.categoryBitMask = Detection.suric
suric.physicsBody?.collisionBitMask = Detection.no
suric.physicsBody?.contactTestBitMask = Detection.monster
suric.physicsBody?.usesPreciseCollisionDetection = true
suric.physicsBody?.dynamic = true
suric.physicsBody?.angularVelocity = -10.0
let offset = location - suric.position
if offset.x < 0 {
return
}
addChild(suric)
let direc = offset.normalized()
let shoot = direc * 1000
let dest = shoot + suric.position
let move = SKAction.moveTo(dest, duration: 2.0)
let stop = SKAction.removeFromParent()
suric.runAction(SKAction.sequence([move, stop]))
}
func suricHit(suric : SKSpriteNode?, monster : SKSpriteNode?) {
if suric != nil && monster != nil {
suric!.removeFromParent()
monster!.removeFromParent()
}
}
func didBeginContact(contact: SKPhysicsContact) {
var first : SKPhysicsBody
var second : SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
first = contact.bodyA
second = contact.bodyB
}
else {
first = contact.bodyB
second = contact.bodyA
}
if (first.categoryBitMask & Detection.monster != 0) && (second.categoryBitMask & Detection.suric != 0) {
suricHit(first.node as? SKSpriteNode, monster: second.node as? SKSpriteNode)
}
}
func createMonster() {
let monster = SKSpriteNode(imageNamed: "monster.png")
let y = random(min: monster.size.height / 2, size.height - monster.size.height)
monster.position = CGPoint(x: self.size.width + monster.size.width / 2, y: y)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size, center: CGPoint(x: monster.position.x / 2, y: monster.position.y))
monster.physicsBody?.usesPreciseCollisionDetection = true
monster.physicsBody?.categoryBitMask = Detection.monster
monster.physicsBody?.contactTestBitMask = Detection.suric
monster.physicsBody?.collisionBitMask = Detection.no
monster.physicsBody?.dynamic = true
addChild(monster)
let duration = random(min: 2.0, 4.0)
let move = SKAction.moveTo(CGPoint(x: -monster.size.width / 2, y: y), duration: NSTimeInterval(duration))
let done = SKAction.removeFromParent()
monster.runAction(SKAction.sequence([move, done]))
}
}
And the contact function works really strange. It runs even without actual contact between suric and monster. I have no idea why this happens. Is it just my fault or just a Xcode bug?
Your physics body of projectile is too big. Nodes contacts through physics bodies not their actual sizes.
suric.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
change size of physics body to something smaller
self.size.width / 2
something like this
suric.physicsBody = SKPhysicsBody(circleOfRadius: suric.size);
I'm writing SpriteKit app for Mac OS. I have two objects (projectile and a monster):
let suric = SKSpriteNode(imageNamed: "projectile.png")
suric.position = player.position
suric.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
suric.physicsBody?.categoryBitMask = Detection.suric
suric.physicsBody?.collisionBitMask = Detection.no
suric.physicsBody?.contactTestBitMask = Detection.monster
suric.physicsBody?.usesPreciseCollisionDetection = true
suric.physicsBody?.dynamic = true
let offset = location - suric.position
if offset.x < 0 {
return
}
addChild(suric)
let direc = offset.normalized()
let shoot = direc * 1000
let dest = shoot + suric.position
let move = SKAction.moveTo(dest, duration: 2.0)
let stop = SKAction.removeFromParent()
suric.runAction(SKAction.sequence([move, stop]))
Monster code:
let monster = SKSpriteNode(imageNamed: "monster.png")
let y = random(min: monster.size.height / 2, size.height - monster.size.height)
monster.position = CGPoint(x: self.size.width + monster.size.width / 2, y: y)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.usesPreciseCollisionDetection = true
monster.physicsBody?.categoryBitMask = Detection.monster
monster.physicsBody?.contactTestBitMask = Detection.suric
monster.physicsBody?.collisionBitMask = Detection.no
monster.physicsBody?.dynamic = true
addChild(monster)
let duration = random(min: 2.0, 4.0)
let move = SKAction.moveTo(CGPoint(x: -monster.size.width / 2, y: y), duration: NSTimeInterval(duration))
let done = SKAction.removeFromParent()
monster.runAction(SKAction.sequence([move, done]))
Detection structure:
struct Detection {
static var no : UInt32 = 0
static var all : UInt32 = UInt32.max
static var monster : UInt32 = 0b1
static var suric : UInt32 = 0b10
}
And then I try to check if there was a contact between these 2 objects:
func didBeginContact(contact: SKPhysicsContact) {
var first : SKPhysicsBody
var second : SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
first = contact.bodyA
second = contact.bodyB
}
else {
first = contact.bodyB
second = contact.bodyA
}
if (first.categoryBitMask & Detection.monster != 0) && (second.categoryBitMask & Detection.suric != 0) {
suricHit(first.node as? SKSpriteNode, monster: second.node as? SKSpriteNode)
}
}
It works even if they didn't actually make a contact. E.g., the projectile is near the top of the window, the monster is at the bottom, but this function works.
EDIT:
I added this code to didBeginContact:
let nodeA = (first.node as! SKSpriteNode)
let nodeB = (second.node as! SKSpriteNode)
println("\(nodeA.position)\t\(nodeA.size)")
println("\(nodeB.position)\t\(nodeB.size)")
println((contact.contactPoint))
println()
The example of out:
(510.201293945312, 285.025665283203) (54.0, 80.0)
(532.54345703125, 378.878845214844) (20.0, 20.0)
(852.469543457031, 530.415222167969)
As you can see, the x position of nodes are 510 and 532, but the contact x point is 852. I just have no idea how and why. Does anybody have a solution?
You're missing an extra set of parenthesis in the if statement.
if ((first.categoryBitMask & Detection.monster != 0) && (second.categoryBitMask & Detection.suric != 0)) {
suricHit(first.node as? SKSpriteNode, monster: second.node as? SKSpriteNode)
}
PS I followed this tutorial and put my project on GitHub, check it out!