I have two separate nodes with their own physics bodies, and when they collide, an SKScene with the high score and replay button should present itself. This is how my scene is called:
func didBeginContact(contact: SKPhysicsContact) {
gameOver()
print("gameOver")
}
And this is how my physics bodies for my nodes are set up:
func createDown(position: CGPoint) -> SKNode {
let circleNode = SKSpriteNode()
let circle = SKSpriteNode(imageNamed: "first#2x")
circleNode.position = CGPointMake(position.x, position.y)
circleNode.physicsBody = SKPhysicsBody(circleOfRadius: 30)
circleNode.physicsBody?.dynamic = false
circle.size = CGSize(width: 75, height: 75)
circleNode.addChild(circle)
circleNode.name = "circleNode"
circle.name = "CIRCLE"
let up = SKAction.moveByX(0, y: -9000, duration: 100)
physicsBody?.categoryBitMask = blackCategory
circleNode.runAction(up)
return circleNode
}
func playerPhysics() {
player.physicsBody = SKPhysicsBody(circleOfRadius: 30)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = blackCategory
}
And here is my gameOver function:
func gameOver() {
gameEnd = true
let reveal = SKTransition.fadeWithDuration(1)
let scene = GameOver(size: self.scene!.size)
view!.presentScene(scene, transition: reveal)
}
Am I missing something? Will post more code if necessary.
Just use intersectsNode and call gameOver() when the two nodes collide.
if yourNode.intersectsNode(yourSecondNode) {
gameOver()
}
SKNode Class Reference: SKNode
You need to add the physics world as a contact delegate for your methods to work.
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
And like Whirlwind said, you need to set your categoryBitMask and contactTestBitMask for the circle node.
Then everything should work. I hope I could help.
Related
I am currently making a game where I need random enemies from my array, to spawn in a random location on repeat. This code seems to work okay other than the fact that it can only rotate through each Enemy once. It comes up with an error saying "Attemped to add a SKNode which already has a parent". Any help? Here is my current code:
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func spawnEnemy() {
let EnemyArray = [Enemy1, Enemy2, Enemy3, Enemy4, Enemy5, Enemy6]
let randomElement = EnemyArray.randomElement()!
self.addChild(randomElement)
var moveEnemy = SKAction.moveTo(y: -800, duration: 4.0)
let deleteEnemy = SKAction.removeFromParent()
let EnemySequence = SKAction.sequence([moveEnemy, deleteEnemy])
randomElement.run(EnemySequence)
}
func runEnemy() {
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(spawnEnemy), SKAction.wait(forDuration: 2.0)])))
}
as jnpdx suggested, you should spawn new instances of your Enemy class rather than starting with an array of them. you can introduce randomness inside the Enemy class -- for example a random start position or a random color. i would also put your movement and removeFromParent code inside the class as well. You didn't post your Enemy code, but it might look something like this
class Enemy:SKNode {
var shape:SKShapeNode?
override init() {
super.init()
//how ever you want to graphically represent your enemy... using a SKShapeNode for demo
shape = SKShapeNode(ellipseOf: CGSize(width: 20, height: 40))
shape?.fillColor = .blue
addChild(shape ?? SKNode())
//randomize starting x position
position.x = CGFloat.random(in: -200...200)
position.y = 200
move()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//move and remove this node using SKAction
func move() {
let move = SKAction.moveTo(y: -200, duration: 4.0)
let delete = SKAction.removeFromParent()
let sequence = SKAction.sequence([move, delete])
self.run(sequence)
}
}
then you would simply activate your spawn point from didMove(to view: SKView) like this
override func didMove(to view: SKView) {
runSpawnPoint()
}
func runSpawnPoint() {
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(spawnEnemy), SKAction.wait(forDuration: 2.0)])))
}
func spawnEnemy() {
let enemy = Enemy() //a brand new Enemy object each time
addChild(enemy)
}
optional: save your spawned Enemy objects in an array if you want to access them later. alternately you can simply query self.children from your SKScene since they're all stored there as well. in which case you don't need an additional array for storage.
I have found an answer. So originally my problem was trying to spawn multiple, different-looking enemies, at random. I realized that I could solve the same issue by changing the texture of the Enemy, instead of creating many different Enemy Nodes. In order to spawn enemies at random with an array of textures, it would look something like this:
var enemy1 = SKTexture(imageNamed: "Enemy1")
var enemy2 = SKTexture(imageNamed: "Enemy2")
var enemy3 = SKTexture(imageNamed: "Enemy3")
var enemy4 = SKTexture(imageNamed: "Enemy4")
var enemy5 = SKTexture(imageNamed: "Enemy5")
var enemy6 = SKTexture(imageNamed: "Enemy6")
let EnemyArray = [Enemy1, Enemy2, Enemy3, Enemy4, Enemy5, Enemy6]
let randomElement = EnemyArray.randomElement()!
let enemy = SKSpriteNode(imageNamed: "")
enemy.name = "Enemy"
enemy.texture = randomElement
enemy.size = CGSize(width: 30, height: 30)
enemy.zPosition = 2
self.addChild(enemy)
var moveEnemy = SKAction.moveTo(y: -800, duration: 4.0)
let deleteEnemy = SKAction.removeFromParent()
let EnemySequence = SKAction.sequence([moveEnemy, deleteEnemy])
enemy.run(EnemySequence)
}
func runEnemy() {
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(spawnEnemy), SKAction.wait(forDuration: 2.0)])))
}
Thanks everyone for the help
This is from a simple game in SpriteKit with a Ball() class that has a function shieldOn() which, for the moment, simply replaces the texture of a single ball to that of a ball surrounded by a shield.
The ball is created like this in GameScene:
func getBall() {
let ball = Ball()
ball.createBall(parentNode: self)
}
Here is the Ball class
class Ball: SKSpriteNode {
func createBall(parentNode: SKNode) {
let ball = SKSpriteNode(texture: SKTexture(imageNamed: "ball2"))
ball.physicsBody = SKPhysicsBody(circleOfRadius: 25)
ball.name = "ball"
parentNode.addChild(ball)
ball.size = CGSize(width: 50, height: 50)
ball.position = CGPoint(x: 20, y: 200)
launch(spriteNode: ball, parentNode: parentNode)
}
private func launch(spriteNode: SKSpriteNode, parentNode: SKNode) {
spriteNode.physicsBody?.applyImpulse(CGVector(dx: 5, dy: 0))
}
func shieldOn() {
self.texture = SKTexture(imageNamed: "ballShield")
}
func shieldOff() {
self.texture = SKTexture(imageNamed: "ball2")
}
}
In the main section of my code (GameScene.swift) I don't have a reference to the ball. So I cycle through all of the nodes on the screen and try to cast the matching one as shown below. I crash with an error saying that it could not cast value of type SKSpriteNode to Ball.
for node in self.children {
if node.name == "ball" {
let ball = node as! Ball
ball.shieldOn()
}
}
I've tried a few variations with no luck. Am I at least working in the right direction? Thanks!
With the new information I think you want something like this:
Ball Class:
class Ball: SKSpriteNode{
init() {
let texture = SKTexture(imageNamed: "ball2")
let size = CGSize(width: 50, height: 50)
super.init(texture: texture, color: UIColor.clear, size: size)
self.name = "ball"
self.physicsBody = SKPhysicsBody(circleOfRadius: size.height/2)
self.physicsBody?.applyImpulse(CGVector(dx: 5, dy: 0))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func shieldOn() {
self.texture = SKTexture(imageNamed: "ballShield")
}
func shieldOff() {
self.texture = SKTexture(imageNamed: "ball2")
}
}
Then use this to create the ball:
func getBall() {
let ball = Ball()
ball.position = CGPoint(x: 20, y: 200)
scene?.addChild(ball)
}
Perhaps a better way to do this would be to keep an array of all Balls created and added to the scene. Then you could just iterate through your array and update their texture. You would not need to enumerate them on the screen, which can decrease performance if there are many moving sprites.
As far as your code goes, it looks like you might be affected by this bug:
https://forums.developer.apple.com/thread/26362
I'm writing a game. It is not detecting any collisions, even though I can see the physics bodies as I have that view turned on.
To set-up the physics world in the game scene, I've coded the following above the class declaration:
struct PhysicsCategory {
static let None: UInt32 = 0
static let Chicken: UInt32 = 0b1
static let Edge: UInt32 = 0b10
}
Then, within the actual class of the scene:
override func didMove(to view: SKView) {
setupNodes()
setupTrial()
let playableRect = CGRect(x: 0, y: 0, width: size.width/2, height: size.height/2)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: playableRect)
self.physicsWorld.contactDelegate = self
self.physicsBody!.categoryBitMask = PhysicsCategory.Edge
self.physicsBody!.contactTestBitMask = PhysicsCategory.Chicken
// This is important for handling all the custom events
enumerateChildNodes(withName: "//*", using: { node, _ in
// we need to limit this to chickens only
if let customNode = node as? CustomNodeEvents {
customNode.didMoveToScene()
}
})
}
Here is the code to detect collisions:
func didBegin(_ contact: SKPhysicsContact) {
print("something happened")
let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == PhysicsCategory.Chicken | PhysicsCategory.Edge {
print("it works!")
}
}
The nodes I'd like to animate are chickens. I want the game to detect when they collide with the edges of the world above.
My chicken subclass is this:
class TargetNode: SKSpriteNode, CustomNodeEvents, InteractiveNode {
func didMoveToScene() {
isUserInteractionEnabled = true
anchorPoint = CGPoint(x: 0, y: 0)
let playableRect = CGRect(x: self.anchorPoint.x, y: self.anchorPoint.y, width: self.size.width, height: self.size.height)
physicsBody = SKPhysicsBody(edgeLoopFrom: playableRect)
physicsBody!.isDynamic = true
physicsBody!.categoryBitMask = PhysicsCategory.Chicken
physicsBody!.contactTestBitMask = PhysicsCategory.Edge | PhysicsCategory.Chicken
physicsBody!.velocity = CGVector(dx: 100, dy: 0)
}
}
EDIT: The nodes are being added to the scene using this method in the game scene file.
func generateItems(targetNumber: Int, target: Bool) {
let movingItems = true
for _ in 0...(targetNumber - 1) {
if (target) {
let name = createTarget()
let targetNode = TargetNode(imageNamed: name)
targetNode.name = name
fgNode.addChild(targetNode)
targetNode.position = generateRandomLocation()
//if movingItems { animateTargets(targetNode) }
}
}
setting your chicken to a SKPhysicsBody(edgeLoopFrom :_) is a bad idea. instead try SKPhysicsBody(rectangleOf :_) to draw a rectangle shape around your chicken, the type of shape you want to draw can vary, check the docs for more information.
Moreover to check weather the chickens have made contact with the playableRect
remove self.physicsBody!.contactTestBitMask = PhysicsCategory.Chicken because we don't want to check if the rect has made contact with the chicken we want to know if the chicken has made contact with the playable Rect. So keep contactTestBitMask on the chicken.
remove : | PhysicsCategory.Chicken from your chicken subclass, unless you want to check weather the chickens collide with each other as well.
lastly: check for collisions, if the chicken makes contact with the playableRect
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == PhysicsCategory.Chicken && contact.bodyB.categoryBitMask == PhysicsCategory.Edge {
print("Chickens have collided with edge")
}
}
I want to detect when two bodies contact without using didBeginContact() function
I try with CGRectIntersectsRect() without any effect.
class GameScene: SKScene, SKPhysicsContactDelegate {
// Player
var _player = SKNode()
var platform = SKNode()
let heroCategory: UInt32 = 1 << 0
let platformCategory: UInt32 = 1 << 1
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Setup
self.anchorPoint = CGPointMake(0.5, 0.5)
self.physicsWorld.gravity = CGVectorMake(0.0, -2.0);
self.physicsWorld.contactDelegate = self
// Start populating
_player = creatPlayer()
self.addChild(_player)
platform = creatPlatform(1, atPosition: CGPoint(x: 0, y: -200))
self.addChild(platform)
let platformTwo = creatPlatform(2, atPosition: CGPoint(x: 0, y: 200))
self.addChild(platformTwo)
}
func creatPlatform(type: Int, atPosition: CGPoint) -> PlatformNode {
let node = PlatformNode()
node.platformType = type
node.position = atPosition
let sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 200, height: 50))
node.addChild(sprite)
node.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
node.physicsBody?.dynamic = false;
node.physicsBody?.categoryBitMask = platformCategory
node.physicsBody?.contactTestBitMask = heroCategory
node.physicsBody?.collisionBitMask = 0;
return node
}
func creatPlayer() -> SKNode {
let playerNode = SKNode()
let sprite = SKSpriteNode(color: UIColor.whiteColor(), size: CGSize(width: 50, height: 50))
playerNode.addChild(sprite)
playerNode.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
playerNode.physicsBody?.dynamic = false
playerNode.physicsBody?.allowsRotation = false
playerNode.physicsBody?.restitution = 1.0
playerNode.physicsBody?.friction = 0.0
playerNode.physicsBody?.angularDamping = 0.0
playerNode.physicsBody?.linearDamping = 0.0
playerNode.physicsBody?.categoryBitMask = heroCategory
playerNode.physicsBody?.contactTestBitMask = platformCategory
playerNode.physicsBody?.collisionBitMask = 0;
return playerNode
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
_player.physicsBody?.dynamic = true
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if(CGRectIntersectsRect(platform.frame, _player.frame)){
println("Collision")
}
}
}
Maybe using CGRectIntersectsRect is a wrong approach, but I can be used instead?
The frame property
The frame property is a rectangle in the parent’s coordinate system
that contains the node’s content, ignoring the node’s children.
Also from docs:
The frame is non-empty if the node’s class draws content.
And because SKNode class does not perform any drawing of its own, and your platform is probably subclass of SKNode and player is SKNode the frame property is always (0,0,0,0).
You can access the real size of a frame in few ways:
You can make player and platform as subclasses of SKSpriteNode.
You can leave them as they are and access the child SKSpriteNode inside them.
You can leave as they are and use calcualteAccumulatedFrame() method.
About calcualteAccumulatedFrame() from docs:
A node’s accumulated frame, retrieved by calling the
calculateAccumulatedFrame method, is the largest rectangle that
includes the frame of the node and the frames of all its descendants.
I created a struct PhysicsCatagory for each of the different objects I want interacting with each other
struct PhysicsCatagory {
static let Blade : UInt32 = 1
static let Laser : UInt32 = 2
}
above my class GameScene
class GameScene: SKScene, SKPhysicsContactDelegate {
and I initialized an SKSpriteNode blade with its physicsBody in the didMoveToView method
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
Blade.position = CGPointMake(self.size.width / 2, (self.size.height / 14))
Blade.anchorPoint = CGPointMake(0.5, -0.13)
Blade.physicsBody = SKPhysicsBody(rectangleOfSize: Blade.size)
Blade.physicsBody?.categoryBitMask = PhysicsCatagory.Blade
Blade.physicsBody?.contactTestBitMask = PhysicsCatagory.Laser
Blade.physicsBody?.dynamic = false
self.addChild(Blade)
}
As well as a SKSpriteNode laser and its physicsbody in the method shootLaser
func shootLaser(){
var Laser = SKSpriteNode(imageNamed: "Laser.png")
Laser.position = Enemy.position
Laser.zPosition = -5
Laser.physicsBody = SKPhysicsBody(rectangleOfSize: Laser.size)
Laser.physicsBody?.categoryBitMask = PhysicsCatagory.Laser
Laser.physicsBody?.contactTestBitMask = PhysicsCatagory.Blade
Laser.physicsBody?.dynamic = false
let action = SKAction.moveBy(laserVector, duration: 0.7)
let actionDone = SKAction.removeFromParent()
Laser.runAction(SKAction.sequence([action,actionDone]))
self.addChild(Laser)
}
But when they collide in the simulation, the didBeginContact method is not called and "Hello" is not printed
func didBeginContact(contact: SKPhysicsContact) {
NSLog("Hello")
}
Why isn't the didBeginContact method being called when they collide? Thanks in advance (:
Sprite Kit does not check for contacts between non-dynamic physics bodies because they aren't expected to move. If you don't want your sprites to fall off the screen due to gravity, set the physics body's affectedByGravity property to false and set dynamic = true.