scope from class root, to inside init, to then inside touchesBegan? - swift

Inside this, I'm not seeing goesTo getting assigned to nextScene. I assume this is a scope problem, but don't know enough to resolve it:
//
import SpriteKit
class MenuButton: SKLabelNode {
var goesTo: SKScene?
override init() {
super.init()
print("test... did this get called? WTF? HOW/WHY/WHERE?")
}
convenience init(text: String, color: SKColor, destination: SKScene){
self.init()
self.text = text
self.fontColor = color
goesTo = destination
self.isUserInteractionEnabled = true
print("gooing tooooo....", text, goesTo!) // WORKS!!!
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let nextScene = goesTo {
self.scene?.view?.presentScene(nextScene, transition: SKTransition.doorsOpenVertical(withDuration: 0.25))
print("going to", nextScene) // This is NIL! SO NEVER GETS CALLED!
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

You code is correct, you can simply test it making for example a:
GameScene.swift
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
print("- \(type(of:self))")
let helloScene = HelloScene.init()
helloScene.name = "helloScene"
let button1 = MenuButton.init(text: "HELLO", color: .red, destination: helloScene)
addChild(button1)
button1.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
}
and you will see a red label in the middle of the screen with "HELLO".
As you can see also your prints looks good for both of your methods.
HelloScene.swift
import Foundation
import SpriteKit
class HelloScene: SKScene {
override func didMove(to view: SKView) {
print("- \(type(of:self))")
self.backgroundColor = .blue
}
}
Debug console:

Related

Is it possible to handle calculations after didApplyConstraints inside of a Delegating class?

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.

invoke private var to convenience init (swift)

Im trying to create a node class of a ball. the ball supposed to have some physics body properties - because of that i created a function that init the physicsbody properties. also, I want that the user will set up the ball radius (via circleOfRadius) which I set up via convenience init. because of that I set the radius var as a private. the problem is the I can set the radius to there private var but then the convenience is not executed.
here is my code:
class BallNode: SKShapeNode {
var radius:CGFloat = 0
private var _R:CGFloat?
override init() {
super.init()
self.setPhysics()
}
var newBallR:CGFloat{
get {
return _R!
}
set {
_R = newValue
self.radius = _R!
print("bsllRRRR:", self.radius)
}
}
convenience init(radius:CGFloat){
self.init(circleOfRadius: radius)
self.radius = radius
print("successful init circle radius", self.radius)
}
func setPhysics() {
self.physicsBody = SKPhysicsBody.init(circleOfRadius: self.radius)
print("physics body is SET with value:", self.radius)
self.physicsBody?.affectedByGravity = true
print("gravity = T")
self.physicsBody?.restitution = 0.2
print("res = 0.2")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let positionOfTouch = touch.location(in: self)
let ball = BallNode(radius:65)
}
I know I can solve that by cancel the private radius var and declare the physics body property inside the convenience init but im not sure if its correct.
one more thing, when I check the code the _R! getting the 65 value, but as I said the convenience init is not executed.
can someone help me with that?
I think its because you are not calling the convenience init. Use BallNode(radius:65.0), then it gets called and myRadius will be set to 65.0 but whats the use, you aren't calling setPhysics() in your convenience init.

can't set SKPhysicsBody inside a function

I created a class that defines a ball of kind SKShapeNode. Inside the class' init() method I try to initialize the ball's physicsBody properties. Unfortunately, the function doesn't really work - it doesn't init its properties. I'm not sure what the problem is, or if I can initialize these properties inside the function.
Here is my code:
class BallNode: SKShapeNode {
var radius:CGFloat = 0
var color:UIColor?
var strokeWidth:CGFloat = 0
private var _name:String?
override init() {
super.init()
self.fillColor = ranColor()
self.setPhysics(body: self)
self.lineWidth = strokeWidth
}
Here is the function I wrote inside the class:
func setPhysics(body: SKShapeNode) {
body.physicsBody?.affectedByGravity = false
print("gravity = F")
body.physicsBody?.restitution = 0.2
body.physicsBody?.isDynamic = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I know the function is running because it prints in the console:
gravity = F
But it doesn't really work because the ball is affected by gravity (its default).
What am I dong wrong?
update:
if I set those properties inside my touchesBegan it's working. for example:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let positionOfTouch = touch.location(in: self)
let ball = BallNode(radius: 65)
ball.Name = "BALL"
print(ball.Name)
ball.position = positionOfTouch
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.isDynamic = false
self.addChild(ball)

Creation of buttons for SpriteKit

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
}
}
}

How to initial correctly between two class

I want to make sure that in class Home could invoke class HUD, and these two part code could be test Okay but when I run in Xcode, it crash and show up a issue:
"Fatal error: use of unimplemented initializer 'init(size:)' for class 'LeftBehind.HUD'"
I hope anyone could told me how to fixed it, thanks.
import UIKit
import SpriteKit
class HUD: SKScene {
let positionX:CGFloat
let positionY:CGFloat
let sceneSelf: SKScene
let year = SKLabelNode()
let month = SKLabelNode()
let day = SKLabelNode()
let hour = SKLabelNode()
let minute = SKLabelNode()
init(positionX:CGFloat,positionY:CGFloat,scene:SKScene) {
self.positionX = positionX
self.positionY = positionY
self.sceneSelf = scene
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func hudShow() {
year.fontName = "Arial"
year.fontSize = 70
year.text = "This is a year"
year.fontColor = UIColor.whiteColor()
year.position = CGPoint(x: positionX , y: positionY)
sceneSelf.addChild(year)
}
}
Below class invoke above class
import SpriteKit
class Home: SKScene {
override func didMoveToView(view: SKView) {
let hud = HUD(positionX: frame.midX, positionY: frame.midY, scene: self)
hud.hudShow()
}
}
Edit: Upload another way which could work but not perfect way which I want to implement with initial rather than function refer.
instead of calling super.init() call super.init(size size: CGSize)
you can use your SKView's bounds.size property to pass into that initializer.