I have this code in SKScene:
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
var touch: AnyObject = touches.anyObject()
var point = getPoint(touch.locationInNode(self))
var name = NSStringFromCGPoint(point)
for children in self.children {
if (children as SKSpriteNode).name == name {
println("exist!")
}
}
var tempNode = self.childNodeWithName(name)
}
I see "exist!" in log, so there is a node with this name in children array, but tempNode is nil. The self.childNodeWithName("//" + name)call returns nil too.
Here is how to accomplish this in Swift... Hope this helps!
var mySprite: SKSpriteNode = childNodeWithName("mySprite") as SKSpriteNode
I've found this strangeness using Swift 2.2, maybe a bug .. you can't use NSStringFromCGPoint and childNodeWithName without cleaning the string from braces:
Using this little method:
func removeBraces(s:String)->String {
return s.stringByTrimmingCharactersInSet(NSCharacterSet.init(charactersInString: "{}"))
}
when you add your SKSpriteNode do for example:
...
mySpriteNode.name = removeBraces(NSStringFromCGPoint(mySpriteNode.position))
...
and to retrieve it for your case :
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
var touch: AnyObject = touches.anyObject()
var point = getPoint(touch.locationInNode(self))
var name = removeBraces(NSStringFromCGPoint(point))
if let child = self.childNodeWithName(name) {
print("I've found: \(child)")
}
...
}
Related
I made a subclass of SKSpriteNode called JoyStick that follows the delegate pattern, where its delegate is GameScene. In an effort to further clean up the GameScene, the JoyStick class also implements its own touchesBegan, touchesMoved, and touchesEnded methods.
The problem is that I have some position calculations in the JoyStick's touchesMoved method that need to be made after certain SKConstraints have been applied to the joystick (such as bounding the joystick handle to the base). I know that there is a didApplyConstraints instance method of SKScene, and that the documentation indicates that it should be overridden and not called as it is already called once per frame.
However, I'm not sure how to integrate didApplyConstraints into my delegating joystick class's touchesMoved method without resorting to moving logic back into my GameScene's didApplyConstraints.
Here is a simplified example of what I currently have implemented with the delegate pattern. Note that the below code allows the variable joyVector to take on values outside of the acceptable range because the constraint hasn't yet been applied.
GameScene.swift
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var shipNode: SKSpriteNode?
var joyStick: JoyStick?
override func didMove(to view: SKView) {
self.view?.isMultipleTouchEnabled = true
// Init the ship
guard let shipNode = self.childNode(withName: "ShipNode") as? SKSpriteNode else {
fatalError("Player node not loaded!")
}
self.shipNode = shipNode
// Init the joystick
joyStick = JoyStick()
joyStick?.isUserInteractionEnabled = true
joyStick?.position = CGPoint(x: -500, y: -200)
joyStick?.delegate = self
self.addChild(joyStick!)
}
}
extension GameScene: JoyStickDelegate{
var objPhysicsBody: SKPhysicsBody? {
return self.shipNode?.physicsBody
}
}
JoyStick.swift
import Foundation
import SpriteKit
protocol JoyStickDelegate: class {
var objPhysicsBody: SKPhysicsBody? {get}
}
class JoyStick: SKSpriteNode {
weak var delegate: JoyStickDelegate!
var joyVector: CGVector = CGVector()
var handle: SKSpriteNode?
init () {
let baseTexture = SKTexture(imageNamed: "joyStickBase")
super.init(texture: baseTexture, color: UIColor.clear, size: baseTexture.size())
// Add the joystick handle onto the base
handle = SKSpriteNode(imageNamed: "joyStickHandle")
handle?.position = CGPoint(x: 0 , y: 0)
// Add constraints
let range = CGFloat(self.size.width/2 - (handle?.size.width)! / 2)
let moveRange = SKRange(lowerLimit: 0, upperLimit: range)
let rangeConstraint = SKConstraint.distance(moveRange, to: CGPoint(x: 0,y: 0))
handle?.constraints = [rangeConstraint]
self.addChild(handle!)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveJoyStick(_ touches: Set<UITouch>) {
guard let fingerPosition = touches.first?.location(in: self) else { return }
self.handle!.position = fingerPosition
}
func getJoyVector(handlePosition: CGPoint) -> CGVector {
return CGVector(dx: handlePosition.x, dy: handlePosition.y)
}
func resetJoyStick() {
delegate?.objPhysicsBody?.velocity = CGVector(dx: 0, dy: 0)
//animation to return the joystick to the center...
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.moveJoyStick(touches)
joyVector = getJoyVector(handlePosition: self.handle!.position)
delegate?.objPhysicsBody?.velocity = joyVector
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
self.moveJoyStick(touches)
joyVector = getJoyVector(handlePosition: self.handle!.position)
delegate?.objPhysicsBody?.velocity = joyVector
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
resetJoyStick()
}
override func touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>) {}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}
}
An alternative implementation that did work correctly was to keep the touch event methods inside the JoyStick class but also drop the delegation pattern all together. Instead, I accessed the JoyStick handlePosition (as a vector) inside GameScene via a getter method and applied it as a velocity inside GameScene's didApplyConstraints method. Here are the two snippets of added code to make this work:
Add to JoyStick.swift:
func getPositionVelocity() -> CGVector {
guard let handlePosition = self.handle?.position else { return CGVector() }
return CGVector(dx: handlePosition.x, dy: handlePosition.y)
}
Add to GameScene.swift:
...
//joyStick?.delegate = self
...
override func didApplyConstraints() {
self.shipNode?.physicsBody?.velocity = self.joyStick?.getPositionVelocity() ?? CGVector()
}
While this achieves the desired behavior of only applying velocities bounded by the radius of the joystick, it seems far less elegant and greatly reduces the amount of encapsulation/generality that I had with the delegate pattern - which becomes noticeable as the game grows.
Ultimately, is it possible to avoid piling in post-constraint logic into a single didApplyConstraints definition in GameScene to achieve this functionality? And why is it that UIResponder touch events seem to always be handled before :didApplyConstraints - even though they seem to be outside of the SKScene Frame-Cycle?
You have two objects with different methods inside. You can not move both methods into one class directly.
But it is possible to do in this way:
class GameScene: SKScene {
var onJoysticNeedVerticalPosition: (()-> CGVector)?
....
override func didApplyConstraints() {
self.shipNode?.physicsBody?.velocity = onJoysticNeedVerticalPosition?() ?? CGVector()
}
}
and
protocol JoyStickDelegate: class {
var objPhysicsBody: SKPhysicsBody? {get}
var onJoysticNeedVerticalPosition: (()-> CGVector)? {get set}
}
then
class JoyStick: SKSpriteNode {
...
init () {
self.delegate?.onJoysticNeedVerticalPosition = {[weak self] in
self?.getPositionVelocity()
}
}
...
func getPositionVelocity() -> CGVector {
guard let handlePosition = self.handle?.position else { return CGVector() }
return CGVector(dx: handlePosition.x, dy: handlePosition.y)
}
}
So in fact it is still two different objects, but the first one (GameScene) hold the closure with calculation logic, that in fact calculated in second one (JoyStick), and second one give it in detail to the first one by using delegate method.
I'm having problems to access custom property of a Node Object in swift. I'm gonna try to explain you.
I create a class that inherit from SkSpriteNode and I create a property that calls ( Has Gluten )
init(foodName: String, foodNote: String, foodImage: SKTexture, hasGluten: Bool) {
self.foodNote = foodNote
self.foodImage = foodImage
self.hasGluten = hasGluten
self.foodName = foodName
super.init(texture: foodImage, color: UIColor.clear, size: CGSize(width: 40, height: 40))
self.position = CGPoint(x: Int.random(in: 0..<355), y: 600)
self.name = self.foodName
}
Until here Ok, but when I add this node to the screen and I active touch Began I can't access the property HasGluten inside this class..
It says that I can acess just native properties like ( Name, position etc. )
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch:UITouch = touches.first!
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if touchedNode.hasGluten == true { callfunction() } else {call another function}
}
Here is the point where I want to access the property. but when I try to access the property HasGluten it doesn't give to me.
If someone could help I will be glad. Thanks
In order to access your property you should cast node object to your custom type:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch:UITouch = touches.first!
let positionInScene = touch.location(in: self)
guard let touchedNode = self.atPoint(positionInScene) as? CustomNode else { return }
if touchedNode.hasGluten == true { callfunction() } else {call another function}
}
Instead CustomNode use correct class name.
I have a game and I have custom levels which is saved as an array. I use the function saveCustomLevels to save the value of the level data. Every time the level data is changed, I do saveCustomLevels(). Whenever I do saveCustomLevels() and close the program and restart it the level data is always not saved and back to the default data. Am I doing something wrong. This is my code.
//in different class
func createLevels{
customLevels = (0...8).map {_ in level}
}
//in regular class
func saveCustomLevels(){
var customDefault = UserDefaults.standard
customDefault.synchronize()
}
override func didMove(to view: SKView) {
var customDefault = UserDefaults.standard
if customDefault.value(forKey: "Custom") != nil
customLevels = customDefault.value(forKey: "Custom") as! Array<Array<square>>
}
square.createLevels()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
}
if squares[i].node.contains(location){
customLevels[zzz][s].startColor = colors[i]
saveCustomLevels()
}
Lets say I have five dice like so:
When I tap, Id like the die face with one dot to replace the one with two dots and (moving the 1 die across the row while moving the other die down the row) and if the last die is at the end, get it to replace the first die in the row. Would replacing the SKTextures have something to do with this? Thank you in advance.
Edit:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "DieFace1"))
DieFace2.runAction(texRight)
}
}
Edit 2:
import SpriteKit
var arrayOfDieFaces = [onE, twO, threE, fouR, fivE]
class GameScene: SKScene {
}
func replace() {
var count = 1
while count <= 5 {
let changeTexture = SKAction.setTexture(SKTexture(imageNamed: "Dice\(count)"))
if count == 5 {
arrayOfDieFaces[0].runAction(changeTexture)
}
else{
arrayOfDieFaces[count].runAction(changeTexture)
}
count += 1
}
arrayOfDieFaces.append(arrayOfDieFaces[0])
arrayOfDieFaces.dropFirst()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
replace()
}
}
This approach changes the position of each dice instead of changing its texture. By changing the positions, it maintains the ability to uniquely identify each dice (e.g., by name).
First, create an SKNode called dice and define the size and spacing between each dice.
let dice = SKNode()
let diceSize = CGSizeMake(10, 10)
let spacing:CGFloat = 2
Second, create your dice nodes and add them, in order, to the dice SKNode and set the positions of each dice with
for i in 0..<6 {
let sprite = SKSpriteNode ...
sprite.physicsBody = SKPhysicsBody( ...
sprite.physicsBody?.categoryBitMask = UInt32(0x1 << i)
sprite.position = CGPointMake(CGFloat(i)*(diceSize.width+spacing) , 0)
dice.addChild (sprite)
}
// Center SKNode in the view
dice.position = CGPointMake (CGRectGetMidX(view.frame),CGRectGetMidY(view.frame))
addChild (dice)
Note that the position of each dice is based on the position of the node within the array.
Lastly, add the following to your code.
func rotate() {
// Get last element in the array
if let last = dice.children.last {
// Remove the last dice from the SKNode
last.removeFromParent()
// Move the last to the front of the array
dice.insertChild(last, atIndex: 0)
// Update the positions of the
for i in 0..<dice.children.count {
dice.children[i].position = CGPointMake(CGFloat(i)*(diceSize.width+spacing) , 0)
}
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for _ in touches {
rotate()
}
}
I think this should work (not tested) assuming your DieFaces are from 1 to 6:
var arrayOfDieFaces = [DieFace1, DieFace2, DieFace3, DieFace4, DieFace5, DieFace6]
func changeTextures() {
var count = 1
while count <= 6 {
let changeTexture = SKAction.setTexture(SKTexture(imageNamed: "DieFace\(count)"))
if count == 6 {
arrayOfDieFaces[0].runAction(changeTexture)
}
else{
arrayOfDieFaces[count].runAction(changeTexture)
}
count += 1
}
arrayOfDieFaces.append(arrayOfDieFaces[0])
arrayOfDieFaces.dropFirst()
}
Just call this fucntion inside the touchesBegan() function.
I have coded a starry sky as follows. Now I would like to remove a star when a user touches it. The following code however removes all the stars on the sky. How can I access a single star node to manipulate it?
override func didMoveToView(view: SKView) {
for(var i = 0; i < stars ; i++) {
planetsLayer.addChild(createPlanet(view))
}
self.addChild(planetsLayer)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(planetsLayer)
let touchedLayer = nodeAtPoint(location)
let touchedNode = nodeAtPoint(location)
touchedNode.removeFromParent()
}
func createPlanet() -> SKShapeNode {
...
var shapeNode = SKShapeNode();
...
return shapeNode
}
Give your planet nodes a name when you create them, and check for this name when you remove them. This will keep you from deleting other nodes that are not planets.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(planetsLayer)
let touchedNode = nodeAtPoint(location)
if touchedNode.name == "planet" {
touchedNode.removeFromParent()
}
}
}
func createPlanet() -> SKShapeNode {
...
var shapeNode = SKShapeNode()
shapeNode.name = "planet"
...
return shapeNode
}