I am trying to make my Bullet fire continuously while touching the screen. This is what I got so far, but it's not really working.
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.setScale(1.5) // Bullet Size
bullet.position = player.position
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet
bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
bullet.physicsBody!.contactTestBitMask = PhysicsCategories.Enemy
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([bulletSound, moveBullet, deleteBullet])
let bulletRepeat = SKAction.repeatForever(bulletSequence)
bullet.run(bulletRepeat)
}
what you need is someway of turning on the fire when you press down on the screen and turning it off when you lift your finger. You can use the touches began to set a variable to true the says you should be firing isFiring = true and then catch that variable in the update. When you lift your finger the touches ended will turn the variable off.
Another nice touch is that you can adjust the firingInterval variable in real time to adjust the rate of fire.
private var updateTime: Double = 0
private var isFiring = false
//rate of fire, 1 means fire once a second
private var firingInterval: Double = 1
override func update(_ currentTime: TimeInterval) {
//check if we should be firing if not get outta here
guard isFiring else { return }
if updateTime == 0 {
updateTime = currentTime
}
if currentTime - updateTime > firingInterval {
self.fireBullet()
updateTime = currentTime
}
}
override touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isFiring = true
}
override touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isFiring = false
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.setScale(1.5) // Bullet Size
bullet.position = player.position
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet
bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
bullet.physicsBody!.contactTestBitMask = PhysicsCategories.Enemy
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([bulletSound, moveBullet, deleteBullet])
//let bulletRepeat = SKAction.repeatForever(bulletSequence)
bullet.run(bulletSequence )
}
You could use a Timer object, started in the touchesBegan and cancel it in the touchesEnded method.
For timers, I use the following class that I've written :
class MyTimerHandlerObject: NSObject {
var timerCounter: CGFloat = 0
var myTimer = Timer()
override init() {super.init()}
#objc func myTimerHandler(_ theTimer: Timer) {
timerCounter += 0.1
// Do what needs do be done here ...
}
func startMyTimer() {
timerCounter = 0
self.myTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(MyTimerHandlerObject.myTimerHandler(_:)), userInfo: nil, repeats: true)
}
func stopMyTimer() {
self.myTimer.invalidate()
timerCounter = 0
}
}
You can then easily start the timer and stop it, by declaring a variable of type MyTimerHandlerObject and use the methods 'StartMyTimer' and 'StopMyTimer'
Related
I'm making a game where the ball(the player) is suppose to avoid other balls passing by the screen. Basically I want the ball always to follow the location of the touch. So wherever I'm moving my finger on the screen, the ball follows.
This is the player Class:
import SpriteKit
struct ColliderType {
static let Player: UInt32 = 1
static let Blue: UInt32 = 2
static let Green: UInt32 = 3
static let Yellow: UInt32 = 4
static let Red: UInt32 = 5
}
class Player: SKSpriteNode {
func initialize() {
self.name = "Player"
self.zPosition = 1
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height /
2)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Player
self.physicsBody?.collisionBitMask = ColliderType.Blue |
ColliderType.Green | ColliderType.Red | ColliderType.Yellow
self.physicsBody?.contactTestBitMask = ColliderType.Blue |
ColliderType.Green | ColliderType.Red | ColliderType.Yellow
}
}
This is the GameplayScene:
import SpriteKit
class GameplayScene: SKScene, SKPhysicsContactDelegate {
var player = Player()
var ball = SKSpriteNode()
var scoreLabel = SKLabelNode()
var score = 0
var counter = Timer()
override func didMove(to view: SKView) {
initialize()
}
override func update(_ currentTime: TimeInterval) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "Retry" {
self.removeAllActions()
self.removeAllChildren()
initialize()
}
if atPoint(location).name == "Quit" {
let mainmenu = MainMenuScene(fileNamed: "MainMenuScene")
mainmenu!.scaleMode = .aspectFill
self.view?.presentScene(mainmenu!, transition:
SKTransition.fade(withDuration: TimeInterval(1)))
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event:
UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event:
UIEvent?) {
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Player" {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Player" && secondBody.node?.name ==
"Red" {
playerDied()
firstBody.node?.removeFromParent()
}
if firstBody.node?.name == "Player" && secondBody.node?.name ==
"Blue" {
playerDied()
firstBody.node?.removeFromParent()
}
if firstBody.node?.name == "Player" && secondBody.node?.name ==
"Green" {
playerDied()
firstBody.node?.removeFromParent()
}
if firstBody.node?.name == "Player" && secondBody.node?.name ==
"Yellow" {
playerDied()
firstBody.node?.removeFromParent()
}
}
func initialize() {
score = 0
physicsWorld.contactDelegate = self
createPlayer()
createBackground()
spawnRedBall()
spawnBlueBall()
spawnGreenBall()
spawnYellowBall()
createLabel()
counter = Timer.scheduledTimer(timeInterval: TimeInterval(0.7),
target: self, selector: "incrementScore", userInfo: nil, repeats: true)
}
func createPlayer() {
player = Player(imageNamed: "Player")
player.initialize()
player.position = CGPoint(x: 0, y: 0)
self.addChild(player)
}
func createBackground() {
let bg = SKSpriteNode(imageNamed: "BG")
bg.name = "BG"
bg.anchorPoint = CGPoint(x: 0.5, y: 0.5)
bg.position = CGPoint(x: 0, y: 0)
self.addChild(bg)
}
func createRedBall() {
let ball = SKSpriteNode(imageNamed: "Red")
ball.name = "Red"
ball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
ball.zPosition = 1
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height /
2)
ball.physicsBody?.categoryBitMask = ColliderType.Red
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.isDynamic = false
ball.position.y = self.size.height + 100
ball.position.x = CGFloat.randomBetweenNumbers(firstNum: -345,
secondNum: 345)
self.addChild(ball)
let destination = self.frame.height * 2
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
ball.run(SKAction.sequence([move, remove]), withKey: "MoveRed")
}
func spawnRedBall() {
let spawn = SKAction.run({ () -> Void in
self.createRedBall()
})
let delay = SKAction.wait(forDuration: TimeInterval(0.5))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "SpawnRed")
}
func createBlueBall() {
let ball = SKSpriteNode(imageNamed: "Blue")
ball.name = "Blue"
ball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
ball.zPosition = 1
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height /
2)
ball.physicsBody?.categoryBitMask = ColliderType.Blue
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.isDynamic = false
ball.position.y = -self.size.height + 100
ball.position.x = CGFloat.randomBetweenNumbers(firstNum: -345,
secondNum: 345)
self.addChild(ball)
let destination = self.frame.height * 2
let move = SKAction.moveTo(y: destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
ball.run(SKAction.sequence([move, remove]), withKey: "MoveBlue")
}
func spawnBlueBall() {
let spawn = SKAction.run({ () -> Void in
self.createBlueBall()
})
let delay = SKAction.wait(forDuration: TimeInterval(0.5))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "SpawnBlue")
}
func createGreenBall() {
let ball = SKSpriteNode(imageNamed: "Green")
ball.name = "Green"
ball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
ball.zPosition = 1
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height /
2)
ball.physicsBody?.categoryBitMask = ColliderType.Green
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.isDynamic = false
ball.position.x = -self.size.width + 200
ball.position.y = CGFloat.randomBetweenNumbers(firstNum: -637,
secondNum: 637)
self.addChild(ball)
let destination = self.frame.height * 2
let move = SKAction.moveTo(x: destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
ball.run(SKAction.sequence([move, remove]), withKey: "MoveGreen")
}
func spawnGreenBall() {
let spawn = SKAction.run({ () -> Void in
self.createGreenBall()
})
let delay = SKAction.wait(forDuration: TimeInterval(0.5))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "SpawnGreen")
}
func createYellowBall() {
let ball = SKSpriteNode(imageNamed: "Yellow")
ball.name = "Yellow"
ball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
ball.zPosition = 1
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height /
2)
ball.physicsBody?.categoryBitMask = ColliderType.Green
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.isDynamic = false
ball.position.x = self.size.width + 200
ball.position.y = CGFloat.randomBetweenNumbers(firstNum: -637,
secondNum: 637)
self.addChild(ball)
let destination = self.frame.height * 2
let move = SKAction.moveTo(x: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
ball.run(SKAction.sequence([move, remove]), withKey: "MoveYellow")
}
func spawnYellowBall() {
let spawn = SKAction.run({ () -> Void in
self.createYellowBall()
})
let delay = SKAction.wait(forDuration: TimeInterval(0.5))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "SpawnYellow")
}
func createLabel() {
scoreLabel.zPosition = 3
scoreLabel.position = CGPoint(x: -320, y: 600)
scoreLabel.fontName = "Verdana"
scoreLabel.fontSize = 70
scoreLabel.text = "0"
self.addChild(scoreLabel)
}
func incrementScore() {
score += 1
scoreLabel.text = String(score)
}
func playerDied() {
counter.invalidate()
let highscore = GameManager.instance.getHighscore()
if highscore < score {
GameManager.instance.setHighscore(highscore: score)
}
let retry = SKSpriteNode(imageNamed: "Retry")
let quit = SKSpriteNode(imageNamed: "Quit")
retry.name = "Retry"
retry.anchorPoint = CGPoint(x: 0.5, y: 0.5)
retry.position = CGPoint(x: -150, y: -50)
retry.zPosition = 2
retry.setScale(0)
quit.name = "Quit"
quit.anchorPoint = CGPoint(x: 0.5, y: 0.5)
quit.position = CGPoint(x: 150, y: -50)
quit.zPosition = 2
quit.setScale(0)
let scaleUp = SKAction.scale(to: 1, duration: TimeInterval(0.5))
retry.run(scaleUp)
quit.run(scaleUp)
self.addChild(retry)
self.addChild(quit)
}
}
Add the following property:
var ballIsTouched = false
and then implement the touch methods:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let location = touches.first.location(in: self) {
if ball.containsPoint(location) {
ballIsTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if (ballIsTouched == true) {
ball.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
ballIsTouched = false
}
I think it is more natural when implementing a dragging, to drag a sprite from the touch location rather than from the center of the sprite. To do this, you should calculate the offset and add it the new sprite's position, like this:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let current = touch.location(in: self)
let previous = touch.previousLocation(in: self)
if ball.contains(current) {
let offset = CGPoint(x: current.x - previous.x , y: current.y - previous.y)
ball.position = CGPoint(x: ball.position.x + offset.x , y: ball.position.y + offset.y)
}
}
}
Here is a sample project to show how to implement a draggable sprite.:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ballIsTouched = false
var ball = SKSpriteNode()
override func didMove(to view: SKView) {
ball = SKSpriteNode(color: .blue, size: CGSize(width:100, height:100))
ball.position = CGPoint(x: 0, y: 0)
addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if ball.contains(touch.location(in: self)) {
ballIsTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if (ballIsTouched == true) {
ball.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
ballIsTouched = false
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
This only allows for the single specified sprite to be dragged. Here is an example with multiple sprites that can be dragged:
import SpriteKit
import GameplayKit
extension SKColor {
static func random() -> SKColor {
let colours = [SKColor.lightGray, SKColor.white, SKColor.gray, SKColor.red, SKColor.green, SKColor.blue, SKColor.cyan, SKColor.yellow, SKColor.magenta, SKColor.orange, SKColor.purple, SKColor.brown]
return colours[Int(arc4random_uniform(UInt32(colours.count)))]
}
}
class GameScene: SKScene {
var touchedSprite : SKSpriteNode?
override func didMove(to view: SKView) {
for i in -1...1 {
let ball = SKSpriteNode(color: SKColor.random(), size: CGSize(width:100, height:100))
ball.position = CGPoint(x: 0, y: i * 200)
addChild(ball)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
touchedSprite = self.atPoint(touch.location(in: self)) as? SKSpriteNode
touchedSprite?.setScale(1.25)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if (touchedSprite != nil) {
touchedSprite?.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchedSprite?.setScale(1)
touchedSprite = nil
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
You could also implement a draggable protocol or something to limit which sprites can be dragged.
I'm trying to make a small mini game where you drag a ball around the screen and every 10 seconds a ball gets added in that follows you. so far the you can drag a ball around the screen and a ball follows you, but when another ball gets added in the balls group together. I think this is because the ball is following me depending on how fast I'm going. so is there a certain way in which I can have the balls follow me at a certain speed constantly, like 10 pixels a second or something, and that should prevent the balls from grouping together.
I am currently working on the score so it should soon go up every second you survive. and you die if you touch one of the balls.
below is the code and a short gif of my current code
!(https://gyazo.com/1d6a56527bfd0884e8a26cff730f4e03)
import SpriteKit
import GameplayKit
struct physicsCatagory{
static let me : UInt32 = 0x1 << 1
static let enemy : UInt32 = 0x1 << 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
private func makeEnemyName() -> String {
enemyCounter += 1
return "enemy\(enemyCounter)"
}
private func addEnemyToDict(enemy: SKSpriteNode, target: SKSpriteNode) {
if let name = enemy.name { spriteDictionary[name] = (enemy, target) }
else { print("enemy not found") }
}
private func removeEnemyFromDict(enemy: SKSpriteNode) {
if let name = enemy.name { spriteDictionary[name] = nil }
else { print("enemy not removed from dictionary!") }
}
private func moveFollowerToTarget(_ sprites: FollowerAndTarget) {
let action = SKAction.move(to: sprites.target.position, duration: 1)
sprites.follower.run(action)
}
private func allEnemiesMoveToTarget() {
for sprites in spriteDictionary.values {
moveFollowerToTarget(sprites)
}
}
let enemySpeed: CGFloat = 300
var me = SKSpriteNode()
// Tuple to keep track of enemy objects:
typealias FollowerAndTarget = (follower: SKSpriteNode, target: SKSpriteNode)
// [followerName: (followerSprite, targetSprite):
var spriteDictionary: [String: FollowerAndTarget] = [:]
// Give each enemy a unique name for the dictionary:
var enemyCounter = 0
var died = Bool()
override func didMove(to view: SKView) {
createScene()
}
func createEnemy () {
if died == true{
}
else {
let enemy = SKSpriteNode(imageNamed: "enemy1")
enemy.name = makeEnemyName()
addEnemyToDict(enemy: enemy, target: me)
moveFollowerToTarget((follower: enemy, target: me))
enemy.size = CGSize(width: 60, height: 60)
enemy.position = CGPoint(x:667, y: 200)
enemy.physicsBody?.restitution = 0.5
enemy.physicsBody = SKPhysicsBody(circleOfRadius: 60)
enemy.physicsBody?.affectedByGravity = false
enemy.zPosition = 2
enemy.physicsBody?.linearDamping = 0
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.categoryBitMask = physicsCatagory.enemy
enemy.physicsBody?.collisionBitMask = physicsCatagory.me
enemy.physicsBody?.contactTestBitMask = physicsCatagory.me
addChild(enemy)
}
}
func didBegin(_ contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCatagory.me && secondBody.categoryBitMask == physicsCatagory.enemy || firstBody.categoryBitMask == physicsCatagory.enemy && secondBody.categoryBitMask == physicsCatagory.me {
died = true
restartScene()
}
}
var lose: SKLabelNode!
func restartScene(){
self.removeAllChildren()
self.removeAllActions()
died = false
if let nextScene = GameScene(fileNamed: "menuScene"){
nextScene.scaleMode = self.scaleMode
let transition = SKTransition.fade(withDuration: 1)
view?.presentScene(nextScene, transition: transition)
}
}
func createScene(){
me = self.childNode(withName: "me") as! SKSpriteNode
me.physicsBody = SKPhysicsBody(circleOfRadius: 20)
me.physicsBody?.affectedByGravity = false
me.physicsBody?.categoryBitMask = physicsCatagory.me
me.physicsBody?.collisionBitMask = physicsCatagory.enemy
me.zPosition = 2
self.physicsWorld.contactDelegate = self
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 4.0)])))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
allEnemiesMoveToTarget()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
allEnemiesMoveToTarget()
}
}
override func update(_ currentTime: TimeInterval) {
// Will iterate through dictonary and then call moveFollowerToTarget()
// thus giving each enemy a new movement action to follow.
allEnemiesMoveToTarget()
}
}
Here you go:
import SpriteKit
import GameplayKit
struct physicsCatagory{
static let me : UInt32 = 0x1 << 1
static let enemy : UInt32 = 0x1 << 2
static let coin : UInt32 = 0x1 << 3
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var lose: SKLabelNode!
var me = SKSpriteNode()
// Tuple to keep track of enemy objects:
typealias FollowerAndTarget = (follower: SKSpriteNode, target: SKSpriteNode)
// [followerName: (followerSprite, targetSprite):
var spriteDictionary: [String: FollowerAndTarget] = [:]
// Give each enemy a unique name for the dictionary:
var enemyCounter = 0
let enemySpeed: CGFloat = 3
var died = Bool()
var timer = SKLabelNode()
var timerValue: Int = 0 {
didSet {
timer.text = "\(timerValue)"
}
}
private func makeEnemyName() -> String {
enemyCounter += 1
return "enemy\(enemyCounter)"
}
private func addEnemyToDict(enemy: SKSpriteNode, target: SKSpriteNode) {
if let name = enemy.name { spriteDictionary[name] = (enemy, target) }
else { print("enemy not found") }
}
private func removeEnemyFromDict(enemy: SKSpriteNode) {
if let name = enemy.name { spriteDictionary[name] = nil }
else { print("enemy not removed from dictionary!") }
}
// dont change anything outside of this, this is what makes the enemy follow you, so i have to have the enemy follow me at a constant speed
private func moveFollowerToTarget(_ sprites: FollowerAndTarget) {
let location = me.position
// Aim
let dx = location.x - sprites.follower.position.x
let dy = location.y - sprites.follower.position.y
let angle = atan2(dy, dx)
sprites.follower.zRotation = angle
// Seek
let vx = cos(angle) * enemySpeed
let vy = sin(angle) * enemySpeed
sprites.follower.position.x += vx
sprites.follower.position.y += vy
}
private func allEnemiesMoveToTarget() {
for sprites in spriteDictionary.values {
moveFollowerToTarget(sprites)
}
}
private func keepEnemiesSeparated() {
for sprites in spriteDictionary.values {
let iterator = sprites.follower
iterator.constraints = []
// get every other follower:
var otherFollowers: [SKSpriteNode] = []
for sprites in spriteDictionary.values {
if sprites.follower == iterator { continue }
else { otherFollowers.append(sprites.follower) }
}
// Assign constrain
for follower in otherFollowers {
let distanceBetween = CGFloat(60)
let constraint = SKConstraint.distance(SKRange(lowerLimit: distanceBetween), to: follower)
iterator.constraints!.append(constraint)
}
}
}
func createEnemy () {
if died { return }
let enemy = SKSpriteNode(color: .green, size: CGSize(width: 60, height: 60))
enemy.size = CGSize(width: 60, height: 60)
enemy.zPosition = 2
enemy.position.y -= size.height / 2
enemy.physicsBody = {
let pb = SKPhysicsBody(circleOfRadius: 30)
pb.restitution = 0.5
pb.affectedByGravity = false
pb.linearDamping = 0
pb.isDynamic = true
pb.categoryBitMask = physicsCatagory.enemy
pb.collisionBitMask = physicsCatagory.me
pb.contactTestBitMask = physicsCatagory.me
return pb
}()
enemy.name = makeEnemyName()
addEnemyToDict(enemy: enemy, target: me)
moveFollowerToTarget((follower: enemy, target: me))
keepEnemiesSeparated()
addChild(enemy)
}
func createCoin () {
let coin = SKSpriteNode(color: .yellow, size: CGSize(width: 20, height: 20))
let height = self.view!.frame.height
let width = self.view!.frame.width
let randomPosition = CGPoint( x:CGFloat( arc4random_uniform( UInt32( floor( width ) ) ) ),
y:CGFloat( arc4random_uniform( UInt32( floor( height ) ) ) )
)
coin.position = randomPosition
addChild(coin)
}
func restartScene(){
self.removeAllChildren()
self.removeAllActions()
died = false
let nextScene = GameScene(size: self.size)
nextScene.scaleMode = self.scaleMode
let transition = SKTransition.fade(withDuration: 1)
view?.presentScene(nextScene, transition: transition)
}
func createScene(){
me = SKSpriteNode(color: .blue, size: CGSize(width: 60, height: 60))
me.physicsBody = SKPhysicsBody(circleOfRadius: 30)
me.physicsBody?.affectedByGravity = false
me.physicsBody?.categoryBitMask = physicsCatagory.me
me.physicsBody?.collisionBitMask = physicsCatagory.enemy
me.zPosition = 2
timer = SKLabelNode(fontNamed: "Chalkduster")
timer.text = "\(timerValue)"
addChild(me)
addChild(timer)
let wait = SKAction.wait(forDuration: 1)
let block = SKAction.run({
[unowned self] in
if self.timerValue >= 0{
self.timerValue += 1
}else{
self.removeAction(forKey: "countdown")
}
})
let sequence = SKAction.sequence([wait,block])
run(SKAction.repeatForever(sequence), withKey: "countdown")
self.physicsWorld.contactDelegate = self
let border = SKPhysicsBody (edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 2.0)])))
run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createCoin), SKAction.wait(forDuration: TimeInterval(arc4random_uniform(11) + 5))])))
}
override func didMove(to view: SKView) {
scene?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
createScene()
}
func didBegin(_ contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCatagory.me && secondBody.categoryBitMask == physicsCatagory.enemy
|| firstBody.categoryBitMask == physicsCatagory.enemy && secondBody.categoryBitMask == physicsCatagory.me {
died = true
restartScene()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
allEnemiesMoveToTarget()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in: self)
me.run(SKAction.moveTo(x: location.x, duration: 0))
me.run(SKAction.moveTo(y: location.y, duration: 0))
allEnemiesMoveToTarget()
}
}
override func update(_ currentTime: TimeInterval) {
// Will iterate through dictonary and then call moveFollowerToTarget()
// thus giving each enemy a new movement action to follow.
allEnemiesMoveToTarget()
}
}
I was coding this game, and the game is over when the pig and fish collide. But when I play it, the pig didn't touch the fish but the game automatically overs. How do i fix that? The method related are did began and eaten etc.
import SpriteKit
import GameplayKit
var totalTime = 0
class GameScene: SKScene,SKPhysicsContactDelegate {
private var label : SKLabelNode?
var flyingPig:SKSpriteNode!
var water = SKSpriteNode()
var scoreLabel :SKLabelNode!
var currentGameState = gameState.atGame
struct PhysicsCategories{
static let None :UInt32 = 0
static let pig : UInt32 = 0b1
static let aFish : UInt32 = 0b100
}
var timer : Timer!
var timing : Timer!
var fish = ["nemo","bluedy","lantern","balloon","Knife"]
var myTime = 0
var lives:[SKSpriteNode]!
override func didMove(to view: SKView) {
totalTime = 0
self.physicsWorld.contactDelegate = self
water = SKSpriteNode(imageNamed: "water")
water.position = CGPoint(x: self.size.width/2,y:self.size.height/2)
water.physicsBody = SKPhysicsBody(rectangleOf: water.size)
water.physicsBody?.affectedByGravity = false
water.physicsBody?.isDynamic = false
self.addChild(water)
flyingPig = SKSpriteNode(imageNamed:"FlyingPig")
flyingPig.position = CGPoint(x:self.size.width/2,y:self.size.height/2)
flyingPig.physicsBody = SKPhysicsBody(rectangleOf: flyingPig.size)
flyingPig.physicsBody!.affectedByGravity=false
flyingPig.physicsBody!.collisionBitMask = PhysicsCategories.None
flyingPig.physicsBody!.categoryBitMask = PhysicsCategories.pig
flyingPig.physicsBody!.contactTestBitMask = PhysicsCategories.aFish
self.addChild(flyingPig)
self.physicsWorld.gravity = CGVector(dx:0,dy:0)
self.physicsWorld.contactDelegate = self
scoreLabel = SKLabelNode(text:"Score: 0")
scoreLabel.position = CGPoint(x:85,y:1270)
scoreLabel.fontName = "PartyLetPlain"
scoreLabel.fontSize = 44
scoreLabel.text = "Score: \(totalTime) km"
self.addChild(scoreLabel)
timer = Timer.scheduledTimer(timeInterval:1.25 ,target:self,selector:#selector(addFish),userInfo:nil,repeats:true)
timing = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
addLives()
}
func addLives()
{
lives = [SKSpriteNode()]
for live in 1...3{
let liveNode = SKSpriteNode(imageNamed:"FlyingPig2")
liveNode.position = CGPoint(x:750-CGFloat(4-live)*liveNode.size.width,y:1270)
self.addChild(liveNode)
lives.append(liveNode)
}
}
func addFish()
{
fish = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: fish) as![String]
let aFish = SKSpriteNode(imageNamed: fish[0])
let fishPosition = GKRandomDistribution(lowestValue:0, highestValue:700)
let position = CGFloat(fishPosition.nextInt())
aFish.position=CGPoint(x:position,y:1200)
aFish.physicsBody = SKPhysicsBody(rectangleOf: aFish.size)
aFish.physicsBody!.affectedByGravity = false
aFish.physicsBody?.isDynamic=false
aFish.physicsBody!.categoryBitMask = PhysicsCategories.aFish
aFish.physicsBody!.collisionBitMask = PhysicsCategories.None
aFish.physicsBody!.contactTestBitMask = PhysicsCategories.pig
self.addChild(aFish)
let animationDuration:TimeInterval = 12
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to:CGPoint(x:position,y:-800),duration:animationDuration))
aFish.run(SKAction.sequence(actionArray))
}
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.pig && body2.categoryBitMask==PhysicsCategories.aFish
{
body2.node?.removeFromParent()
gameOver()
}
}
enum gameState{
case before
case atGame
case after
}
func gameOver(){
self.removeAllActions()
currentGameState=gameState.after
let changeSceneAction = SKAction.run(changeScene)
let waitToChangeScene = SKAction.wait(forDuration:0.1)
let changeSceneSequence = SKAction.sequence([waitToChangeScene,changeSceneAction])
self.run(changeSceneSequence)
}
func changeScene(){
let newScene = GameOverScene(size:self.size)
newScene.scaleMode = self.scaleMode
let transition = SKTransition.fade(withDuration: 0.1)
self.view!.presentScene(newScene,transition:transition )
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ){
let location = touch.location(in: self)
if self.flyingPig.contains(location){
flyingPig.position = location
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ){
let location = touch.location(in: self)
if flyingPig.contains(location){
flyingPig.position = location
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
func tick()
{
totalTime+=1
}
}
body1.categoryBitMask == PhysicsCategories.pig && body2.categoryBitMask==PhysicsCategories.aFish
change to this
let contactMask = bodyA.contact.categoryBitMask || bodyB.contact.categoryBitMask
switch contactMask
case PhysicsCategories.pig || PhysicsCategories.aFish :
// code
default:
break
So I keep getting this error at random times during the play of the game, "fatal error: unexpectedly found nil while unwrapping an Optional value" in my asteroid battle game. I'll post the code here and if anyone could help figure it out I would appreciate it. If you don't understand any part you can ask me
import SpriteKit
import Darwin
struct PhysicsCatagory {
static let Asteroids : UInt32 = 1 //00000000000000000000000000000001
static let Bullet : UInt32 = 2 //0000000000000000000000000000010
static let Fighter : UInt32 = 3 //0000000000000000000000000000100
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var Score = Int()
var scoreLabel = UILabel()
var Fighter = SKSpriteNode(imageNamed: "Fighter")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
physicsWorld.contactDelegate = self
Fighter.position = CGPointMake(self.size.width / 2, self.size.height / 7)
Fighter.physicsBody = SKPhysicsBody(rectangleOfSize: Fighter.size)
Fighter.physicsBody?.affectedByGravity = false
Fighter.physicsBody?.categoryBitMask = PhysicsCatagory.Fighter
Fighter.physicsBody?.contactTestBitMask = PhysicsCatagory.Asteroids
Fighter.physicsBody?.dynamic = false
var Timer = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: Selector("spawnBullets"), userInfo: nil, repeats: true)
var asteriodsTimer = NSTimer.scheduledTimerWithTimeInterval(0.35 , target: self, selector: Selector("spawnAsteroids"), userInfo: nil, repeats: true)
var furiousAsteriodsTimer = NSTimer.scheduledTimerWithTimeInterval(0.9 , target: self, selector: Selector("spawnFuriousAsteroids"), userInfo: nil, repeats: true)
self.addChild(Fighter)
scoreLabel.text = "\(Score)"
scoreLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
scoreLabel.backgroundColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.3)
scoreLabel.textColor = UIColor.whiteColor()
self.view?.addSubview(scoreLabel)
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == PhysicsCatagory.Asteroids) && (secondBody.categoryBitMask == PhysicsCatagory.Bullet) || (firstBody.categoryBitMask == PhysicsCatagory.Bullet) && (secondBody.categoryBitMask == PhysicsCatagory.Asteroids)){
bulletHitAsteroids(firstBody.node as! SKSpriteNode, Bullet: secondBody.node as! SKSpriteNode)
}
}
func bulletHitAsteroids(Asteroids: SKSpriteNode, Bullet: SKSpriteNode){
Asteroids.removeFromParent()
Bullet.removeFromParent()
Score++
scoreLabel.text = "\(Score)"
}
func bulletHitFuriousAsteroids(FuriousAsteroids: SKSpriteNode, Bullet: SKSpriteNode){
FuriousAsteroids.removeFromParent()
Bullet.removeFromParent()
Score = Score + 2
scoreLabel.text = "\(Score)"
}
func spawnBullets(){
let Bullet = SKSpriteNode(imageNamed: "Bullet")
Bullet.zPosition = -5
Bullet.position = CGPointMake(Fighter.position.x, Fighter.position.y)
let action = SKAction.moveToY(self.size.height + 30, duration: 0.9)
let actionDone = SKAction.removeFromParent()
Bullet.runAction(SKAction.sequence([action, actionDone]))
Bullet.physicsBody = SKPhysicsBody(rectangleOfSize: Bullet.size)
Bullet.physicsBody?.categoryBitMask = PhysicsCatagory.Bullet
Bullet.physicsBody?.contactTestBitMask = PhysicsCatagory.Asteroids
Bullet.physicsBody?.affectedByGravity = false
Bullet.physicsBody?.dynamic = false
self.addChild(Bullet)
}
func spawnAsteroids(){
let Asteroids = SKSpriteNode(imageNamed: "Asteroid")
let minValue = self.size.width / 8
let maxValue = self.size.width - 20
let spawnPoint = UInt32(maxValue - minValue)
Asteroids.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)) , y: self.size.height)
Asteroids.physicsBody = SKPhysicsBody(rectangleOfSize: Asteroids.size)
Asteroids.physicsBody?.categoryBitMask = PhysicsCatagory.Asteroids
Asteroids.physicsBody?.contactTestBitMask = PhysicsCatagory.Bullet
Asteroids.physicsBody?.affectedByGravity = false
Asteroids.physicsBody?.dynamic = true
let action = SKAction.moveToY(-50, duration: 2.5)
let actionDone = SKAction.removeFromParent()
Asteroids.runAction(SKAction.sequence([action, actionDone]))
self.addChild(Asteroids)
}
func spawnFuriousAsteroids(){
let FuriousAsteroids = SKSpriteNode(imageNamed: "FuriousAsteroid")
let minValue = self.size.width / 8
let maxValue = self.size.width - 20
let spawnPoint = UInt32(maxValue - minValue)
FuriousAsteroids.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)) , y: self.size.height)
FuriousAsteroids.physicsBody = SKPhysicsBody(rectangleOfSize: FuriousAsteroids.size)
FuriousAsteroids.physicsBody?.categoryBitMask = PhysicsCatagory.Asteroids
FuriousAsteroids.physicsBody?.contactTestBitMask = PhysicsCatagory.Bullet
FuriousAsteroids.physicsBody?.affectedByGravity = false
FuriousAsteroids.physicsBody?.dynamic = true
let action = SKAction.moveToY(-50, duration: 1.5)
let actionDone = SKAction.removeFromParent()
FuriousAsteroids.runAction(SKAction.sequence([action, actionDone]))
self.addChild(FuriousAsteroids)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
Fighter.position.x = location.x
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
Fighter.position.x = location.x
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
It is crashing because one of the bodies doesn't have its node. When crash happen, try to type something like this in debugger console :
po firstBody.node, or po secondBody.node. One of these two will be nil.
This is happening when more than two bodies make contact as in your example, where you can have a situation where bullet and a two asteroids might be in a contact at the same time. Because SpriteKit can check for a contact only between two bodies at the time, something like this will happen:
didBeginContact is invoked with two physicsBodies passed in (bullet and first asteroide)
now, you are force unwrapping nodes associated with those bodies and removing the asteroid and the bullet from its parent
then, because there are more contacts occurred
didBeginContact is invoked again, with another two physics bodies, but one of those physicsBodies is bullet's physics body again. Another body is , let say furious asteroid.
now, again you are force unwrapping nodes associated with those two physics bodies, but one of them (bullet) is nil.
Solution would be something like this in your didBeginContact method:
if let firstNode = firstBody.node as? SKSpriteNode, let secondNode = secondBody.node as? SKSpriteNode {
bulletHitAsteroids(firstNode, Bullet: secondNode)
}
This way, one bullet will destroy only one asteroid. Which I guess is what you want and what make sense after all.
I want to generate an infinite amount of nodes that fall from the top of the screen and destroy them by just clicking on them, it is simple but I am having serious problems with it. When the nodes are generated by the loop I can only generate one and it starts moving instead of falling vertically. It also disappears and appears constantly.
Here is my code, hope you can help.
Thank you!!
import SpriteKit
class DestroyScene: SKScene , SKPhysicsContactDelegate{
var velocity:CGFloat = 0
let scoreText = SKLabelNode(fontNamed: "Arial Rounded MT Bold")
var score = 0
var lastYieldTimeInterval:NSTimeInterval = NSTimeInterval()
var lastUpdateTimerInterval:NSTimeInterval = NSTimeInterval()
var gameOver = false
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "circuloAzulArt")
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVectorMake(0, -velocity)
}
func addAlien(){
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "circuloAzulArt")
alien.name = "alien"
alien.physicsBody = SKPhysicsBody(circleOfRadius: alien.size.width/2)
alien.physicsBody?.dynamic = true
var actionArray:NSMutableArray = NSMutableArray()
var actionArray2:NSMutableArray = NSMutableArray()
alien.removeFromParent()
if gameOver == false{
let minX = alien.size.width/2
let maxX = self.frame.size.width - alien.size.width/2
let rangeX = maxX - minX
let position:CGFloat = CGFloat(arc4random()) % CGFloat(rangeX) + CGFloat(minX)
alien.position = CGPointMake(position, self.frame.size.height + alien.size.height)
self.addChild(alien)
let minDuration = 3
let duration = Int(minDuration)
actionArray.addObject(SKAction.moveTo(CGPointMake(position, -alien.size.height), duration: NSTimeInterval(duration)))
actionArray.addObject(SKAction.removeFromParent())
alien.runAction(SKAction.sequence(actionArray))
}
}
func updateWithTimeSinceLastUpdate(timeSinceLastUpdate:CFTimeInterval){
var randomNum = Double(arc4random_uniform(20))
var xTime = ((randomNum / 20) + 0.25)
lastYieldTimeInterval += timeSinceLastUpdate
if (lastYieldTimeInterval > xTime){
lastYieldTimeInterval = 0
randomNum = Double(arc4random_uniform(25))
addAlien()
}
}
override func update(currentTime: CFTimeInterval) {
var timeSinceLastUpdate = currentTime - lastUpdateTimerInterval
lastUpdateTimerInterval = currentTime
if score < 60{
velocity = CGFloat(score*3)
}else{
velocity = CGFloat(210)
}
if (timeSinceLastUpdate > 1){
timeSinceLastUpdate = 1/60
lastUpdateTimerInterval = currentTime
}
updateWithTimeSinceLastUpdate(timeSinceLastUpdate)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.alien{
alien.removeFromParent()
score++
}
}
}
}
Everytime you add an alien you remove the last alien with this line of code:
alien.removeFromParent()
You have to create a new variable node in the addAlien() function and give the node a name
for example:
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "alien")
alien.name = "alien"
Then you can remove the alien in the touchesBegan function by checking the node name
let node = self.nodeAtPoint(location)
if (node.name == "alien") {
}
And for the gravity you should move self.physicsWorld.gravity = CGVectorMake(0, -velocity) to the didMoveToView() function and change the last parameter to a constant