I have a scene that is calling the next one using a transition like this:
SKTransition *transition = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:0.5];
SKView *skView = (SKView *)self.view;
SKScene * scene = [[GameOverScene alloc] initWithSize:self.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene transition:transition];
The elements that compose GameOverScene (buttons, images, etc.) are added on its init method.
The problem is that the transition is not seen. One scene immediately cuts to the other one.
I guess that transition happens before the next scene has a chance to build its elements.
I have tried to move the creation of the next scene to didMoveToView without success.
For test purposes I have tried to delay the presentScene line in times even bigger than 2 seconds. When I do that I barely see the end frames of the transition.
How do I do that? What is the correct way of building the next scene and doing a transition that works.
If the new scene is particular resource heavy, i.e., requires a lot of texture loading, that will delay any frame rendering. If the texture loading takes longer than your transition time, you will miss all of it because the first frame that will be displayed has been rendered after your transition is finished.
I ran into this as well, although I was better able to determine the root cause because I had a transition of 2 seconds of which only the last 0.5 seconds were shown.
How to fix this? Preload your textures. See Apple Docs.
+ (void)preloadTextures:(NSArray *)textures withCompletionHandler:(void (^)(void))completionHandler
I should emphasize the differences between SKSprite, SKTexture and your "image.png".
SKSprite draws itself based on its texture property (or background color), and size. You can use one SKTexture for many sprites. In turn, an image file ("image.png") can supply multiple SKTexture objects.
Important: you want to use textures from the array of SKTexture objects passed to the method above to actually benefit from the texture loading. This will require some form of texture management.
If your problem is indeed texture related, let me know if you need me to expand on the texture management. A related post (which may be a bit dry) can be found here: SO sktexture-preloading.
I wrote exactly the same code in my app to show the game over scene with one minor difference. My code is as follows:
- (void)showGameOverScene
{
SKTransition* reveal = [SKTransition doorsCloseVerticalWithDuration:0.5];
SKScene* gameOverScene = [[RSGameOverScene alloc] initWithSize:self.size];
[self.view presentScene:gameOverScene transition: reveal];
}
And it's been working like a charm. The only difference is that I am not calling:
scene.scaleMode = SKSceneScaleModeAspectFill;
In my RSGameOverScene I set up nodes in -(id)initWithSize:(CGSize)size, and again there is no problems. Everything just works.
I can't see a reason why this line could be a source of a problem but you might want to try commenting it out to see if it helps. However your code looks ok so the source of the problem can be somewhere else.
Related
A SpriteKit game presents MainMenuScene then LevelSelectionScene and finally the GamePlayScene. Once the game is over, in GamePlayScene, the user is transferred back to MainMenuScene like so:
MainMenuScene *newScene = [MainMenuScene sceneWithSize:self.view.bounds.size];
[self.view presentScene:newScene transition:[SKTransition fadeWithDuration:0.5]];
It takes me to the scene but the buttons, that worked previously, don't work anymore. I press them, they seem to do their little animation as if they're being pressed but they don't present me appropriate scenes they're supposed to.
I have a very specific bundle of code activated every time a scene is about to be left and I don't actually know if it influences this whole conundrum:
-(void)willMoveFromView:(SKView *)view
{
/* Remove anything stuck in memory. */
[self removeAllActions];
[self removeAllChildren];
[self removeFromParent];
[self.scene removeAllActions];
[self.scene removeAllChildren];
[self.scene removeFromParent];
//[self.scene.view removeFromSuperview]; <--TOTALLY DESTROYS ALLS SCENES IN THE GAME. Don't use it.
}
Has any had previously working scenes presented as partially working duds?
From what you said looks like the button is triggering the animation but not the action, still not much information to be sure how to exactly fix it, probably the creation of the actions of the button is working. Double check if the part of the code that creates the SKActions is actually running and active.
So I poked around and found out the culprit is in the class I used to create buttons. It's called AGSpriteButton and I highly recommend it (You can find it on github). It has built-in SELECTORS, ACTIONS and BLOCKS functionality offered by default that is very easy to tap into. I'm not sure as to why, but SELECTORS stopped functioning correctly. I just switched to ACTIONS calling my methods & everything works perfectly.
In order to define an rectangular edge, this is the code I wrote:
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
SKNode *edge = [SKNode node];
edge.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self addChild:edge];
}
I want this edge to wrap the whole screen, i.e bottom, top, left and right borders.
I wish that the objects that I add to my scene bounce on all borders. But, those objects only bounce on bottom and top part of the edge.
P.S: The same code, worked about a year ago when SpriteKit GameScene.mclass had -(id)initWithSize:(CGSize)size instead of -(void)didMoveToView:(SKView *)view.
Currently SpriteKit by default loads a scene from .sks file and by default, the scene size is set to 1024x768. That is probably why you are getting unexpected results (self.frame has different size in compare to a view).
There are few things you should keep in mind these days:
When scene is loaded from .sks file, initWithSize: is never called. initWithCoder: is called instead. If you want to use initWithSize: you should create a scene in "old" way - using sceneWithSize:
In initWithSize: the view is always nil, so all the code which requires a view, should be moved to didMoveToView.
In viewDidLoad a final size of a view may not be known yet. A proper implementation of viewWillLayoutSubviews can be used to get around this. Read more here.
What I would suggest you, is to create a scene inside viewWillLayoutSubviews using sceneWithSize: method and initialize it with view.bounds.size. After that, you can create borders like this:
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
Hint: It can be useful to enable visual physics representation. You can do this from view controller:
skView.showsPhysics = YES;
If all this doesn't help, check if your view has correct size. View can be wrongly sized if wrong launch images are supplied.
Hope this helps and make sense.
I have a very simple SpriteKit game written in Swift with two scenes that I am trying to transition between. This code works perfectly:
let skView = self.view as SKView
let scene = GameScene(size: self.scene.size)
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
//This line shows the new scene immediately (as expected)
skView.presentScene(scene)
The trouble comes when I replace the above line of code with this:
let sceneTransition = SKTransition.doorsCloseHorizontalWithDuration(2.0)
skView.presentScene(scene, transition: sceneTransition)
When I do this nothing happens (the current scene remains on screen). I have tried several different transitions, but always with the same result.
Any thoughts?
SOLVED.
It turns out that there is nothing wrong with the code above. The problem was that I was trying to execute it in the update function based on a specific SKPhysicsBody's position. The transition doesn't happen instantaneously so it gave time for update to get called repeatedly which re-triggered the transition over and over again. This resulted in the transition never happening.
The fix was to add a bool to check if I have started the transition, so I only attempt it once.
If anyone else experiences this, you might also want to pause the outgoing scene. Some of the logic in it might be changing the state or interfering with the transition.
try setting
transition.pausesOutgoingScene = YES;
to see if that help,
either use that as a solution, if that's your intent, or set the breakpoints (try update: after the transition is created) and see what's up.
I have a CCB file with a timeline animation in it. I load the file like this.
CCSprite *spriteAnimation = (CCSprite*)[CCBReader load:#"MyGreatAnimation"];
spriteAnimation.paused = TRUE;
At some point later, I add it to the scene and run the animation
[MyScene addChild:spriteAnimation];
CCAnimationManager* animationManager = _deletionAnimaion.userObject;
[animationManager runAnimationsForSequenceNamed:#"Default Timeline"];
This is great. My animation runs. I then remove spriteAnimation from the scene until I need it again.
[spriteAnimation removeFromParent];
.
The problem
I can't figure out how to get the animation to run the next time I add it to the scene.
I've tried:
[animationManager jumpToSequenceNamed:#"Default Timeline" time:0];
And also..
[animationManager runAnimationsForSequenceNamed:#"Default Timeline"];
But the animation doesn't seem to run. If at this point I call:
spriteAnimation.userObject.runningSequenceName
to see the running sequence, it returns NULL.
.
My Question
How do I arbitrarily run a timeline animation repeatedly?
To be clear, I'm not asking about looping the animation. I want to start it from frame 1 whenever I need.
If you need it repeatedly, you shouldn't remove it from the scene:
[spriteAnimation removeFromParent];
Instead, just make the animation invisible for the time being:
spriteAnimation.visible = NO;
Later just make it visible again.
After all when you remove a node it is gone from the scene hierarchy unless you addChild: it back in which I can't see in your code above.
I'm working on a little App, where I load a tilemap ( JSTilemap ) into sprite kit.
This all works fine except that the tile map looks normal and fine in portrait but when I flip to landscape it seems to blow up. I have not added any code just imported JSTilemap header and this code initwithsize.
self.map = [JSTileMap mapNamed:#"tilemap.tmx"];
[self addChild:self.map];
I have also downloaded a simple project with a tile map and when I rotate the size stays the same, only that if i try to mimic the code my tilemap still seems to blow up when I rotate it.
Is there some setting to restrict this that I am missing?
You should use -(id)initWithSize:(CGSize)size instead of -(void)didMoveToView:(SKView *)view
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
JSTileMap* tiledMap = [JSTileMap mapNamed:#"level1.tmx"];
if (tiledMap)
[self addChild:tiledMap];
self.userInteractionEnabled = YES;
}
return self;
}
Also in GameViewController (if you are using default template classes) you should change scene initializing with sceneWithSize
/*
// Create and configure the scene.
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
*/
// Create and configure the scene.
SKScene * scene = [GameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
I assume by "blow up" you mean stretch and/or zoom inappropriately? Try changing your scaleMode on your SKScene to be SKSceneScaleModeAspectFill.
Just so you know, when running your application the iPhone always begins in Portrait mode. This has nothing to do with Sprite Kit. It's about general iOS lifecycle & view issues. Skewing content or loosing content entirely happens because of this. When making a game usually Portrait or Landscape is used. Using them together is pretty rare. To understand why your content is being "stretched" write the following code into your SKScene's initWithSize method:
NSLog(#"In initWithSize, at %0.f wide and %0.f high", size.width, size.height);
Fire the game up & look at the NSLog message. Even though we think we started in Landscape mode (that is if you tilted your phone & waited for the game to load or did that a bit later), the NSLog message displays "In initWithSize, at 320 wide and 568 high" (I'm using an iPhone 5S). That means the scene is being rendered in Portrait mode even if we selected Landscape mode in the Targets/Deployment Info section. We want the width to be more then the height (568 wide and 320 high)! But why is all of this happening? The issue is internally. The iOS app life cycle begins EVERY application in Portrait and ONLY switches to Landscape during the loading process. So the Storyboard loads, it loads that initial View Controller and it still thinks it's in Portrait orientation. This view controller will automatically load the View object (for Sprite Kit that's the SKView). We're still in Portrait orientation. The View object loaded and that will cause viewDidLoad to be called back in the View Controller. We're still in Portrait orientation. But in the viewDidLoad method that is where we create our scene (SKScene) passing in the current height & width. Still Portrait orientation. All of this is taking a fraction of a second, but it's only after this that the application realizes it's supposed to be in Landscape and the View Controller switches to Landscape taking the View with it. Depending on your settings, what the Scene is going to do is trying to react to this by scaling up to fill the available space and we're going to loose content because internally it's still in Portrait orientation...just scaled up. How do we fix this? Our scene is being created in viewDidLoad, but when this is happening the application DOES NOT yet know we're supposed to be in Landscape mode. So we need to create our scene, but we just need to do it a bit later in the process - after the iOS application life cycle has realized we're in Landscape mode. There's a couple of different methods to do this. The one I like is viewWillLayoutSubviews. It is another built-in View Controller method. It gets called automatically after viewDidLoad and after the shift to Landscape mode. So if we post-pone our scene creation by a few milliseconds until we're in viewWillLayoutSubviews method then we can create it with the right orientation. So copy & paste all the code in viewDidLoad (apart from the call to [super viewDidLoad];) and put it into viewWillLayoutSubviews. There is only 1 more issue now. It is possible viewWillLayoutSubviews will be called a couple of times or at least more then once during the lifecycle. That's ok. We just don't want to create the scene several times. So once we have grabbed hold of the view we're going to create an if statement to check if there's a Scene object in that view. If not, we'll create it and return it. Otherwise, we don't need to do that. Here's how all of this code looks inside the View Controller that creates our Scene.
//Method can be deleted (It's called behind the scenes regardless).
-(void)viewDidLoad
{
[super viewDidLoad];
}
//Called AFTER viewDidLoad & after the view shifts to landscape (which is what we want).
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews]; //Don't really need to call this method (but a good habit just in case Apple ever changes that).
//Configure the view.
_skView = (SKView *)self.view;
_skView.showsFPS = YES;
_skView.showsNodeCount = YES;
//_skView.showsPhysics = YES;
//viewWillLayoutSubviews might get called several times & that's ok. But, we don't want several GameScenes to be created. Therefore...
//Is there a scene object in that view? If there ISN'T, we'll create it.
if (!_skView.scene)
{
//Create & configure the scene.
SKScene *sceneGame = [MainMenuScene sceneWithSize:_skView.bounds.size];
sceneGame.scaleMode = SKSceneScaleModeAspectFill;
//Present the scene.
[_skView presentScene:sceneGame transition:[SKTransition fadeWithDuration:0.5]];
}
//If there is, we don't need to do that.
}
Now go ahead and run this. Everything should look good now. The NSLog message should now say "In initWithSize, at 568 wide and 320 high". Hurray!