Going to the previous scene SpriteKit - swift

I'm making a game. On the main menu I have a button for "Start Game" "High Scores" "Options" "Store".
I have various scenes for the different levels. Each of those scenes has a "pause button" which takes you to the "Options" scene. The Options scene has a "BACK" button.
How do I get that "BACK" button to return to what have scene called it?
Right now the "BACK" button only has the ability to return to the menu ie SceneSkip in my code, regardless of what scene called it:
func back(){
let sceneSkip = SceneSkip(fileNamed: "SceneSkip")
sceneSkip?.scaleMode = .aspectFill
self.view?.presentScene(sceneSkip!, transition: SKTransition.fade(withDuration: 0.5))
}

So I've been working on a game in my spare time and I have solved pretty much exactly what you're doing. Here are a couple things I learned in the process.
1) When you present a scene, your scene's didMove(to: view) method is going to be called. Usually you do your initialization in that method so if you are just pausing, and you present the scene again, you're likely going to mess up the game state.
2) Because of number 1, you can't really present a new scene each time the user wants to pause.
So... the solution I've been using, which is the result of a number of google searches, experimenting and etc, is to actually use a separate view for all my popup/pause type scenes. I set it up like this in my GameViewController viewWillLayoutSubviews where skView is the main view:
let psize = view.bounds
popup = SKView.init(frame: psize)
skView.addSubview(popup!)
popup?.allowsTransparency=true
popup?.isHidden=true
Then, later when any scene anywhere in the game wants to add a popup, I added the following functions:
func showPopupScene(_ scene : SKScene) {
(self.view as? SKView)?.scene?.isPaused = true
popup?.isHidden=false
popup?.presentScene(scene)
}
func closePopup() {
popup?.isHidden=true
if let v=view as? SKView {
(v.scene as? PopupNotify)?.onPopupClosed()
v.scene?.isPaused=false
}
}
Now, any scene can create a scene, and show it as a popup with showPopupScene. The popup scene then needs to call closePopup and the game returns to the scene where it left off.
Other items:
I wanted my game scenes to behave correctly with pausing popups as well as when they are paused from coming out of background etc... so I override isPaused:
override var isPaused : Bool {
get {
guard let v = self.view?.window?.rootViewController as? GameViewController else {
return super.isPaused
}
guard let p = v.popup else { return super.isPaused }
return super.isPaused || !p.isHidden
}
set(newPaused) {
super.isPaused = newPaused
physicsWorld.speed = newPaused ? 0 : 1.0
}
}
Also, the PopupNotify protocol for my scenes helped where I wanted scenes to be aware that the popup was closed in case they needed to make any changes according to whatever the popup was showing.
Hope I'm not forgetting anything, but the combination of these additions has provided pretty easy popup/pause management.

I think that your are going about "levels" in a difficult way.
What I suggest you do is...
Have a scene for your menu which transitions to your GameScene.
In your GameScene you load all generic objects that are common to the game regardless of the level (such as gameHud, score labels, pause buttons, options buttons etc.)
you then load your level scene into GameScene based on a parameter (levelID).
this way none of your generic objects have to be created in multiple scenes greatly reducing the chance of redundancy errors.
Completely optional portion
I don't like to switch scenes in the middle of a game for pausing or options. it creates to many opportunities for something to go wrong (failing to load data, failing to save locations, score, animations etc.).
I create my pause and option dialogs as SKSpriteNodes and pause the game and display the dialog over top of the GameScene. That way when I am done with the dialog I can just remove it, and unpause the game and everything goes back to how it was without having to load the scene all over.

Related

How to set an action to menubar icon on swipe gesture in swift/SwiftUI

I'm trying to get a small test app I'm working on to pop up a mini window with a swiftUI view when the menubar icon is swipe down on. Rather than clicking on the button for the window to pop up, a two finger swipe down as if the user pulled down on the icon for the window to show up.
I'm still a bit new to swift but I have the following so far in the AppDelegate.
if let button = statusBar.button{
let iconImage = NSImage(named: NSImage.Name(("iconPic")))
button.image = iconImage
button.action = #selector(self.handleOpenPlayerDescriptionWindow(_:))
button.sendAction(on: .swipe)
// button.sendAction(on: .leftMouseDown)
}
#objc func handleOpenPlayerDescriptionWindow(_ sender: NSMenuItem){
//code to show swiftUI Window
...
}
The pop-up window is simply just a placeholder Text view with an NBA player's game stats. I've commented out the sendAction with 'leftMouseDown' in the code above because that works perfectly fine, but when I change it to 'swipe' as a sendAction, it does nothing. I've also tried 'beginGesture', 'endGesture', and 'gesture' but all didn't work, the closest success was 'leftMouseDragged' but not really what I'm going for.
I'm using SwiftUI and not storyboard if maybe that's the issue
There is limitation of this method, as documented only specific masks are checked by this method, swipe is not.
> Declaration
>
> func sendAction(on mask: NSEvent.EventTypeMask) -> Int Discussion
>
> You use this method during mouse tracking when the mouse button
> changes state, the mouse moves, or if the cell is marked to send its
> action continuously while tracking. Because of this, the only bits
> checked in mask are NSLeftMouseDownMask, NSLeftMouseUpMask,
> NSLeftMouseDraggedMask, and NSPeriodicMask, which are declared in the
> NSEvent class reference.

How do I end a game once a condition has been met?

I'm making a battleship game on Xcode 11 using Storyboards, and I've pretty much done everything except a Game Over screen and stop registering the guesses once the counter reaches 0. This is also my first time making a game on my own. Here's one of the IBActions I made for the buttons:
#IBAction func square1Button(_ sender: Any) {
if randomNum == 1 {
square1.image = UIImage(named: "ShipPatrolHull")
} else {
guesses -= 1
Turns.text = String(guesses)
if guesses == 0 {
}
}
}
The if statement in the else statement is empty because I don't know what to add there to make the variable guesses stop going down and to make a Game Over screen to show. I've also added a Storyboard called GameOver and is linked back to the main storyboard.
You could display a Game Over UIAlertController and then have the game reset when they select "Play Again." However, you mention that you already have another Storyboard for a separate Game Over screen. All you need to do is present that Game Over view controller. Make sure to set the storyboard identifier for the view controller you are trying to present so you can access it in code. See below for guidance,
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = self.storyboard.instantiateViewController(withIdentifier: "GameOverView")
From here you can easily present it.
self.present(controller, animated: true, completion: nil)
Lastly, to make the guesses stop going down, reorganize your if-statements in order to only subtract one from the guesses if the guesses value is not equal to zero, otherwise... game over.
if guesses != 0 {
Turns.text = String(guesses)
guesses -= 1
} else {
//Game over, reset, etc.
}

SKView.presentScene(scene) doesn't work but SKView.presentScene(scene, transition) do work

I'm building a game. With a main ViewController and sub controller for each scene: the start screen and the game level
At startup I present the StartScreen. Then When the play button is pressed I present the Game scene (and add his controller)
Once the game is over I present the Start screen.
Problem : the start screen doesn't appear and the game level stays in place.
The code is actually called and the SKView.scene hold a reference to the right one (start screen). but nothing change visually.
public func gameEnded() {
print("game ended", skView.scene)
gameSceneController.willMove(toParent: nil)
gameSceneController.removeFromParent()
skView.presentScene(startScene)
print(skView.scene)
}
However, if I add a transition parameter to the presentScene method everything works as intended... The start screen replace the game scene.
public func gameEnded() {
print("game ended", skView.scene)
gameSceneController.willMove(toParent: nil)
gameSceneController.removeFromParent()
skView.presentScene(startScene, transition: .crossFade(withDuration: 0.2)) //the ONLY difference in the whole code base
print(skView.scene)
}
Any idea what could be the reason?
Nota : I'm currently using Xcode 11 beta 7

Skscene transitioning in Swift

In essence, I have a menu scene and level scene and I want to be able to transition back and forth. If you look here you can see the first present scene which starts off showing the menu.
if let view = self.view as! SKView? {
let testLevel = LevelMenu()
testLevel.size = self.view.frame.size
testLevel.initializeMenu(NumberOfLevels: 6, Restricted: true, MenuNumber: 1)
testLevel.scaleMode = .fill
view.presentScene(testLevel)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
(LevelMenu is a custom class which extends SKScene, initialize menu is a method which I use to set up the specifics of the scene.)
Now inside the LevelMenu class if a couple conditions are met the following code is called to go into a level.
if let view = self.view as SKView? {
view.presentScene(currentLevel)
}
(currentLevel is a variable which stores the current Level object.)
Also, when the levels are set up they have the same code as the menu when its set up.
let TempLevel = Level()
TempLevel.size = self.size
TempLevel.setUp(package: menuNumber, numberInPackage: i, locked:
restricted, menu: self)
TempLevel.name = "Package \(menuNumber), Level \(i)"
TempLevel.scaleMode = .fill
levels.append(TempLevel)
(Level is another custom SKScene, setUp() is a method which sets it up, and levels is an array which holds all the levels.)
To transition back to the menu I call this code again just with the menu which has been passed through to the class.
if let view = self.view as SKView? {
view.presentScene(currentLevel)
}
This is the issue: When I call the transition inside the menu to go to a level it works perfectly. Transitions animates works perfectly fine; however, when I call the presentScene() to go back, it will print out the text I put in the viewDidAppear() except it still shows the level Skscene and the level Skscene still has working buttons.
I tried removing scenes from their parents and it seems to not work and from what I have read be a bad idea. I theorize that the menu scene is loaded the whole time and just blocked and when I call presentScene it says "hey I'm here." but fails to realize I want it up front. If there could be some way to stop presenting the level scene and just have the menu show up that would be great since once you finish a level it shouldn't just sit around.
Thank you in advance for all help offered.
So though more investigation and debugging I found out that the scene was actually being loaded but for some reason was not being displayed. After playing around with the visual settings I thought about making sure the size of the scene was the proper size and I found that by setting the size of the menu before going back it now shows the menu.
I hope this question helps someone out there who ever encounters a similar issue, thanks internet for nothing.

Swift: Long press cancelled by movie playing. How do I persist the long press?

I'm building some functionality similar to SnapChat. Press and hold on a view, it brings up a movie to play, then returns to the main view controller when the movie finishes (currently working), or when the user picks up their finger (not working. That's what this question is about).
My problem lies in the IBAction, when the video comes up, the UIGestureRecognizerState changes from Began to Cancelled.
I'd like for the state to not change until the user lifts their finger, which should register UIGestureRecognizerState as Ended
Any tips on how to do that would be awesome. Thanks!
class ViewController: UIViewController {
var moviePlayer: MPMoviePlayerViewController!
#IBAction func handleLongPress(recognizer: UILongPressGestureRecognizer) {
println("\(recognizer)")
if recognizer.state == UIGestureRecognizerState.Began {
playVideo()
}
if recognizer.state == UIGestureRecognizerState.Ended {
self.moviePlayer.moviePlayer.stop()
}
}
func videoHasFinishedPlaying(notification: NSNotification){
println("Video finished playing")
}
func playVideo() {
// get path and url of movie
let path = NSBundle.mainBundle().pathForResource("IMG_8602", ofType:"MOV")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = MPMoviePlayerViewController(contentURL: url)
presentMoviePlayerViewControllerAnimated(moviePlayer)
// construct the views
moviePlayer.view.frame = self.view.bounds
self.view.addSubview(moviePlayer.view)
// remove controls at top and bottom of video
moviePlayer.moviePlayer.controlStyle = MPMovieControlStyle.None
NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoHasFinishedPlaying:",
name: MPMoviePlayerPlaybackDidFinishNotification, object: nil)
}
}
Edit: One possible solution
So this was a total fluke, but I figured out how to do this in this context.
I simply removed the line presentMoviePlayerViewControllerAnimated(moviePlayer) from my playVideo function.
Not entirely sure why that worked but it worked for my context.
This is ONE possibility but I'm open to better suggestions.
Add a 'master view' on top of where you need the touch event
Add the gesture recognizer to that view
Handle the logic within the gesture as you are now, but the subview that needs to be added needs to be added below the view thats receiving the touch.
You can now get the .ended state when the user lifts their finger.
Word of caution. If your view has other touch events make sure this 'master view' is not over top of them because it will intercept those events and hog it for its gesture (unless you set it up to recognize them simultaneously). If thats the case, you can make this 'master view' only a small port of the screen where you need to listen for the touch event without interruption