Creation of buttons for SpriteKit - swift

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

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.

How to add a 30 second timer to end a game round?

I am currently experimenting with some code that I found on the internet about a game where you have to click on one set of items and avoid clicking on the other. I am currently trying to add a timer to the game so that it lasts of a total of 30 seconds but I am really struggling to do so as I am quite inexperienced with this programming language.
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController, SCNSceneRendererDelegate {
var gameView:SCNView!
var SceneGame:SCNScene!
var NodeCamera:SCNNode!
var targetCreationTime:TimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
View_in()
initScene()
initCamera()
}
func View_in(){
gameView = self.view as! SCNView
gameView.allowsCameraControl = true
gameView.autoenablesDefaultLighting = true
gameView.delegate = self
}
func initScene (){
SceneGame = SCNScene()
gameView.scene = SceneGame
gameView.isPlaying = true
}
func initCamera(){
NodeCamera = SCNNode()
NodeCamera.camera = SCNCamera()
NodeCamera.position = SCNVector3(x:0, y:5, z:10)
SceneGame.rootNode.addChildNode(NodeCamera)
}
func createTarget(){
let geometry:SCNGeometry = SCNPyramid( width: 1, height: 1, length: 1)
let randomColor = arc4random_uniform(2
) == 0 ? UIColor.green : UIColor.red
geometry.materials.first?.diffuse.contents = randomColor
let geometryNode = SCNNode(geometry: geometry)
geometryNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
if randomColor == UIColor.red {
geometryNode.name = "enemy"
}else{
geometryNode.name = "friend"
}
SceneGame.rootNode.addChildNode(geometryNode)
let randomDirection:Float = arc4random_uniform(2) == 0 ? -1.0 : 1.0
let force = SCNVector3(x: randomDirection, y: 15, z: 0)
geometryNode.physicsBody?.applyForce(force, at: SCNVector3(x: 0.05, y: 0.05, z: 0.05), asImpulse: true)
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if time > targetCreationTime{
createTarget()
targetCreationTime = time + 0.6
}
cleanUp()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: gameView)
let hitList = gameView.hitTest(location, options: nil)
if let hitObject = hitList.first{
let node = hitObject.node
if node.name == "friend" {
node.removeFromParentNode()
self.gameView.backgroundColor = UIColor.black
}else {
node.removeFromParentNode()
self.gameView.backgroundColor = UIColor.red
}
}
}
func cleanUp() {
for node in SceneGame.rootNode.childNodes {
if node.presentation.position.y < -2 {
node.removeFromParentNode()
}
}
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
You could use a Timer object, documented here. Just set up the timer when you want the game to start, probably once you've finished all your initializations. When you set up the timer, just wait for it to call back to your code when it finishes and run whatever logic you want to use to terminate your game.
EDIT
Create a variable representing the time you want your game will end:
var time: CGFloat = 60
Then, add an SCNAction to your scene so that each second it will decrease this variable value, for example in the viewDidLoad:
//One second before decrease the time
let wait = SCNAction.wait(forDuration: 1)
//This is the heart of this answer
// An action that reduce the time and when it is less than 1 (it reached zero) do whatever you want
let reduceTime = SCNAction.run{ _ in
self.time -= 1
if self.time < 1 {
// Do whatever you want
// for example show a game over scene or something else
}
}
}
SceneGame.rootNode.run(SCNAction.repeatForever(SCNAction.sequence([wait,reduceTime])))
If you want, you can show the remaining time by using SKLabel on an HUD, which is an SKScene used as overlay.
You can check this tutorial for how to create an HUD
As well, you can use an SCNText, this is the documentation about it

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

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:

Callback for MSSticker Peels in iOS 10 iMessage sticker app

I'm experimenting with sticker iMessage apps in iOS 10 and I'm running into an issue with the override func didStartSending(_ message: MSMessage, conversation: MSConversation) method in MSMessagesAppViewController. When "peeling" a sticker from an MSStickerView, I would expect to receive some sort of callback on the didStartSending method. But it appears this is not the case. Does anyone know if this is the expected behavior and/or if there's another way to subscribe to callbacks for when these stickers are peeled, dragged, and dropped into the MSConversation? I realize that didStartSending is reserved for when the user taps the send button, but surely there should be some way of knowing when users drag MSStickers without hacking together some UIView dragging/rect-reading heuristic.
Messages View Controller:
class MessagesViewController: MSMessagesAppViewController {
var nYCStickersBroswerViewController: NYCStickersBroswerViewController!
override func viewDidLoad() {
super.viewDidLoad()
nYCStickersBroswerViewController = NYCStickersBroswerViewController(stickerSize: .regular)
nYCStickersBroswerViewController.view.frame = self.view.frame
self.addChildViewController(nYCStickersBroswerViewController)
nYCStickersBroswerViewController.didMove(toParentViewController: self)
self.view.addSubview(nYCStickersBroswerViewController.view)
nYCStickersBroswerViewController.loadStickers()
nYCStickersBroswerViewController.stickerBrowserView.reloadData()
}
...
override func didStartSending(_ message: MSMessage, conversation: MSConversation) {
// Called when the user taps the send button.
print(message) // should this not contain the sticker that is peeled, dragged, and dropped into the conversation?
}
}
Sticker Browser:
import Foundation
import UIKit
import Messages
class ASSticker: MSSticker {
var identifier: String?
}
class NYCStickersBroswerViewController: MSStickerBrowserViewController {
var stickers = [ASSticker]()
override func viewDidLoad() {
super.viewDidLoad()
}
func changeBrowswerViewBackgroundColor(color: UIColor) {
stickerBrowserView.backgroundColor = color
}
func loadStickers() {
createSticker(name: "brooklyn", localizedDescription: "Brooklyn Bridge Sticker")
createSticker(name: "liberty", localizedDescription: "Statue of Liberty Sticker")
createSticker(name: "love", localizedDescription: "I Love New York Sticker")
createSticker(name: "mets", localizedDescription: "New York Mets Sticker")
createSticker(name: "rangers", localizedDescription: "New York Rangers Sticker")
createSticker(name: "subway", localizedDescription: "New York City MTA Subway Train Sticker")
}
func createSticker(name: String, localizedDescription: String) {
guard let stickerPath = Bundle.main.pathForResource(name, ofType: "png") else {
print("Call ae cab, you're intoxicated.")
return
}
let stickerURL = URL(fileURLWithPath: stickerPath)
let sticker: ASSticker
do {
try sticker = ASSticker(contentsOfFileURL: stickerURL, localizedDescription: localizedDescription)
sticker.identifier = "something unique"
stickers.append(sticker)
} catch {
print("Call a cab, you're intoxicated.")
}
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return self.stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return self.stickers[index]
}
}
Here's a subclass and delegate that will tie into the tap and long press gesture recognizers that MSStickerView is using for select and peel interactions. If the implementation of MSStickerView changes this may no longer provide events, but shouldn't crash.
import UIKit
import Messages
protocol InstrumentedStickerViewDelegate: class {
func stickerViewDidSelect(stickerView: MSStickerView)
func stickerViewDidPeel(stickerView: MSStickerView)
}
class InstrumentedStickerView: MSStickerView {
weak var delegate: InstrumentedStickerViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
for gestureRecognizer in gestureRecognizers ?? [] {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer {
tapGestureRecognizer.addTarget(self, action: #selector(didTap))
} else if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer {
longPressGestureRecognizer.addTarget(self, action: #selector(didLongPress))
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func didTap(tapGestureRecognizer: UITapGestureRecognizer) {
if tapGestureRecognizer.state == .Recognized {
delegate?.stickerViewDidSelect(self)
}
}
func didLongPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .Began {
delegate?.stickerViewDidPeel(self)
}
}
}
This is a workaround for sticker peeled and tapped events, it is not guaranteed that a particular sticker will be inserted but an approximation of such data points - you will have to use your subclass of MSStickerView. This solution is not futureproof, but works for the time being IMHO, therefore I welcome other ideas.
import UIKit
import Messages
class CustomStickerView : MSStickerView{
class GestureRecognizerReceiver : NSObject, UIGestureRecognizerDelegate{
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
let _recognizerDelegate = GestureRecognizerReceiver()
weak var _recognizer: UITapGestureRecognizer? = nil
func setupTapRecognizer(){
if _recognizer == nil {
let r = UITapGestureRecognizer(target: self, action: #selector(_customTapReceived))
r.cancelsTouchesInView = false
r.delegate = _recognizerDelegate
addGestureRecognizer(r)
_recognizer = r
}
}
func _customTapReceived(){
if let s = sticker{
Analytics.shared.reportEvent(name: "Sticker Inserted", description: s.localizedDescription)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let s = sticker{
Analytics.shared.reportEvent(name: "Sticker Peeled", description: s.localizedDescription)
}
}
}
Usage:
let sv = CustomStickerView(frame: _stickerViewHolder.bounds, sticker: sticker)
sv.setupTapRecognizer()
_stickerViewHolder.addSubview(sv)
sv.startAnimating()

CanĀ“t adress an object outside my Class

I am trying to develop a game as a complete beginner. I setup a game scene that does reference a object called taxiNode and BlockNode correctly.
I now want to make things interactive and want to add an impulse on the taxiNode, when tapping the BlockNode. For that I set up func interact() within my BlockNode class, but I can not access my TaxiNode.
Here is my code for the BlockNode Class
import SpriteKit
class BlockNode: SKSpriteNode, CustomNodeEvents, InteractiveNode {
func didMoveToScene() {
print("block added")
userInteractionEnabled = true
}
func interact() {
taxiNode.physicsBody!.applyForce(CGVectorMake(0, 400))
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
print("destroy block")
//interact()
}
}
My GameScene Class looks like this
import SpriteKit
struct PhysicsCategory {
static let None: UInt32 = 0
static let Taxi: UInt32 = 0b1 // 1
static let Block: UInt32 = 0b10 // 2
static let Edge: UInt32 = 0b100 // 4
/* static let Edge: UInt32 = 0b1000 // 8
static let Label: UInt32 = 0b10000 // 16
static let Spring:UInt32 = 0b100000 // 32
static let Hook: UInt32 = 0b1000000 // 64 */
}
protocol CustomNodeEvents {
func didMoveToScene()
}
protocol InteractiveNode {
func interact()
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var taxiNode: TaxiNode!
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Calculate playable margin
let maxAspectRatio: CGFloat = 16.0/9.0 // iPhone 5
let maxAspectRatioHeight = size.width / maxAspectRatio
let playableMargin: CGFloat = (size.height - maxAspectRatioHeight)/2
let playableRect = CGRect(x: 0, y: playableMargin,
width: size.width, height: size.height-playableMargin*2)
physicsBody = SKPhysicsBody(edgeLoopFromRect: playableRect)
physicsWorld.contactDelegate = self
physicsBody!.categoryBitMask = PhysicsCategory.Edge
enumerateChildNodesWithName("//*", usingBlock: {node, _ in
if let customNode = node as? CustomNodeEvents {
customNode.didMoveToScene()
}
})
taxiNode = childNodeWithName("taxi") as! TaxiNode
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
And I get the following error within my BlockNode Class
"Use of unresolved identifier "taxiNode"
Does anyone have a clue what I need to fix to adress the taxiNode and make the receive my impulse?
Look up scope of variables to learn more.
Your block Node does not know what taxi node is, nor should it.
What you need to do here is let your blockNode know what taxi is.
To do this, you have to pass it in:
First establish the function correctly:
func interact(taxiNode : TaxiNode) {
taxiNode.physicsBody!.applyForce(CGVectorMake(0, 400))
}
Then when you need to interact:
blockNode.interact(taxiNode)
Make sure you fix your protocol to handle this.
protocol InteractiveNode {
func interact(taxiNode:TaxiNode)
}