I am making a game with sprite kit in xcode and i want let the game finish when the player touch the monster, do you know how to use categorymask?
here is part of the code :
import SpriteKit
import GameplayKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate {
var player : SKSpriteNode!
var playerCategory : UInt32 = 0x1 << 0
var gameTimer2 : Timer!
var possiblealien = ["alien", "alien2", "alien3"]
let alienCategory : UInt32 = 0x1 << 1
override func didMove(to view: SKView) {
player = SKSpriteNode (imageNamed: "player")
player.position = CGPoint (x: self.frame.size.width / 2 - 500 , y:
player.size.height / 2 - 560)
player.size = CGSize(width: player.size.width * 6 ,
height: player.size.height * 6)
self.addChild(player)
player.zPosition = 2
self.physicsWorld.gravity = CGVector (dx: 0 , dy: 0 )
self.physicsWorld.contactDelegate = self
player.physicsBody?.contactTestBitMask = playerCategory
player.physicsBody? = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody?.isDynamic = true
player.physicsBody?.categoryBitMask = monsterCategory
player.physicsBody?.collisionBitMask = 0
gameTimer2 = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(addalien), userInfo: nil, repeats: true)
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { ( data: CMAccelerometerData?, error:Error?)in
if let accelerometerData = data {
let acceleration = accelerometerData.acceleration
self.xAcceleration = CGFloat(acceleration.x) * 0.65 + self.xAcceleration * 0.25
}
}
#objc func addalien () {
possiblealien = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: possiblealien) as! [String]
let alien = SKSpriteNode(imageNamed: possiblealien[0])
let randomalienposition = GKRandomDistribution(lowestValue: -480, highestValue: +800 )
let position = CGFloat(randomalienposition.nextInt())
alien.position = CGPoint (x: position - 200 , y: self.frame.size.height * 0.5 + alien.size.height)
alien.physicsBody = SKPhysicsBody(rectangleOf: alien.size)
alien.physicsBody?.isDynamic = true
alien.physicsBody?.categoryBitMask = alienCategory
alien.physicsBody?.contactTestBitMask = playerCategory
alien.physicsBody?.collisionBitMask = 0
alien.zPosition = 2
alien.size = CGSize(width: alien.size.width * 1 ,
height: alien.size.height * 1)
self.addChild(alien)
let animationduration:TimeInterval = 6
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: position, y: -1000 ),duration: animationduration))
actionArray.append(SKAction.removeFromParent())
alien.run(SKAction.sequence(actionArray))
}
I see your problem, you need to declare it's physics body in a Physics Category, Otherwise, there would be no body for it at all
struct PhysicsCategory {
static let None:UInt32 = 0
static let All:UInt32 = UInt32.max
static let Player:UInt32 = 0b1
Also, The contactbitmasks should be what they will collide with along with collisiontestbitmask. catergorybitmask should be the category of the sprite in the struct
ex:
let alien = SKSpritenode()
alien.physicsBody?.categoryBitMask = PhysicsCategory.Alien
alien.physicsBody?.contactBitMask = PhysicsCategory.Player
alien.physicsBody?.collisionTestBitMask = PhysicsCategory.Player
Also, you can use | to declare if they will collide with multiple things
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 have the SKContactDelegate setup
class GameScene: SKScene, SKPhysicsContactDelegate
{
override func didMoveToView(view: SKView)
{
self.physicsWorld.contactDelegate = self
Here I setup the enum
enum ColliderType:UInt32
{
case Player = 1
case Boundary = 2
}
Here I make the player node.
let playerTexture = SKTexture(imageNamed: "testCircle1.png")
thePlayer = SKSpriteNode(texture: playerTexture)
thePlayer.position = CGPointMake(frame.size.width - 350, frame.size.height/2)
thePlayer.zPosition = 5
thePlayer.size = CGSize(width: 100, height: 100)
thePlayer.name = "playerNode"
thePlayer.physicsBody = SKPhysicsBody(circleOfRadius: playerTexture.size().height/2)
thePlayer.physicsBody!.dynamic = false
thePlayer.physicsBody!.allowsRotation = false
thePlayer.physicsBody!.categoryBitMask = ColliderType.Player.rawValue
thePlayer.physicsBody!.contactTestBitMask = ColliderType.Boundary.rawValue
thePlayer.physicsBody!.collisionBitMask = ColliderType.Boundary.rawValue
addChild(thePlayer)
Then I setup the Boundaries
ground = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(frame.size.width - 150, 10))
ground.position = CGPointMake(frame.size.width/2 - 75, 96)
ground.zPosition = 5
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(ground.size.width, ground.size.height))
ground.physicsBody!.dynamic = false
ground.physicsBody!.allowsRotation = false
ground.physicsBody!.categoryBitMask = ColliderType.Boundary.rawValue
ground.physicsBody!.contactTestBitMask = ColliderType.Player.rawValue
ground.physicsBody!.collisionBitMask = ColliderType.Player.rawValue
addChild(ground)
sky = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(frame.size.width - 150, 10))
sky.position = CGPointMake(frame.size.width/2 - 75, frame.size.height - 96)
sky.zPosition = 5
sky.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(frame.size.width - 150, 10))
sky.physicsBody!.dynamic = false
sky.physicsBody!.allowsRotation = false
sky.physicsBody!.categoryBitMask = ColliderType.Boundary.rawValue
sky.physicsBody!.contactTestBitMask = ColliderType.Player.rawValue
sky.physicsBody!.collisionBitMask = ColliderType.Player.rawValue
addChild(sky)
Then the collision detection
func didBeginContact(contact: SKPhysicsContact)
{
print("Contact")
if contact.bodyA.categoryBitMask == ColliderType.Boundary.rawValue || contact.bodyB.categoryBitMask == ColliderType.Boundary.rawValue
{
print("Contact")
thePlayer.removeAllActions()
}
}
I tried putting the print outside of the if statement to see if it was even detecting collision at all, but it wasn't.
I have looked at many tutorials and followed what they did but it just won't work and I only have a couple more weeks to turn this in.
In the player node
remove this:
thePlayer.physicsBody!.dynamic = false
That is setting the physics body to be a boundary and not a body...
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!