Objective-C. How is your first SKScene initialised? - ios

This method sets up the initial scene and is called as soon as the application opens:
#import "GameScene.h"
#import "WarScene.h"
#import "Math.h"
#implementation GameScene
{
SKNode *map;
float oldY;
float oldX;
BOOL buttonClicked;
int thisButton;
float oldMapPosition;
float midScreenX;
float midScreenY;
int separation;
float sendY;
BOOL interacting;
CGVector speed;
float oldPosition;
float newPosition;
BOOL isReleased;
int iterations;
float diff;
BOOL comeToStop;
BOOL actualStop;
BOOL clicked;
}
int numberOfLevels = 20;
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
map = [SKNode node];
separation = [UIScreen mainScreen].bounds.size.height;
if (UIDeviceOrientationIsLandscape((UIDeviceOrientation)[[UIApplication sharedApplication] statusBarOrientation]))
{
//landscape mode
midScreenX = ([[UIScreen mainScreen]bounds].size.width>[[UIScreen mainScreen]bounds].size.height?[[UIScreen mainScreen]bounds].size.width:[[UIScreen mainScreen]bounds].size.height)/2;
midScreenY = ([[UIScreen mainScreen]bounds].size.width<[[UIScreen mainScreen]bounds].size.height?[[UIScreen mainScreen]bounds].size.width:[[UIScreen mainScreen]bounds].size.height)/2;
}
else
{
//portrait mode
midScreenX = [[UIScreen mainScreen]bounds].size.width/2;
midScreenY = [[UIScreen mainScreen]bounds].size.height/2;
}
NSLog(#"Initial Width: %f Height: %f", midScreenX*2, midScreenY*2);
map.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: CGSizeMake([UIScreen mainScreen].bounds.size.width, separation*(numberOfLevels+1))];
map.physicsBody.affectedByGravity = NO;
map.physicsBody.allowsRotation = NO;
clicked = NO;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tap];
self.backgroundColor = [SKColor blackColor];
for (int i = 0; i<numberOfLevels; i += 1) {
SKLabelNode *title;
{
title = [SKLabelNode labelNodeWithFontNamed:#"Cochin"];
title.text = [NSString stringWithFormat: #"Level %d", 1+i];
title.fontSize = 60;
title.position = CGPointMake(CGRectGetMidX(self.frame),
((i+1)*separation));
title.fontColor = [UIColor whiteColor];
title.name = [NSString stringWithFormat: #"Level %d", 1+i];
[map addChild:title];
}
}
[self addChild:map];
}
This works exactly as I intended it to, however, when I call this from another class:
-(void)continueMethod:(UIButton*)button{
NSLog(#"InitwithSize to GameScene Width: %f Height: %f", midScreenX*2, midScreenY*2);
GameScene *map = [[GameScene alloc] initWithSize: CGSizeMake(midScreenX*2 ,midScreenY*2)];
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
SKView * skView = (SKView *)self.view;
UIView *viewToRemove = [self.view viewWithTag:3];
[viewToRemove removeFromSuperview];
[skView presentScene:map transition:reveal];
}
The scene which should be set up doesn't appear as I intended it to, or indeed how it used to look the last time that it was initialised. The separation variable appears bigger, the text appears bigger and everything is wider. I've verified that the UIScreen that I have initialised is exactly the same throughout.
This led me to question how the initial scene of a SKSprite application comes to be and whether it is different to "initWithSize:" however, looking through all the code that comes in the game template I can't ever see my first scene get called. The closest that I can find to initWithSize: is the following from the GameViewController-
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
Firstly, is this what is initialising my initial scene? Secondly is it in anyway different to initWithSize: ? And finally if the answer to the previous tow questions is yes, should it be used instead of initWithsize: ?

Yes, this is the initialization of the initial scene! What you are seeing in the View Controller is a slightly different initialization. You are using the sks file, first of all, and initWithCoder: is being called when the view controller unarchives it. However, this isn't very different from initWithSize if you specify all of the same dimensions and properties.
That being said, the scene's scaleMode in your initialization and the view controller's scaleMode is different. As you can see, the view controller specifies:
scene.scaleMode = SKSceneScaleModeAspectFill;
The AspectFill scale mode according to Apple:
The scaling factor of each dimension is calculated and the smaller of the two is chosen. Each axis of the scene is scaled by the same scaling factor. This guarantees that the entire scene is visible but may require letterboxing in the view.
When you initialize your scene, on the other hand, you leave the default scaleMode, which happens to be SKSceneScaleModeFill. This maps each axis of the scene to the view, so you get a distorted scene. What you need to do is simply state:
map.scaleMode = SKSceneScaleModeAspectFill;
This will use the same scale mode as in the original scene and the distortion that you see now will be gone.
Hope this helps, let me know if you have questions.

Related

SKCropNode issue

Spent a whole day on this. I don't find this in the iOS docs or on SO.
I have a SKShapeNode* that is like a window in my app, and I add a background that is an SKSpriteNode, and the background has another 10 SKSpriteNode as its children. So the node tree is like this:
SKScene -> window -> background -> (child1, ..., child10)
The dimensions are such that the background matches the size of the window, and all the background's children fit inside the background.
I want to zoom when I click inside the window (have the background & 10 children all zoom together). I accomplish this by setting the background's xScale & yScale, and the children inherit this scaling. But I also don't want to spill outside the window's boundaries, so I made a SKCropNode, and added the background as its child. Now the background doesn't spill out:
SKScene -> window -> SKCropNode -> background -> (child1, ..., child10)
Problem is, the background's children spill out when zooming. This is counter-intuitive to me. I tried searching online and looking in docs, "does SKCropNode crop its children & all descendants"? Since the answer appears to be no, I thought to change all 10 children's parent from background to SKCropNode:
SKScene -> window -> SKCropNode -> (background, child1, ..., child10)
Now I scale the SKCropNode. This scales background and all children, but now it spills to outside the window again. (Later in the game, the number of children may increase from 10 to 300, and I don't want to do a for loop on 300 items. So I want to be able to set scale on just one parent.)
I finally decided to try something a bit "hacky". This I did not find anywhere online, so I'm wondering if I'm in "undefined behavior" territory.
SKScene -> window -> SKCropNode1 -> SKCropNode2 -> (background, child1, ..., child10)
I added another SKCropNode on top of my original SKCropNode. Now, I only scale SKCropNode2. This works. However, now I'm getting very strange behavior. My SKShapeNode buttons (completely outside the window) will disappear one by one, then come back, and cycle like this. Further, the "nodes: 10, 60.0 fps" in the lower right will disappear too and return in the cycle. By cycle I mean me clicking inside the window zooming. It seems I've hit a bug in SpriteKit? I set the zPosition of the buttons to 20, way higher than anything else (5 and below). I also set skview.ignoresSiblingOrder = false; Appreciate any help or advice on how to accomplish this!
Edit: In reply to the comments, I did not use a simulator. I tested this on my iPad Pro and iPhone 6+, both running iOS 9.2. Below is my code that compiles & reproduces the behavior. I also took out the zooming code, but it's still reproducible. Please try to tap on the spaceship (Apple's sample image) about 30 times, you will start to see it then.
MainScene.h
#import <UIKit/UIKit.h>
#import <SpriteKit/SpriteKit.h>
#interface MainScene : SKScene<NSStreamDelegate>
#property (strong, nonatomic) SKCropNode* skcrop;
#end
MainScene.m
#import "MainScene.h"
#implementation MainScene
- (void)didMoveToView: (SKView*)view { }
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ((int)[touches count] != 1) return;
UITouch* touch = [touches anyObject];
const CGPoint location = [touch locationInNode:self];
{ // without the 6 lines below, the disappearing-sprites behavior is gone
SKShapeNode* newshape = [SKShapeNode shapeNodeWithRectOfSize:
CGSizeMake(10.0, 10.0) cornerRadius:1.0];
newshape.position = location;
newshape.zPosition = 5;
newshape.fillColor = [UIColor purpleColor];
[self.skcrop addChild:newshape];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { }
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { }
#end
GameViewController.m:
#import "GameViewController.h"
#import "MainScene.h"
#import <CoreFoundation/CoreFoundation.h>
#implementation GameViewController
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeLeft
| UIInterfaceOrientationMaskLandscapeRight;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Configure SKView
SKView* skview = (SKView*)self.view;
skview.showsFPS = true;
skview.showsNodeCount = true;
skview.ignoresSiblingOrder = false;
skview.multipleTouchEnabled = false;
// Get Screen Size
// IPad Pro prints: screen size 768 1024
// IPhone 6+ prints: screen size 375 667
const int screenWidth = floor(0.5+skview.bounds.size.width);
const int screenHeight = floor(0.5+skview.bounds.size.height);
NSLog(#"screen size %d %d", screenWidth, screenHeight);
const double width = (screenWidth < 375) ? 360 : 720;
// Configure SKScene
MainScene *skscene = [[MainScene alloc]
initWithSize:CGSizeMake(screenWidth, screenHeight)];
skscene.scaleMode = SKSceneScaleModeFill;
skscene.backgroundColor = [UIColor whiteColor];
[skview presentScene:skscene];
// Set up window's crop mask
const CGSize winSurface = CGSizeMake(width, width);
const CGPoint winPosition = CGPointMake(
CGRectGetMidX(skscene.frame), CGRectGetMidY(skscene.frame));
NSLog(#"pos %f %f", winPosition.x, winPosition.y);
SKSpriteNode* winMaskParent = [[SKSpriteNode alloc]
initWithColor:[UIColor redColor] size:winSurface];
[winMaskParent retain];
winMaskParent.position = winPosition;
SKCropNode* scnParent = [SKCropNode node];
scnParent.zPosition = 1;
scnParent.maskNode = winMaskParent;
[skscene addChild:scnParent];
SKSpriteNode* winMask = [[SKSpriteNode alloc]
initWithColor:[UIColor blueColor] size:winSurface];
[winMask retain];
winMask.position = winPosition;
SKCropNode* scn = [SKCropNode node];
scn.zPosition = 1;
scn.maskNode = winMask;
[scnParent addChild:scn];
// Add window sprite
SKSpriteNode* win =
[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
win.zPosition = 2;
win.position = winPosition;
[scn addChild:win];
for (int i = 0; i < 5; ++i) {
const double height = 30.0;
const double width = 50.0;
const double posY = screenHeight - (1+i)*100.0;
const double posX = screenWidth - width - 10.0;
SKShapeNode* button = [SKShapeNode shapeNodeWithRectOfSize:
CGSizeMake(width, height) cornerRadius:1.0];
button.position = CGPointMake(posX, posY);
button.zPosition = 15;
button.fillColor = [UIColor greenColor];
button.lineWidth = 1.0;
button.glowWidth = 0.0;
[skscene addChild:button];
}
skscene.skcrop = scn;
return;
}
#end
Edit 2: I removed the nested SKCropNode so that there is only 1 layer of SKCropNode. The button sprites disappear after a few clicks on the spaceship.

SKSpriteNode position

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];
}

CCNode Hud, not acting as Hud but "scrolling with scene"

Attempting to simply have a scene where I have the gameplay, and the hud. Since I have it positioning the scene (which contains both layers), I assume this is why my Menu button is moving even though it's in the Hud Layer.
To fix it I would think that I would maybe have to change this line
[self setCenterOfScreen: mainChar.position];
to something like this:
[gameLayer setCenterOfScreen: mainChar.position];
But then I get this:
No visible #interface for CCNode declares the selector
'setCenterOfScreen:'
Here is the
GameScene (commented where I think issue is):
+ (GameScene *)scene
{
return [[self alloc] init];
}
// -----------------------------------------------------------------------
- (id)init
{
self = [super init];
if (!self) return(nil);
CGSize winSize = [CCDirector sharedDirector].viewSize;
self.userInteractionEnabled = YES;
self.theMap = [CCTiledMap tiledMapWithFile:#"AftermathRpg.tmx"];
self.contentSize = theMap.contentSize;
CCNode *gameLayer = [CCNode node];
gameLayer.userInteractionEnabled = YES;
gameLayer.contentSize = self.contentSize;
[self addChild:gameLayer];
[gameLayer addChild:theMap z:-1];
self.mainChar = [CCSprite spriteWithImageNamed:#"mainChar.png"];
mainChar.position = ccp(200,300);
[gameLayer addChild:mainChar];
// POSSIBLY WHY BOTH LAYERS ARE SHIFTING, RATHER THEN HUD STANDING STILL,
// I essentially want the gameLayer to be centered on screen, not both.
// I tried [gameLayer setCenterOfScreen: mainChar.position] but got error posted above
[self setCenterOfScreen: mainChar.position];
CCNode *hudLayer = [CCNode node];
hudLayer.userInteractionEnabled = YES;
hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);
[self addChild:hudLayer];
CCButton *backButton = [CCButton buttonWithTitle:#"[ Menu ]" fontName:#"Verdana-Bold" fontSize:18.0f];
backButton.positionType = CCPositionTypeNormalized;
backButton.position = ccp(0.85f, 0.95f); // Top Right of screen
[backButton setTarget:self selector:#selector(onBackClicked:)];
[hudLayer addChild:backButton];
return self;
}
but then I just get:
Just confused on how I would set up a scene, to initially have 2 layers - one that keeps position in say the top right as a HUD, and the other that scrolls with scene for gameplay when telling it to.
This is related to the other question you asked here (Adding a Hud Layer to Scene Cocos2d-3) which I posted the answer to. In the future it is better to modify your original OP in your first question rather than create a new question that is almost the same.
Hope this helped.

iOS Endless Runner Lag SpriteKit

I have an iOS endless runner game where the path appears from the top and scrolls down.
Here's how I do it: 2 sprites are created and when the first one disappears from the screen, I load another sprite from an image and run the same logic.
The problem: There's a small lag right after the first sprite disappears and the new one appears.
Edit: This problem only occurs when running on iPhone 5. iPhone 4 works perfectly !
Anyone have any ideas? or has run into this problem before and solved it?
if (CurrentObstacle.frame.origin.y < -self.frame.size.height + 10) {
// clear old obstacle
CurrentObstacle = nil;
CurrentObstacle = nextObstacle;
currentObstacleImage = nextObstacleImage; // for pixel processing stuff
[self generateObstacle];
}
- (void)generateObstacle{
// genrate random image name
int i = rand()%10+1;
NSString *imageName = [NSString stringWithFormat:#"ob%i", i];
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
if(screenSize.height == 480)
{
//Load 3.5 size
imageName = [imageName stringByAppendingString:#"small"];
}
imageName = [imageName stringByAppendingString:#".png"];
// create obstacle
nextObstacle = [SKSpriteNode spriteNodeWithImageNamed:imageName];
nextObstacleImage = [imageDictionary objectForKey:imageName];
nextObstacle.size = self.size;
nextObstacle.position = CGPointMake(self.view.center.x, 1.48*nextObstacle.size.height);
// set speed to be the same as the other obstacles
nextObstacle.speed = CurrentObstacle.speed;
// show obstacle
[self addChild:nextObstacle];
// move obstacle
SKAction *moveAction = [SKAction moveByX:0 y:-2*self.size.height-1 duration:8];
[nextObstacle runAction:moveAction];
}
Ok, solved this problem by removing the #2x images. Apparently processing big images was the cause of the lag.
Hope this helps someone!
You shouldn't load the image each time you want to add an obstacle. You should do a methode like this that you would call at "initWithSize"
//MyScene.h
#interface MyScene : SKScene{
SKTexture * textureObstacle1;
SKTexture * textureObstacle2;
//...//
}
-(id)initWithSize;
-(void)LoadSprites;
#end
//MyScene.m
#implementation MyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self LoadSprites];
}
return self;
}
-(void)LoadSprites{
textureObstacle1=[[SKTexture alloc] initWithImageNamed:#"obstacle1.png"];
//...//
}
- (void)generateObstacle{
//...//
nextObstacle = [SKSpriteNode alloc]init];
[nextObstacle setTexture:textureObstacle1];
}
#end

Not working right, start and end positon

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.

Resources