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}
}
}
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().
So basically I have a node called tree and I designed it in a .sks file.
I added a custom class for the skspritenode but the custom class doesn't seem to affect the node.
I am trying to detect touches on the node and its children.
This node is being transfered to multiple scenes using removefromparent() and addchild() functions so instead of writing duplicate code on each scene to detect the touch I am trying to use the custom node class to do it... any help would be appreciated.
note i have super.init function with the texture as it is necessary but I would like to use the node that was already created in the scene.
My class code
import SpriteKit
class tree: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "tree")
super.init(texture: texture, color: SKColor.clear, size: texture.size())
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
print("touched!")
}
}
}
There is a good explanation in this answer that should be clear your ideas about SKS files and subclassing.
About your code it is a good habit to use uppercase letter for the class names, in your case I prefer to use class Tree instead of class tree.
About your second issue, you can use userData to transfer your object from a scene to another as explained below in my example:
import SpriteKit
class GameScene: SKScene {
private var label : SKLabelNode?
var tree : Tree!
override func didMove(to view: SKView) {
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
self.isUserInteractionEnabled = true
tree = Tree()
tree.name = "tree"
addChild(tree)
tree.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "tree" {
tree.removeFromParent()
let sceneToMoveTo = Scene2.init(size: self.size)
sceneToMoveTo.userData = NSMutableDictionary()
sceneToMoveTo.userData?.setObject(tree, forKey: "tree" as NSCopying)
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: gameTransition)
}
}
}
}
class Scene2: SKScene {
var tree:Tree!
override func didMove(to view: SKView) {
print("This is the scene: \(type(of:self))")
guard let previousValue = self.userData?.value(forKey: "tree") else { return }
if previousValue is Tree {
tree = previousValue as! Tree
addChild(tree)
tree.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "tree" {
tree.removeFromParent()
if let sceneToMoveTo = SKScene(fileNamed: "GameScene") {
sceneToMoveTo.scaleMode = .aspectFill
sceneToMoveTo.userData = NSMutableDictionary()
sceneToMoveTo.userData?.setObject(tree, forKey: "tree" as NSCopying)
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: gameTransition)
}
}
}
}
}
As you can read I've the control of touches both in my parent class and in my node, this can be made possible by changing the touchesBegan method of your custom SKSpriteNode as:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
print("touched!")
}
guard let parent = self.parent else { return }
parent.touchesBegan(touches, with: event)
}
Remember that you should extend this approach also to the other touches method if you want to use them..
I am trying to delete an image that is moving by touch and only that image. I spawn multiple images of that same image and I only want to delete the ones that are being touched. How can I do this? Any ideas? I am running IOS app Game and SpriteKit.
import SpriteKit
`class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "Hello, World!"
myLabel.fontSize = 45
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(myLabel)
}
func generateNode() {
let Bullet = SKSpriteNode(imageNamed: "Bullets")
Bullet.name = "generatedNode"
}
func touchesCancelled(touches: NSSet, withEvent event: UIEvent?) {
let touch = touches.anyObject() as! UITouch?
if let location = touch?.locationInNode(self)
{
for Bullet in self.nodesAtPoint(location)
{
if Bullet.name == "generatedNode"
{
Bullet.removeFromParent()
}
}
}
}
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
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
I am making a game that, when the game loads in, doesn't require the user to tap the screen to start, but starts straight away.
My code with the "tap to start":
import Foundation
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var isStarted = false
override func didMoveToView(view: SKView) {
//backgroundColor = UIColor.greenColor()
addTapToStartLabel()
func addTapToStartLabel() {
let tapToStartLabel = SKLabelNode(text: "Tap to start!")
tapToStartLabel.name = "tapToStartLabel"
tapToStartLabel.position.x = view!.center.x
tapToStartLabel.position.y = view!.center.y + 40
tapToStartLabel.fontColor = UIColor.whiteColor()
tapToStartLabel.fontName = "Helvetica"
tapToStartLabel.fontSize = 22.0
addChild(tapToStartLabel)
}
func start() {
isStarted = true
let tapToStartLabel = childNodeWithName("tapToStartLabel")
tapToStartLabel?.removeFromParent()
square1.stop()
movingGround.start()
wallGen.startGenWallsEvery(1)
diamondGen.startGenDiamondsEvery(1)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if isGameOver {
restart()
} else if !isStarted {
start()
} else {
square1.flip()
}
}
I have tried a couple of things and I can't seem to figure it out.
First, your didMoveToView is missing }
Your game have started, but you should override update() function that is called at the beginning of every frame and do some actions there.