How to use the SpriteKit method body(at : CGPoint)? - swift

I have game that during the game your finger moves around and should avoid hitting some obstacles. I know how to use collision but I recently heard that there is a funtion called the body(at : point).
I should say that I have masked my obstacles. Because of that I can't use the contains(point) function.
I really want to use the body(at : point) function otherwise I know how to work with collisions.
Some of my code:
play_button = self.childNode(withName: "play_button") as! SKSpriteNode
for t in touches{
let fingerlocation = t.location(in: self)
if physicsworld.body(at : fingerlocation)
{
let so = level6(fileNamed:"level6")
so?.scaleMode = .aspectFill
self.view?.presentScene(so!, transition:
SKTransition.crossFade(withDuration: 1))
}
}
but it does not work.

physicsworld.body returns an SKPhysicsBody if found, so you just need to check if it exists
if let _ = physicsworld.body(at : fingerlocation)
If your goal is to make a button, then I would not do this approach.
I would recommend just subclassing SKSpriteNode and enabling the isUserInteractionEnable`
class Button : SKSpriteNode
{
override init()
{
super.init()
isUserInteractionEnabled = true
}
//then in your touchesBegan/touchesEnded code (wherever you placed it)
....
let so = level6(fileNamed:"level6")
so?.scaleMode = .aspectFill
scene.view?.presentScene(so!, transition:
SKTransition.crossFade(withDuration: 1))
....
}

I've had a similar problem, "if let body = physicsWorld.body(at: touchLocation){...}" was responding wherever I've touched the screen.
I figured out, that by default the SKScene object created its own SKPhysicsBody which was set on top of all other SKPhysicsBodies.
I solved this problem by making a new SKPhysicsBody for the scene, which didn't interfere anymore. Hope this code will help you.
inside of didMove:
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody

Related

How can I handle a touch as a consequence of a previous touch? (Swift, SpriteKit)

This is a chess game in Swift using SpriteKit.
What I would like to happen is for the touched piece to move to the square touched on the next touch. When I run, the piece does not move when I try this. I know for certain that my code works up to and including 'case: "p"', and that the sprite is detecting the first touch.
In the image I've tried to implement this second touch using 'touch2', is this the correct thing to do? Thanks.
This is how I'm detecting the first touch and identifying the piece which has been touched. touchedPiece is the identified piece. moveset is an attribute determining the possible moves.
for touch in touches
{
let location = touch.location(in:self)
let firstTouchedNode = atPoint(location)
let touchedPiece:SKSpriteNode = (firstTouchedNode as? SKSpriteNode)!
switch touchedPiece.moveset {
case "p":
let locationToMove = touch.location(in:self)
let moveto = findpawnmoves(piece: touchedPiece)
spawnMoves(arr: moveto)
And here is the attempt to move the piece after locationToMove has been decided.
if moveto[0].contains(locationToMove){
move(piece: touchedPiece, location: moveto[0].position)
whiteMoved = true
deleteMoves(arr: moveto)
This is an example of how I am creating an array of possible squares to move to. I'll use the knight moves because it's the most concise.
func createMoveNode(piece: SKSpriteNode, up:Int, across:Int) -> SKSpriteNode{
let moveNode = SKSpriteNode(color: .clear, size: CGSize(width: CGFloat(cellsize), height: CGFloat(cellsize)))
moveNode.anchorPoint = CGPoint(x:0.5, y:0.5)
moveNode.position = CGPoint(x: piece.position.x + CGFloat(Double(across)*d), y: piece.position.y + CGFloat(Double(up)*d))
return moveNode
}
func findknightmoves(piece: SKSpriteNode) -> Array<SKSpriteNode>{
let move1 = createMoveNode(piece: piece, up:2, across:-1)
let move2 = createMoveNode(piece: piece, up:2, across:1)
let move3 = createMoveNode(piece: piece, up:-2, across:-1)
let move4 = createMoveNode(piece: piece, up:-2, across:1)
return [move1,move2,move3,move4]
}
func move(piece: SKSpriteNode, location: CGPoint){
piece.position = location
}
func spawnMoves(arr: Array<SKSpriteNode>){
for i in 0...arr.count-1{
arr[i].zPosition = 1
self.addChild(arr[i])
}
}
func deleteMoves(arr: Array<SKSpriteNode>){
for i in 0...arr.count-1{
arr[i].removeFromParent()
}
}

SKSpriteNode physics body created from texture results in nil value

I'm currently working on a small iOS game. In its current iteration, 20 targets spawn and move across the screen space-invaders style, and you control a little ship to shoot and destroy them. The code for my targets, the player ship's bullets, and a simple collision detection function I've written in the interim are as follows:
class Red_Target: SKSpriteNode{
var game_scene: GameScene!
private var ship_texture: SKTexture!
convenience init(scale: CGFloat, game_world: GameScene){
self.init(texture: SKTexture(imageNamed: "Proto Target"))
self.ship_texture = SKTexture(imageNamed: "Proto Target")
self.setScale(scale)
game_scene = game_world
game_scene.addChild(self)
self.position = CGPoint(x: game_scene.view!.bounds.width/10, y: 9 * game_scene.view!.bounds.height/10)
//self.physicsBody = SKPhysicsBody(texture: ship_texture, size: self.size)
self.physicsBody = SKPhysicsBody(circleOfRadius: 13)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.categoryBitMask = CollisionType.Enemy.rawValue
self.physicsBody!.contactTestBitMask = CollisionType.Player_Bullet.rawValue
}
func move() {
self.run(space_invaders(scene: game_scene))
}
}
class PC_Bullet: SKSpriteNode{
convenience init(scale: CGFloat){
self.init(imageNamed: "Goodbullet")
self.setScale(scale)
self.physicsBody = SKPhysicsBody(circleOfRadius: 3)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.categoryBitMask = CollisionType.Player_Bullet.rawValue
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.contactTestBitMask = CollisionType.Enemy.rawValue
}
}
func didBegin(_ contact: SKPhysicsContact) {
contact.bodyA.node!.removeFromParent()
contact.bodyB.node!.removeFromParent()
}
}
This code, in its current iteration, works just fine. However, if the line defining the target's physicsbody as its texture is uncommented and the line defining physicsbody as circleOfRadius is removed, the game will consistently crash after the 5th target is destroyed, claiming that didBegin unwraps a nil value. Why does this only happen from physics bodies with textures? Is there any way I could change the code for this to work? I would love to be able to use the physics body from texture function later on, when working with more irregular shapes.
You are pulling a classic nooby mistake. You are removing your bodies too early. If you browse Stackoverflow you will find a plethera of ways to solve it.
The basic idea is do not remove your sprites until the end of the physics phase because your 1 sprite could have multiple contact points to handle. So come up with a way to flag sprites that need to be deleted, and remove them during the didSimulatePhysics function.

Change SKScene using presentScene()

In my SpriteKit Game i'm using:
self.scene!.removeFromParent()
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
var scene: PlayScene!
scene = PlayScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
skView.presentScene(scene, transition: SKTransition.fadeWithColor(SKColor(red: 25.0/255.0, green: 55.0/255.0, blue: 12.0/255.0, alpha: 1), duration: 1.0))
to move from one scene to another. But how can I go back to the original scene? Using the same principle of code always led to a major crash..
I made an example where global structure is used to track the info about previousScene. It can be done with a custom property as well, or by using userData which every node has. The logic is the same. Also, I've removed debugging code (debug label code etc.) because it is not important for everything to work.
Example might be better if I added a few buttons where each links to the certain scene, but I left just one button to keep everything short as possible.
What you need to know about this example (you will change this rules according to your game, but the logic is the same - set the previousScene before an actual transition):
there are three scenes, WelcomeScene (default one), MenuScene and a GameScene.
tapping on the black button takes you to the GameScene. There is an exception to this rule when current scene is a GameScene. In that case, transition will take you to the previousScene.
tapping anywhere around the black button will take you to the previous scene. There is an exception to this rule when WelcomeScene is loaded for the first time (previousScene is not set) and a transition will take you to the MenuScene in that case.
-in your GameViewController you should set up a WelcomeScene to be a default one. Otherwise, you should change a code a bit to handle situations what happening when previousScene is not set (like I did in touchesBegan of WelcomeScene).
So those are rules I've made, just in order to make all those transitions a bit more meaningful...
Here is the code (BaseScene.swift):
import SpriteKit
enum SceneType: Int {
case WelcomeScene = 0
case MenuScene //1
case GameScene //2
}
struct GlobalData
{
static var previousScene:SceneType?
//Other global data...
}
class BaseScene:SKScene {
let button = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: 50, height: 50))
override func didMoveToView(view: SKView) {
setupButton()
}
private func setupButton(){
if (button.parent == nil){
//Just setup button properties like position, zPosition and name
button.name = "goToGameScene"
button.zPosition = 1
button.position = CGPoint(x: CGRectGetMidX(frame), y: 100)
addChild(button)
}
}
func goToScene(newScene: SceneType){
var sceneToLoad:SKScene?
switch newScene {
case SceneType.GameScene:
sceneToLoad = GameScene(fileNamed: "GameScene")
case SceneType.MenuScene:
sceneToLoad = MenuScene(fileNamed: "MenuScene")
case SceneType.WelcomeScene:
sceneToLoad = WelcomeScene(fileNamed:"WelcomeScene")
}
if let scene = sceneToLoad {
scene.size = size
scene.scaleMode = scaleMode
let transition = SKTransition.fadeWithDuration(3)
self.view?.presentScene(scene, transition: transition)
}
}
}
Every scene (WelcomeScene, MenuScene, GameScene) inherits from a BaseScene class (which is subclass of a SKScene). I guess, there is no need to explain that, but feel free to ask if something confuses you. The important method here (which is used by every subclass) is goToScene(scene:SceneType) and its parameter (of type SceneType) which tells us what type of scene a method should load.
SceneType is just an enum which holds integers...So actually we are not working with objects here, thus there is no fear of strong reference cycles.
Next, there are other scenes (WelcomeScene.swift):
import SpriteKit
class WelcomeScene:BaseScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
self.backgroundColor = SKColor.darkGrayColor()
}
deinit {print ("WelcomeScene deinited")}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
if let location = touch?.locationInNode(self){
//Give a priority to a button - if button is tapped go to GameScene
let node = nodeAtPoint(location)
if node.name == "goToGameScene"{
GlobalData.previousScene = SceneType.MenuScene
goToScene(SceneType.GameScene)
}else{
//Otherwise, do a transition to the previous scene
//Get the previous scene
if let previousScene = GlobalData.previousScene {
GlobalData.previousScene = SceneType.WelcomeScene
goToScene(previousScene)
}else{
// There is no previousScene set yet? Go to MenuScene then...
GlobalData.previousScene = SceneType.WelcomeScene
goToScene(SceneType.MenuScene)
}
}
}
}
}
To keep short as possible, everything is commented. Next code (MenuScene.swift):
import SpriteKit
class MenuScene: BaseScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
backgroundColor = SKColor.purpleColor()
}
deinit {
print ("MenuScene deinited") //If this method isn't called, you might have problems with strong reference cycles.
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
if let location = touch?.locationInNode(self){
//Give a priority to a button - if button is tapped go to GameScene
let node = nodeAtPoint(location)
if node.name == "goToGameScene"{
GlobalData.previousScene = SceneType.MenuScene
goToScene(SceneType.GameScene)
}else{
//Otherwise, do a transition to the previous scene
//Get the previous scene
if let previousScene = GlobalData.previousScene {
GlobalData.previousScene = SceneType.MenuScene
goToScene(previousScene)
}
}
}
}
}
And for the end (GameScene.swift):
import SpriteKit
class GameScene: BaseScene{
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
self.backgroundColor = SKColor.orangeColor()
}
deinit {print ("GameScene deinited")}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Here, we ignore black button because we don't want to transition to the same scene
if let previousScene = GlobalData.previousScene {
GlobalData.previousScene = SceneType.GameScene
goToScene(previousScene)
}
}
}
Preview:
Just read again the rules from the beginning and you will be fine (eg. in GameScene black button doesn't work, or on first launch previousScene is not set , so you will be transitioned to the MenuScene by default).
That would be it. Hope this helps a bit. You can copy and paste the code to test it and improve it to your needs. Still, not sure that you really need this. It looks that you just need to correctly transition between scenes.
HINT: What is important here is that every scene BaseScene, WelcomeScene... has it own .sks file. You create those from File->New->File->Resource and name it appropriately (like BaseClass.sks, WelcomeScene.sks...) Also, it is your job to maintain the state of GlobalData.previousScene variable (eg. set it before the transition is made).
You would need to create a property in your new scene that stores the previous one, something like previousScene. Then you can set it like this: scene.previousScene = self.scene. In you new scene, you can now go back to the previous scene with skView.presentScene(previousScene)
And I'd advise against naming the new scene you are going to present scene because your current scene is also named scene, so if you accidentally forget the self in self.scene then that may cause a lot of confusion. I'd name it something like newScene or sceneToPresent.
Also, your first line, self.scene!.removeFromParent(), isn't necessary. You don't need to remove the current scene before presenting a new one.

SKSpriteNode won't position after didBeginContact

I'm working with collision detection with SpriteKit and Swift. I have a SKScene that responds to a collision with the didBeginContact function:
func didBeginContact(contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == ColliderType.Food && contact.bodyB.categoryBitMask == ColliderType.Head) {
placeFoodInRandomGridLocation()
}
}
func placeFoodInRandomGridLocation() {
let randomX = arc4random_uniform(UInt32(myGrid.columnCount))
let randomY = arc4random_uniform(UInt32(myGrid.rowCount))
foodSpriteHolder.position = CGPoint(x: colLines[Int(randomX)], y: rowLines[Int(randomY)])
}
The problem is that I can easily adjust the position of the foodSpriteHolder before this didBeginContact function fires. It simply will not move the foodSpriteHolder when the placeFoodInRandomGridLocation is called.
It seems like a scope issue. I'm just not sure how to isolate why the position won't update. I can even make the foodSpriteHolder visibility hidden in this flow...So, I know i can access it.
For reference here is how the physics body is setup for the Food class item that is within the foodSpriteHolder:
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size, center: CGPointMake(reducedSize/2, reducedSize/2))
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Food
Lastly the placeFoodInRandomGridLocation function definitely gets called...The position just won't update.
Thanks for any ideas,
Josh
The answer in this case was to remove the node when the collision is detected:
contact.bodyA.node?.removeFromParent()
Now I can create a new node in a new position. Since affectedByGravity is set to false the node wouldn't update its position.

Changing view with SKView?

I want to know how to change to a scene when the character collides with the enemy at game over. I have made a scene under main.storyboard and I want to know how to hook it up through code, I only know how to hook it up using buttons but thats not what I am looking for as you would not press a button when you die to take you to the game over scene.
UPDATE:
func gameOver() {
gameDelegate?.gameDelegateGameOver(score)
let gameOverScene: GameOverScene = GameOverScene(size: self.size)
self.view!.presentScene(gameOverScene, transition: SKTransition.fadeWithDuration(0.0))
Thats what I have for my gameOver when collision is detected. Yes it does take me to a new scene but not the scene I made in main.storyboard.
What you could do is to create a collision boolean and if it's true (hence, something has collided), you can present a new scene with view.presentScene(YOUR_SCENE, SK_ANIMATION) in the update function.
EDIT:
I've found the tutorial from where you got the code (or at least I assume you did) and got it working with the following:
In "didMoveView" add:
player.physicsBody?.categoryBitMask = PhysicsCategory.Player
player.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
player.physicsBody?.collisionBitMask = PhysicsCategory.None
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.dynamic = true
(PhysicsCategory.Player is just a value I added in the PhysicsCategory)
Then, in the function where you add the enemy sprites you have to add in order to make the two collide:
monster.physicsBody?.contactTestBitMask = PhysicsCategory.Player
Last but not least, you have to add the following code to add "an action" to the collision the didBeginContact function:
if ((secondBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
(firstBody.categoryBitMask & PhysicsCategory.Player != 0)) {
let gameOverScene = GameOverScene(size: self.size, won: false)
view?.presentScene(gameOverScene, transition: SKTransition.flipHorizontalWithDuration(0.5))
}
Hope it's working for you now!
If you want to present a storyboard scene, you need to use something like
let gameOverScene = self.storyboard!.instantiateViewControllerWithIdentifier("GameOverViewController") as! GameOverViewController
self.view!.presentScene(gameOverScene, transition: SKTransition.fadeWithDuration(0.0))