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.
Related
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!
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.
i'm developing an application that allows the user to rotate the needles of a clock. for that i'm using the transform property of the needle's imageview :
CGAffineTransform newTrans = CGAffineTransformRotate(initialTransform, -angleDif);
minutesNeedle.transform = newTrans;
for the purpose of detecting the user's touch i must detect the needle's space (minute or hour's frame). And here my problem show off, after rotating the image it looks like the image is stretched and interfere with the other image. the UIView class documentation says that after being transformed the view's frame changes and you must use center and bounds properties. However, i cannot use the bounds property to detect the touches user which are located in the superview.
is there any other way to do this animation without changing the frame size (i doubt). Or can i resize the frame in the touchesEnded method (i've tried but in vain). Please help i'm blocking there for one week!! Thanks
Maybe I'm missing something, but it seems like you can avoid this conflict altogether. Create a separate view for handling touches, independent of the needle view. On a touch event, move the needle view appropriately. This separate view would be the size of the clock essentially and be overlaid on the other views, having a clear background. It would have a delegate with appropriate callbacks so your view controller (or whatever owns the needle) can adjust it.
As far as what the callbacks would be, I would just forward the touch events like:
- (void)touchReceiverView:(TouchReceiverView *)view didReceiveTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
Do it with CALayers is WAY much easier and performance is better this way.
CALayer *handLayer = [CALayer layer];
handLayer.contents = (id)[UIImage imageNamed:#"hand.png"].CGImage;
handLayer.anchorPoint = CGPointMake(0.5,0.0)];
[myview.layer addSublayer:handLayer];
//i.e.: if handLayer represents the seconds hand then repeat this every second ;)
handLayer.transform = CGAffineTransformMakeRotation (angle); //set the angle here
Someone wrote a ClockView sample using CALayers, maybe you find it useful.
What I want to accomplish is to make my CCSprite follow another CCSprite when it is touching it. Now what I mean by follow is, lets say there is an animation of another CCSprite moving up the screen. So if this sprite hits my main sprite, my main sprite should move up the screen with it. This other sprite will be a platform, so technically in the end I would want the sprite to be on top of the other sprite but it would be moving along the top of the platform CCSprite as if the platform was carrying the main sprite.
Now I know how to do the collision detection part and make the other sprite animate but how would I just make my platform 'carry' my main CCSprite also kind of like how an elevator works?
I think you can place flag to check the condition "when to follow" in update: method you can reposition the second sprite according to position of first Sprite.
-(void)update:(CCTime)delta
{
if(condition == YES)
{
secondSprite.position = ccp ( firstSprite.position.x + xOffSet , firstSprite.position.y + yOffSet);
}
}
Try this according to your code.. Basic logic is here..
You can use the CCFollow action to have any node follow any other node.
You can move the first sprite without using action, by manually updating his position in a nextFrame:(ccTime) dt kinda function, doing so would be easy to update another sprite position based on your sprite position plus an offset to position it on top.
Another solution could be removing the follower sprite from its parent and adding as a child of the moving node, i think this can be done without flickering and performance loss.
edit:
another solution (maybe the best one)
a good approach, that works great with Actions so you dont need to manually schedule movements
You can add a property to the moving sprite (you need to subclass from CCSprite, if you havent already done so), to keep a reference to the follower
#property (readwrite, nonatomic, assign) CCSprite *follower;
#property (readwrite, nonatomic) BOOL followerActive;
then in your game inits, you can create both sprites, and add them as childs of your main layer, then you add the weak reference from your object to the follower
platform.follower = followerSprite;
and when you need to enable the follow platform.followerActive = YES;
at this point in the moving sprite, you can override the setPosition property
-(void) setPosition:(CGPoint)position {
if(self.followerActive) {
self.follower.position = ccpAdd(position, offset);
}
[super setPosition:position];
}
I never developed in Cocos2D. However, this animated app is not easy to make with regular UIView animation and CAAnimation.
I want a number of UIImageView's (from 1 to 30) to float around the screen with the certain path and I want them to be responsive for touch (they would do some animation when touched). I also need them to go back and forth the screen (new path would be calculated) when they are touched or reach the edge of screen. It's important to retrieve X and Y position of each element whenever needed.
Question is: what Cocos2D classes are best looking at (for a beginner) to make that happen? I've tried UIView animation and CAAnimation but I came across some difficulties so I have a feeling Cocos2D may bring better results. Thank you.
Yes, cocos2d makes it much easier. You want to create a CCSprite with the initWithFile: method. Example:
CCSprite *mySprite = [CCSprite initWithFile:#"fire.png"];
[self addChild:mySprite];
Where fire.png has been added to the project and self is the scene instance.