So when you create a project using the SpriteKit template. You have your View controller and your SKScene.
From my view controller I start my game with the code given by default and present the scene.
In my TCAViewcontroller.m
- (IBAction)startGame:(id)sender {
NSLog(#"Start Game triggered");
mainPageImage.hidden = 1;
// Configure the view.
// Configure the view after it has been sized for the correct orientation.
SKView *skView = (SKView *)self.view;
if (!skView.scene) {
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
TCAMyScene *theScene = [TCAMyScene sceneWithSize:skView.bounds.size];
theScene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:theScene];
}
}
When the user loses in the game I would like to dismiss the scene and go back to my view controller I have. I can't seem to find anything with my searches to going back to the original view controller, just pushing to a game over scene. But I don't want to push to another scene, just dismiss the current scene and go back to my TCAViewController. Please answer using code for clarification Thanks
Your scene needs to offer a line of communication to your controller to indicate that is finished. You could, for example, create a delegate protocol and corresponding property in your scene. An example:
#protocol TCAMySceneDelegate;
#interface TCAMyScene : SKScene
#property (nonatomic, weak> id<TCAMySceneDelegate> delegate;
#end
#protocol TCAMySceneDelegate <NSObject>
- (void)mySceneDidFinish:(TCAMyScene *)gameScene;
#end
Then, in the .m of your TCAMyScene
- (void)endTheGame {
// Other game-ending code
[self.delegate mySceneDidFinish:self];
}
In your view controller, set itself as the delegate for your scene and implement the method:
- (IBAction)startGame:(id)sender {
// Other code
TCAMyScene *theScene = [TCAMyScene sceneWithSize:skView.bounds.size];
theScene.scaleMode = SKSceneScaleModeAspectFill;
theScene.delegate = self;
// Other code
}
- (void)mySceneDidFinish:(TCAMyScene *)myScene {
// logic for dismissing the view controller
}
Related
New to iOS development & Objective-C and am a little unsure how to go about solving this issue.
I'm working on a project that works like a video player. There are two ViewControllers:
MenuViewController (has a list of titles that act as buttons)
PlayerViewController (view where video plays)
In the MenuViewController, I want to be able to click onto a button (video title) :
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
[self presentModalViewController:vc animated:YES];
}
and have an action that's currently defined in the PlayerViewController automatically execute as soon as its loaded.
Is there a way to have a button in one ViewController call the action in another ViewController as soon as that second ViewController has loaded?
Thanks!
The right way to solve this problem will be set up a delegate pattern between the two view-controllers.
Example:
#protocol PlayerViewControllerDelegate<NSObject>
{
-(void)playerViewControllerViewDidLoad:(PlayerViewController *)playerVC;
}
Then, in PlayerViewController.h create a weak delegate variable:
#property (nonatomic, weak) id<PlayerViewControllerDelegate> delegate;
In PlayerViewController.m, notify the delegate on viewDidLoad:
-(void)viewDidLoad {
[super viewDidLoad];
[self.delegate playerViewControllerViewDidLoad:self]
}
In MenuViewController:
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
vc.delegate = self
[self presentViewController:vc animated:YES];
}
Finally, implement the protocol in MenuViewController, and you're ready to go:
#interface MenuViewController : UIViewController<PlayerViewControllerDelegate>
#end
#implementation MenuViewController
-(void)playerViewControllerViewDidLoad:(PlayerViewController*)playerVC
{
[playerVC playVideo];
}
#end
It is usually not good practice to have one controller controlling the behavior of another. Instead, you can have the MenuViewController create the PlayerViewController and set variables so the new player knows how to behave based on its internal state.
There are several UIViewController methods that you can override in order to perform actions during the controller's lifecycle. Based on your question it seems like you want the viewDidLoad method.
I am not sure how you are passing videos between controllers, but if you were using URLs (to Youtube videos for example) then you could do something like the following:
// MenuViewController.m
- (IBAction)videoOne:(id)sender {
PlayerViewController* vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
// Pass any necessary data to the controller before displaying it
vc.videoURL = [self getURLForSender:sender];
[self presentViewController:vc animated:YES completion:nil];
}
// PlayerViewController.m
- (void)viewDidAppear {
// View did appear will only be called after the controller has displayed
// its primary view as well as any views defined in your storyboard or
// xib. You can safely assume that your views are visible at this point.
[super viewDidAppear];
if (self.videoURL) {
[self playVideo];
}
}
You would need to define the property videoURL on PlayerViewController and expose it publicly. If you are using local files (such as from the user's photo storage) you could pass the video to the new view controller before presenting it.
There are other UIViewController lifecycle methods that you can override. They are explained in more depth in this post as well as Apple's UIViewController Documentation.
Edit: changed presentModalViewController:animated: to presentViewController:animated:completion: and changed viewDidLoad to viewDidAppear as it seems more appropriate for the question.
presentModalViewController:animated: is deprecated. Use presentViewController:animated:completion: instead.
In the completion Block, you can call a method on the presented view controller:
[self presentViewController:otherVC
animated:YES
completion:^{ [otherVC startPlaying]; }];
The completion is run after the presented controller's viewDidAppear.
I am developing a game and would like to have one sprite-kit scene have its own view controller (a conclusion I've reached after much debate), and would like to avoid using storyboard (I can accomplish what I want with storyboard, but would like to learn how to do it without storyboard).
In my main view controller, after clicking a button, I have the following code
MyViewController2 *test = [[MyViewController2 alloc] init];
test.view = [[SKView alloc] init];
[self presentViewController: test animated:YES completion:nil];
However, this just transitions a grey screen with nothing on it (did not overwrite "-(id)init" yet). The following code gets called if I overwrite the "-(id)init" method in MyViewController2:
-(id)init
{
NSLog(#"init overwrite works");
SKView * skView = (SKView *)self.view;
SKScene2 *scene = [SKScene2 sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeFill;
[skView presentScene:scene];
return self;
}
but it won't display because I get this error:
-[UIView presentScene:]: unrecognized selector sent to instance
I know what this error means (the presentScene can only be executed by skView and can't be executed by UIView), but even though when I create the ViewController and in its init I set views to be only SKView, I still get this error. How can I fix this? Thanks for your time and help.
The view property is managed by the UIViewController and represents the root view. You cannot simply assign your own view at any point.
Option 1
If you wish to replace the view property with your own view you can override UIViewController's loadView method and assign the view there. It is documented in the class reference for this method:
The view controller calls this method when its view property is
requested but is currently nil. This method loads or creates a view and assigns it to the view property
...
You can override this method in order to create your views manually.
If you choose to do so, assign the root view of your view hierarchy to
the view property
So it will look something like this:
- (void)loadView
{
self.view = [[SKView alloc] init];
}
Option 2
You can add your SKView as a subview.
Add a property to your view controller so you can access it when necessary:
#property (strong, nonatomic) SKView *skView;
Initialize and add it as a subview when the view is loaded:
- (void)viewDidLoad
{
[super viewDidLoad];
self.skView = [[SKView alloc] init];
[self.view addSubview:self.skView];
SKScene2 *scene = [SKScene2 sceneWithSize:self.skView.bounds.size];
scene.scaleMode = SKSceneScaleModeFill;
[self.skView presentScene:scene];
}
I am new to the spritekit system. However, I have some questions. I have an app that has a menu screen, instructions page, etc..., none of which recquire me loading a skcene. However, I want to be able to load my "Game" scene on the press of a button. How would I do that regarding code?
FIXED:
I just did not check the IB settings for one of my views: my view type should be SKView.
Create new SKView in your view controller's -viewDidLoad: method
SKView *spriteView = (SKView *) self.view;
Then create new SKScene class in a separate file
#interface GameScene : SKScene
#end
Return to your previous view controller and place this code in your
button's event handler
GameScene* scene = [[GameScene alloc] initWithSize:CGSizeMake(768,1024)];
SKView *spriteView = (SKView *) self.view;
[spriteView presentScene: scene];
More info in Apple's documentation.
When Im finished with my SKScene is there a way to dismiss the SKScene from within my SKScene class?
If not back in my Viewcontroller where I present my SKScene [skView presentScene:theScene]; is there a way to restart the scene or remove in from my SKView?
The SKScene Class Reference and SKView Class Reference are no help.
Update:
The following code removes my scene from my SKView [yourSKView presentScene:nil]; but when Im back in my view controller the scene is still running in the background. I can always pause it when the game is over and I'm sent back to my view controller(menu) but I'm wondering is there another method other then pausing it like completely removing it?
-(void)endTheGame {
[highscoreLabel removeFromSuperview];
NSLog(#"Game Over");
//would like to end here before calling the below method in my view controller
[self.delegate mySceneDidFinish:self];
}
Having met a similar issue I stumbled around your question, and since nobody gave a decent answer here's how I solved it:
in my scene I called both lines
[self removeFromParent];
[self.view presentScene:nil];
in my controller (the controller that displays the SKScene) I changed the template code from Apple, which was creating and presenting my scene from viewDidLoad in order to create and present my scene in viewWillAppear, only if the view's scene is nil
here's my Swift code, even if you're using Objective C you'll understand what it does; the key line being the "if skView.scene == nil" test :
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let skView = self.view as SKView
if skView.scene == nil {
let scene = GameScene(size:skView.bounds.size)
scene.controller = self
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
You can use:
[yourSKView presentScene:nil];
to remove the scene.
"You can't go "Back to the View Controller" from a scene. The scene is a View, the view controller controls and displays views. Use the view controller to change views. Remember the view controller itself is not a view." -Wharbio
Best solution here is to create another View Controller. This view controller will be my menu. Then the other viewcontroller will act as a host for the skscene.
In my situation I then use my menu viewcontroller to dismiss the viewcontroller displaying in the skview.
From within your SKScene, you can simply do [self.view presentScene:aNewScene] to present another scene
I completely remove scenes by using this block in my view controller:
Obviously you will need to declare your Size, and "newSKview"
SKView * skView = (SKView *)self.view;
SKScene *newScene = [[newSKView alloc]initWithSize:size];
newScene.scaleMode = SKSceneScaleModeAspectFill;
SKScene *oldScene=(skView.scene);
[oldScene removeFromParent];
[skView presentScene:newScene];
This works fine for me. None of my Scenes are retained or strong.
I prefer to use an additional UIViewController for SKView and Notification Center in where the UIViewController made SkScene.
The callback function is
#objc func cb_dismissingVC(_ notificaiton: Notification) {
self.dismiss(animated: true, completion: nil)
}
Maybe my variant will be helpful:
[[self.scene childNodeWithName:#"YourChildNodeName"] removeFromParent];
I have two parts to my project. Part one is made in the storyboard, and the second is an SKView. How can I go from my second part in the SKView back to the main UIView?
Create custom ViewController: File - New - File - Objective-C class. Enter a name: GameSceneViewController. Subclass of UIViewController.
Override viewWillAppear method:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
GameScene *scene = [[GameScene alloc] initWithSize:self.view.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[self.skView presentScene:scene];
}
skView property will be an IBOutlet for SKView. Also make sure you've imported SpriteKit framework:
#import <SpriteKit/SpriteKit.h>
Add new ViewController to the storyboard.
In the Identity inspector enter a custom class for the added ViewController: GameSceneViewController
Add a subview to a root view:
In the Identity inspector enter a custom class for the added View: SKView
Create an IBOutlet for the added SKView.
Now you should be able to use segues for switching between ViewControllers