I have a simple game where meteors are spawned and the player must dodge these meteors. I am trying to detect if the player (rocketship) has collided with the meteor (enemy) with PhysicsBody. However, nothing happens when they collide. The console should print "Crash detected"
Here's my code:
import SpriteKit
import GameplayKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate{
let player = SKSpriteNode(imageNamed: "spaceship")
let stars = SKSpriteNode(imageNamed: "stars")
let meteor = SKSpriteNode(imageNamed: "meteor")
override func didMove(to view: SKView) {
print(frame.size.width)
print(frame.size.height)
stars.position = CGPoint(x:0, y:0)
stars.zPosition = 1
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
player.position = CGPoint(x:0, y:-320)
player.zPosition = 4
player.physicsBody = SKPhysicsBody()
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = 0
player.physicsBody?.collisionBitMask = 1
player.physicsBody?.contactTestBitMask = 1
self.addChild(player)
self.addChild(stars)
addMeteor()
}
func addMeteor() {
meteor.physicsBody = SKPhysicsBody(circleOfRadius: meteor.size.width / 2)
meteor.physicsBody?.affectedByGravity = false
meteor.setScale(0.50)
meteor.position = CGPoint(x:Int(arc4random()%300),y:Int(arc4random()%600))
//meteor.position = CGPoint(x:0 , y:0)
meteor.zPosition = 4
meteor.physicsBody?.categoryBitMask = 1
meteor.physicsBody?.collisionBitMask = 0
self.addChild(meteor)
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = player.position
bullet.setScale(0.5)
bullet.zPosition = 3
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
fireBullet()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let previousPointOfTouch = touch.previousLocation(in: self)
let amountDragged = pointOfTouch.x - previousPointOfTouch.x
player.position.x += amountDragged
}
}
func didBegin(_ contact: SKPhysicsContact) {
print("Crash detected")
}
override func update(_ currentTime: TimeInterval) {
meteor.position.y -= 6
if meteor.position.y < player.position.y - 300{
meteor.removeFromParent()
addMeteor()
}
}
}
The program should detect collision and then execute the didBegin function (which prints "Crash detected" to the console). However, when I run the program, the console doesn't have anything printed to it. Anybody know what im doing wrong? Thanks!
in didMove method add this line of code physicsWorld.contactDelegate = self. you set playerCateoryBitmask to 0. that means nothing can hit the player. use player.physicsBody?.categoryBitMask = 2 and also set your meteor a contactTestBitMask like so: meteor.physicsBody?.contactTestBitMask
Related
I'm using SpriteKit for iOS app with Swift.
I made a small SKShapeNode("ball") and a big circle path("room"), and I want a SKShapeNode to stay within the circle.
Here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var isFingerOnBall = false
var ball: SKShapeNode!
var room: SKShapeNode!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if let body = physicsWorld.body(at: touchLocation) {
if body.node!.name == "ball" {
isFingerOnBall = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if isFingerOnBall {
ball.position = touchLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isFingerOnBall = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
let radius:CGFloat = 60
// BALL
ball = SKShapeNode(circleOfRadius: radius)
ball.name = "ball"
ball.fillColor = .red
ball.strokeColor = .clear
ball.position = CGPoint(x: 0, y: 150)
ball.physicsBody = SKPhysicsBody(circleOfRadius: radius)
ball.physicsBody?.isDynamic = true
scene?.addChild(ball)
// ROOM
room = SKShapeNode(circleOfRadius: radius * 5)
room.name = "room"
room.strokeColor = .white
room.lineWidth = 10
room.position = CGPoint(x: 0, y: 0)
room.physicsBody = SKPhysicsBody(edgeLoopFrom: room.path!)
room.physicsBody?.isDynamic = false
scene?.addChild(room)
}
}
I expected the room's SKPhysicsBody would limit the ball go out beyond the path,
OK image
but when my finger drags the ball out of the circle(room), it goes out too.
No good image
Thanks in advance.
There is a few ways to do this although the simplest I can think of is to replace you didMove(to view: SKView) method with this:
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
// BALL
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .yellow
ball.strokeColor = .yellow
ball.lineWidth = 5
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
ball.physicsBody?.isDynamic = true
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = 2
ball.physicsBody?.contactTestBitMask = 0
addChild(ball)
// ROOM
let room = SKShapeNode(circleOfRadius: 200)
room.fillColor = .clear
room.strokeColor = .red
room.lineWidth = 5
room.physicsBody = SKPhysicsBody(edgeLoopFrom: room.path!)
room.physicsBody?.isDynamic = false
room.physicsBody?.affectedByGravity = false
room.physicsBody?.categoryBitMask = 2
room.physicsBody?.collisionBitMask = 0
room.physicsBody?.contactTestBitMask = 0
addChild(room)
}
Basically you just need to set the PhysicsBody CategoryBitMask and Collision Bit mask. Also don't assign a solid physics body notice the use of the "edgeFromLoop" to make the rooms physic body.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if isFingerOnBall {
let roomRadius: CGFloat = 200
let hyp = sqrt(touchLocation.x * touchLocation.x + touchLocation.y * touchLocation.y)
if abs(hyp) > roomRadius {
ball.position = ball.position
} else {
ball.position = touchLocation
}
}
}
Here you are getting the hypotenuse of the new touch point and checking to see that its greater then the radius of the room. If it is then the position of the ball remains the same, if not then update the ball position as normal.
Hope this helps and enjoy
Is there a way to use velocity to make a sprite follow your finger? I want a sprite to follow my finger but I don't want it to be drag and drop. I want it to be able to interact with other sprite with the delegate properties.
For example: I want a ball to reset when it hits a wall.
I have the code set up to reset the ball. When the ball hits the wall, it doesn't reset. I put in a print statement to see if its registering the collisions. When the ball hits the wall, it prints the statement.
Thats a problem with how I'm making the ball move, right?
Or could there be an error with my code.
Code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKSpriteNode()
var danger1 = SKSpriteNode()
var danger2 = SKSpriteNode()
var goal = SKSpriteNode()
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
ball = self.childNode(withName: "ball") as! SKSpriteNode
danger1 = self.childNode(withName: "danger1") as! SKSpriteNode
danger2 = self.childNode(withName: "danger2") as! SKSpriteNode
goal = self.childNode(withName: "goal") as! SKSpriteNode
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 0
danger1.physicsBody = SKPhysicsBody(rectangleOf: danger1.size)
danger1.physicsBody?.categoryBitMask = PhysicsCategories.dangerCategory
danger1.physicsBody?.isDynamic = false
danger2.physicsBody = SKPhysicsBody(rectangleOf: danger2.size)
danger2.physicsBody?.categoryBitMask = PhysicsCategories.dangerCategory
danger2.physicsBody?.isDynamic = false
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
ball.physicsBody?.categoryBitMask = PhysicsCategories.ballCategory
ball.physicsBody?.contactTestBitMask = PhysicsCategories.dangerCategory | PhysicsCategories.goalCategory
ball.physicsBody?.collisionBitMask = PhysicsCategories.none
ball.physicsBody?.isDynamic = true
ball.physicsBody!.affectedByGravity = false
goal.physicsBody = SKPhysicsBody(rectangleOf: goal.size)
goal.physicsBody?.categoryBitMask = PhysicsCategories.goalCategory
goal.physicsBody?.isDynamic = false
setupPhysics()
startGame()
}
func setupPhysics() {
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
physicsWorld.contactDelegate = self
}
func startGame() {
ball.position = CGPoint(x: 0, y: 550)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
ball.position.x = location.x
ball.position.y = location.y
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if contactMask == PhysicsCategories.ballCategory | PhysicsCategories.dangerCategory {
print("Contact")
} else if contactMask == PhysicsCategories.ballCategory | PhysicsCategories.goalCategory {
print("goal contact")
}
}
}
As you mentioned, you should use velocity instead of position.
You have to apply the difference of the positions. In order to make it a bit smoother, you should also apply weight to your velocity.
This has not been tested and looks more like pseudo code, but that's what you should do basically.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let destination = touch.location(in: self)
let position = ball.position
let direction = destination - position // the vector of the difference
let weight = 0.95 // 0.0 to 1.0
ball.velocity = CGVector(direction * weight) // multiply the vector by the weight
}
}
You should do this in a loop that runs every frame and use a flag to see if there's a finger touching the screen, because it doesn't apply velocity when you touch the screen but don't move your finger.
I am building a spritekit game and have just started the project. I have a circle on the screen which starts in the center, and when i drag my finger from the circle outward, it will show a dotted line/bezierpath connected to the ball which will help the user see where it is aiming the ball. When the user lifts their finger, the ball will shoot in the opposite direction of the aim line. (Think a game like soccer stars or pool). The issue is that the maneuver works the first time when everything starts in the middle: I drag my finger and the ball shoots in opposite direction then stops. But when I try it again, the position of the aiming line says it is the same as the ball (It should be), but then it shows up like an inch away from the ball on the screen. I feel like this may be an issue that the scene(s) behind the objects may not be the same size? But I'm confused because I think I'm only using one scene.
GameViewController viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.size = view.bounds.size
//scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
GameScene Code (Doubt you need all of it but whatever):
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 35)
var touchingBall = false
var aimLine = SKShapeNode()
var startAimPoint = CGPoint()
var endAimPoint = CGPoint()
let damping:CGFloat = 0.94
override func didMove(to view: SKView) {
ball.fillColor = SKColor.orange
ball.name = "ball"
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
var physicsBody = SKPhysicsBody(circleOfRadius: 35)
ball.physicsBody = physicsBody
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.friction = 10.0
ball.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOUCHES BEGAN.")
for touch in touches {
print("TB: \(touchingBall)")
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "ball" {
// touched inside node
if ball.physicsBody!.angularVelocity <= 0.0{
touchingBall = true
startAimPoint = ball.position
print(touchingBall)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOCUHES MOVED.")
for touch in touches {
let location = touch.location(in: self)
if touchingBall{
endAimPoint = location
assignAimLine(start: startAimPoint, end: endAimPoint)
print("Moving touched ball")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches ended. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x - startAimPoint.x) * 3, dy: -(endAimPoint.y - startAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches cancelled. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x - startAimPoint.x) * 3, dy: -(endAimPoint.y - startAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
print(ball.physicsBody!.velocity)
let dx2 = ball.physicsBody!.velocity.dx * damping
let dy2 = ball.physicsBody!.velocity.dy * damping
ball.physicsBody!.velocity = CGVector(dx: dx2, dy: dy2)
}
func assignAimLine(start: CGPoint, end: CGPoint){
aimLine.removeFromParent()
var bezierPath = UIBezierPath()
bezierPath.move(to: start)
bezierPath.addLine(to: shortenedEnd(startPoint: start, endPoint: end))
var pattern : [CGFloat] = [10.0, 10.0]
let dashed = SKShapeNode(path: bezierPath.cgPath.copy(dashingWithPhase: 2, lengths: pattern))
aimLine = dashed
aimLine.position = ball.position
aimLine.zPosition = 0
self.addChild(aimLine)
}
func hypotenuse(bp: UIBezierPath) -> Double{
var a2 = bp.cgPath.boundingBox.height * bp.cgPath.boundingBox.height
var b2 = bp.cgPath.boundingBox.width * bp.cgPath.boundingBox.width
return Double(sqrt(a2 + b2))
}
func hypotenuse(startP: CGPoint, endP: CGPoint) -> Double{
var bezierPath = UIBezierPath()
bezierPath.move(to: startP)
bezierPath.addLine(to: endP)
return hypotenuse(bp: bezierPath)
}
func shortenedEnd(startPoint: CGPoint, endPoint: CGPoint) -> CGPoint{
var endTemp = endPoint
//while hypotenuse(startP: startPoint, endP: endTemp) > 150{
endTemp = CGPoint(x: endTemp.x / 1.01, y: endTemp.y / 1.01)
//}
return endTemp
}
func addTestPoint(loc: CGPoint, color: UIColor){
var temp = SKShapeNode(circleOfRadius: 45)
temp.fillColor = color
temp.position = loc
self.addChild(temp)
}
}
I tried printing the frame size for the scene and it says 400 something x 700 something (I am testing on iPhone 6 Plus), and it says the UIScreen is same size so i don't know what issue is. Overall, I just need the aiming line to be on the center of the circle more than just the first time I try the maneuver. Thanks.
Like I mentioned in the comments, your problem was how you were laying out your paths. The code below makes the path relative to the ball instead of absolute to the scene. I also fixed the issue with creating new shapes every time.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 35)
var touchingBall = false
var aimLine = SKShapeNode()
var endAimPoint = CGPoint()
override func didMove(to view: SKView) {
ball.fillColor = SKColor.orange
ball.name = "ball"
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
ball.position = CGPoint(x: frame.midX, y: frame.midY)
let physicsBody = SKPhysicsBody(circleOfRadius: 35)
physicsBody.affectedByGravity = false
physicsBody.friction = 10.0
physicsBody.linearDamping = 0.94
ball.physicsBody = physicsBody
self.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOUCHES BEGAN.")
for touch in touches {
print("TB: \(touchingBall)")
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "ball" {
// touched inside node
if ball.physicsBody!.angularVelocity <= 0.0{
touchingBall = true
aimLine.path = nil
self.addChild(aimLine)
print(touchingBall)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOCUHES MOVED.")
for touch in touches {
let location = touch.location(in: self)
if touchingBall{
endAimPoint = self.convert(location, to: ball)
assignAimLine(end: endAimPoint)
print("Moving touched ball")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches ended. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x) * 3, dy: -(endAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches cancelled. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x) * 3, dy: -(endAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
print(ball.physicsBody!.velocity)
//let dx2 = ball.physicsBody!.velocity.dx * damping
//let dy2 = ball.physicsBody!.velocity.dy * damping
//ball.physicsBody!.velocity = CGVector(dx: dx2, dy: dy2)
}
func assignAimLine(end: CGPoint){
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint.zero)
bezierPath.addLine(to: end)
let pattern : [CGFloat] = [10.0, 10.0]
aimLine.position = ball.position
aimLine.path = bezierPath.cgPath.copy(dashingWithPhase: 2, lengths: pattern)
aimLine.zPosition = 0
}
func addTestPoint(loc: CGPoint, color: UIColor){
var temp = SKShapeNode(circleOfRadius: 45)
temp.fillColor = color
temp.position = loc
self.addChild(temp)
}
}
I have a SpriteKit game and I am using a SKAction to move my meteor (the meteors are the enemies) all the way to the end of the screen where the player (the player is at the bottom of the screen, and they can only move on the x-axis to dodge meteors not the y) must dodge these meteors. Right I'm only trying to get one meteor on the screen at a time which moves towards the direction of the player using SKAction. There should only be on meteor at a time on the screen, but I get the error 'Attempted to add a SKNode which already has a parent'. My SKAction adds the meteor to the screen but then deletes it after the meteor has moved to its destination which is all then way to the bottom of the screen. So why am I getting this error and how can I fix it?
NOTE: The SKAction removes the meteor from the screen by using meteor.removeFromParent()
Here is my code:
import SpriteKit
import GameplayKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate{
let player = SKSpriteNode(imageNamed: "spaceship")
let stars = SKSpriteNode(imageNamed: "stars")
let meteor = SKSpriteNode(imageNamed: "meteor")
var scoreLabel = SKLabelNode(fontNamed: "AmericanTypewriter-Bold")
var score:Int = 0
var playerLost:Bool = false
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
print(frame.size.width)
print(frame.size.height)
stars.position = CGPoint(x:0, y:0)
stars.zPosition = 1
player.position = CGPoint(x:0, y:-320)
player.zPosition = 4
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = 2
player.physicsBody?.collisionBitMask = 2
player.physicsBody?.contactTestBitMask = 1
self.addChild(player)
self.addChild(stars)
self.addMeteor()
self.setLabel()
//spawnEnemySKAction()
}
func spawnEnemySKAction() {
let spawn = SKAction.run(addMeteor)
let waitToSpawn = SKAction.wait(forDuration: 1)
let spawnSequence = SKAction.sequence([spawn, waitToSpawn])
let spawnForever = SKAction.repeatForever(spawnSequence)
self.run(spawnForever)
}
func didBegin(_ contact: SKPhysicsContact) {
gameOver()
}
func addMeteor() {
meteor.physicsBody = SKPhysicsBody(texture: meteor.texture!, size: meteor.size)
meteor.physicsBody?.affectedByGravity = false
meteor.setScale(0.50)
meteor.position = CGPoint(x:Int(arc4random()%300),y:Int(arc4random()%600))
//meteor.position = CGPoint(x:0 , y:0)
meteor.zPosition = 4
let moveMeteor = SKAction.moveTo(y: player.position.y - 300, duration: 1.5)
let deleteMeteor = SKAction.removeFromParent()
let meteorSequence = SKAction.sequence([moveMeteor, deleteMeteor])
meteor.run(meteorSequence)
meteor.physicsBody?.categoryBitMask = 1
meteor.physicsBody?.collisionBitMask = 2
meteor.physicsBody?.contactTestBitMask = 2
self.addChild(meteor)
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = player.position
bullet.setScale(0.5)
bullet.zPosition = 3
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
func setLabel() {
scoreLabel.text = "Score: " + String(score)
scoreLabel.position = CGPoint(x: 290, y: 590)
scoreLabel.zPosition = 20
}
func gameOver() {
playerLost = true
var gameOverLabel: SKLabelNode!
gameOverLabel = SKLabelNode(fontNamed: "AmericanTypewriter-Bold")
gameOverLabel.text = "Game Over! You lost! Your score was: " + String(score)
gameOverLabel.zPosition = 10
gameOverLabel.position = CGPoint(x: 0, y:0)
self.addChild(gameOverLabel)
player.removeFromParent()
meteor.removeFromParent()
stars.removeFromParent()
scoreLabel.removeFromParent()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if playerLost == false {
fireBullet()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let previousPointOfTouch = touch.previousLocation(in: self)
let amountDragged = pointOfTouch.x - previousPointOfTouch.x
player.position.x += amountDragged
}
}
override func update(_ currentTime: TimeInterval) {
addMeteor()
if meteor.position.y < player.position.y - 300 && playerLost == false {
meteor.removeFromParent()
addMeteor()
if playerLost == false {
score += 1
}
scoreLabel.removeFromParent()
scoreLabel.text = "Score: " + String(score)
self.addChild(scoreLabel)
}
}
}
Thanks for taking the time to look at this question!
I simply had to remove the self.addChild(meteor) in the update function
I am making a game in Xcode with Swift 4, SpriteKit. My crash detection is working, however when I try to make text appear onto the screen when the player crashes, the text doesn't appear. My game basically has the use control a rocket ship which must dodge meteors, the player also has the ability to fire bullets. I have detected when the rocket ship hits a meteor but cannot get text to appear onto the screen. I know for a fact that the crash detection is working, because whenever I add a print statement in the didBegin function, the print statement executes. So it must be that way im displaying the label, but im not sure what I'm doing wrong with the label.
Here's my code:
import SpriteKit
import GameplayKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate{
let player = SKSpriteNode(imageNamed: "spaceship")
let stars = SKSpriteNode(imageNamed: "stars")
let meteor = SKSpriteNode(imageNamed: "meteor")
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
print(frame.size.width)
print(frame.size.height)
stars.position = CGPoint(x:0, y:0)
stars.zPosition = 1
player.position = CGPoint(x:0, y:-320)
player.zPosition = 4
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.isDynamic = false
player.physicsBody?.categoryBitMask = 2
player.physicsBody?.collisionBitMask = 1
player.physicsBody?.contactTestBitMask = 1
self.addChild(player)
self.addChild(stars)
self.addMeteor()
}
func didBegin(_ contact: SKPhysicsContact) {
gameOver()
}
func addMeteor() {
meteor.physicsBody = SKPhysicsBody(circleOfRadius: meteor.size.width / 2)
meteor.physicsBody?.affectedByGravity = false
meteor.setScale(0.50)
meteor.position = CGPoint(x:Int(arc4random()%300),y:Int(arc4random()%600))
//meteor.position = CGPoint(x:0 , y:0)
meteor.zPosition = 4
meteor.physicsBody?.categoryBitMask = 1
meteor.physicsBody?.collisionBitMask = 0
meteor.physicsBody?.contactTestBitMask = 2
self.addChild(meteor)
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = player.position
bullet.setScale(0.5)
bullet.zPosition = 3
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
func gameOver() {
print("Game Over!")
var gameOverLabel: SKLabelNode!
gameOverLabel = SKLabelNode(fontNamed: "Chalkduster")
gameOverLabel.text = "Game Over! You lost!"
gameOverLabel.horizontalAlignmentMode = .right
gameOverLabel.position = CGPoint(x: 0, y:0)
self.addChild(gameOverLabel)
print("Label added")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
fireBullet()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let previousPointOfTouch = touch.previousLocation(in: self)
let amountDragged = pointOfTouch.x - previousPointOfTouch.x
player.position.x += amountDragged
}
}
override func update(_ currentTime: TimeInterval) {
meteor.position.y -= 6
if meteor.position.y < player.position.y - 300{
meteor.removeFromParent()
addMeteor()
}
}
}
Not sure what I'm doing wrong? I call the gameOver() function in didBegin which is supposed to be executed every time collision is detected. But nothing happens? The meteor simply goes past the rocket ship. If someone could help me that'd be awesome!
As i see you are trying to center the label on the screen, you may try to add this to your code, on gameover func:
1.Try change the position to this
gameOverLabel.position = CGPoint(x: (self.scene!.frame.width / 2) - (self.scene!.frame.width * self.scene!.anchorPoint.x) / 2, y:(self.scene!.frame.height / 2) - (self.scene!.frame.height * self.scene!.anchorPoint.y))
2.I don't know if you have something that is over the label and affecting its view, so try to:
gameOverLabel.zPosition = 10 //A number higher than any other zPosition
3.The label may be too small...try:
gameOverLabel.fontSize = 30 //Adjust it to what you want
4.Just a tip:
move the:
var gameOverLabel: SKLabelNode!
to next line after the
let meteor = SKSpriteNode(imageNamed: "meteor")
This way you will have a universal label, and do not have to regenerate the label every time.
If you do this, just a final tip...I don't know if you have a func like gameRestart() or something like this...but make sure that when you restart the game, you remove the label:
gameOverLabel.removeFromParent()
If you don't do this, when the app try to add the label again it will crash!
Hope it helps!
You are not moving any of your objects as far as your physics in concerned. You need to apply forces instead of manually moving a position. Also your player dynamic is set to false, which means that he never moves, so contacts only happen on the meteor end.