Does anybody know why this won't work anymore in Xcode 7 GM?
I have debugged and verified, but even though the nodes are appearing on the scene, they no longer pass touch events to parent nodes.
Below is a fully working example. If you replace the default GameScene from the "New Sprite Kit Game" you can reproduce it yourself.
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let card = Card(imageNamed: "card_background_blank")
card.name = "Card"
let stack = Stack(imageNamed: "stack_background")
stack.name = "Stack"
addChild(stack)
stack.addChild(card)
card.position = CGPointMake(100,100)
stack.zPosition = 1
card.zPosition = 1
stack.position = CGPointMake(506,428)
card.userInteractionEnabled = false
stack.userInteractionEnabled = true
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Touch")
moveAllCardsAndReturn()
}
func moveAllCardsAndReturn () {
var wait:NSTimeInterval = 1.0
let waitIncrement:NSTimeInterval = 0.05
let duration:NSTimeInterval = 0.15
func removeAndMove(card:Card) {
let oldParent = card.parent!
card.removeFromParent()
addChild(card)
card.position = CGPointMake(500,48)
var sequence = [SKAction]()
sequence.append(SKAction.waitForDuration(wait))
sequence.append(SKAction.runBlock({
card.removeFromParent()
oldParent.addChild(card)
card.position = CGPointMake(100,100)
}))
card.runAction(SKAction.sequence(sequence))
wait += waitIncrement
}
let stack = childNodeWithName("Stack")
let card = stack?.childNodeWithName("Card")
removeAndMove(card as! Card)
}
}
class Stack:SKSpriteNode {
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Stack Touch")
}
}
class Card:SKSpriteNode {
}
What happens is the scene works correctly, but touches no longer pass to the parent node, even though userInteractionEnabled is false for the card and true for the parent node of the card (a Stack:SKSpriteNode)
So, I have updated this with a working test case. It is a bug, as it works in iOS 8.4 sim, but not in 9 GM. I have submitted it to apple.
This is a bug.
I have created a tester class to test for the presence of the bug and act accordingly...
Can be found here
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 have the below code from a project available at: https://github.com/FlexMonkey/SmoothScribble/tree/master/SmoothScribble
Currently this project only runs on iOS9 as the project uses UIStackView and coalescedTouchesForTouch which are only available for iOS9. However I wish to convert the project to make it available for iOS7 and iOS8 as well. How do I go about doing that?
I think I might have a handle on how I might go about replacing UIStackView with a UIView. However, how do I handle sections that refer to coalescedTouches?
For instance, how do I convert the below code snippet so that it conforms to a traditional touchesMoved that works on iOS8 and iOS9?
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
guard let
touch = touches.first,
coalescedTouches = event?.coalescedTouchesForTouch(touch),
touchOrigin = touchOrigin
else
{
return
}
coalescedTouches.forEach
{
hermiteScribbleView.appendScribble($0.locationInView(touchOrigin))
simpleScribbleView.appendScribble($0.locationInView(touchOrigin))
}
}
import UIKit
class ViewController: UIViewController
{
let stackView = UIStackView()
let hermiteScribbleView = HermiteScribbleView(title: "Hermite")
let simpleScribbleView = SimpleScribbleView(title: "Simple")
var touchOrigin: ScribbleView?
override func viewDidLoad()
{
super.viewDidLoad()
view.addSubview(stackView)
stackView.addArrangedSubview(hermiteScribbleView)
stackView.addArrangedSubview(simpleScribbleView)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
guard let
location = touches.first?.locationInView(self.view) else
{
return
}
if(hermiteScribbleView.frame.contains(location))
{
touchOrigin = hermiteScribbleView
}
else if (simpleScribbleView.frame.contains(location))
{
touchOrigin = simpleScribbleView
}
else
{
touchOrigin = nil
return
}
if let adjustedLocationInView = touches.first?.locationInView(touchOrigin)
{
hermiteScribbleView.beginScribble(adjustedLocationInView)
simpleScribbleView.beginScribble(adjustedLocationInView)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
guard let
touch = touches.first,
coalescedTouches = event?.coalescedTouchesForTouch(touch),
touchOrigin = touchOrigin
else
{
return
}
coalescedTouches.forEach
{
hermiteScribbleView.appendScribble($0.locationInView(touchOrigin))
simpleScribbleView.appendScribble($0.locationInView(touchOrigin))
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
{
hermiteScribbleView.endScribble()
simpleScribbleView.endScribble()
}
override func motionBegan(motion: UIEventSubtype, withEvent event: UIEvent?)
{
if motion == UIEventSubtype.MotionShake
{
hermiteScribbleView.clearScribble()
simpleScribbleView.clearScribble()
}
}
override func viewDidLayoutSubviews()
{
stackView.frame = CGRect(x: 0,
y: topLayoutGuide.length,
width: view.frame.width,
height: view.frame.height - topLayoutGuide.length).insetBy(dx: 10, dy: 10)
stackView.axis = view.frame.width > view.frame.height
? UILayoutConstraintAxis.Horizontal
: UILayoutConstraintAxis.Vertical
stackView.spacing = 10
stackView.distribution = UIStackViewDistribution.FillEqually
}
}
Full project code available at: project available at: https://github.com/FlexMonkey/SmoothScribble/tree/master/SmoothScribble
Yesterday I also had a question on compatibility and it turned out that it could be solved easily by Availability Condition, like this:
if #available(iOS 9.0, *) {
let store = CNContactStore()
} else {
// Fallback on earlier versions
}
You can read this article to know more about it.
http://useyourloaf.com/blog/checking-api-availability-with-swift.html
I do not know if we have the same problem. But, in my view, I could have two snippets of code comforming to iOS 9 and iOS 8 respectively, and use Availability Condition or #available Attribute to resolve compatibility problem.
How to tap the correct SKSpriteNode in my iOS game created in swift (XCode 7, SpriteKit)? Finding the node seems not to be the issue, since in the test, 'exists' returns true. When I change my test to "app.tap()" then it does work. Except I have multiple SKSpriteNodes, so this does not solve my problem.
Below you can find code snippets. I hope you can help me with my problem. Any suggestions/hints are welcome.
Code snippet from my SKScene:
override func didMoveToView(view: SKView) {
var newGameButton : SKSpriteNode!
newGameButton = SKSpriteNode(imageNamed: "button")
newGameButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)+5)
newGameButton.size = CGSizeMake(200.0, 40.0)
newGameButton.name = "newGame"
newGameButton.isAccessibilityElement = true
newGameButton.userInteractionEnabled = true
newGameButton.accessibilityLabel = "newGame"
self.addChild(newGameButton)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
let nodes = self.nodesAtPoint(location)
for node in nodes {
if (node.name == "newGame") {
viewController.beginNewGame()
break
}
}
}
Current setup in UITest:
import XCTest
class MemoryPlayUITests: XCTestCase {
override func setUp() {
super.setUp()
continueAfterFailure = false
XCUIApplication().launch()
}
override func tearDown() {
super.tearDown()
}
func testStartNewGame() {
let app = XCUIApplication()
let newGameButton = app.otherElements["newGame"]
XCTAssertEqual(newGameButton.label, "newGame")
XCTAssertTrue(newGameButton.exists)
newGameButton.tap()
...{test logic to test if new game is started}
}
}
So I'm having difficulty trying to do something that should be easy, clearly I don't see whats wrong.
This is the code:
import SpriteKit
var money = "0"
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let moneyLabel = SKLabelNode(fontNamed:"Times New Roman")
moneyLabel.text = money;
moneyLabel.fontSize = 14;
moneyLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(moneyLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
money + 1
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
What this is suppose to do is have a label display a Variable, and when the user touches the screen the variable changes by 1, going up by 1 each time its pressed. The Label is suppose to change with the variable.
How do I make this work?
The problem
You are simply adding 1 to money
money + 1
This code:
does not change the money property
does not change the text in you moneyLabel
is illegal since you cannot sum a String and an Int
Solution
This code should do the job
import SpriteKit
class GameScene: SKScene {
private var money = 0 {
didSet {
self.moneyLabel?.text = money.description
}
}
private var moneyLabel : SKLabelNode?
override func didMoveToView(view: SKView) {
let moneyLabel = SKLabelNode(fontNamed:"Times New Roman")
moneyLabel.text = money.description
moneyLabel.fontSize = 14
moneyLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(moneyLabel)
self.moneyLabel = moneyLabel
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
money += touches.count
}
}
Explaination
1)
As you can se I added an observer to the money property (by the way I made it an Int property, not a String as in your code!).
didSet {
self.moneyLabel?.text = money.description
}
Thanks to the observer, each time the money changes, the moneyLabel node is retrieved and it text gets updated.
2)
At the end of didMoveToView I am saving moneyLabel into an instance property
self.moneyLabel = moneyLabel
so I can easily retrieve (we have seen this in the previous point)
3)
Finally in touchesBegan I simply increase the money property by the number of received touches.
money += touches.count
Thanks to the observer, each change to the money property does trigger an updated to the text inside the moneyLabel node.
Hope this helps.
There are three problems here:
money should be an Int, so you can add to it.
You haven't actually updated money to a new value.
You'll need to update the label in addition to money.
Take a look at the changes below; each comment explains why a change was necessary:
import SpriteKit
class GameScene: SKScene {
var money = 0 //now an Int, so we can add to it later
let moneyLabel = SKLabelNode(fontNamed:"Times New Roman") //keep this around so we can update it later
override func didMoveToView(view: SKView) {
moneyLabel.text = String(money) //convert money to a String so we can set the text property to it
moneyLabel.fontSize = 14
moneyLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(moneyLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
money = money + 1 //update money to itself + 1
moneyLabel.text = String(money) //update the label, too
}
}
}
I'm building a 2D game, the aim of the game is to jump over obstacles on the ground and flying in the air. At present the character can jump multiple times in mid air and has the ability to fly.
I'm looking to only let the character jump every second or so, is there a way to set a time delay on 'touchesBegan'. Ideally id like to be able to alter the time delay to ensure gameplay is fun yet challenging.
Below is a copy of my touchesBegan, let me know if you need more script or the below isn't quite right.
Thank you.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
cat.physicsBody!.velocity = CGVectorMake(0, 0)
cat.physicsBody!.applyImpulse(CGVectorMake(0, 20))
}
let runningBar = SKSpriteNode(imageNamed: "ground")
var velocityY = CGFloat(0)
var onGround = true
func didMoveToView(view: SKView) {
initHero()
}
func initHero() {
hero = SKSpriteNode(imageNamed: "hero")
self.heroBaseline = self.runningBar.position.y + (self.runningBar.size.height / 2) +
(self.hero.size.height / 2)
self.hero.position = CGPointMake(
CGRectGetMinX(self.frame) + (self.hero.size.width / 3) + (self.hero.size.width / 4),
self.heroBaseline)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if self.onGround {
self.velocityY = -18.0
self.onGround = false
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if self.velocityY < -9.0 {
self.velocityY = -9.0
}
}
You need to create a baseline (ground) and limit it to that like in the code above.