My problem is that when I tried to move to my next Scene using self.scene?.presentScene() an error message pops up and says 'presentScene is not a member of SKScene'.
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = SKColor.blackColor()
let start = SKLabelNode(fontNamed: "Chalkduster")
start.position = CGPoint (x: self.frame.width/2-75, y: self.frame.height/2+100)
start.fontSize = 35
start.text = "Start"
addChild(start)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
let MyScene = SecondScene(size: self.size)
MyScene.scaleMode = scaleMode
self.scene?.presentScene(MyScene)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
presentScene is a method of SKView, not SKScene.
So to load a new scene you should write something like this
class GameScene: SKScene {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let myScene = SecondScene(size: self.size)
myScene.scaleMode = scaleMode
self.view?.presentScene(myScene)
}
}
Transition
You can also present the new scene with a transition effect. In this case one this method and pass a SKTransition as second param.
self.view?.presentScene(myScene, transition: SKTransition.crossFadeWithDuration(1))
In the example above I am using a Cross Fade transition having a duration of 1 second. But there are many other transitions to choose from.
Related
I am trying to implement moving to another scene when a wheel stops rotating. The code I have is shown below. I cannot figure out how to detect when the velocity has reached 0.0?
import SpriteKit
import GameplayKit
class GameplayScene: SKScene {
var player: Player?;
override func didMove(to view: SKView) {
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
So from here, I would like to execute the following when the wheel has stopped spinning:
let play_scene = Questions(fileNamed: "QuestionsScene")
play_scene?.scaleMode = .aspectFill
self.view?.presentScene(play_scene!, transition: SKTransition.doorsOpenVertical(withDuration: 1))
I have now edited the class and it looks as follows:
import SpriteKit
import GameplayKit
class GameplayScene: SKScene, SKSceneDelegate {
var player: Player?
override func didMove(to view: SKView) {
self.delegate = self
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.pinned = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func didSimulatePhysics() {
if ((player?.physicsBody?.angularVelocity)! <= CGFloat(0.01))
{
print("Got it")
}
}
}
Problem is I still receive an error on the if statement within didSimulatePhysics function. The error I receive is "Thread 1: EXC_BAD_INSTRUCTION...."
Your wheel's SKPhysicsBody has a built-in property, angularVelocity, that tells you how fast it's spinning. You're already using it when you set it to r to start the wheel spinning.
To watch angularVelocity you can use didSimulatePhysics(). It gets called once every frame, right after the physics calculations are done. That will look something like this:
func didSimulatePhysics() {
if wheelIsSpinning && angularVelocity != nil && angularVelocity! <= CGFloat(0.001) {
wheelIsSpinning = false
// wheel has stopped
// add your code here
}
}
Due to the vagaries of physics modeling, the angularVelocity might never be exactly zero. So instead we watch for it to be less than some arbitrary threshold, in this case 0.001.
You don't want it to execute every frame when the wheel isn't moving, so I added a property wheelIsSpinning to keep track. You'll need to add it as an instance property to GameplayScene, and set it to true in spin().
I am trying to create a "Space Invaders" game in swift, when the user touches the screen a bullet is shot from the ship, but when I try to touch it again while the bullet is moving across the screen I have an NSException and the game breaks. How do I set up the action so that there can be multiple instances of the action, so that the shooter is semi automatic. Below is my current scene controller.
import SpriteKit
class GameScene: SKScene {
let background = SKSpriteNode(imageNamed: "background")
let heroShip = SKSpriteNode(imageNamed: "heroShip")
let bullet = SKSpriteNode(imageNamed: "bullet")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
heroShip.position = CGPointMake(self.size.width/6.0, self.size.height/2.0)
self.heroShip.zPosition = 1.0
self.addChild(background)
self.addChild(heroShip)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
bullet.position = CGPointMake(heroShip.position.x + bullet.size.width/2, heroShip.position.y)
let action = SKAction.moveToX(self.frame.width + self.bullet.size.width, duration: 0.5)
self.addChild(bullet)
bullet.runAction(action, completion: {
self.bullet.removeAllActions()
self.bullet.removeFromParent()
})
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
If you want to have multiple bullets shot from one ship, you have to create multiple instances of a bullet. What you have now is bullet property of GameScene class which is a mistake.
You probably want to instantiate bullets dynamically in your IBAction
So try something like this:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = CGPointMake(heroShip.position.x + bullet.size.width/2, heroShip.position.y)
let action = SKAction.moveToX(self.frame.width + bullet.size.width, duration: 0.5)
self.addChild(bullet)
bullet.runAction(action, completion: {
bullet.removeAllActions()
bullet.removeFromParent()
})
}
and remove
let bullet = SKSpriteNode(imageNamed: "bullet")
from the top of the class
I'm moving a sprite by using the touchesMoved method. So far this is working fine, but the result looks a little bit unnatural. The sprite stops immediately, once the movement of my finger stops. A more natural impression would be, if the sprite continuous the movement a little time with an ease out function. Any idea how to implement this?
Here is my code:
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode?
var xOrgPosition: CGFloat = 0.0
override func didMoveToView(view: SKView) {
sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite!.xScale = 0.1
sprite!.yScale = 0.1
sprite!.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(sprite!)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let xTouchPosition = touch.locationInNode(self).x
if xOrgPosition != 0.0 {
// calculate the new position
sprite!.position = CGPoint(x: sprite!.position.x - (xOrgPosition - xTouchPosition), y: sprite!.position.y)
}
// Store the current touch position to calculate the delta in the next iteration
xOrgPosition = xTouchPosition
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Reset value for the next swipe gesture
xOrgPosition = 0
}
}
Update:
I've made a short sample project, based on the answers from hamobi
Tutorial
Git repository
i think this might be what youre going for. let me know if you have any questions :)
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode?
var xOrgPosition: CGFloat?
override func didMoveToView(view: SKView) {
sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite!.xScale = 0.1
sprite!.yScale = 0.1
sprite!.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(sprite!)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
xOrgPosition = touch.locationInNode(self).x
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
xOrgPosition = nil
}
override func update(currentTime: NSTimeInterval) {
if let xPos = xOrgPosition {
sprite!.position.x += (xPos - sprite!.position.x) * 0.1
}
}
}
I am trying to do a transition between game menu and the first level.
But instead of all Level1 (it is built by using an Level1.sks file(here I have designed a level) and Level1.swift(here I have created my "hero" etc.)) on the screen appears just hero, but no locations that was designed in Level1.sks.
How I can resolve this problem?
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (playbutton .containsPoint(location)){
runAction(SKAction.playSoundFileNamed("play.wav", waitForCompletion: false))
let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1)
let scene = Level1(size: CGSizeMake(1024, 768))
scene.scaleMode = SKSceneScaleMode.AspectFill
self.scene?.view?.presentScene(scene, transition: transition)
transition.pausesOutgoingScene = true
}
You need to unarchive the sks file into an SKScene object so that the elements from the file can be loaded into the scene.
XCode usually generates a method called unarchiveFromFile in the SKScene's .swift file.
The following code should work:
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (playbutton .containsPoint(location)){
runAction(SKAction.playSoundFileNamed("play.wav", waitForCompletion: false))
if let scene = Level1.unarchiveFromFile("GameScene") as? GameScene {
let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1)
scene.scaleMode = SKSceneScaleMode.AspectFill
self.scene?.view?.presentScene(scene, transition: transition)
transition.pausesOutgoingScene = true
}
}
}
}
I have a custom class that is an SKNode, which in turn has several SKSpriteNodes in it. Is there a way I can detect touches on these child SKSpriteNodes from my game scene?
I'm working in swift
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
if([yourSprite containsPoint: touchLocation])
{
//sprite contains touch
}
}
Source: http://www.raywenderlich.com/84434/sprite-kit-swift-tutorial-beginners
Examine Apple's SceneKitVehicle demo. Someone kindly ported it to Swift.
The code you want is in the GameView.swift file. In the GameView you'll see the touchesBegan override. Here's my version of it for Swift 2.1:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let scene = self.overlaySKScene else {
return
}
let touch = touches.first!
let viewTouchLocation = touch.locationInView(self)
let sceneTouchPoint = scene .convertPointFromView(viewTouchLocation)
let touchedNode = scene.nodeAtPoint(sceneTouchPoint)
if (touchedNode.name == "Play") {
print("play")
}
}
If it's not clear; the GameView is set as the app's view class by way of the Storyboard.
You'll need to compare the location of your Touch to the location of your SKNode
You can get the location of your touch at one of the following methods, using locationInNode():
touchesBegan()
touchesMoved()
touchesEnded()
Swift 5+
If you wish to encapsulate touch logic in a node and deal with it locally, you can simply set interaction to true in the corresponding node.
isUserInteractionEnabled = true
And then, of course, override touchesBegan to your desire.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
If you still wish to receive touches in the scene when touches occur inside a child node, you can, for instance, define a protocol and property for the child node's delegate and set the scene to be it.
e.g:
final class GameScene: SKScene {
private let childNode = ChildNode()
override func didMove(to view: SKView) {
addChild(childNode)
childNode.delegate = self
}
}
extension GameScene: TouchDelegate {}
protocol TouchDelegate {
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
}
final class ChildNode: SKSpriteNode {
var delegate: TouchDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
delegate?.touchesBegan(touches, with: event)
}
}