Gamescene Share Button

GameScene Share Button

I ran into this same issue two days ago. I fixed it using this stackoverflow answer

I kept getting the same error and it was because rootViewController was not the current top viewController in the hierarchy. It was trying to use my initial viewController when I wanted the viewController in control of the current SKScene.

Hope this helps

    func shareGame() {
if var top = scene?.view?.window?.rootViewController {
while let presentedViewController = top.presentedViewController {
top = presentedViewController
}
let activityVC = UIActivityViewController(activityItems: ["I am playing Crashbox! Check it out."], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = view
top.present(activityVC, animated: true, completion: nil)
}
}

How to fix the share button on SpriteKit Swift when Players plays the game again Share Button shows up on the game scene?

The first rule when making a SpriteKit game is to try to not use UIKit.
All of your UI should be created directly in the SKScenes using SpriteKit APIs (SKLabelNodes, SKSpriteNodes, SKNodes etc).
There are exceptions to this, like maybe using UICollectionViews for massive level select menus, but basic UI should never be done using UIKit.

So you should be making your buttons using SKSpriteNodes and add them directly to the SKScene you want.

There are plenty of tutorials to google on how to do this, a simple one is this

https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/

For a more complete one check out apples sample game "DemoBots" or have a look at these cool projects on gitHub.

https://github.com/nguyenpham/sgbutton

https://github.com/jozemite/JKButtonNode

In SpriteKit games you only tend to have 1 view Controller (GameViewController) which will present all your SKScenes (GameScene, MenuScene etc). If you use UIKit elements they get added to the GameViewController, and therefore they will be shown in all scenes (like your share button).

self.view?.addSubview(shareButton) // self.view is your GameViewController

If you have a game with more than 1 SKScene and quite a few buttons this will be madness to manage.

On the other hand if you use SpriteKit APIs, and because every SKScene starts in a clean state when you enter it, you dont have to worry about any of this.

If you insist on using UIKit than you will have to either remove or hide the share button before you transition to game scene and unhide it or add it again when you want to.

 shareButton.isHidden = true

or

 shareButton.removeFromSuperview()

Finally as good practice your properties should start with small letters not capital letters

shareButton = ...

Hope this helps

SpriteKit share button not working

I believe the issue is that you are creating a new GameViewController and because you are doing that it is crashing. Looks like it has something to do with the fact it can't find a SKView because it isn't being loaded from a storyboard.

With that being said you don't want a new GameViewController you want to use the current one. There are a couple of ways to go about this. The simplest could be this...

-(IBAction)runShareGame:(id)sender{

UIViewController *vc = self.view.window.rootViewController;

if ([vc isKindOfClass:[GameViewController class]])
{
GameViewController *myViewController = (GameViewController *)vc;
[myViewController shareGame];
}
}

However if you are using a navigation controller or something like that you may not get your GameViewController back and instead get a UINavigationController.

Best approach would be to create a delegate and assign it when you create your scene. This question has a bunch of different approaches to what you are trying to do. How do I present a UIViewController from SKScene? however your app is crashing because you are recreating the GameViewController which makes your question a bit different. One person does mention the delegate approach but you would be creating the protocol on Scene not the view controller.

Edit

https://stackoverflow.com/a/21917919/851041

Here is a good use of the delegate to comunicate back to your View Controller.

Hopefully that was helpful.

Adding button to GameScene.swift programatically and transitioning scenes when pressed

What do you mean transition to a new view?. When making SpriteKit games we tend to transition between SKScenes and not views. So do not try to create different views or viewControllers for each scene, just transition between SKScenes directly.

In regards to buttons, there is plenty tutorials available. You basically create a SKSpriteNode as a button and look for it in touchesBegan and do something when its pressed.

This is a simple example here

https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/

Essential you can create a button subclass similar to this.

class Button: SKNode {
let defaultButton: SKSpriteNode
let activeButton: SKSpriteNode
var action: () -> ()

init(defaultButtonImage: String, activeButtonImage: String, buttonAction: @escaping () -> ()) {
defaultButton = SKSpriteNode(imageNamed: defaultButtonImage)
activeButton = SKSpriteNode(imageNamed: activeButtonImage)
activeButton.isHidden = true
action = buttonAction

super.init()

isUserInteractionEnabled = true
addChild(defaultButton)
addChild(activeButton)
}

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
activeButton.isHidden = false
defaultButton.isHidden = true
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)

if defaultButton.contains(location) {
activeButton.isHidden = false
defaultButton.isHidden = true
} else {
activeButton.isHidden = true
defaultButton.isHidden = false
}
}
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)

if defaultButton.contains(location) {
action()
}

activeButton.isHidden = true
defaultButton.isHidden = false
}
}
}

and than in your relevant SKScene add your buttons

 class StartScene: SKScene {

override func didMove(to view: SKView) {

let playButton = Button(defaultButtonImage: "button", activeButtonImage: "button_active", buttonAction: loadGameScene)
playButton.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(playButton)
}

func loadGameScene() {
let scene = GameScene(...)
let transition = SKTransition....
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
}

You can also check out Apples sample game DemoBots for another button class example using protocols.

Hope this helps

SpriteKit and SwiftUI. GameScene func not working from UI button

Two problems:

  1. Your scene is a computed property so you are recreating the scene every time you try use it in SpriteView.
  2. You are creating a new scene when you try add the ball. This should be added to the current scene, not a new instance.

Change scene to this:

let scene: GameScene = {
let scene = GameScene()
scene.size = CGSize(width: 800, height: 930)
scene.scaleMode = .fill
scene.backgroundColor = .red

return scene
}()

And change the action on the pause button to this:

scene.ball()
print("Image tapped!")

Side note: with a fixed scene size like this, you got to be careful - you can only really see the play/pause buttons on iPad screens since the scene size is so big. Consider keeping it at a specific aspect ratio instead, then fitting it to the screen.

Changing Scenes In Spritekit, Detecting Touches on Buttons

The touches parameter inside of your touchesBegan(_: with:) method is of type Set<UITouch>, i.e. a set of touches. As stated in the Apple Documentation for Sets, a set is used when the order of the items in the collection are not a concern. Therefore, you cannot assume that the first item in touches is the first touch in the set. Knowing this, I recommend refactoring your code inside of HomeScene.swift to the following:

import SpriteKit

class HomeScene: SKScene {

private var playButton: SKSpriteNode?

override func sceneDidLoad() {
self.playButton = self.childNode(withName: "//starButton") as? SKSpriteNode
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}

func touchDown(atPoint pos: CGPoint) {
let nodes = self.nodes(at: pos)

if let button = playButton {
if nodes.contains(button) {
let gameScene = SKScene(fileNamed: "GameScene")
self.view?.presentScene(gameScene)
}
}
}

The touchDown(atPoint:) method does all the heavy lifting and touchesBegan(_: with:) is freed up to listen for touch events. Another thing to note when calling childNode(withName:):

  • Adding / before the name of the file starts searching for nodes beginning at the root of your file hierarchy.
  • Adding // before the name of the file starts searching for nodes beginning at the root of your file hierarchy, then recursively down the hierarchy.
  • Adding * before the name of the file acts as a character wildcard and allows you to search for file names that are wholly or partially unknown.

Making a button not hidden in GameScene

You either can create a delegate for you scene which will call appropriate method on gameOver and do something like

// in the view controller
func gameSceneDidSendGameOver() {
self.segueToMainMenu.hidden = false
}

//in the scene
var gameOverDelegate : GameOverDelegate?

func gameOver() {
gamOverDelegate?.gameSceneDidSendGameOver()
}

Or do it a little bit of ugly way like:

func gameOver() {
let gvc = self.viewController as GameViewController
gvc.segueToMainMenu.hidden = false
}

Edit: Declaration of the delegate protocol.

protocol GameOverDelegate {
func gameSceneDidSendGameOver()
}

class GameViewController : ViewController, GameOverDelegate {
...
func gameSceneDidSendGameOver() {
self.segueToMainMenu.hidden = false
}
}

linking GameViewController.swift to GameScene.swift

This seems to be quite a common problem people have with SpriteKit games so lets go through the difference between SpriteKit games and UIKit apps.

When you make a regular UIKit app, e.g. YouTube, Facebook, you would use ViewControllers, CollectionViews, Views etc for each screen/menu that you see (Home screen, Channel screen, Subscription channel screen etc). So you would use UIKit APIs for this such as UIButtons, UIImageViews, UILabels, UIViews, UICollectionViews etc. To do this visually we would use storyboards.

In SpriteKit games on the other hand it works differently. You work with SKScenes for each screen that you see (MenuScene, SettingsScene, GameScene, GameOverScene etc) and only have 1 ViewController (GameViewController). That GameViewController, which has a SKView in it, will present all your SKScenes.

So we should add our UI directly in the relevant SKScenes using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes, SKNodes etc. To do this visually we would use the SpriteKit scene level editor and not storyboards.

So the general logic would be to load your 1st SKScene as usual from the GameViewController and than do the rest from within the relevant SKScenes. Your GameViewController should basically have next to no code in it beyond the default code. You can also transition from 1 scene to another scene very easily (GameScene -> GameOverScene).

If you use GameViewController for your UI it will get messy really quickly if you have multiple SKScenes because UI will be added to GameViewController and therefore all SKScenes. So you would have to remove/show UI when you transition between scenes and it would be madness.

To add a label in SpriteKit it would be something like this

 class GameScene: SKScene {

lazy var scoreLabel: SKLabelNode = {
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.text = "SomeText"
label.fontSize = 22
label.fontColor = .yellow
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return label
}()

override func didMove(to view: SKView) {

addChild(scoreLabel)
}
}

To make buttons you essentially create a SKSpriteNode and give it a name and then look for it in touchesBegan or touchesEnded and run an SKAction on it for animation and some code after.

enum ButtonName: String {
case play
case share
}

class GameScene: SKScene {

lazy var shareButton: SKSpriteNode = {
let button = SKSpriteNode(imageNamed: "ShareButton")
button.name = ButtonName.share.rawValue
button.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return button
}()

override func didMove(to view: SKView) {

addChild(shareButton)
}

/// Touches began
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

for touch in touches {
let location = touch.location(in: self)
let node = atPoint(location)

if let nodeName = node.name {
switch nodeName {
case ButtonName.play.rawValue:
// run some SKAction animation and some code
case ButtonName.share.rawValue:

let action1 = SKAction.scale(to: 0.9, duration: 0.2)
let action2 = SKAction.scale(to: 1, duration: 0.2)
let action3 = SKAction.run { [weak self] in
self?.openShareMenu(value: "\(self!.score)", image: nil) // image is nil in this example, if you use a image just create a UIImage and pass it into the method
}
let sequence = SKAction.sequence([action1, action2, action3])
node.run(sequence)

default:
break
}
}
}
}
}

To make this even easier I would create a button helper class, for a simple example have a look at this
https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/

You can also check out Apple's sample game DemoBots for a more feature rich example.

This way you can have things such as animations etc in the helper class and don't have to repeat code for each button.

For sharing, I would actually use UIActivityController instead of those older Social APIs which might become deprecated soon. This also allows you to share to multiple services via 1 UI and you will also only need 1 share button in your app. It could be a simple function like this in the SKScene you are calling it from.

func openShareMenu(value: String, image: UIImage?) {
guard let view = view else { return }

// Activity items
var activityItems = [AnyObject]()

// Text
let text = "Can you beat my score " + value
activityItems.append(text as AnyObject)

// Add image if valid
if let image = image {
activityItems.append(image)
}

// Activity controller
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)

// iPad settings
if Device.isPad {
activityController.popoverPresentationController?.sourceView = view
activityController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
activityController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
}

// Excluded activity types
activityController.excludedActivityTypes = [
UIActivityType.airDrop,
UIActivityType.print,
UIActivityType.assignToContact,
UIActivityType.addToReadingList,
]

// Present
view.window?.rootViewController?.present(activityController, animated: true)
}

and then call it like so when the correct button was pressed (see above example)

openShareMenu(value: "\(self.score)", image: SOMEUIIMAGE)

Hope this helps



Related Topics



Leave a reply



Submit