I can pause and unpause the SKScene with a pause button(SKSpriteNode) and a simple method.
-(void) pauseGame {
SKView *view = (SKView *) self.view;
if(!view.paused){
view.paused = YES;
}else{
view.paused = NO;
[pauseLabel removeFromParent];
}
}
And call it in the touchesBegan Method
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// If game is paused allow the button to be touched
if (isGamePaused == NO) {
//if pause button touched, pause the game
if (node == pauseButton) {
[self pauseGame];
}
}
}
This way works fine. I can push the button to pause the game and push it again to unpause the game. However, I want to give the user a way to know the game is paused other then the screen be frozen, so I tried this:
I create this SKLabel.
-(void) addPauseLabel {
// Creating label
pauseLabel = [SKLabelNode labelNodeWithFontNamed:universalFont];
pauseLabel.text = #"Game is Paused";
pauseLabel.fontSize = 42;
pauseLabel.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2 + 60);
pauseLabel.zPosition = 6;
[self addChild:pauseLabel];
}
Then I created a sequence in which I add the label then pause the game
-(void) pauseSequence{
SKAction *pauseSequence = [SKAction sequence:#[
[SKAction performSelector:#selector(addPauseLabel) onTarget:self],
[SKAction performSelector:#selector(pauseGame) onTarget:self],
]];
[self runAction:pauseSequence];
}
And finally, in the touchesBegan method, I call [self pauseSquence]; at the end instead of [self pauseGame];
When I push the pause button the label is added and the game is pauseed, but pushing the button again to unpuase the game doesn't do anything. Why is that? Any help would be greatly appreciated. Thank you.
You can't run an action while the view is paused, so you should call [self pauseSequence] if the view is not paused and [self pauseGame] if the view is paused.
In the touchesBegan method
if (!self.view.paused) {
// Add label and pause
[self pauseSequence];
}
else {
// Resume game
[self pauseGame];
}
The most likely reason your unpause button doesn't work is that after you pause, no touches can be detected. You are essentially locking yourself out of the scene, meaning the scene can't do work to unpause itself.
The generally accepted way to pause a SpriteKit game is to not pause the scene, but to create multiple nodes. One node will contain your gameplay (the "Game Layer") and the other will contain pause elements (the "Pause Layer"). When your scene detects the pause button has been pressed, set everything in the Game Layer to paused, then bring up your Pause Layer. When the resume button is pressed, hide the Pause Layer and unpause the Game Layer.
You should know that the sequence serves no purpose here. It's best to just call:
[self addPauseLabel];
[self pauseGame];
Since that's the main change it's possible that the odd and unnecessary use of a sequence causes this behavior.
Related
Okay so I have to Scenes: TitleScene and GameScene. Obviously there is a lot of stuff in GameScene and that resulted in something very annoying. I have a play-button in the TitleScene and whenever that gets pressed I present the GameScene.
I do that like this:
In TitleScene.m :
-(void)didMoveToView: (SKView *)view {
if(!self.contentCreated) {
self.createSceneContents;
self.contentCreated = YES;
}
}
-(void)createSceneContents {
//among other TitleScene stuff
self.gameScene = [GameScene sceneWithSize:self.frame.size];
self.gameScene.scaleMode = SKScaleModeResizeFill;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
if ([self.playButton containsPoint:location]) {
[self.view presentScene:self.gameScene];
}
}
The Problem is that whenever I press the play-button it causes like a second to two seconds of lag before transitioning to the next scene.
I create the GameScene just like the TitleScene with a lot of stuff in the createSceneContents method. I know it is not the simulators fault, because I tested it on my iPhone 6.
Can I pre-load all the GameScene's stuff when the app is first opened, or is there another way to stop this lag?
Now I am working on a SKScene. I have two ViewController files and I created two scene files which is subview of a SKScene. I know I can perform many SKActions in the scene files. But I want to do some SKAction in the method appEntersBackground: in the file ViewController.m. But it seems the SKActions in this method don't work and I don't know why.
In the ViewController.m file:
-(void)appEntersBackground {
SKAction *action = [SKAction customActionWithDuration:0.1 actionBlock:^(SKNode *node, CGFloat elapsedTime) {
//perform some actions
}
[myNode runAction: action];
}
You cannot perform an SKAction from the viewController. You might however, trigger a method in the scene itself when the application enters the background and perform the necessary operations there.
Paste the appEntersBackground method in the SKScene itself. Then call the following notification in the didMoveToView method.
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appEntersBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
//The rest of your setup goes here
}
-(void)appEntersBackground {
SKAction *action = [SKAction customActionWithDuration:0.1 actionBlock:^(SKNode *node, CGFloat elapsedTime) {
//perform some actions
}
[myNode runAction: action];
}
The method should get called when the application enters the background.
I am trying to add Vungle video ads in my sprite kit skscene. I have a sprite node which is when clicked, should load the ad. The guide provided by Vungle https://github.com/Vungle/vungle-resources/blob/master/iOS-resources/iOS-dev-guide.md shows how to place an ad through a view controller.
VungleSDK* sdk = [VungleSDK sharedSDK];
[sdk playAd:self];
I have different SKScene and i want to play ad in the scene rather than i the view controller. How can i achieve it.
Following is my SKScene code where the user is clicking an SKSpriteNode and i want the ad to load.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
SKNode *n = [self nodeAtPoint:[touch locationInNode:self]];
if ( [n.name isEqual: #"play"]) {
[self levelSelect];
}
else if( [n.name isEqual: #"coins"]){
VungleSDK* sdk = [VungleSDK sharedSDK];
[sdk playAd:self.view]; //TODO
}
}
This gives error as i am not passing a view controller to the method playAd. Can someone guide me?
Sovled this so if anyone else gets the same problem, here is the solution:-
In your view controller , Do this inside viewDidLoad method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"playVungle" object:nil];
also create a method
-(void)playVungleAd{
VungleSDK* sdk = [VungleSDK sharedSDK];
[sdk playAd:self];
}
Don't forget to import VungleSDK/VungleSDK.h
Now in your skscene, inside your touches began method do this
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
SKNode *n = [self nodeAtPoint:[touch locationInNode:self]];
if ( [n.name isEqual: #"play"]) {
[self levelSelect];
}
else if( [n.name isEqual: #"coins"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"playVungle" object:nil]; //Sends message to viewcontroller to show ad.
}
}
Here we are sending a message to view controller to play the vungle ad. Now when you touch your "coins" skspritenode in your scene, it should play the video ad.
I'd really like to get to the bottom of why this code causes intermittent response to touch input... Even with NSLog as the first instruction...
I've just made a new project in cocos2d with Box2d for a game that at this stage needs to just a few simple things...
A basket that appears in the centre of the screen. It must be a b2Fixture and fall onto a surface. Then if the user touched the screen, I want the basket to zoom to the touch point, and from there the user can drag it around the screen.
When the user lets go, the basket drops... I have this working right now...
However the BUG is that touching the screen doesn't always work... It intermittently responds to touches, and therefore intermittently calls the methods.
As you will see below, I have used NSLog to check when each methods are being called. The result is that sometimes you have to lift your finger off the screen and then back on several times, and then "seemingly at random", it will decide to run the code....
Heres what I got...
My touch methods....
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Multi Touch Moved...");
if (_mouseJoint == NULL) return;
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
_mouseJoint->SetTarget(locationWorld);
}
-(void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
NSLog(#"\nThe touch was CANCELED...");
}
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
NSLog(#"Single Touch Moved...");
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
NSLog(#"\n\nTouch did begin...");
if (_mouseJoint != NULL)
{
_mouseJoint->SetTarget(locationWorld);
NSLog(#"The IF statment was met...");
return;
}
NSLog(#"The IF statment was NOT met...Running _mouseJoint setup...");
b2MouseJointDef md;
md.bodyA = _groundBody;
md.bodyB = _body;
md.target = _body->GetPosition();
md.collideConnected = true;
md.maxForce = 100000000.0f * _body->GetMass();
_mouseJoint = (b2MouseJoint *)_world->CreateJoint(&md);
_body->SetAwake(true);
_mouseJoint->SetTarget(locationWorld);
}
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (_mouseJoint != nil) {
_world->DestroyJoint(_mouseJoint);
_mouseJoint = nil;
}
}
And this is the interface that some of the code refers to...
#interface HelloWorldLayer : CCLayerColor
{
b2World *_world;
b2Body *_body;
b2Fixture *_bodyFix;
b2MouseJoint *_mouseJoint;
b2Body *_groundBody;
}
The only idea another member helped me determine is, that because I'm working within a single scene, and I have/had a CCMenu and a CCLabelTTF on the screen, is it possible that the CCMenu is still intercepting touches, and if so, how can I destroy the CCMenu after my animation has finished?
Pressing the only button item simply CCmoves the title and label(CCMenuItem) off the screen vertically... But the object still exists...
The problem here was that my CCMenu was still receiving touches (I assume in parts of the screen where the CCMenuItems where formed).
The solution was to declare my CCMenu in my #implementation rather than in my init scene setup.
#implementation HelloWorldLayer
{
CCLabelTTF *label;
CCLabelTTF *startTheGameLabel;
CCSprite *theCattery;
CCMenu *_menu;
}
Then in my init, rather than declaring it there, i simply assign to the one in the #implementation.
-(id) init { if( (self=[super initWithColor:ccc4(50, 180, 220, 255)]) )
{
//.. Other "irrelevant to question" scene setup stuff here...//
// Start the game button
startTheGameLabel = [CCLabelTTF labelWithString:#"Save Some Kitties!" fontName:#"Zapfino" fontSize:20];
CCMenuItemLabel *startTheGameLabelItem = [CCMenuItemLabel itemWithLabel:startTheGameLabel target:self selector:#selector(startGameStub:)];
// Push the menu
_menu = [CCMenu menuWithItems: startTheGameLabelItem, nil];
[self addChild:_menu];
//.. Other "irrelevant to question" scene setup stuff here...//
}
This gives me access to the CCMenu throughout my class, so I can later disable touch input once the user has made a selection.
-(void) startGameStub:(id)sender
{
CGSize size = [[CCDirector sharedDirector] winSize];
// Clear the labels off the screen
CMoveTo *moveTheTitleLabelAction = [CCMoveTo actionWithDuration:1.0 position:ccp(label.position.x, size.height + label.boundingBox.size.height)];
CCMoveTo *moveTheStartLabelAction = [CCMoveTo actionWithDuration:1.0 position:ccp(startTheGameLabel.position.x, size.height + startTheGameLabel.boundingBox.size.height)];
// Commit actions
[label runAction:moveTheTitleLabelAction];
[startTheGameLabel runAction:moveTheStartLabelAction];
// LIFE SAVING MESSAGE!!!
[_menu setTouchEnabled:NO]; // This is what fixes the problem
}
I would like some of my Sprite Kit nodes to behave like UIButtons. I tried 2 approaches:
1) Use touchesBegan: - this works if a user is careful, but seems to fire multiple times, faster than I can disable interaction, resulting in a button being able to be activated multiple times:
spriteNode.userInteractionEnabled = YES;
//causes the following to fire when node receives touch
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.userInteractionEnabled = NO;
DLog(#"Do node action");
self.userInteractionEnabled = YES;
}
2)I switched to Kobold-Kit as a layer on top of iOS Sprite Kit. One of the things it allows me to do is add button - like behavior to any node. However, I'm running into an issue where a if I have 2 buttons stacked on top of each other, tapping the top button activates both. Boolean flags can prevent repeated interactions in this case. I tracked the issue of stacked buttons firing together to this call within KKScene:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
for (id observer in _inputObservers)
{
if ([observer respondsToSelector:#selector(touchesBegan:withEvent:)])
{
[observer touchesBegan:touches withEvent:event];
}
}
}
The scene simply sends notifications to all nodes within the scene. This causes buttons behaviors that are stacked on top of each other to fire together.
Is there a way or example that shows how to properly arrange sprite nodes to allow behavior similar to UIButton, where I can have only the top button activate, and each activation disables the button for a short time afterwards?
#property (nonatomic, strong) UITapGestureRecognizer *tapGesture;
self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureStateChanged:)];
self.tapGesture.delegate = self;
[self.view addGestureRecognizer:self.tapGesture];
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.tapGesture) {
return CGRectContainsPoint(self.neededSprite.frame, [self convertPoint:[sender locationInView:self.view] toNode:self]);
}
return YES;
}
- (void)tapGestureStateChanged:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
/* do stuff here */
}
}
The SpriteKit Programming Guide suggests using a SKNode name to gate the behavior that you want to achieve when touched. The example in the section called "Using Actions to Animate Scenes" overrides the touchesBegan:withEvent: method and only runs when the name is not nil. When you're done, just reset the name so that you can catch the next touch.
- (void)touchesBegan:(NSSet *) touches withEvent:(UIEvent *)event {
SKNode *helloNode = [self childNodeWithName:#"helloNode"];
if (helloNode != nil) {
helloNode.name = nil;
SKAction *moveUp = [SKAction moveByX: 0 y: 100.0 duration: 0.5];
SKAction *zoom = [SKAction scaleTo: 2.0 duration: 0.25];
SKAction *pause = [SKAction waitForDuration: 0.5];
SKAction *fadeAway = [SKAction fadeOutWithDuration: 0.25];
SKAction *remove = [SKAction removeFromParent];
SKAction *moveSequence = [SKAction sequence:#[moveUp, zoom, pause, fadeAway, remove]];
[helloNode runAction: moveSequence];
}
}
Another idea would be to perform your re-enabling of the user interaction to touchesEnded.