I have a scene with buttons and node Level in which I load another scene (layer created with Sprite Builder):
-(void) didLoadFromCCB {
//the Gameplay Scene loaded
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
//load level in gameplay scene
CCScene *level = [CCBReader loadAsScene:#"Scenes/scene_01"];
[_levelNode addChild:level];
}
Everything is loading and I have a gameplay scene with common buttons and a scene_01 with sprites added as child.
Now I am trying to detect if a sprite from scene_01 was touched. I can track if touch occurred on _levelNode:
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint _touchLocation = [touch locationInNode:_levelNode];
CCLOG(#"levelNode touched");
// Define which touched Object
if (CGRectContainsPoint([_part01 boundingBox], _touchLocation))
{
_part01.position = _touchLocation;
_touchingPart01 = YES;
_touchPoint = _touchLocation;
CCLOG(#"_part01 touched");
}
}
But the second part of code above is not working and console says the sprites were not found at all:
[1531:60b] CCBReader: Couldn't find member variable: _part01
My conclusion is - the parent scene has no access to sprites in loaded scene and the questions are:
Is it a good practice to have node where game switches child:levels instead of loading levels as scenes?
If Yes - how do I manage the objects inside that level, do I need more precise selector? (something like:if (CGRectContainsPoint([***level.***_part01 boundingBox], _touchLocation)))
I think I could create .ccb files and create classes to describe these sprites in Cocos but it would be 5 sprites per level and dozens of levels which means I get 50+ classes which I believe isn't a good way to create a game. (I can't create a class for part and change its link to sprite due the lack of skill).
After some more digging and modifying the question I did next:
Have my Gameplay scene add the level scene as a child (and the general buttons such as next, prev, back...):
Gameplay.m
-(void) didLoadFromCCB {
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
//load level
CCScene *level = [CCBReader loadAsScene:#"Scenes/scene_01"];
[_levelNode addChild:level];
}
Created Scene_01 class and have all game mechanics happen in that class:
Scene_01.m
#implementation Scene_01 {
CCSprite *_part01;
...}
-(void) didLoadFromCCB {
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
}
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint _touchLocation = [touch locationInNode:self];
// Define which touched Object
if (CGRectContainsPoint([_part01 boundingBox], _touchLocation))
{
//some action if _part01 is touched
CCLOG(#"_part01");
}
}
This is working for me now. The reasons I didn't go this way from the beginning and my steps to it are next:
I had my sprites named _part01,... and assigned to "Doc root var" (in Sprite Builder), to have them accessible for the scene_01 but when I named the root node of that scene my project was crashing on build so I figured this was because named node can't be added as child to other scene (in Xcode).
Changed assignment of variables to "Don't assign" and the error: [1531:60b] CCBReader: Couldn't find member variable: _part01 disappeared from logs, so this was all about linking objects I use, not about accessing them.
Stepped back and repeated creating class for this scene, moved the logic inside that class, added the scene as child to Gameplay scene - voila, it works!
P.S. I'm still not sure why it didn't work the first time if it does now, looking for any comments that may help understand this situation. Thanks.
Related
I'm working in objective c/cocos2d. I have a scene named AllLevelAndModes which I add a CCScrollView named levelScroll of type class EasyLevelSelectScene. On this level scroller, I have a bunch of buttons for my game level. When someone touches one of the buttons, a popup comes up displaying some stats about the level. I add this popup by add childing it to EasyLevelSelectScene when a button is tapped. This child is of kind of class LevelSelectDropdown. I want to make a functionality such that if a user taps outside the popup (which is smaller than the whole screen), that the popup disappears.
Now I suppose I could get the size of the popup and the screen and if a user taps outside of some range of x and y, the popup disappears. But that is a little cumbersome. Is there a better way to do this? I was trying to basically get the name of the class that is tapped by running the following code once the popup comes.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocationTemp = [[CCDirector sharedDirector] convertToGL: [touch locationInView:touch.view]];
CCResponderManager *responder = [[CCDirector sharedDirector] responderManager];
CCNode *node = [responder nodeAtPoint:touchLocationTemp];
CCLOG(#"class type %#", node); //Always reads 'EasyLevelSelectScene' no matter where I tap
if([node isKindOfClass:[EasyLevelSelectScene class]]) {
//go back to level select screen
}
}
Unfortunately this doesn't work though, since wherever I tap it logs EasyLevelSelectScene, even when I tap right on top of my popup! Why is it doing this, and is there a way to get the class name of the top-most child? Thanks for the help!
I have made a graph with data in a UIView called HeartrateGraph. In a UIViewController named HRGraphInfo, I have a connected label that should output values when the graph is touched. The problem is, I don't know how to send a touched event using delegates from the UIView to the UIViewController.
Here is my touch assignment code in the UIView:
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
for (int i = 0; i < kNumberOfPoints; i++)
{
if (CGRectContainsPoint(touchAreas[i], point))
{
graphInfoRF.heartRateGraphString = [NSString stringWithFormat:#"Heart Rate reading #%d at %# bpm",i+1, dataArray[i]];
graphInfoRF.touched = YES;
break;
}
}
This segment of code is in a touchesBegan and properly stores the data value and number in the object graphInfoRF (I just did not show the declarations of dataArray, kNumberOfPoints, etc).
I am able to access graphInfoRF in the UIViewController using:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (graphInfoRF.touched == YES) {
self.heartRateLabel.text = graphInfoRF.heartRateGraphString;
}
else {
self.heartRateLabel.text = #"No data got over to this file";}
}
The label will show the correct string, but only after the data point on the graph is touched AND the label is touched right after. How do I change the touchesBegan so that once I touch the data point on the graph it will fill the label with the data automatically without the need for a second and separate touch on the label?
All ViewControllers comes with a single view it manages once it's initialized. You should be familiar with this view, you see it whenever you use a ViewController in the Interface Builder and you can access it using self.view if you're modifying a subclass.
Since ViewControllers come with a view, it also receives touch events for that view. Implementing touchesBegan in the ViewController will then receive events for that view, and normally any subviews that view is managing. Since you've down your own implementation of 'touchesBegan' in your HeartRateGraph, and since HeartRateGraph is a subview of ViewControllers main view, HeartRateGraph will receive and handle the touch event first before the ViewController ever has a chance to receive and handle the event like it normally would (think of bubbling up).
So what's happening is, the code to change the label in ViewController is only called when the label is touched because the label is a subview of the ViewController's main view... and also label doesn't have its own touches implementation, so ViewController and is able to retrieve and handle the event the way you want only when you click somewhere outside the graph. If you understand then there are two ways to solve this.
Either pass the event up to your superview
[self.superview touchesBegan:touches withEvent:eventargs];
or the proper recommended way of doing it:
Protocols and Delegates where your View makes a delegate call to it ViewController letting it know the graph has been touched and the ViewController needs to update its contents
Up until now I've been working on a Sprite Kit game, with a new game starting every time I hit the "Play" button in xCode. Now I'm trying to implement a "New Game" button within the game which will reset the game and start a new one.
What are the steps I need to take in order to ensure that my old game scenes are gone, and don't have any dangling references?
Do I need to do any kind of manual cleanup within a scene? (as in the old viewDidUnload) or will ARC take care of everything?
Do I need to explicitly remove any view controller views that I might've added from within a scene to it's superview?
Are there any built-in methods that can help me reset my game?
Here's what I was using until now.
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
//set the view only once, because this method will be called again on rotation
if(!self.kkView.scene)
{
landscapeSize = self.kkView.bounds.size;
self.kkView.showsFPS = NO;
self.kkView.showsNodeCount = NO;
self.kkView.showsDrawCount = NO;
MyScene * gameScene = [MyScene sceneWithSize:landscapeSize];
gameScene.scaleMode = SKSceneScaleModeAspectFill;
IntroScene * scene = [IntroScene sceneWithSize:landscapeSize];
scene.gameScene = gameScene;
scene.scaleMode = SKSceneScaleModeAspectFill;
[self.kkView presentScene:scene];
}
}
ARC should take care of cleaning up object creation (like +sceneWithSize:). I also believe that if you're removing the entire SKScene that cleanup is done for all attached nodes and actions for you (possibly in the -willMoveFromView: method).
However, if you are looking to keep the scene and reset the nodes and actions inside the scene subclass itself, then you could do something like the following before the setup portion of your scene (either in the -didMoveToView: method or your own setup method):
[self removeAllChildren];
[self removeAllActions];
...
[self addChild:node];
etc
I'm not exactly sure what's going on and hope that I can provide enough relevant code to find an answer. I've set up my gesture recognizer in my appDelegate.m:
CCScene *scene = [HomeLayer scene];
HomeLayer *layer = (HomeLayer *) [scene.children objectAtIndex:0];
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
[director_.view addGestureRecognizer:gestureRecognizer];
m._gestureRecognizer = gestureRecognizer;
I've inserted some debugging messages to try to pinpoint at what point the app crashes:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
NSLog(#"Handle Pan From");
as well as some printouts for ccTouchBegan/Moved/Ended.
Every time the app crashes, it's while things are "moving", (ended never gets called), and handlePanFrom never gets called either.
Background info: My app has buttons that I use to switch between scenes, for example:
- (void) doSomethingThree: (CCMenuItem *) menuItem
{
NSLog(#"The third menu was called");
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[HomeLayer scene] ]];
}
If I start up my app and go directly to the HomeLayer scene, and try to drag, the app crashes instantly 100% of the time (ccMoved gets called 1-2 times before crash). Clicking does not cause the app to crash, only anything that would invoke handlePanFrom.
The strange thing is that if I drag around on any other scene, the app does not crash, and handlePanFrom successfully gets called. Then, when I go back to the HomeLayer scene and drag around, it won't crash for some time, and it seems directly related to how long I spend dragging around on a different scene.
Has anyone seen these symptoms before? I'm not sure if the information I provided is relevant or correct, I'm still trying to learn my way around iphone dev. I'll also be happy for any debugging tips (those assembly looking hex lines aren't particularly enlightening to me...)
I figured out the problem with the help of NSZombies, finding out that the program was crashing while trying to reference the deallocated method handlePanFrom.
The ultimate root of the problem was that HomeLayer was being instantiated twice, the first time in appDelegate.m, and the 2nd time when i was doing the replaceScene.
This resulted in the first layer eventually losing all of its references and being deallocated while the gestureRecognizer was still trying to reference [layer handlePanFrom], causing the crash.
The problem was fixed by moving the gestureRecognizer from the appDelegate.m to HomeLayer.m, and for anyone who needs gestures across multiple layers, here's a piece of code that will remove any existing references of the gestureRecognizer to the view, and then add a new one that targets a method in the layer:
+(CCScene *) scene
{
HomeLayer *layer = [HomeLayer node];
[scene addChild: layer];
for (UIGestureRecognizer *gr in [[CCDirector sharedDirector].view gestureRecognizers]) {
// note that sharedDirector is a singleton and therefore is the same CCDirector
// as the one used in appDelegate.m
[[CCDirector sharedDirector].view removeGestureRecognizer:gr];
}
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
[[CCDirector sharedDirector].view addGestureRecognizer:gestureRecognizer];
return scene;
}
Hopefully that may help someone in the future who is trying to work with multiple scenes/layers in a view =)
so i'm currently working on an app that draws something on the screen, like a graph and things like that. while testing it and it's functionality i came across this issue.
i have a button that when pressed goes to a settings scene. i initialize the scene like this
+(id) scenew
{
CCScene *scene = [CCScene node];
Settings *layer = [Settings node];
[scene addChild: layer];
return scene;
}
in that scene i have two more buttons, a done button and a what's new button.
done = [CCMenuItemImage itemFromNormalImage:#"done.png" selectedImage:#"done.png" target:self selector:#selector(done:)];
pressing the what's new button goes instantly but the done button has a 2-3 seconds delay.
the app has some console messages appearing and it seems that when it is pressed it recalculates the whole graph just like when starting the app.
all the buttons call the same CCDirector function which is replaceScene.
-(void) whatNew: (id)sender
{
[[CCDirector sharedDirector] replaceScene: [New scene]];
}
-(void) done: (id)sender
{
[[CCDirector sharedDirector] replaceScene: [Main scene]];
}
is there a way for me to optimize this a little...i mean...an efficient way to use the replaceScene, or maybe something else that doesn't force the recalculation of the graph? because whenever that button is pressed it practically jumps at the top of my graph class implementation which has 4500+ lines O.o
I assume the first button you mentioned above is inside the Main scene, going to the Setting scene.
To retain the Main scene in the memory while transferring the app flow to the Setting scene, all you have to do is to use pushScene: instead of replaceScene::
// in Main scene class
[[CCDirector sharedDirector] pushScene:[Setting scene]];
Then, from inside Setting scene, simply use popScene to return to the Main scene:
-(void) done: (id)sender
{
[[CCDirector sharedDirector] popScene];
}
Basically pushScene: simply push the new scene on top of the current scene while popScene pops it out, reverting the app flow back to the previous scene that still remains in the device memory.