I would like to create a main menu for my game in swift.
I am using the following code:
import SpriteKit
class menuScene: SKScene {
//Adding Start Button
let startButton = SKSpriteNode(imageNamed: "playButton")
override func didMove(to view: SKView) {
//Temporary Background
backgroundColor = SKColor.darkGray
//Start Button
startButton.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(startButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self); //Finding location of touch
if atPoint(location) == startButton {
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
view!.presentScene(scene, transition: SKTransition.doorsOpenVertical(withDuration: 1))
}
}
}
}
}
When I run this however, My app crashes and highlights if atPoint(location) == startButton {. with "Thread 1, Breakpoint 1.1"
Im not entirely sure what this is but I hope someone can help. Thanks!
Custom SKViews
Let's say, like this M.W.E., you want a menu, a difficulty, and a game scene.
Then you can make a series of custom SKViews to transition between.
GameViewController
This code loads the menuScene:
override func viewDidLoad() {
super.viewDidLoad()
let menuScene = MenuScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
menuScene.scaleMode = .resizeFill
skView.presentScene(menuScene)
}
MenuScene
class MenuScene: SKScene {
let playButton = SKLabelNode()
override init(size: CGSize) {
super.init(size: size)
backgroundColor = SKColor.white
playButton.fontColor = SKColor.black
playButton.text = "play"
playButton.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(playButton)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if playButton.contains(touchLocation) {
let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
let difficultyScene = DifficultyScene(size: self.size)
self.view?.presentScene(difficultyScene, transition: reveal)
}
}
}
DifficultyScene
class DifficultyScene: SKScene {
let easyButton = SKLabelNode()
let hardButton = SKLabelNode()
let menuButton = SKLabelNode()
override init(size: CGSize) {
super.init(size: size)
backgroundColor = SKColor.white
easyButton.fontColor = SKColor.black
easyButton.text = "easy"
hardButton.fontColor = SKColor.black
hardButton.text = "hard"
menuButton.fontColor = SKColor.black
menuButton.text = "menu"
easyButton.position = CGPoint(x: size.width / 2, y: size.height / 2)
hardButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - easyButton.fontSize * 2)
menuButton.position = CGPoint(x: size.width / 4 * 3, y: size.height / 4)
addChild(easyButton)
addChild(hardButton)
addChild(menuButton)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if easyButton.contains(touchLocation) {
let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
let gameScene = GameScene(size: self.size, difficulty: easyButton.text!)
self.view?.presentScene(gameScene, transition: reveal)
}
if hardButton.contains(touchLocation) {
let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
let gameScene = GameScene(size: self.size, difficulty: hardButton.text!)
self.view?.presentScene(gameScene, transition: reveal)
}
if menuButton.contains(touchLocation){
let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
let menuScene = MenuScene(size: self.size)
self.view?.presentScene(menuScene, transition: reveal)
}
}
}
GameScene
add this to your GameScene:
init(size: CGSize, difficulty: String) {
super.init(size: size)
gameDifficulty = difficulty
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Storyboards
Alternatively, you can use Storyboards. In the M.W.E. for another S.O. question they have a basic "menu" set up.
In your case, what you would do is:
go to Main.storyboard.
on the right-hand tool bar, find view controller
drag view-controller into Main.storyboard
click on the new view-controller
click - on the right-hand tool bar - the identity inspector (looks like a business card)
change Class to GameViewController
click on view within the hierarchy on the left (under the new view controller)
click the identity inspector
change class to SKView
click on the original view controller
click on the identity inspector
change class to UIViewController
click on the view within the original UIViewController
click on identity inspector
change class to UIView
find button at the bottom of the right-hand side tool bar
drag it onto the first view
right click drag from the button to the second view
on the pop-up menu, under action segue, click show
right click drag from the button up, add horizontally center constraints
right click drag from the button to the right, add vertically center constraints
Images
Related
I'm pretty new to Swift and I'm having some trouble implementing a leaderboard into my game.
So far, I'm able to authenticate local player and set up leaderboard in iTunes Connect.
However, I'm unable to display the leaderboard itself, if I run the below code, it will abort when I click on GK Leaderboard button in SKScene. So, the question is how can I display the GK leaderboard from SKScene? Thanks!
GameViewController.swift
import SpriteKit
import GameKit
class GameViewController: UIViewController, GKGameCenterControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SceneMenu(size: view.frame.size)
scene.scaleMode = .aspectFill
scene.backgroundColor = .white
let view = view as! SKView
view.presentScene(scene)
view.showsFPS = true
view.showsNodeCount = true
authenticateLocalPlayer()
}
func authenticateLocalPlayer() {
GKLocalPlayer.local.authenticateHandler = { viewController, error in
}
}
func showLeaderboard() {
let gcVC = GKGameCenterViewController(leaderboardID: "com.generic.leaderboard", playerScope: .global, timeScope: .allTime)
gcVC.gameCenterDelegate = self
present(gcVC, animated: true)
}
func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismiss(animated: true)
}
}
SceneMenu.swift
import SpriteKit
import GameKit
class SceneMenu: SKScene {
override init(size: CGSize) {
super.init(size: size)
let btnGK = SKLabelNode(text: "GameKit")
btnGK.name = "btn_gk"
btnGK.fontSize = 20
btnGK.fontColor = SKColor.blue
btnGK.fontName = "Avenir"
btnGK.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(btnGK)
let btnLeaderboard = SKLabelNode(text: "GK Leaderboard")
btnLeaderboard.name = "btn_leaderboard"
btnLeaderboard.fontSize = 20
btnLeaderboard.fontColor = SKColor.blue
btnLeaderboard.fontName = "Avenir"
btnLeaderboard.position = CGPoint(x: size.width / 2, y: size.height / 2 - 50)
addChild(btnLeaderboard)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let action = atPoint(location)
switch action.name {
case "btn_gk":
print("btn_gk")
GKLeaderboard.submitScore(10, context: 0, player: GKLocalPlayer.local, leaderboardIDs: ["com.generic.leaderboard"]) { _ in }
case "btn_leaderboard":
print("btn_leaderboard")
GameViewController().showLeaderboard()
default:
print("nothing")
}
}
}
}
I can’t provide any code right now but what you really should be doing is using something like UIKit to create a UIView for the leader board and then embed that into a SKScene.
There is an interface something like SKView that is backed by a UIView which makes this fairly easy to do.
Keep Sprite Kit for the game stuff. Use UIKit for the UI stuff. 😃
I tried copying the template code from a SpriteKit project into a playground, but all I get is a grey screen when I present the Scene. Do I need to create an SKScene and if so how do I assign it to the scene class that I am using.
The following is my code to create and present the scene:
#objc func goToGameScene(){
print("GameView Loaded")
sceneView = SKView(frame: CGRect(x: 0, y: 0, width: 666, height: 500))
sceneView.backgroundColor = UIColor.black
PlaygroundPage.current.liveView = sceneView
if let view = self.sceneView 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
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
And this is my SKScene class, which has a filename of GameScene.swift.
import Foundation
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var bg = SKSpriteNode()
func didBegin(_ contact: SKPhysicsContact) {
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
let bgTexture = SKTexture(image: UIImage(named: "MainScreenBackground.png")!)
let moveBGAnimation = SKAction.move(by: CGVector(dx:-bgTexture.size().width, dy:0), duration: 11)
let shiftBackground = SKAction.move(by: CGVector(dx: bgTexture.size().width, dy:0), duration: 0)
let repeatAnimationBg = SKAction.repeatForever(SKAction.sequence([moveBGAnimation, shiftBackground]))
var q = 0
while(q < 3){
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: bgTexture.size().width * CGFloat(q), y: self.frame.midY)
bg.size.height = self.frame.height
bg.run(repeatAnimationBg)
self.addChild(bg)
q+=1
bg.zPosition = -1
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
}
}
Assuming you have dragged and dropped your MainScreenBackground.png image into you Assets.xcassets folder, you can use the code below to add the image to your scene as a SKSpriteNode.
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
let bgTexture = SKSpriteNode(imageNamed: "MainScreenBackground")
self.addChild(bgTexture)
...
Hello I have a SCNScene with a sprite kit overlay. I also have a subview that contains just a UISlider. The Slider controls the value of one of the SKLabel nodes in the sprite kit overlay. However when I am moving the slider the label does not update. It only updates once I touch outside of the slider. So I am guessing that the SCNScene and the overlay don't update while my touches are in the subview and only update once I touch back to the main view. How can I fix this?
Thanks!
My project is in swift, but if you only know objective-c ill still gratefully take your answer and translate on my own.
SceneKit Code:
class MyMarbleScene: UIViewController {
var scene: SCNScene!
var sceneView: SCNView!
var hud : MyMarbleSpriteKit!
var cameraNode: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
scene = SCNScene()
sceneView = SCNView()
sceneView.frame = self.view.frame
sceneView.autoresizingMask = UIViewAutoresizing.allZeros
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = false
sceneView.allowsCameraControl = false
sceneView.backgroundColor = UIColor.blackColor()
self.view = sceneView
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 20, z: 60)
hud = MyMarbleSpriteKit(size: self.view.frame.size, game: self)
sceneView.overlaySKScene = hud
let frame = CGRectMake(10.0, self.view.frame.height*0.5, 180.0, 10.0)
let slider = UISlider(frame: frame)
slider.addTarget(self, action: "sliderValueChanged:", forControlEvents: UIControlEvents.ValueChanged)
slider.backgroundColor = UIColor.clearColor()
slider.minimumValue = 1.0
slider.maximumValue = 10.0
slider.continuous = true
slider.value = 5
self.view?.addSubview(slider)
}
func sliderValueChanged(sender: UISlider) {
var currentValue = Int(sender.value)
println("\(currentValue)")
self.hud.updateLabel(currentValue)
}
}
SpriteKit code
class MyMarbleSpriteKit: SKScene {
var myScene: MyMarbleScene!
var SensLabelTwo = SKLabelNode()
override func didMoveToView(view: SKView) {
}
init(size: CGSize, game:MyMarbleScene) {
super.init(size: size)
myScene = game
let SensLabel = SKLabelNode()
SensLabel.text = "Marble Sensitivity:"
SensLabel.fontName = "AvenirNext-HeavyIta"
SensLabel.fontSize = 40
SensLabel.fontColor = UIColor.whiteColor()
SensLabel.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Top
SensLabel.position = CGPoint(x: 170, y: size.height-20)
self.addChild(SensLabel)
SensLabelTwo.text = "5"
SensLabelTwo.fontName = "AvenirNext-HeavyIta"
SensLabelTwo.fontSize = 60
SensLabelTwo.fontColor = UIColor.whiteColor()
SensLabelTwo.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Top
SensLabelTwo.position = CGPoint(x: 100, y: size.height-80)
self.addChild(SensLabelTwo)
}
func updateLabel(text: Int){
println("helllo")
SensLabelTwo.text = String(format:"%i", text)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
the 2D scene is not automatically refreshed when it's modified.
You will have to wait for the 3D scene to be redisplayed first. This happens when 3D animations or actions are running or when playing is set to YES. Alternatively, you can manually request a refresh with -[SCNView setNeedsDisplay].
This is my first project with SpriteKit and I am following THIS tutorial
But when I try to give the position to the Image as he did into that tutorial at 20:10 with this code :
playScene.swift
import SpriteKit
class playScene : SKScene {
let runningBar = SKSpriteNode(imageNamed: "bar")
override func didMoveToView(view: SKView) {
println("We are at the new scene!")
self.backgroundColor = UIColor(hex: 0x80D9FF, alpha: 1)
self.runningBar.anchorPoint = CGPointMake(0.5, 0.5)
self.runningBar.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - (self.runningBar.size.height / 2))
self.addChild(self.runningBar)
}
override func update(currentTime: NSTimeInterval) {
}
}
I want to give position at bottom of the screen but I got this Output:
But the output should be:
GameScene.swift (If needed)
import SpriteKit
class GameScene: SKScene {
let playButton = SKSpriteNode(imageNamed: "play")
override func didMoveToView(view: SKView) {
self.playButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(self.playButton)
self.backgroundColor = UIColor(hex: 0x80D9FF, alpha: 1)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches{
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.playButton{
var scene = playScene(size: size.self)
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Can anybody tell me how can I achieve this?
Thanks In advance.
You need to look at the tutorial properly. You have set the following lines wrong:
self.runningBar.anchorPoint = CGPointMake(0.5, 0.5)
self.runningBar.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - (self.runningBar.size.height / 2))
They should be:
self.runningBar.anchorPoint = CGPointMake(0, 0.5)
self.runningBar.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + (self.runningBar.size.height / 2))
For a better understanding of the coordinate system, have a look at the documentation.
Ok. this code is driving me crazy. It just don't work. The only message I received is "Attemped to add a SKNode which already has a parent". Yes I know that there has been some discussions here, but none of them give the solution I need.
This is the code. I really appreciate any help.
import SpriteKit
class MyScene: SKScene {
let intervalShapeCreation:NSTimeInterval = 2.0 // Interval for creating the next Shape
let gravitationalAcceleration:CGFloat = -0.5 // The gravitational Y acceleration
let shapeSequenceAction = SKAction.sequence([
SKAction.scaleTo(1.0, duration: 0.5),
SKAction.waitForDuration(2.0),
SKAction.scaleTo(0, duration: 0.5),
SKAction.removeFromParent()
])
override init(size: CGSize) {
super.init(size: size)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
addBackground()
initializeScene()
}
// MARK: Level Building
func initializeScene() {
self.physicsWorld.gravity = CGVectorMake(0.0, gravitationalAcceleration)
runAction(SKAction.repeatActionForever(
SKAction.sequence([SKAction.runBlock(self.createShape),
SKAction.waitForDuration(intervalShapeCreation)])))
}
func addBackground() {
let backgroundAtlas = SKTextureAtlas(named: "background")
let background = SKSpriteNode(texture: backgroundAtlas.textureNamed("background"))
background.position = CGPoint(x: size.width/2, y: size.height/2)
background.anchorPoint = CGPointMake(0.5, 0.5)
background.zPosition = -1
background.name = "background"
self.addChild(background)
}
func createShape() {
let newShape = sSharedAllPossibleShapes[0]
print("\n shape creada: \(newShape.name)")
newShape.position = CGPointMake(size.width / 2, CGFloat( Int.random(fromZeroToMax: 500)))
self.addChild(newShape)
newShape.runAction(shapeSequenceAction)
}
}
createShape doesn't actually create a SKShapeNode. It gets the first shape from the sSharedAllPossibleShapes array, then adds it as child to self. The second time you call this method that shape already has a parent and can't be added again.
You have to create a new instance of SKShapeNode. The way I see it your array here really needs to contain the CGPath objects that define the shape, not the nodes themselves because you can't reuse nodes the way you intended to.