I have the following code in Swift for a simple platform game:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var me : SKSpriteNode?
var ceiling : SKSpriteNode?
var studentTimer : Timer?
var cloudTimer : Timer?
let meCategory : UInt32 = 0x1 << 1
let studentCategory : UInt32 = 0x1 << 2
let cloudCategory : UInt32 = 0x1 << 3
let groundAndCeilingCategory : UInt32 = 0x1 << 4
var numberofStudents = 0
var education = ["edu1", "edu2", "edu3", "edu4"]
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
me = childNode(withName: "me") as? SKSpriteNode
me?.physicsBody?.categoryBitMask = meCategory
me?.physicsBody?.contactTestBitMask = studentCategory
me?.physicsBody?.collisionBitMask = groundAndCeilingCategory
// make me run
var meRun : [SKTexture] = []
for number in 1...6 {
meRun.append(SKTexture(imageNamed: "Armature_Run_\(number)"))
}
me?.run(SKAction.repeatForever(SKAction.animate(with: meRun, timePerFrame: 0.1)))
ceiling = childNode(withName: "ceiling") as? SKSpriteNode
ceiling?.physicsBody?.categoryBitMask = groundAndCeilingCategory
ceiling?.physicsBody?.collisionBitMask = meCategory
startTimers()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
me?.physicsBody?.applyForce(CGVector(dx: 0, dy: 20000))
var meJump : [SKTexture] = []
for number in 0...9 {
meJump.append(SKTexture(imageNamed: "Armature_Jump_0\(number)"))
}
me?.run(SKAction.animate(with: meJump, timePerFrame: 0.1))
}
func createStudent() {
let student = SKSpriteNode(imageNamed: "student")
student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
student.physicsBody?.affectedByGravity = false
student.physicsBody?.categoryBitMask = studentCategory
student.physicsBody?.contactTestBitMask = meCategory
student.physicsBody?.collisionBitMask = 0
addChild(student)
student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height)*2))
let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
func startTimers() {
studentTimer = Timer.scheduledTimer(withTimeInterval: 4, repeats: true, block: { (timer) in
if self.numberofStudents < 4 {
self.createStudent()
}
})
cloudTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { (timer) in
self.createCloud()
})
}
func createCloud() {
let cloud = SKSpriteNode(imageNamed: "cloud")
cloud.zPosition = -1
cloud.physicsBody = SKPhysicsBody(rectangleOf: cloud.size)
cloud.physicsBody?.affectedByGravity = false
cloud.physicsBody?.collisionBitMask = 0
cloud.physicsBody?.categoryBitMask = cloudCategory
addChild(cloud)
let maxY = size.height / 2 - cloud.size.height / 2
let minY = CGFloat(0)
let range = maxY - minY
let cloudY = maxY - CGFloat(arc4random_uniform(UInt32(range)))
cloud.position = CGPoint(x: size.width / 2 + cloud.size.width / 2, y: cloudY)
let moveLeft = SKAction.moveBy(x: -size.width - cloud.size.width, y: 0, duration: 4)
cloud.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == studentCategory {
contact.bodyA.node?.removeFromParent()
numberofStudents += 1
print(numberofStudents)
print(education[(numberofStudents)-1])
}
if contact.bodyB.categoryBitMask == studentCategory {
contact.bodyB.node?.removeFromParent()
numberofStudents += 1
print(numberofStudents)
print(education[(numberofStudents)-1])
}
}
}
I'm working on what happens when the main character ("me") collides with the "student".
Prior to adding 'physicsWorld.contactDelegate = self' the "student" showed up perfectly, moving across the screen. However, when I add this line of code and run the app, the student is invisible. I believe it is still there somewhere as the print common is still running when the character collides, but it is not visibly colliding with anything.
I removed the contact.bodyB.node?.removeFromParent() and ran it, and indeed the "student" does show again, but obviously does not disappear on contact.
I've looked over this for ages and am sure I'm missing something obvious but cannot work out what it is.
you are not supplying us with all of the information.
As it is your code works as expected.
Here is a vid of your code running (I had to use my own images)
Here is my analysis of what might be wrong.
I removed all the animation sequence items because they have no bearing on this.
I notice you are not putting zPositions...tsk tsk tsk. All items should have a zPosition.
you are not showing us how you define the physicsBody on your "me" sprite. Assumably in the editor? You should include that info next time.
I had to create the "me" character and position it in code since you don't show us where that comes from either. I also created the physicsBody in code to get it to work.
Here is the code I used
me = SKSpriteNode(imageNamed: "orange")
me.position = CGPoint(x: -400, y: (-size.height / 2) + ((me.size.height) * 2))
me.zPosition = 10
addChild(me)
me.physicsBody = SKPhysicsBody(rectangleOf: me.size)
me.physicsBody?.categoryBitMask = meCategory
me.physicsBody?.contactTestBitMask = studentCategory
me.physicsBody?.collisionBitMask = groundAndCeilingCategory
me.physicsBody?.affectedByGravity = false
func createStudent() {
let student = SKSpriteNode(imageNamed: "apple")
student.setScale(0.5)
student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height) * 2))
student.zPosition = 10
addChild(student)
student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
student.physicsBody?.affectedByGravity = false
student.physicsBody?.categoryBitMask = studentCategory
student.physicsBody?.contactTestBitMask = meCategory
student.physicsBody?.collisionBitMask = 0
let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
On a side note, it is not necessary to use timers. SpriteKit has a built in feature for this the "update" func.
Related
I have a small test project (my first using Swift / XCode) which is designed to move me away from HTML5 and Canvas for game production.
The code compiles and runs fine. I use my iPhone as the test device rather than the built in simulator.
The symptoms of the problem are
that the lasers being repeatedly fired from the player's ship appear to occasionally bend around the aliens
the names being pulled out from the nodes are being shown as their default names not the names I assigned to them at creation
In some cases the collision works fine and the alien explosion is generated and the alien sprite node is removed from the scene.
I have named the alien nodes "alien" and the laser nodes "laser".
Both have their contactTestBitMask set to the same value.
Here is my GameScene.swift code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var lastUpdateTime: TimeInterval = 0
var delta: TimeInterval = 0
var sp_player: SKSpriteNode!
var stars: SKSpriteNode!
var deeperstars: SKSpriteNode!
var laser: SKSpriteNode!
var alien: SKSpriteNode!
var explosionSplat1: SKSpriteNode!
var playerScore: UInt32!
struct PhysicsCategory {
static let base:UInt32 = 0x1 << 0
static let alien:UInt32 = 0x1 << 1
static let laser:UInt32 = 0x1 << 2
static let player:UInt32 = 0x1 << 3
}
override func didMove(to view: SKView) { // called when the scene is presented into view (happens only once)
playerScore = 0
physicsWorld.contactDelegate = self
physicsWorld.gravity = .zero
// BACKGROUND
backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 48/255, alpha: 1.0)
print("Background color is set")
// WRAP THE STARFIELDS
// Front most layer of stars
let starsTexture = SKTexture(imageNamed: "stars.png")
let bgAnimation = SKAction.move(by: CGVector(dx: 0, dy: -starsTexture.size().height), duration: 5)
let bgReset = SKAction.move(by: CGVector(dx: 0, dy: starsTexture.size().height), duration: 0)
let bgConstantMotion = SKAction.repeatForever(SKAction.sequence([bgAnimation,bgReset]))
// Back layer of slower stars
let deeperStarsTexture = SKTexture(imageNamed: "stars-deeper.png")
let deeperStarsbgAnimation = SKAction.move(by: CGVector(dx: 0, dy: -deeperStarsTexture.size().height), duration: 8)
let deeperStarsbgReset = SKAction.move(by: CGVector(dx: 0, dy: deeperStarsTexture.size().height), duration: 0)
let deeperStarsbgConstantMotion = SKAction.repeatForever(SKAction.sequence([deeperStarsbgAnimation,deeperStarsbgReset]))
var i: CGFloat = 0
while i < 3
{
stars = SKSpriteNode(texture: starsTexture)
stars.position = CGPoint(x: frame.midX, y: starsTexture.size().height * i)
stars.size.height = frame.height
stars.run(bgConstantMotion)
stars.zPosition = -1
addChild(stars)
deeperstars = SKSpriteNode(texture: deeperStarsTexture)
deeperstars.position = CGPoint(x: frame.midX, y: deeperStarsTexture.size().height * i)
deeperstars.size.height = frame.height
deeperstars.run(deeperStarsbgConstantMotion)
deeperstars.zPosition = -1
addChild(deeperstars)
i += 1
}
// PLAYER
let playerTexture1 = SKTexture(imageNamed: "player-1.png")
let playerTexture2 = SKTexture(imageNamed: "player-2.png")
let playerAnimation = SKAction.animate(with: [playerTexture1, playerTexture2], timePerFrame: 0.2)
let constantAnimation = SKAction.repeatForever(playerAnimation)
sp_player = SKSpriteNode(texture: playerTexture1)
sp_player.position = CGPoint(x: frame.midX, y: (sp_player.size.height * 2))
sp_player.physicsBody = SKPhysicsBody(rectangleOf: sp_player.size)
sp_player.physicsBody!.isDynamic = false
sp_player.name = "player"
sp_player.run(constantAnimation)
addChild(sp_player)
// PLACE ALIENS
let alienTexture1 = SKTexture(imageNamed: "alien-1a.png")
let alienTexture2 = SKTexture(imageNamed: "alien-1b.png")
let alienAnimation = SKAction.animate(with: [alienTexture1, alienTexture2], timePerFrame: 0.4)
let constantAlienAnimation = SKAction.repeatForever(alienAnimation)
var x: CGFloat = 0, y: CGFloat = 0
while y < 6
{
while x < 6
{
alien = SKSpriteNode(texture: alienTexture1)
alien.position = CGPoint(x: 32 + (x * alien.size.width), y: (frame.size.height - (alien.size.height * 1.5) - (alien.size.height * y)))
print("Setting y to \(frame.size.height - (alien.size.height * y))")
alien.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: alien.size.width, height: alien.size.height))
alien.physicsBody!.isDynamic = false
alien.name = "alien"
alien.physicsBody!.contactTestBitMask = PhysicsCategory.laser
alien.run(constantAlienAnimation)
addChild(alien)
x += 1
}
y += 1
x = 0
}
print("Sprites added to scene")
spawnLasers()
}
func spawnLasers()
{
let delay1 = SKAction.wait(forDuration: 0.5)
let spawn = SKAction.run {
let laserTexture = SKTexture(imageNamed: "laser-1.png")
self.laser = SKSpriteNode(texture: laserTexture)
self.laser.position = CGPoint(x: self.sp_player.position.x, y: self.sp_player.position.y + self.sp_player.size.height)
self.laser.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.laser.size.width, height: self.laser.size.height))
self.laser.physicsBody!.isDynamic = true
self.laser.physicsBody!.linearDamping = 0
self.laser.physicsBody!.allowsRotation = false
self.laser.physicsBody!.contactTestBitMask = PhysicsCategory.laser
self.laser.name = "laser"
self.addChild(self.laser)
let shoot = SKAction.moveTo(y: self.frame.size.height, duration: 1)
let killLaser = SKAction.removeFromParent()
let handleLaser = SKAction.sequence([shoot,killLaser])
self.laser.run(handleLaser)
}
let action = SKAction.sequence([delay1,spawn])
let constantLasers = SKAction.repeatForever(action)
self.run(constantLasers)
}
func didBegin(_ contact: SKPhysicsContact) {
var check: UInt32 = 0
if contact.bodyA.node != nil
{
check += 1
}
if contact.bodyB.node != nil
{
check += 1
}
if check == 2
{
if contact.bodyA.node!.name == "alien" && contact.bodyB.node!.name == "laser"
{
// EXPLOSION
let explosionSplatTexture1 = SKTexture(imageNamed: "explosion-1a.png")
let explosionSplatTexture2 = SKTexture(imageNamed: "explosion-1b.png")
let explosionSplatTexture3 = SKTexture(imageNamed: "explosion-1c.png")
let explosionSplatTexture4 = SKTexture(imageNamed: "explosion-1d.png")
let explosionSplatAnimation = SKAction.animate(with: [explosionSplatTexture1, explosionSplatTexture2, explosionSplatTexture3, explosionSplatTexture4], timePerFrame: 0.1)
let killExplosion = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSplatAnimation,killExplosion])
explosionSplat1 = SKSpriteNode(texture: explosionSplatTexture1)
explosionSplat1.name = "explosion"
explosionSplat1.position = CGPoint(x: contact.bodyA.node!.position.x, y: contact.bodyA.node!.position.y)
addChild(explosionSplat1)
explosionSplat1.run(explosionSequence)
self.playerScore += 1
print("Score: \(self.playerScore!)")
contact.bodyA.node?.removeFromParent()
print("Alien named \(contact.bodyA.node?.name ?? "defaultAlienName") from scene")
contact.bodyB.node?.removeFromParent()
print("Laser named \(contact.bodyB.node?.name ?? "defaultLaserName") from scene")
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
//print("Contact ended between \(contact.bodyA) and \(contact.bodyB)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
// if let touch = touches.first {
// let position = touch.location(in: view)
// storedTouch = position
// }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first {
let position = touch.location(in: view)
var playerpos: CGPoint!
playerpos = sp_player.position
let pl_move = SKAction.move(to: CGPoint(x: position.x, y: playerpos.y), duration: 0.1)
sp_player.run(pl_move)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
/*
Need to figure out how to use storedTouch properly
to move player relative to the screen touch co-ordinates
*/
// if let touch = touches.first {
// let position = touch.location(in: view)
// }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (lastUpdateTime > 0)
{
delta = currentTime - lastUpdateTime
} else {
delta = 0
}
lastUpdateTime = currentTime
}
}
When the game runs it produces this screen:
You can see the unexpected laser behavior here:
In my diags I get the following output from the collision function:
Score: 1
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 2
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 3
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 4
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
Score: 5
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
Score: 6
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
This is most likely my complete lack of understanding for optionals and how collision actually works. I'd be super grateful for any insights.
In your alien loop, as well as spawnLasers(), you are not giving the sprite nodes an actual PhysicsBody category. For sprites to be able to detect contact between one another, they need a category name and a contact name.
So in your while loop (building the aliens), you need to have this:
alien.physicsBody!.categoryBitMask = PhysicsCategory.alien
alien.physicsBody!.contactTestBitMask = PhysicsCategory.laser
And in spawnLasers(), you want this added:
self.laser.physicsBody!.categoryBitMask = PhysicsCategory.laser
and change the contactTestBitMask to alien, not laser:
self.laser.physicsBody!.contactTestBitMask = PhysicsCategory.alien
Hopefully you can see that the alien category wants to know when lasers touch, and the laser category wants to know when the aliens touch.
To visually help you, turn on the show physics option, this way you can see the actual physics bodies you are dealing with.
To do this, in your GameViewController (or similar), find:
showsFPS = true
showsNodeCount = true
You want to add the following:
showsPhysics = true
This will help with seeing the actual physics bodies on screen.
In:
func didBegin(_ contact: SKPhysicsContact)
you are only testing for BodyA being alien and BodyB being laser.
This is the contact test:
if contact.bodyA.node!.name == "alien" && contact.bodyB.node!.name == "laser"
I believe BodyA could be laser and BodyB be alien. Basically the physics engine contact events could be "A hitting B", or "B hitting A".
Therefore, a quick and dirty solution is to create another if statement below the current one, but changing the body names, so:
if contact.bodyA.node!.name == "laser" && contact.bodyB.node!.name == "alien" {
and duplicate the code from your existing if statement, and changing the two print statements.
This isn't the ideal way to do it, but hopefully when you tidy it up you'll get an understanding of what the physics contact is doing.
I am hoping once you have implmented the above, you will be in a much better shape.
I'm currently working on my first test game in which the player have to jump over some randomly generated bars.
I wrote a func which should generate the bars outside of my scene.
My problem at the moment is that when I'm trying to call the created bars with "self.childNode(withName:)" xCode is telling me "Fatal error: Unexpectedly found nil while unwrapping an Optional value".
I've already read the Apple Documentation for "childNode(withName:)" and added "//" before the name of the node. In addition I used the stackoverflow search but I can't find anything that solved my problem.
//
// GameScene.swift
// PlaxerJump
//
//
import SpriteKit
import AVFoundation
class GameScene: SKScene {
let bottom1 = SKSpriteNode(imageNamed: "Bottom")
let bottom2 = SKSpriteNode(imageNamed: "Bottom")
let player = SKSpriteNode(imageNamed: "Player")
var velocity = CGFloat(0)
var onGround = true
var value = CGFloat(5)
override func didMove(to view: SKView) {
// Hintergrund
self.backgroundColor = SKColor.lightGray
bottom1.anchorPoint = CGPoint.zero
bottom1.position = CGPoint.zero
bottom1.zPosition = 1
self.addChild(bottom1)
bottom2.anchorPoint = CGPoint.zero
bottom2.position = CGPoint(x: bottom1.size.width - 1, y: 0)
bottom2.zPosition = 1
self.addChild(bottom2)
// Spieler
player.position = CGPoint(x: player.size.width / 2 + 20, y: bottom1.size.height + player.size.height / 2)
player.zPosition = 2
self.addChild(player)
//Balken
addBalken(xScale: 1.5, yScale: 1, name: "ba1", xPoint: 0)
}
func addBalken(xScale: CGFloat, yScale: CGFloat, name: String, xPoint: CGFloat) {
let balken = SKSpriteNode(imageNamed: "Balken")
balken.anchorPoint = CGPoint.zero
balken.position = CGPoint(x: self.size.width + (2 * balken.size.width) + xPoint, y: bottom1.size.height - 16)
balken.zPosition = 1
balken.xScale = xScale
balken.yScale = yScale
balken.name = name
addChild(balken)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if onGround == true {
velocity = -16
onGround = false
self.run(SKAction.playSoundFileNamed("Jump.wav", waitForCompletion: false))
}
}
override func update(_ currentTime: TimeInterval) {
//Player
player.zRotation -= CGFloat(Double.pi * 5) / 180
velocity += 0.6
player.position.y -= velocity
if player.position.y <= bottom1.size.height {
player.position.y = bottom1.size.height
velocity = 0
onGround = true
}
//Bottom
bottom1.position.x -= 4
bottom2.position.x -= 4
if bottom1.position.x < -bottom1.size.width {
bottom1.position.x = bottom2.position.x + bottom2.size.width
} else if bottom2.position.x < -bottom2.size.width {
bottom2.position.x = bottom1.position.x + bottom1.size.width
}
//Balken - ** THIS IS THE PART WHERE THE ERROR OCCURS **
let balke1 = self.childNode(withName: "//ba1") as! SKSpriteNode
balke1.position.x -= value
if balke1.position.x < self.size.width {
balke1.position.x = self.size.width + (2 * balke1.size.width)
value = CGFloat(arc4random_uniform(UInt32(value)))
}
}
}
I just want to call the node so I can use it to implement the bars in the game.
Change this line of code:
addBalken(xScale: 1.5, yScale: 1, name: "//ba1", xPoint: 0)
to:
addBalken(xScale: 1.5, yScale: 1, name: "ba1", xPoint: 0)
The // only applies when searching for the node, so keep these characters in this line:
let balke1 = self.childNode(withName: "//ba1") as! SKSpriteNode
EDIT:
I think the root cause of your problem is that you forgot to call addChild in your addBalken function. Simply creating a node isn't enough. The node must also be added to the scene as well. So this is the final code:
func addBalken(xScale: CGFloat, yScale: CGFloat, name: String, xPoint: CGFloat) {
let balken = SKSpriteNode(imageNamed: "Balken")
balken.anchorPoint = CGPoint.zero
balken.position = CGPoint(x: self.size.width + (2 * balken.size.width) + xPoint, y: bottom1.size.height - 16)
balken.zPosition = 1
balken.xScale = xScale
balken.yScale = yScale
balken.name = name
//add the node to the scene
addChild(balken)
}
I am trying to find a way to shoot more than one bullets as power up increases. Also it's only going up straight on the first 4 power ups, but I would like it to have a little angle as it reaches 5 and up.
Can someone help me implement that with the following codes I currently have?
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player: SKSpriteNode!
var scoreLabel: SKLabelNode!
var score: Int = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
var gameTimer: Timer!
var possibleAliens = ["alien", "alien2", "alien3"]
//bitmask for alien and torpedo's physics body
let alienCategory:UInt32 = 0x1 << 1
let photonTorpedoCategory:UInt32 = 0x1 << 0
//lives
var livesArray:[SKSpriteNode]!
//powerUp
var powerUp: Int = 1
//didMove
override func didMove(to view: SKView) {
addLives()
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 / 3.6, y: player.size.height / 2 + 20)
self.addChild(player)
//physicsWorld
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
//score and scoreLabel
scoreLabel = SKLabelNode(text: "Score: 0")
scoreLabel.position = CGPoint(x: 80, y: self.frame.size.height - 60)
scoreLabel.fontName = "AmericanTypewriter-Bold"
scoreLabel.fontSize = 28
scoreLabel.fontColor = UIColor.white
score = 0
self.addChild(scoreLabel)
//create a timeInterval that can be changed depending on the difficulty
var timeInterval = 0.6
if UserDefaults.standard.bool(forKey: "hard"){
timeInterval = 0.2
}
//gameTimer
gameTimer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(addAlien), userInfo: nil, repeats: true)
//motion Manager initialization in didMove
motionManger.accelerometerUpdateInterval = 0.2
//creatingan acceleration data in didMove
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 addLives
func addLives() {
//initialize livesArray from GameScene
livesArray = [SKSpriteNode]()
for live in 1 ... 3 {
let liveNode = SKSpriteNode(imageNamed: "shuttle")
liveNode.name = "live\(live)"
liveNode.position = CGPoint(x: self.frame.size.width - CGFloat((4-live)) * liveNode.size.width, y: self.frame.size.height - 60)
self.addChild(liveNode)
livesArray.append(liveNode)
}
}
//func addAlien
func addAlien() {
//using GK, pick possibleAliens[arrays] randomly, and shuffle
possibleAliens = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: possibleAliens) as! [String]
//bring the aliens to random position
let alien = SKSpriteNode(imageNamed: possibleAliens[0])
let randomAlienPosition = GKRandomDistribution(lowestValue: 0, highestValue: 414)
//make the position constant, use randomAlien and get next integer and use CGFloat then set alien position
let position = CGFloat(randomAlienPosition.nextInt())
alien.position = CGPoint(x: position, y: self.frame.size.height + alien.size.height)
//physicsBody of addAlien
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)
//make aliens move
let animationDuration: TimeInterval = 6
//SKAction to alien will make alien move from top to bottom of the screen, then remove alien from screen and from parent so it doesnt consume too much memory
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: position, y: -alien.size.height), duration: animationDuration))
//THIS ACTION WILL SEE IF IT REACHES THE FINAL DESTINATION BEFORE IT GETS ERASED AND TAKES A LIFE
//A RUN ACTION THAT WILL PLAY SOUND WHEN PLAYER LOSES SOUND.
actionArray.append(SKAction.run{
self.run(SKAction.playSoundFileNamed("loose.mp3", waitForCompletion: false))
if self.livesArray.count > 0 {
let liveNode = self.livesArray.first
liveNode!.removeFromParent()
self.livesArray.removeFirst()
if self.livesArray.count == 0{
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
let gameOver = SKScene(fileNamed: "GameOverScene") as! GameOverScene
gameOver.score = self.score
self.view?.presentScene(gameOver, transition: transition)
}
}
})
actionArray.append(SKAction.removeFromParent())
//make a run function on the alien to pass allong actionArray
alien.run(SKAction.sequence(actionArray))
}
//fire fireTorpedo when tapped
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
fireTorpedo()
}
//func FireTorpedo or bullet
func fireTorpedo(){
//adds sound, image and position of the torpedo
self.run(SKAction.playSoundFileNamed("torpedo.mp3", waitForCompletion: false))
let torpedoNode = SKSpriteNode(imageNamed: "torpedo")
//powerUp switch
switch (powerUp)
{
case 1:
torpedoNode.position.x = player.position.x
torpedoNode.position.y = player.position.y
case 2:
torpedoNode.position.y = player.position.y
torpedoNode.position.x = player.position.x - 10
torpedoNode.position.x = player.position.x + 10
default:
print("out of torpedo ammo")
break
}
torpedoNode.position.y += 5
//add physicsBody for torpedo just like the aliens
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)
//add animation like in alien
let animationDuration: TimeInterval = 0.3
//make torpedo move up and disappear
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())
//run the torpedo
torpedoNode.run(SKAction.sequence(actionArray))
}
func didBegin(_ contact: SKPhysicsContact){
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
//check if two bodies touch
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask{
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
//to findout which body is the torpedo and which is alien
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?){
if let explosion = SKEmitterNode(fileNamed: "Explosion"){
if let alien = alienNode{
explosion.position = alien.position
}
self.addChild(explosion)
self.run(SKAction.playSoundFileNamed("explosion.mp3", waitForCompletion: false))
if let torpedo = torpedoNode{
torpedo.removeFromParent()
}
if let alien = alienNode{
alien.removeFromParent()
}
//see the explosion effect longer and not disappear immediately with run function with action and completion handler
self.run(SKAction.wait(forDuration: 2)){
explosion.removeFromParent()
}
//add and update score
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)
}
}
}
I am trying to use the SKPhysicsContactDelegate function in SpriteKit and it will not seem to work. I want one sprite to perform an action when it hits the other. I have set up breakpoints at the didBeginContact function and for some reason my application never calls this function. All help appreciated. Code posted below.
struct PhysicsCatagory {
static let Enemy :UInt32 = 0x1 << 0
static let Slider :UInt32 = 0x1 << 1
static let Circle :UInt32 = 0x1 << 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var EnemyTimer = NSTimer()
var Circle = SKSpriteNode()
var Slider = SKSpriteNode()
var FastButton = SKNode()
var Title = SKSpriteNode()
var Text = SKSpriteNode()
var Path = UIBezierPath()
var gameStarted = Bool()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.backgroundColor = UIColor.whiteColor()
Circle = SKSpriteNode(imageNamed:"blueCircle")
Circle.size = CGSize(width: 140, height: 140)
Circle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
Circle.zPosition = 1.0
self.addChild(Circle)
Slider = SKSpriteNode(imageNamed: "blocker1")
Slider.size = CGSize(width: 15, height: 50)
Slider.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 + 80)
addChild(Slider)
Slider.zPosition = 1.0
moveClockWise()
}
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.node != nil && contact.bodyB.node != nil{
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if ((firstBody.name == "Enemy") && (secondBody.name == "Slider")){
collisionBall(firstBody, Slider: secondBody)
}
else if ((firstBody.name == "Slider") && (secondBody.name == "Enemy")) {
collisionBall(secondBody, Slider: firstBody)
}
}
}
func collisionBall(Enemy : SKSpriteNode, Slider : SKSpriteNode){
Enemy.physicsBody?.dynamic = true
Enemy.physicsBody?.affectedByGravity = true
Enemy.physicsBody?.mass = 4.0
Slider.physicsBody?.mass = 4.0
Enemy.removeAllActions()
Enemy.physicsBody?.contactTestBitMask = 0
Enemy.physicsBody?.collisionBitMask = 0
Enemy.name = nil
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Slider.hidden = false
FastButton.hidden = false
Title.hidden = true
Text.hidden = true
EnemyTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(GameScene.Enemies), userInfo: nil, repeats: true)
//Physics
Slider.physicsBody?.categoryBitMask = PhysicsCatagory.Slider
Slider.physicsBody?.collisionBitMask = PhysicsCatagory.Enemy
Slider.physicsBody?.contactTestBitMask = PhysicsCatagory.Enemy
Slider.name = "Slider"
Slider.physicsBody?.dynamic = true
Slider.physicsBody?.affectedByGravity = true
if gameStarted == false{
gameStarted = true
}
else if gameStarted == true{
}
}
func moveClockWise(){
let dx = Slider.position.x / 2
let dy = Slider.position.y / 2
let rad = atan2(dy, dx)
let Path = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2, y: self.frame.height / 2), radius: 90, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(Path.CGPath, asOffset: false, orientToPath: true, speed: 150)
//let rotate = SKAction.rotateByAngle(75, duration: 100)
Slider.runAction(SKAction.repeatActionForever(follow).reversedAction())
//Slider.runAction(SKAction.repeatActionForever(rotate).reversedAction())
}
func Enemies(){
let Enemy = SKSpriteNode(imageNamed: "darkRedDot")
Enemy.size = CGSize(width: 20, height: 20)
//Physics
Enemy.physicsBody = SKPhysicsBody(circleOfRadius: Enemy.size.width / 2)
Enemy.physicsBody?.categoryBitMask = PhysicsCatagory.Enemy
Enemy.physicsBody?.contactTestBitMask = PhysicsCatagory.Slider //| PhysicsCatagory.Circle
Enemy.physicsBody?.collisionBitMask = PhysicsCatagory.Slider //| PhysicsCatagory.Circle
Enemy.physicsBody?.dynamic = true
Enemy.physicsBody?.affectedByGravity = false
Enemy.name = "Enemy"
The contact is not beginning because your slider has no physics body, making it impossible to recognize contact. Unless, it's not in this code, your slider has no physics body, but your enemy does. Try declaring Slider.phisicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "blocker1"), size: Slider.size)
Also, you should read about standard naming conventions in Swift. It is recommended that your variables always start with lowercase letters (eg. enemyTimer, circle, slider, fastButton, etc...).
I wanted to create a small animation of a car driving down the road, so I made an atlas of 9 different pictures. The car simply looks like its wheels are rotating and the car is bouncing a bit as it drives along. I already made an SKSpriteNode with an image and added a physics body on it so that it can jump and be affected by gravity.
So I was wondering how to add either a physics body to an SKAtlasTexture or create an animation through my image.xcassets folder. I tried to just change the SKSpriteNode to SKAtlasTexture, but that obviously didn't work as there are no physics bodies in SKAtlasTexture. So that's where I'm at. Any suggestions or solutions would be greatly appreciated.
Here some parts of my code:
class PlayScene: SKScene, SKPhysicsContactDelegate {
let road = SKSpriteNode(imageNamed: "road")
var origRoadPositionX = CGFloat(0)
var maxRoad = CGFloat(0)
var groundSpeed = 3
var carBaseLine = CGFloat(0)
let car = SKSpriteNode(imageNamed: "car")
enum ColliderType:UInt32{
case car = 1
case tower = 2
}
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor(hex: 0x80E8FF)
self.physicsWorld.contactDelegate = self
//Car
self.car.position = CGPointMake(CGRectGetMinX(self.frame)-20 + self.car.size.width, self.carBaseLine)
self.car.physicsBody = SKPhysicsBody (rectangleOfSize: self.car.size)
self.car.physicsBody?.allowsRotation = false
self.car.physicsBody?.affectedByGravity = false
self.car.physicsBody?.categoryBitMask = ColliderType.car.rawValue
self.car.physicsBody?.contactTestBitMask = ColliderType.tower.rawValue
self.car.physicsBody?.collisionBitMask = ColliderType.tower.rawValue
self.addChild(car)
If more code is needed in order to find a solution, let me know and i can supply more of it.
You can use atlas folder for performing animation with images.
Consider below example:
import SpriteKit
class GameScene: SKScene {
var bombFrames : [SKTexture]!
var bomb : SKSpriteNode!
let NoneCategory : UInt32 = 0x1 << 0
let ProjectileCategory : UInt32 = 0x1 << 2
let bombCategory : UInt32 = 0x1 << 7
override func didMoveToView(view: SKView) {
/* Setup your scene here */
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addBomb), SKAction.waitForDuration(5.0)])))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addBomb() {
let Name = "Bomb"
let AnimatedAtlas = SKTextureAtlas(named: Name)
var Framese = [SKTexture]()
let numImages = AnimatedAtlas.textureNames.count
for var i=1; i<=numImages; i++ {
let TextureName = "\(i)"
Framese.append(AnimatedAtlas.textureNamed(TextureName))
}
bombFrames = Framese
let firstFrame = bombFrames[0]
bomb = SKSpriteNode(texture: firstFrame)
let actualY = random(min: bomb.size.height/2, max: size.height - bomb.size.height/2)
bomb.position = CGPoint(x: size.width + bomb.size.width/2, y: actualY)
bomb.physicsBody = SKPhysicsBody(texture: bomb.texture, size: bomb.texture!.size())
bomb.physicsBody?.dynamic = true
bomb.physicsBody?.categoryBitMask = bombCategory
bomb.physicsBody?.contactTestBitMask = ProjectileCategory
bomb.physicsBody?.collisionBitMask = NoneCategory
bomb.physicsBody?.usesPreciseCollisionDetection = true
addChild(bomb)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.moveTo(CGPoint(x: -bomb.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bomb.runAction(SKAction.sequence([actionMove, actionMoveDone]))
playBombAnimation()
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func playBombAnimation() {
bomb.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(bombFrames, timePerFrame: 0.1, resize: false, restore: true)), withKey:"bombAnimation")
}
}
And don't forget to add atlas folder into your project navigator like this:
As you can see in code you can add physics body to your sprite. and if you want you can try this way.
Hope this will help.