Please help me understand the following behavior (iOS Sprite Kit).
The output of the code I present below is:
1.skView.bounds.size = 768.00, 1024.00
2.skView.bounds.size = 1024.00, 768.00
As seen above, width and height are switched between the two methods, and that causes my second scene to be presented not in the correct scale.
My game will run in landscape mode only, which means that in fact, the second width x height ratio is the correct one (though it is the first that renders the scene in the correct aspect ratio, which in itself is a mystery to me).
Can anyone tell me how I can fix this behavior? What am I doing wrong?
- (void)viewDidLoad
{
[super viewDidLoad];
self.currentGameState = GS_INTRO_SCENE;
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
printf("1.skView.bounds.size = %.2f, %.2f\n", skView.bounds.size.width, skView.bounds.size.height);
SKScene *scene = [SKTGameIntroScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.currentGameState == GS_INTRO_SCENE) {
self.currentGameState = GS_GAME_PLAY;
SKView * skView = (SKView *)self.view;
printf("2.skView.bounds.size = %.2f, %.2f\n", skView.bounds.size.width, skView.bounds.size.height);
SKScene *nextScene = [SKTMyScene sceneWithSize:skView.bounds.size];
nextScene.scaleMode = SKSceneScaleModeAspectFill;
SKTransition *fadeIn = [SKTransition fadeWithDuration:5.0f];
[skView presentScene:nextScene transition:fadeIn];
}
}
I greatly appreciate it in advance.
** edit: **
#giorashc solved my problem suggesting I move the scene initiation to -(void)viewWillLayoutSubviews method.
But my second scene remains stretched.... here is the full code for SKTViewController.m after the change (can anyone say why second view is still stretched?):
//
// SKTViewController.m
// SpriteKitTest
//
// Created by Claudia Dazcal on 13/07/14.
// Copyright (c) 2014 DazcalFamily_inc. All rights reserved.
//
#import "SKTViewController.h"
#import "SKTMyScene.h"
#include "SKTGameIntroScene.h"
typedef enum
{
GS_INTRO_SCENE,
GS_GAME_PLAY
}GameStates;
#interface SKTViewController ()
#property GameStates currentGameState;
#property BOOL firstSceneLoaded;
#end
#implementation SKTViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.currentGameState = GS_INTRO_SCENE;
self.firstSceneLoaded = NO;
}
-(void)viewWillLayoutSubviews
{
if (!self.firstSceneLoaded) {
self.firstSceneLoaded = YES;
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
printf("1.skView.bounds.size = %.2f, %.2f\n", skView.bounds.size.width, skView.bounds.size.height);
SKScene *scene = [SKTGameIntroScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.currentGameState == GS_INTRO_SCENE) {
self.currentGameState = GS_GAME_PLAY;
SKView * skView = (SKView *)self.view;
printf("2.skView.bounds.size = %.2f, %.2f\n", skView.bounds.size.width, skView.bounds.size.height);
//CGSize DebugSize = CGSizeMake(skView.bounds.size.height, skView.bounds.size.width);
//SKScene *nextScene = [SKTMyScene sceneWithSize:DebugSize];
SKScene *nextScene = [SKTMyScene sceneWithSize:skView.bounds.size];
nextScene.scaleMode = SKSceneScaleModeAspectFill;
//SKTransition *fadeIn = [SKTransition fadeWithDuration:5.0f];
//[skView presentScene:nextScene transition:fadeIn];
[skView presentScene:nextScene];
}
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskAllButUpsideDown;
} else {
return UIInterfaceOrientationMaskAll;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
* edit 2 **
Ok, it`s all sorted out now.
#giorashc solved my problem right away when he suggested moving the scene initiation to viewWillLayoutSubviews. It turns out that since I was previously working with the wrong bounds, I set up my original scene with the wrong values and now that it was fixed, it looked stretched.
But it`s fixed now. Thank you so much #giorashc!!
Initialize your scene in the viewWillLayoutSubviews method. In this method the view bounds are updated correctly.
Make sure though that you initialize the scene only once as this method is called whenever the view controller presents another view controller or the application is changing its orientation.
In the first method the view hasn't rotated to landscape mode yet. In the second it has rotated to landscape mode so is now reporting bounds that are wider than it is tall.
Related
I have got the following problem in SpriteKit: My aim is to let bonbons fall continuously from the top of the screen. These bonbons can be collected by the player. Every 0.8 seconds or so, another bonbon should fall down and when the player collides with one of the bonbons, it should disappear. This is my code:
-(void)populate {
for (int i = 0; i < 2; i++) {
[self generate];
}
}
-(void)generate {
Y = (arc4random() % 280) - 140;
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
bonbon.size = CGSizeMake(20, 20);
bonbon.name = #"bonbon";
bonbon.physicsBody.categoryBitMask = bonbonCategory;
bonbon.position = CGPointMake(Y, 500);
bonbon.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bonbon.size.width/2];
bonbon.physicsBody.dynamic = YES;
[bonbon addChild:bonbon];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if ([contact.bodyA.node.name isEqualToString: #"wallLeft"] || [contact.bodyB.node.name isEqualToString: #"wallLeft"])
{
[hero.physicsBody applyImpulse: CGVectorMake(100, 60)];
self.jumpDirection = YES;
}
else if ([contact.bodyA.node.name isEqualToString: #"bonbon"] || [contact.bodyB.node.name isEqualToString: #"bonbon"])
{
[world enumerateChildNodesWithName:#"bonbon" usingBlock:^(SKNode *node, BOOL *stop) {
PointsLabel *pointsLabel = (PointsLabel *)[self childNodeWithName:#"pointsLabel"];
[pointsLabel increment];
NSLog(#"didContactBonbon");
[bonbon removeFromParent];
NSLog(#"removedFromParent");
}];
}
else {
[hero.physicsBody applyImpulse: CGVectorMake(-100, 60)];
self.jumpDirection = NO;
}
}
I hope you understand my problem. Currently, no bonbons are falling at all. If you need more information, please do not hesitate to ask.
Greets
edit: I hope the formatting is better now. I am quite a newbie, i'm sorry for foolish mistakes :) This is in my MyScene.m, when is call the populate in the initWithSize method with [self populate]; I receive the message signal SIGABRT. What did I do wrong?
I think the problem is that you are adding your SKSpriteNode named bonbon to itself with :
- (void)generate {
...
bonbon = [SKSpriteNode spriteNodeWithImageNamed:#"Bonbon.png"];
...
[bonbon addChild:bonbon];
}
Try to add bonbon to your SKScene instance.
To use an SKView instance you have to create a UIViewController and give its view the SKView class. You can do that in your storyboard.
Then, in your viewController :
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
// Configure the scene
SKScene *scene = [SKScene sceneWithSize:self.view.bounds.size];
// You can add objects to your scene
[scene addChild:bonbon];
// Present the scene.
[skView presentScene:scene];
}
#end
This is a very simple example but if you want more information, I suggest you read this tutorial: Sprite Kit Tutorial for Beginners.
I'm trying to call my newExplosion method (in a touchesBegan method) that is in my Scene class from my ViewController.m class. The problem is that it doesn't add the SKEmitterNode to the View (which is what the newExplosion method is supposed to do). It works when the touchesBegan method is in the Scene class, but not when it is called from my ViewController class.
This is my Scene class:
#import "Scene.h"
#import <SpriteKit/SpriteKit.h>
#implementation Scene : SKScene
- (void) newExplosion: (float)x : (float) y {
SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:#"crouchEffect" ofType:#"sks"]];
emitter.position = CGPointMake(x,y);
[self addChild:emitter];
}
// This works VVVV
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self newExplosion:100 :100];
}
This is the SpriteKit related stuff in my ViewController.m class:
- (void)viewDidLoad{
SKView * skView = _skView;
SKScene * scene = [Scene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
// Doesn't work VVVV
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
Scene * ss = [[Scene alloc] init];
[ss newExplosion:100:100];
}
I added a debug message in my newExplosion method to see if it was actually running it and it printed it out. So it's being called, but not adding the SKEmitterNode to the view.
You are creating a new scene everytime you touch the screen, but you never add this to the view, I think you want to do something like this
//In view controller interface add
{
Scene * _scene;
}
//In implementation, change the following
- (void)viewDidLoad{
SKView * skView = _skView;
_scene = [Scene sceneWithSize:skView.bounds.size];
_scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:_scene];
}
// Doesn't work VVVV
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[_scene newExplosion:100:100];
}
I am trying to (learn how to) build a game using SpriteKit and i ran into a problem that has been bugging me for hours.
I have a class named Tank with the following constructor:
+ (instancetype)tankAtPosition:(CGPoint)position {
Tank *tank = [self spriteNodeWithImageNamed:#"tank_base_1"];
tank.position = position;
tank.anchorPoint = CGPointMake(0.5, 0.5);
return tank;
}
In my scene i have the following constructor:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"level_bg_1"];
background.xScale = 0.40f;
background.yScale = 0.40f;
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:background];
Tank *tank = [Tank tankAtPosition:CGPointMake(CGRectGetMidX(self.frame), 512)];
[self addChild:tank];
}
return self;
}
which compiled results in the following render:
Everything ok for now, however, if i change the y of the tank to 256:
Tank *tank = [Tank tankAtPosition:CGPointMake(CGRectGetMidX(self.frame), 256)];
I get this:
As far as i know, the bottom is y = 0 and and the middle point y = 512, so when i specify a y = 256 it should be centered in the bottom half of the screen. Why is is near the edge?
The testing device is a ipad retina mini and in the Deployment Info > Devices i specified iPad.
What am i missing? Thank you.
i figured it out. the frame size was all messed up because i set my game to run in landscape only. solution: initialize the scene in viewDidLayoutSubviews instend of viewDidLoad
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [Level sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
I'm trying to graph some trigonometric functions on an SKScene.. I'm using an SKShapeNode for each point in the screen so when it reaches the left side I remove if from the parent.
The problem is that, for some reason it only draws on a portion of the screen as if it were contained by a smaller view.. The position on the screen is not matching the real screen... for example if I place it at 100 it is actually at a different place.. Plus the size of the area where it graphs is reduced...
There is some code at the bottom
I hope someone could help me! Thank you very much!
Anything else that might help ask and I'll re-edit the post.
Thank you!
Here is some code:
- (void) createTrigonometricFunction
{
[self calculateFunction];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, groundOriginLocation.x,groundOriginLocation.y + groundPointPrevious.y);
CGPathAddLineToPoint(pathToDraw, NULL, groundOriginLocation.x + 1,groundOriginLocation.y + groundPointCurrent.y);
SKShapeNode * currentLine = [SKShapeNode node];
currentLine.position = CGPointMake(groundOriginLocation.x,groundOriginLocation.y);
currentLine.path = pathToDraw;
CGPathRelease(pathToDraw);
[currentLine setStrokeColor:[UIColor whiteColor]];
currentLine.name = #"terrainLine";
currentLine.lineWidth = 1;
[currentScene addChild: currentLine];
groundPointPrevious = groundPointCurrent;
//NSLog(#"%f - %f",currentLine.position.x,currentLine.position.y);
}
- (void) calculateFunction
{
groundDominio += 1;
groundPointCurrent.x = groundOriginLocation.x;
groundPointCurrent.y = 2*(sin(degreesToRadian(groundDominio)*2)/5*degreesToRadian(180))*cos(degreesToRadian(150) + degreesToRadian(groundDominio)*5)*sin(degreesToRadian(groundPointCurrent.x));
groundPointCurrent.y = radianToDegrees(groundPointCurrent.y);
}
//The view controller:: (This is how I load it)
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
//skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [MainGame sceneWithSize: skView.bounds.size];
//scene.scaleMode = SKSceneScaleModeAspectFill;
NSLog(#"%f $$ %f",self.view.frame.size.height,self.view.frame.size.width);
// Present the scene.
[skView presentScene:scene];
}
Do you draw on the scene or on another node?
If you are drawing on another node, your drawing will be off since the default anchor point for node is 0.5, 0.5 (it is pinned to scene with its center) but the actual 0.0 position inside the node is not its center.
I am trying to fill my SKScene with tiles, in an iPad app that only supports landscape mode. Within the scene I detect h & w as such:
int h = [UIScreen mainScreen].bounds.size.height;
int w = [UIScreen mainScreen].bounds.size.width;
Unfortunately, the dimensions sent back are the reverse of what they need to be. There are lots of topics in SO discussion this problem within the content of a view controller or a view, and the solution seems to be to detect screen size in viewWillAppear, not in viewDidLoad. This does not seem to be a valid solution for SKScenes, however.
Any suggestions?
Try, not using viewDidLoad and use this
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
skView.showsFPS = YES;
skView.showsNodeCount = YES;
SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
}
It doesn't make sense to initialize the scene in the layout handler. Resize the scene in the layout handler. (That's what it's for.)
#implementation SGOMyScene
{
SKScene *scene;
}
- (void)viewDidLoad
{
[super viewDidLoad];
SKView *skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
scene = [SGOMyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[skView presentScene:scene];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
SKView *skView = (SKView *)self.view;
scene.size = skView.bounds.size;
}
Your scene should implement - (void)didChangeSize:(CGSize)oldSize to move everything into the right place.
Hooray, now your game handles device rotations too.