I'm having problem with the positioning of the explosion particle in my SpriteKit game. When the missiles collide with the enemies I call the "func explosion", but the explosion is not where the killed enemy is, but rather in the middle of the screen. I have set the "explosion.position to match with the "enemy.position" but still the explosion happens in the middle of the screen. How can I change this so that the explosion position is the same as the position of the killed enemy?
func spawnExplosion() {
let explosionEmmiter = Bundle.main.path(forResource:
"ParticleExplosion", ofType: "sks")
let explosion = NSKeyedUnarchiver.unarchiveObject(withFile:
explosionEmmiter as! String) as! SKEmitterNode
explosion.position = CGPoint(x: enemy.position.x, y:
enemy.position.y)
explosion.zPosition = 3
explosion.targetNode = self
self.addChild(explosion)
let wait = SKAction.wait(forDuration: 0.5)
let removeExplosion = SKAction.run {
explosion.removeFromParent()
}
self.run(SKAction.sequence([wait, removeExplosion]))
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Enemy" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Enemy" && secondBody.node?.name ==
"Missile" {
spawnExplosion()
}
func createEnemiesLeft() {
let enemyTexture1 = SKTexture(imageNamed: "Enemy1")
let enemyTexture2 = SKTexture(imageNamed: "Enemy2")
let enemyTexture3 = SKTexture(imageNamed: "Enemy3")
let enemyTexture4 = SKTexture(imageNamed: "Enemy4")
let enemyTexture5 = SKTexture(imageNamed: "Enemy5")
let enemyTexture6 = SKTexture(imageNamed: "Enemy6")
let enemyTexture7 = SKTexture(imageNamed: "Enemy7")
let enemyTexture8 = SKTexture(imageNamed: "Enemy8")
let enemyTexture9 = SKTexture(imageNamed: "Enemy9")
let enemyTexture10 = SKTexture(imageNamed: "Enemy10")
let animation = SKAction.animate(with: [enemyTexture1,
enemyTexture2, enemyTexture3, enemyTexture4, enemyTexture5,
enemyTexture6, enemyTexture7, enemyTexture8, enemyTexture9,
enemyTexture10], timePerFrame: 0.1)
let makeEnemyRun = SKAction.repeatForever(animation)
let enemy = SKSpriteNode(texture: enemyTexture1)
enemy.name = "Enemy"
enemy.setScale(0.1)
enemy.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.height
/ 2)
enemy.physicsBody?.categoryBitMask = ColliderType.Enemy
enemy.physicsBody?.affectedByGravity = false
enemy.zPosition = 3
enemy.position.y = self.frame.height + 100
enemy.position.x = CGFloat.randomBetweenNumbers(firstNum: -347.5,
secondNum: -85)
enemy.run(makeEnemyRun)
self.addChild(enemy)
let destination = self.frame.height * 0.5
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
enemy.run(SKAction.sequence([move, remove]), withKey:
"MoveEnemiesLeft")
}
func spawnEnemiesLeft() {
let spawn = SKAction.run({ () -> Void in
self.createEnemiesLeft()
})
let delay = SKAction.wait(forDuration: TimeInterval(1))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey:
"SpawnEnemiesLeft")
}
func createEnemiesRight() {
let enemyTexture1 = SKTexture(imageNamed: "Enemy1")
let enemyTexture2 = SKTexture(imageNamed: "Enemy2")
let enemyTexture3 = SKTexture(imageNamed: "Enemy3")
let enemyTexture4 = SKTexture(imageNamed: "Enemy4")
let enemyTexture5 = SKTexture(imageNamed: "Enemy5")
let enemyTexture6 = SKTexture(imageNamed: "Enemy6")
let enemyTexture7 = SKTexture(imageNamed: "Enemy7")
let enemyTexture8 = SKTexture(imageNamed: "Enemy8")
let enemyTexture9 = SKTexture(imageNamed: "Enemy9")
let enemyTexture10 = SKTexture(imageNamed: "Enemy10")
let animation = SKAction.animate(with: [enemyTexture1,
enemyTexture2, enemyTexture3, enemyTexture4, enemyTexture5,
enemyTexture6, enemyTexture7, enemyTexture8, enemyTexture9,
enemyTexture10], timePerFrame: 0.1)
let makeEnemyRun = SKAction.repeatForever(animation)
let enemy = SKSpriteNode(texture: enemyTexture1)
enemy.name = "Enemy"
enemy.setScale(0.1)
enemy.anchorPoint = CGPoint(x: 0.5, y: 0.5)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.height
/ 2)
enemy.physicsBody?.categoryBitMask = ColliderType.Enemy
enemy.physicsBody?.affectedByGravity = false
enemy.zPosition = 3
enemy.position.y = self.frame.height + 100
enemy.position.x = CGFloat.randomBetweenNumbers(firstNum: 85,
secondNum: 347.5)
enemy.run(makeEnemyRun)
self.addChild(enemy)
let destination = self.frame.height * 0.5
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
enemy.run(SKAction.sequence([move, remove]), withKey:
"MoveEnemiesRight")
}
func spawnEnemiesRight() {
let spawn = SKAction.run({ () -> Void in
self.createEnemiesRight()
})
let delay = SKAction.wait(forDuration: TimeInterval(1))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey:
"SpawnEnemiesRight")
}
}
Ok, you are creating a lot of enemy and you must get the collision position. so:
func spawnExplosion(pos:CGPoint) {
let explosionEmmiter = Bundle.main.path(forResource:
"ParticleExplosion", ofType: "sks")
let explosion = NSKeyedUnarchiver.unarchiveObject(withFile:
explosionEmmiter as! String) as! SKEmitterNode
explosion.position = pos
explosion.zPosition = 3
explosion.targetNode = self
self.addChild(explosion)
let wait = SKAction.wait(forDuration: 0.5)
let removeExplosion = SKAction.run {
explosion.removeFromParent()
}
self.run(SKAction.sequence([wait, removeExplosion]))
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Enemy" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Enemy" && secondBody.node?.name ==
"Missile" {
spawnExplosion(pos: firstBody.node.position)
}
Related
I'm a beginner in game development in Xcode and my player keeps falling through the ground that I added on the GameScene.
In the GameScene I only added the ground as an SKTexture and a player, which is also an SKTexture. There's also a physicsCategories structure.
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
struct physicsCategories {
static let None: UInt32 = 0
static let Player: UInt32 = 0b1
static let Ground: UInt32 = 0b100
}
override func didMove(to view: SKView) {
self.backgroundColor = UIColor.blue
self.anchorPoint = CGPoint(x: 0.0, y: 0.0)
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector(dx: 0, dy: -2)
let player = Player(scene: self)
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody?.categoryBitMask = physicsCategories.Player
player.physicsBody?.contactTestBitMask = physicsCategories.Ground
player.physicsBody?.collisionBitMask = physicsCategories.Ground
player.physicsBody?.affectedByGravity = true
player.physicsBody?.isDynamic = true
addChild(player)
createGrounds()
}
func didBegin(_ contact: SKPhysicsContact) {
var body1 = SKPhysicsBody()
var body2 = SKPhysicsBody()
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
body1 = contact.bodyA
body2 = contact.bodyB
} else {
body1 = contact.bodyB
body2 = contact.bodyA
}
if (body1.categoryBitMask == physicsCategories.Player && body2.categoryBitMask == physicsCategories.Ground) {
body1.node?.position.y = 80
}
}
func createGrounds() {
let backgroundTexture = SKTexture(imageNamed: "ground")
for i in 0 ... 3 {
let background = SKSpriteNode(texture: backgroundTexture)
background.zPosition = 2
background.anchorPoint = CGPoint(x: 0.0, y: 0.0)
background.size = CGSize(width: (self.scene?.size.width)!, height: 250)
background.position = CGPoint(x: (backgroundTexture.size().width * CGFloat(i)), y: -(self.frame.size.height/2))
background.physicsBody = SKPhysicsBody(rectangleOf: background.size)
background.physicsBod
background.physicsBody?.isDynamic = false
background.physicsBody!.affectedByGravity = false
background.physicsBody?.categoryBitMask = physicsCategories.Ground
background.physicsBody?.contactTestBitMask = physicsCategories.Player
background.physicsBody?.collisionBitMask = physicsCategories.Player
background.physicsBody?.applyForce(CGVector(dx: 0, dy: 2))
addChild(background)
let moveLeft = SKAction.moveBy(x: -backgroundTexture.size().width, y: 0, duration: 4)
let moveReset = SKAction.moveBy(x: backgroundTexture.size().width , y: 0, duration: 0)
let moveLoop = SKAction.sequence([moveLeft, moveReset])
let moveForever = SKAction.repeatForever(moveLoop)
background.run(moveForever)
}
}
}
I'm working on a project / game and I'm stuck at adding new enemies to the game and to program them. This is how my code looked before I started changing and only had one type of "alien". I want to create two new types, one that you lose 5 points from hitting and one 10. In the code now, there is only one of three and it gives +5 points. How would you guys add the two new? I think I'm overthinking it and that's why I messed up.
import SpriteKit
import GameplayKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate {
var starfield:SKEmitterNode!
var player:SKSpriteNode!
var scoreLabel:SKLabelNode!
var score:Int = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
var gameTimer:Timer!
var possibleAliens = ["alien"]
//This is the "alien you earn +5 point from shooting. Im trying to get 2 new aliens to the scene, one that you lose -5 points from shooting and one you lose -10 on.
//I have tried to rewrite the codes to the two new, but they still earn +5 points from shooting all 3 types. What would you guys do and how?
let alienCategory:UInt32 = 0x1 << 1
let photonTorpedoCategory:UInt32 = 0x1 << 0
let motionManger = CMMotionManager()
var xAcceleration:CGFloat = 0
override func didMove(to view: SKView) {
starfield = SKEmitterNode(fileNamed: "Starfield")
starfield.position = CGPoint(x: 0, y: 1472)
starfield.advanceSimulationTime(10)
self.addChild(starfield)
starfield.zPosition = -1
player = SKSpriteNode(imageNamed: "shuttle")
player.position = CGPoint(x: self.frame.size.width / 2, y: player.size.height / 2 + 20)
self.addChild(player)
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
scoreLabel = SKLabelNode(text: "Score: 0")
scoreLabel.position = CGPoint(x: 100, y: self.frame.size.height - 60)
scoreLabel.fontName = "AmericanTypewriter-Bold"
scoreLabel.fontSize = 36
scoreLabel.fontColor = UIColor.white
score = 0
self.addChild(scoreLabel)
gameTimer = Timer.scheduledTimer(timeInterval: 0.75, target: self, selector: #selector(addAlien), userInfo: nil, repeats: true)
motionManger.accelerometerUpdateInterval = 0.2
motionManger.startAccelerometerUpdates(to: OperationQueue.current!) { (data:CMAccelerometerData?, error:Error?) in
if let accelerometerData = data {
let acceleration = accelerometerData.acceleration
self.xAcceleration = CGFloat(acceleration.x) * 0.75 + self.xAcceleration * 0.25
}
}
}
func addAlien () {
possibleAliens = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: possibleAliens) as! [String]
let alien = SKSpriteNode(imageNamed: possibleAliens[0])
let randomAlienPosition = GKRandomDistribution(lowestValue: 0, highestValue: 414)
let position = CGFloat(randomAlienPosition.nextInt())
alien.position = CGPoint(x: position, y: self.frame.size.height + alien.size.height)
alien.physicsBody = SKPhysicsBody(rectangleOf: alien.size)
alien.physicsBody?.isDynamic = true
alien.physicsBody?.categoryBitMask = alienCategory
alien.physicsBody?.contactTestBitMask = photonTorpedoCategory
alien.physicsBody?.collisionBitMask = 0
self.addChild(alien)
let animationDuration:TimeInterval = 6
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: position, y: -alien.size.height), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
alien.run(SKAction.sequence(actionArray))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
fireTorpedo()
}
func fireTorpedo() {
self.run(SKAction.playSoundFileNamed("torpedo.mp3", waitForCompletion: false))
let torpedoNode = SKSpriteNode(imageNamed: "torpedo")
torpedoNode.position = player.position
torpedoNode.position.y += 5
torpedoNode.physicsBody = SKPhysicsBody(circleOfRadius: torpedoNode.size.width / 2)
torpedoNode.physicsBody?.isDynamic = true
torpedoNode.physicsBody?.categoryBitMask = photonTorpedoCategory
torpedoNode.physicsBody?.contactTestBitMask = alienCategory
torpedoNode.physicsBody?.collisionBitMask = 0
torpedoNode.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(torpedoNode)
let animationDuration:TimeInterval = 0.3
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: player.position.x, y: self.frame.size.height + 10), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
torpedoNode.run(SKAction.sequence(actionArray))
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & photonTorpedoCategory) != 0 && (secondBody.categoryBitMask & alienCategory) != 0 {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
}
}
func torpedoDidCollideWithAlien (torpedoNode:SKSpriteNode, alienNode:SKSpriteNode) {
let explosion = SKEmitterNode(fileNamed: "Explosion")!
explosion.position = alienNode.position
self.addChild(explosion)
self.run(SKAction.playSoundFileNamed("explosion.mp3", waitForCompletion: false))
torpedoNode.removeFromParent()
alienNode.removeFromParent()
self.run(SKAction.wait(forDuration: 2)) {
explosion.removeFromParent()
}
score += 5
}
override func didSimulatePhysics() {
player.position.x += xAcceleration * 50
if player.position.x < -20 {
player.position = CGPoint(x: self.size.width + 20, y: player.position.y)
}else if player.position.x > self.size.width + 20 {
player.position = CGPoint(x: -20, y: player.position.y)
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
I wouldn't recommend following this answer if your game becomes more complex. Normally I would say put the aliens in subclasses initialized with their own categorybitmasks and other properties, but to avoid modifying your code too much I just changed the addAlien() method and didBeginContact method. Each alien has its own categoryBitMask and textureName.
let alienCategories:[UInt32] = [(0x1 << 1),(0x1 << 2),(0x1 << 3)]
let alienTextureNames:[String] = ["alien1","alien2","alien3"]
This will make a random Alien using the values in the collections above
func addAlien () {
//Random index between 0 and 2 changing the texture and category bitmask
let index = Int.random(in: 0...2)
let textureName = alienTextureNames[index]
let alien = SKSpriteNode(imageNamed: textureName)
//Use Int.random() if you don't want a CGFLoat
let xPos = CGFloat.random(in: 0...414)
let yPos = frame.size.height + alien.size.height
alien.position = CGPoint(x: xPos, y: yPos)
alien.physicsBody = SKPhysicsBody(rectangleOf: alien.size)
alien.physicsBody?.isDynamic = true
alien.physicsBody?.categoryBitMask = alienCategories[index]
alien.physicsBody?.contactTestBitMask = photonTorpedoCategory
alien.physicsBody?.collisionBitMask = 0
self.addChild(alien)
let moveAction = SKAction.moveTo(y: -alien.size.height, duration: 6)
alien.run(moveAction, completion: { alien.removeFromParent() })
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody:SKPhysicsBody
var secondBody:SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == photonTorpedoCategory && secondBody.categoryBitMask == alienCategories[0] {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
score += 5
}else if firstBody.categoryBitMask == photonTorpedoCategory && secondBody.categoryBitMask == alienCategories[1] {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
score -= 5
}else if firstBody.categoryBitMask == photonTorpedoCategory && secondBody.categoryBitMask == alienCategories[2] {
torpedoDidCollideWithAlien(torpedoNode: firstBody.node as! SKSpriteNode, alienNode: secondBody.node as! SKSpriteNode)
score -= 10
}
}
Change the torpedoNode.physicsBody?.contactTestBitMask = alienCategory line in the fireTorpedo method to alien.physicsBody?.contactTestBitMask = (alienCategories[0] | alienCategories[1] | alienCategories[2])
I am creating a game in which balls spawn on top and after they fall down and bounce off the ground(this works),I am working on collisions now, my bullets hit the ball and kind of bounces off. If you need any additional code I will post it. (Edited)
import Foundation
import SpriteKit
Did move to view
struct PhysicsCategory {
static let none: UInt32 = 0b0
static let player: UInt32 = 0b1
static let ball: UInt32 = 0b10
static let bullet: UInt32 = 0b100
static let ground: UInt32 = 0b1000
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
let sceneBody = SKPhysicsBody(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
player.position = CGPoint(x: self.size.width/2, y: self.size.height / 8)
var timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(spawnBullet), userInfo: nil, repeats: true)
player.size = CGSize(width: 100, height: 100)
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = true
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
ball.physicsBody?.restitution = 1
ball.physicsBody?.linearDamping = 0
bullet.size = CGSize(width: 30, height: 30)
bullet.zPosition = -5
bullet.position = CGPoint(x: player.position.x, y: player.position.y)
let action = SKAction.moveTo(y: self.size.height + 30, duration: 1.0)
bullet.run(SKAction.repeatForever(action))
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.name = "bullet"
player.physicsBody?.categoryBitMask = PhysicsCategory.player
player.physicsBody?.collisionBitMask = PhysicsCategory.none
player.physicsBody?.contactTestBitMask = PhysicsCategory.ball
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet
bullet.physicsBody?.collisionBitMask = PhysicsCategory.none
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.ball
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bullet
ball.physicsBody?.collisionBitMask = PhysicsCategory.ground
ground.physicsBody?.categoryBitMask = PhysicsCategory.ground
ground.physicsBody?.collisionBitMask = PhysicsCategory.ball
ground.physicsBody?.contactTestBitMask = PhysicsCategory.none
player.name = "player"
addChild(player)
ballSpawner(delay: 2.0)
spawnGround()
}
//didBegin function
func didBegin(_ contact: SKPhysicsContact) {
// print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
I THINK THIS PART IS WRONG
Is my case right? Because when I try to write a bullet and ball it gives me an error - " Binary operator '|' cannot be applied to two 'SKSpriteNode' operands". So only default case is called.
switch contactMask {
//This case never gets called, i can't see it in the console when i run it
case PhysicsCategory.bullet | PhysicsCategory.ball:
let bullet = contact.bodyA.categoryBitMask ==
PhysicsCategory.bullet ? contact.bodyA.node : contact.bodyB.node
let ball = contact.bodyA.categoryBitMask ==
PhysicsCategory.ball ? contact.bodyA.node : contact.bodyB.node
collisionBulletAndBall(ball: (ball)!, bullet: (bullet)!)
print("bullet and ball have contacted.")
case PhysicsCategory.bullet:
print("bullet contact")
case PhysicsCategory.ball & PhysicsCategory.bullet:
print("collision ball with bullet")
default:
print("Some other contact occurred")
}
}
func gameOver() {
print("game over")
}
func collisionBulletAndBall(ball: SKNode, bullet: SKNode) {
ball.removeFromParent()
bullet.removeFromParent()
print("collision")
}
#objc func spawnBullet() {
var bullet = SKSpriteNode(imageNamed: "bullet")
bullet.size = CGSize(width: 30, height: 30)
bullet.zPosition = -5
bullet.position = CGPoint(x: player.position.x, y: player.position.y)
let action = SKAction.moveTo(y: self.size.height + 30, duration: 1.0)
bullet.run(SKAction.repeatForever(action))
bullet.name = "bullet"
self.addChild(bullet)
bullets.append(bullet)
}
func spawnScoreBall() {
var ballSize = CGSize(width: 30, height: 30)
//Random Size
let randomSize = arc4random() % 3
switch randomSize {
case 1:
ballSize.width *= 1.2
ballSize.height *= 1.2
case 2:
ballSize.width *= 1.5
ballSize.height *= 1.5
default:
break
}
var ball = SKSpriteNode(imageNamed: "ball")
ball.size = ballSize
let y = size.height-ballSize.height/2
//Random x
var randomX = CGFloat(arc4random() % UInt32(size.width - ballSize.width))
randomX -= size.width/2-ballSize.width*4
ball.position = CGPoint(x: randomX, y: y)
//Moving logic
// let moveDownAction = SKAction.moveBy(x: 0, y: -size.height + ball.size.height, duration: 2.0)
// let destroyAction = SKAction.removeFromParent()
// let sequenceAction = SKAction.sequence([moveDownAction, destroyAction])
// ball.run(sequenceAction)
//Rotation
var rotateAction = SKAction.rotate(byAngle: 1, duration: 1)
let randomRotation = arc4random() % 2
if randomRotation == 1 {
rotateAction = SKAction.rotate(byAngle: -1, duration: 1)
}
let repeatForeverAction = SKAction.repeatForever(rotateAction)
ball.run(repeatForeverAction)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.restitution = 1
ball.physicsBody?.linearDamping = 0
ball.zPosition = 3
blocks.append(ball)
self.addChild(ball)
}
Spawn ground action
func spawnGround() {
ground = SKSpriteNode(color: UIColor.white, size: CGSize(width: self.frame.size.width, height: 50))
ground.position = CGPoint(x: self.size.width/2, y: 0)
}
player and bullet don't appear to have physics bodies (there is no player.physicsBody = SKPhysicsBody... and bullet.physicsBody = SKPhysicsBody...
ball's physicsbody is assigned way after you assign the physics body's attributes:
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
ball.physicsBody?.contactTestBitMask = PhysicsCategory.bullet | PhysicsCategory.ground | PhysicsCategory.player
ball.physicsBody?.collisionBitMask = PhysicsCategory.bullet | PhysicsCategory.ground | PhysicsCategory.player
and then later
ball.physicsBody = SKPhysicsBody...
so all those ball.physicsBody assignments did nothing, as physicsBody? was 'nil'
Edit : Your didBegin looks a bit messy. Try this:
func didBegin(_ contact: SKPhysicsContact) {
print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case PhysicsCategory.bullet | PhysicsCategory.ball:
let bullet = contact.bodyA.categoryBitMask == bullet ? contact.bodyA.node : contact.bodyB.node
let ball = contact.bodyA.categoryBitMask == ball ? contact.bodyA.node : contact.bodyB.node
ball.removeFromParent()
bullet.removeFromParent()
print("bullet and ball have contacted.")
default:
print("Some other contact occurred")
}
I removed the setting of the ball's physics body's isDynamic and affectedByGravity properties as you are removing it.
Create enemy
touchesBegan and didBegin contact function
My enemy node is not being removed from the scene every time my sword node touches it. I'm just wondering if anyone could explain to me what I'm doing wrong?
(UPDATE BELOW)
import SpriteKit
import GameplayKit
import AVFoundation
class LevelTwo: SKScene, SKPhysicsContactDelegate{
var levelBg = SKSpriteNode(imageNamed: "level2")
var hero = SKSpriteNode()
var enemy = SKSpriteNode()
var sword = SKSpriteNode()
var health1 = SKSpriteNode(imageNamed: "playerhplv2")
var health2 = SKSpriteNode(imageNamed: "playerhplv2")
var health3 = SKSpriteNode(imageNamed: "playerhplv2")
var musicPath = URL(fileURLWithPath: Bundle.main.path(forResource: "gameMusic", ofType: "mp3")!)
var musicGamePlayer = AVAudioPlayer()
var runMonster = SKAction()
var waitMonster = SKAction()
var sequenceMonster = SKAction()
var repeatMonster = SKAction()
enum CollisionNum: UInt32{
case swordNum = 1
case enemyNum = 2
case playerNum = 4
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
///music
do{
musicGamePlayer = try AVAudioPlayer(contentsOf: musicPath)
musicGamePlayer.prepareToPlay()
musicGamePlayer.numberOfLoops = -1
musicGamePlayer.play()
}
catch{
print(error)
}
//bg
levelBg.position = CGPoint(x: 0, y: 0)
levelBg.zPosition = 1
levelBg.size = levelBg.texture!.size()
levelBg.setScale(1.25)
self.addChild(levelBg)
//hero
let playerTexture = SKTexture(imageNamed: "main")
hero = SKSpriteNode(texture: playerTexture)
hero.position = CGPoint(x: 0, y: 0)
hero.zPosition = 2
hero.setScale(0.6)
hero.physicsBody = SKPhysicsBody(texture: playerTexture, size: CGSize(width: hero.size.width, height: hero.size.height))
hero.physicsBody!.categoryBitMask = CollisionNum.playerNum.rawValue
hero.physicsBody!.collisionBitMask = CollisionNum.enemyNum.rawValue //player is allowed to bump into rocks and skulls
hero.physicsBody!.contactTestBitMask = CollisionNum.enemyNum.rawValue // same as collisions
hero.physicsBody!.isDynamic = false
self.addChild(hero)
//health1
health1.position = CGPoint(x: 130, y: 150)
health1.zPosition = 3
health1.setScale(0.75)
self.addChild(health1)
//health2
health2.position = CGPoint(x: 230, y: 150)
health2.zPosition = 3
health2.setScale(0.75)
self.addChild(health2)
//health3
health3.position = CGPoint(x: 320, y: 150)
health3.zPosition = 3
health3.setScale(0.75)
self.addChild(health3)
runMonster = SKAction.run(addMonster)
waitMonster = SKAction.wait(forDuration: 0.3)
sequenceMonster = SKAction.sequence([runMonster,waitMonster])
repeatMonster = SKAction.repeatForever(sequenceMonster)
run(repeatMonster)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locale = touch.location(in: self)
hero.position.x = locale.x
hero.position.y = locale.y
}
}
func addMonster(){
//random position based off the bg size
let monsterHigherX = Int(levelBg.size.width)
let monsterHigherY = Int(levelBg.size.height)
let monsterLowerX = monsterHigherX * -1
let monsterLowerY = monsterHigherY * -1
let randomLocaleX = Int(arc4random_uniform(UInt32(monsterHigherX - monsterLowerX))) + monsterLowerX
let randomLocaleY = Int(arc4random_uniform(UInt32(monsterHigherY - monsterLowerY))) + monsterLowerY
let movementEnemy = SKAction.moveBy(x: -5, y: -5, duration: 0.2)
let movementForever = SKAction.repeatForever(movementEnemy)
let enemyTexture = SKTexture(imageNamed: "boss0")
enemy = SKSpriteNode(texture: enemyTexture)
enemy.zPosition = 2
enemy.setScale(0.5)
enemy.position = CGPoint(x: randomLocaleX, y: randomLocaleY)
enemy.physicsBody = SKPhysicsBody(texture: enemyTexture, size: CGSize(width: enemy.size.width, height: enemy.size.height))
enemy.physicsBody!.isDynamic = true
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.categoryBitMask = CollisionNum.enemyNum.rawValue
enemy.physicsBody!.collisionBitMask = CollisionNum.swordNum.rawValue
enemy.physicsBody!.contactTestBitMask = CollisionNum.swordNum.rawValue
enemy.run(movementForever)
self.addChild(enemy)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let swordTexture = SKTexture(imageNamed: "blade-0")
sword = SKSpriteNode(texture: swordTexture)
sword.setScale(0.50)
sword.zPosition = 2
sword.position = hero.position
sword.physicsBody = SKPhysicsBody(texture: swordTexture, size: CGSize(width: sword.size.width, height: sword.size.height))
sword.physicsBody!.velocity = CGVector(dx: 1200, dy:0)
sword.physicsBody!.isDynamic = true
sword.physicsBody!.affectedByGravity = true
sword.physicsBody!.usesPreciseCollisionDetection = true
sword.physicsBody!.categoryBitMask = CollisionNum.swordNum.rawValue
sword.physicsBody!.collisionBitMask = CollisionNum.enemyNum.rawValue
sword.physicsBody!.contactTestBitMask = CollisionNum.enemyNum.rawValue
self.addChild(sword)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == CollisionNum.swordNum.rawValue | CollisionNum.enemyNum.rawValue {
enemy.removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
}
}
(Ive attached my whole level2 class) Thank you so much for the suggestion; however, when I tried implementing this I still run into the same problem (im running this on the iphone simulator) Im wondering whether the error is with my enum or my implementation of my physics with my nodes
You do not want to remove "enemy" because "enemy" is always the last monster you added. You need to check which contactBody is the enemy so you can remove it. You can do that by guaranteeing which node you want to associate as A, and which you want to associate as B by looking at the categoryBitMask value:
func didBegin(_ contact: SKPhysicsContact) {
//This guarantees the lower categoryBitMask (Providing you are only using one) is in A
let bodyA = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
if bodyA.categoryBitMask == CollisionNum.swordNum.rawValue && bodyB.categoryBitMask == CollisionNum.enemyNum.rawValue {
bodyB.node.removeFromParent()
}
}
Of course this will lead to problems with multiple collisions, so instead you may want to do:
var removeNodes = SKNode()
func didBegin(_ contact: SKPhysicsContact) {
//This guarantees the lower categoryBitMask (Providing you are only using one) is in A
let bodyA = contact.bodyA.categoryBitMask <= contact.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask ? contact.bodyB : contact.bodyA
if bodyA.categoryBitMask == CollisionNum.swordNum.rawValue && bodyB.categoryBitMask == CollisionNum.enemyNum.rawValue {
bodyB.node.moveToParent(removeNodes)
}
}
func didFinishUpdate(){
removeNodes.removeAllChildren()
}
Try this:
func didBegin(_ contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == CollisionNum.swordNum.rawValue | CollisionNum.enemyNum.rawValue {
enemy.removeFromParent()
}
}
You were only testing if bodyA is equal to the enemy, however, bodyA may be equal to the sword instead.
The problem: I seem to be having a little trouble getting my player to collide with a coin, and adding +1 to a coinLabel upon the collision. The player should continue moving after coming in contact with the coin.
What I have now: With the code I have now, the player travels through the coin, but there is no collision that takes place and +1 isn't added to the coin label.
I am still learning the swift language, so I appreciate any help that is given.
Code:
struct ColliderType {
static let playerCategory: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
static let coinCategory: UInt32 = 0x1 << 2
static let bodyA: UInt32 = 0x1 << 4
static let bodyB: UInt32 = 0x1 << 8
}
override func didMoveToView(view: SKView) {
var coinInt = 0
self.physicsWorld.gravity = CGVectorMake(0.0, -7.0)
physicsWorld.contactDelegate = self
player = SKSpriteNode(imageNamed: "player")
player.zPosition = 1
player.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 5.12)
player.physicsBody?.dynamic = true
player.physicsBody?.allowsRotation = false
self.addChild(player)
generateCoins()
coin = SKSpriteNode( imageNamed: "coin")
coin.physicsBody? = SKPhysicsBody(circleOfRadius: coin.size.height / 10)
coin.physicsBody?.dynamic = false
coin.physicsBody?.allowsRotation = false
coin.zPosition = 1
self.addChild(coin)
player.physicsBody?.categoryBitMask = ColliderType.playerCategory
player.physicsBody?.contactTestBitMask = ColliderType.boundary
player.physicsBody?.collisionBitMask = ColliderType.coinCategory | ColliderType.boundary
coin.physicsBody?.categoryBitMask = ColliderType.coinCategory
coin.physicsBody?.contactTestBitMask = ColliderType.playerCategory
coin.physicsBody?.collisionBitMask = ColliderType.playerCategory
func didPlayerCollideWithCoin(player: SKSpriteNode, coin: SKSpriteNode) {
self.coin.removeFromParent()
self.coin += 1
coinLabel.text = "\(coinInt)"
}
func generateCoins() {
if(self.actionForKey("spawning") != nil){return}
let coinTimer = SKAction.waitForDuration(7, withRange: 2)
let spawnCoin = SKAction.runBlock {
self.coin = SKSpriteNode( imageNamed: "coin")
self.coin.physicsBody = SKPhysicsBody(circleOfRadius: self.coin.size.height / 10)
self.coin.name = "coin"
self.coin.physicsBody?.dynamic = false
self.coin.physicsBody?.allowsRotation = false
var coinPosition = Array<CGPoint>()
coinPosition.append((CGPoint(x:340, y:103)))
coinPosition.append((CGPoint(x:340, y:148)))
coinPosition.append((CGPoint(x:340, y:218)))
coinPosition.append((CGPoint(x: 340, y:343)))
let spawnLocation = coinPosition[Int(arc4random_uniform(UInt32(coinPosition.count)))]
let action = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 4.4))
self.coin.runAction(action)
self.coin.position = spawnLocation
self.addChild(self.coin)
print(spawnLocation)
}
let sequence = SKAction.sequence([coinTimer, spawnCoin])
self.runAction(SKAction.repeatActionForever(sequence), withKey: "spawning")
}
func didBeginContact(contact:SKPhysicsContact) {
let bodyA: SKPhysicsBody = contact.bodyA
let bodyB: SKPhysicsBody = contact.bodyB
if ((bodyA.categoryBitMask == ColliderType.playerCategory) && (bodyB.categoryBitMask == ColliderType.coinCategory)){
didPlayerCollideWithCoin(bodyA.node as! SKSpriteNode, coin: bodyB.node as! SKSpriteNode)
}
}
You could try leaving your contactTestBitmasks the same but remove the collisionBitmasks between the player and coin:
player.physicsBody?.categoryBitMask = ColliderType.playerCategory
player.physicsBody?.contactTestBitMask = ColliderType.boundary
player.physicsBody?.collisionBitMask = ColliderType.boundary
coin.physicsBody?.categoryBitMask = ColliderType.coinCategory
coin.physicsBody?.contactTestBitMask = ColliderType.playerCategory
This way, when the player collides with a coin it will register, but it wont "bounce off" and will continue moving in the same direction.
*Note: Using this MAY require you to use the
func didBeginContact(contact: SKPhysicsContact) {
method instead of
func didPlayerCollideWithCoin(player: SKSpriteNode, coin: SKSpriteNode) {
But I recommend trying it with the didPlayerCollideWithCoin() method first.
In case you need it, it is implemented like this:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.categoryBitMask == playerCategory && secondBody.categoryBitMask == coinCategory {
print("Your player passes through the coin")
score = score + 1
}
}
For more details see this tutorial from Ray Wenderlich:
https://www.raywenderlich.com/123393/how-to-create-a-breakout-game-with-sprite-kit-and-swift