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()
}
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 am working on a code to allow a user to update a SKLabelNode by minuting text into a UI text field as below.
I would ideally like the text to be updated as they type, one letter at a time. But I am a bit stuck as to how to do this?
At the moment I just have another button "labelNode2" which updates the text when pressed.
import SpriteKit
import GameplayKit
class GameScene: SKScene, UITextFieldDelegate {
private var field : UITextField?
lazy var labelNode = self.childNode(withName: "labelNode") as? SKLabelNode
lazy var labelNode2 = self.childNode(withName: "labelNode2") as? SKLabelNode
override func didMove(to view: SKView) {
labelNode?.text = "Test"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let largeLocation = touch.location(in: self)
let nodes = self.nodes(at: largeLocation)
for node in nodes {
if node.name == "labelNode" {
labelNode?.text = "hello"
if (field == nil) {
field = UITextField()
field?.frame = CGRect(x:100,y:100, width:100, height:30)
field?.delegate = self;
field?.backgroundColor = .red
field?.isHidden = true;
self.view?.addSubview(field!)
}
field?.text = labelNode?.text
field?.becomeFirstResponder
}
else {
field?.resignFirstResponder
//To hide keyboard
}
if node.name == "labelNode2" {
labelNode?.text = field?.text
}
}
}
}
}
First of all, I had to comment out your line field.isHidden = true.
Second of all, there are no semicolons in swift.
And finally... add this and you should be fine...
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//labelNode?.text = field?.text
labelNode?.text = (field?.text as NSString?)?.replacingCharacters(in: range, with: string)
return true
}
Why did I comment out that first line? Because the text change does not take effect until you return true...
I am creating the main menu for a sprite kit application I am building. Throughout my entire project, I have used SKScenes to hold my levels and the actual gameplay. However, now I need a main menu, which holds buttons like "Play," "Levels," "Shop," etc... However, I don't feel really comfortable the way I am adding buttons now, which is like this:
let currentButton = SKSpriteNode(imageNamed: button) // Create the SKSpriteNode that holds the button
self.addChild(currentButton) // Add that SKSpriteNode to the SKScene
And I check for the touch of the button like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
for node in self.nodes(at: touchLocation) {
guard let nodeName = node.name else {
continue
}
if nodeName == ButtonLabel.Play.rawValue {
DispatchQueue.main.asyncAfter(deadline: .now()) {
let transition = SKTransition.reveal(with: .left, duration: 1)
self.view?.presentScene(self.initialLevel, transition: transition)
self.initialLevel.loadStartingLevel()
}
return
}
if nodeName == ButtonLabel.Levels.rawValue {
slideOut()
}
}
}
However, I don't know if this is considered efficient. I was thinking of using UIButtons instead, but for that would I have to use an UIView?
Or can I add UIButtons to an SKView (I don't really get the difference between an SKView, SKScene, and UIView) What is recommended for menus?
I totally agree with #Whirlwind here, create a separate class for your button that handles the work for you. I do not think the advice from #ElTomato is the right advice. If you create one image with buttons included you have no flexibility on placement, size, look and button state for those buttons.
Here is a very simple button class that is a subclass of SKSpriteNode. It uses delegation to send information back to the parent (such as which button has been pushed), and gives you a simple state change (gets smaller when you click it, back to normal size when released)
import Foundation
import SpriteKit
protocol ButtonDelegate: class {
func buttonClicked(sender: Button)
}
class Button: SKSpriteNode {
//weak so that you don't create a strong circular reference with the parent
weak var delegate: ButtonDelegate!
override init(texture: SKTexture?, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(0.9)
self.delegate.buttonClicked(sender: self)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(1.0)
}
}
This button can be instantiated 2 ways. You can create an instance of it in the Scene editor, or create an instance in code.
class MenuScene: SKScene, ButtonDelegate {
private var button = Button()
override func didMove(to view: SKView) {
if let button = self.childNode(withName: "button") as? Button {
self.button = button
button.delegate = self
}
let button2 = Button(texture: nil, color: .magenta, size: CGSize(width: 200, height: 100))
button2.name = "button2"
button2.position = CGPoint(x: 0, y: 300)
button2.delegate = self
addChild(button2)
}
}
func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}
You have to remember to make the scene conform to the delegate
class MenuScene: SKScene, ButtonDelegate
func buttonClicked(sender: Button) {
print("you clicked the button named \(sender.name!)")
}
For simple scenes what you are doing is fine, and actually preferred because you can use the .SKS file.
However, if you have a complex scene what I like to do is subclass a Sprite and then override that node's touchesBegan.
Here is a node that I use in all of my projects... It is a simple "on off" button. I use a "pointer" to a Boolean via the custom Reference class I made, so that way this node doesn't need to be concerned with your other scenes, nodes, etc--it simply changes the value of the Bool for the other bits of code to do with what they want:
public final class Reference<T> { var value: T; init(_ value: T) { self.value = value } }
// MARK: - Toggler:
public final class Toggler: SKLabelNode {
private var refBool: Reference<Bool>
var value: Bool { return refBool.value }
var labelName: String
/*
var offText = ""
var onText = ""
*/
func toggleOn() {
refBool.value = true
text = labelName + ": on"
}
func toggleOff() {
refBool.value = false
text = labelName + ": off"
}
/*init(offText: String, onText: String, refBool: Reference<Bool>) {
ref = refBool
super.init(fontNamed: "Chalkduster")
if refBool.value { toggleOn() } else { toggleOff() }
isUserInteractionEnabled = true
}
*/
init(labelName: String, refBool: Reference<Bool>) {
self.refBool = refBool
self.labelName = labelName
super.init(fontNamed: "Chalkduster")
isUserInteractionEnabled = true
self.refBool = refBool
self.labelName = labelName
if refBool.value { toggleOn() } else { toggleOff() }
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if refBool.value { toggleOff() } else { toggleOn() }
}
public required init?(coder aDecoder: NSCoder) { fatalError("") }
override init() {
self.refBool = Reference<Bool>(false)
self.labelName = "ERROR"
super.init()
}
};
This is a more elaborate button than say something that just runs a bit of code when you click it.
The important thing here is that if you go this route, then you need to make sure to set the node's .isUserInteractionEnabled to true or it will not receive touch input.
Another suggestion along the lines of what you are doing, is to separate the logic from the action:
// Outside of touches func:
func touchPlay() {
// Play code
}
func touchExit() {
// Exit code
}
// In touches began:
guard let name = node.name else { return }
switch name {
case "play": touchPlay()
case "exit": touchExit()
default:()
}
PS:
Here is a very basic example of how to use Toggler:
class Scene: SKScene {
let spinnyNode = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
// This is the reference type instance that will be stored inside of our Toggler instance:
var shouldSpin = Reference<Bool>(true)
override func didMove(to view: SKView) {
addChild(spinnyNode)
spinnyNode.run(.repeatForever(.rotate(byAngle: 3, duration: 1)))
let toggleSpin = Toggler(labelName: "Toggle Spin", refBool: shouldSpin)
addChild(toggleSpin)
toggleSpin.position.y += 100
}
override func update(_ currentTime: TimeInterval) {
if shouldSpin.value == true {
spinnyNode.isPaused = false
} else if shouldSpin.value == false {
spinnyNode.isPaused = true
}
}
}
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
}
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)")
}
...
}