I have two CCMenu instances. At some point in the game, menu A is overlapped by menu B. However, when I press a button within menu B, the one that "gets it" is menu A.
How can I give touch priority to CCMenu B?
I tried this:
[[CCTouchDispatcher sharedDispatcher] setPriority:-130 forDelegate:menuB];
However, Xcode says that this delegate (menuB) was not found.
Okay, I fixed this, but I still think there should be a better way.
First, we have to edit CCMenu's interface. We have to create a new integer property.
#interface CCMenu : CCLayer <CCRGBAProtocol>
{
tCCMenuState state_;
CCMenuItem *selectedItem_;
GLubyte opacity_;
ccColor3B color_;
int extraTouchPriority; // Our new integer
}
#property (readwrite) int extraTouchPriority;
Now change the registerWithTouchDispatcher method to this:
-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:kCCMenuTouchPriority - extraTouchPriority swallowsTouches:YES];
}
Done. Now, when you have to give your CCMenu instance more priority than others, just give a higher extraTouchPriority value to it after initializing it.
I had the same problem. What i did is copied the entire CCMenu from cocos2d library, renamed it and then modified kCCMenuTouchPriority to what i wanted. Note that you have to rename kCCMenuTouchPriority for the custom menu. I used kkCCMenuTouchPriority.
I called it in code like this:
CCMenuPopUp *menu =[CCMenuPopUp menuWithItems:item1,nil];
I tried to subclass it but i ran into some problems and gave up and gone with the solution above.
the CCTouchDispatcher thing doesn't work because the menu isn't inited yet when you call it
Here's anoter variation on one of the anwers above, which doesn't alter the cocos2D code base, because that is bad practice: https://gist.github.com/tudormunteanu/6174624
Related
I'm building a cross platform app, and 1 of those platforms is Macos.
Now the core of my code is written in C++, and Obj-C++.
I create a window like this:
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(x, y, width, height) styleMask:macosWindowStyle backing:NSBackingStoreBuffered defer:false];
but I wanted to listen to the window. I could've subclassed it, but I chose not to.
The other way to get events from the NSWindow was to set a delegate.
Now since my code was in Obj-C++, I couldn't have a C++ class inherit from a Obj-C protocol.
So, I created a Obj-C header, which implemented NSWindowDelegate.
Here it is:
#interface SomeClass : NSObject<NSWindowDelegate>
#end
I overrode windowShouldClose as such:
- (BOOL)windowShouldClose:(NSWindow *)sender {
NSLog(#"Hello!");
return true;
}
and in my Obj-C++ file, I did this:
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(x, y, width, height) styleMask:macosWindowStyle backing:NSBackingStoreBuffered defer:false];
SomeClass* someClass = [[SomeClass alloc] init];
[window setDelegate:someClass];
However, when I pressed the X button, nothing happened.
I then proceeded to test the same thing in Swift, same result.
I then realized that the delegate was being destroyed because it was a weak reference.
My question is, how do I get around this?
OK, I figured it out.
I for some reason thought I could not have Obj-C class pointers in my Obj-C++ code. I thought that was one of the limitations.
I'm trying to schedule selectors with CCCallFunc but the selector isn't getting called.
This works:
[self launchCreature];
This does not:
id launchCreatureAction = [CCCallFunc actionWithTarget:self selector:#selector(launchCreature)];
[self runAction:launchCreatureAction];
launchCreature is defined in the parent class, and the CCCallFunc works when used in the parent class. Nothing is showing up in the debugger when I run the action; it just doesn't run the code. (It never hits the breakpoint I have defined at the first line of launchCreature.)
Am I missing about something about how to use CCCallFunc, or how self works in subclasses?
Edit: I misremembered how my code is set up. launchCreature and the code above are BOTH defined in the parent class. The difference between working and not working is that it worked in an instance of the parent class, but doesn't work in an instance of the subclass. This no longer seems to work in the parent class either. Sorry for the confusion.
It works for me. You could try making a call to [super launchCreature] in [self launchCreature] in the subclass and see if the break point is being hit.
EDIT: I am using ARC
In my code (based on the ShootEmUp example in this book, which I highly reccomend, source code in chapter 8 available here) I often use the trick of accessing the GameScene via:
+(GameScene*) sharedGameScene;
which returns a reference to the static instance of GameScene and is used by all children of GameScene (e.g. ShipEntity, InputLayer.. etc..) to dialog with GameScene (EDIT: aka singleton instance of GameScene).
To create multiple levels I thought of implementing a method calledsceneWithId:(int) method where I load different level data each time.
EDIT: Code snippet of init method:
-(id) initWithId:(int)sceneId
{
CCLOG(#"scene With id");
if ((self = [super init]))
{
//All common initialization happens here..
switch (sceneId) {
case 1:
[self loadFirstLevelData];
break;
case 2:
[self loadSecondLevelData];
break;
default:
[self loadSecondLevelData];
break;
}
//Other common stuff..
[self setUpCounters];
[self setUpWeaponsMenu];
[self scheduleUpdate];
[self schedule:#selector(timerUpdate:) interval:1];
InputLayerButtons* inputLayer = [InputLayerButtons node];
[self addChild:inputLayer z:1 tag:GameSceneLayerTagInput];
}
EDIT: Is that init method ok? I have found this post which uses dispatch_once. Should I do the same?
Or should I pheraps create a GameScene class and then sublcass it?
E.g. FirstGameScene : GameScene
EDIT: I have followed the advice of #LearnCocos2D and used the cleanup method, and I used it to stop a singleton object music layer to play (the MusicLayer object is initialized in AppDelegate and I meant to use it to "manage" the music across all scenes - the problem was that without stopping it in dealloc it would have kept playing the music that was loaded at init time).
-(void) loadFirstLevelData{
//HERE WILL LOAD ALL SPECIFIC ELEMENTS: ENEMIES, BONUSES etc..
//AS WELL AS THE MUSIC FOR THE LEVEL
[[MusicLayer sharedMusicLayer] _loadMusic:#"1.mp3"];
[[MusicLayer sharedMusicLayer] playBackgroundMusicFile: #"1.mp3"];
}
-(void) cleanup
{
//Should I remove all child loaded in LoadLevelData??
CCLOG(#"cleanup GameScene");
[[MusicLayer sharedMusicLayer] stopAllMusic];
//MusicLayer is not a child of GameScene but of AppDelegate - the idea is to keep loading and unloading music files - sometimes I need to keep playing the file between scenes and hence I used the singleton pattern for this as well..
[super cleanup];
}
But I still have some doubts:
Is it ok to have several loadLevelData methods in GameScene class? Each method can be 200 lines long! I tried to sublcass GameScene but is a bit messy. I explain better. I imported "GameScene.h" in the header file of the subclass and by doing so I expected that if I had ovverriden only certain methods (e.g. init) I would have been able to see the various classes imported in GameScene (e.g. InputLayerButtons). It is not the case. So I probably don't understand how imports work in Objective-C
Is it ok to remove specifc children in the cleanup method? I thought that I would remove all child that are added in the LoadLevelXXXData method to reduce the memory usage.
I have set a bounty for this question but I will probably need to test the answer and re-edit as I don't have a clear enough understanding of the subject to be super precise in the question. Hope is ok.
PS: Would be great if someone would feel like sharing a UML style diagram of a Cocos2D Shooter Game where with various levels and GameScene using singleton pattern :).
I'll focus on the questions on the bottom:
Is it ok to have several loadLevelData methods in GameScene class? Each method can be 200 lines long! I tried to sublcass GameScene but
is a bit messy. I explain better. I imported "GameScene.h" in the
header file of the subclass and by doing so I expected that if I had
ovverriden only certain methods (e.g. init) I would have been able to
see the various classes imported in GameScene (e.g.
InputLayerButtons). It is not the case. So I probably don't understand
how imports work in Objective-C
There's nothing wrong with having long methods. However I suspect your loading methods perform very similar routines, so you should check if you can generalize these into subroutines. A good indicator is if several lines of code are absolutely identical except for the parameters or variable names. The best practice is to write identical code only once, and execute it many times with varying parameters.
The #import statement is used to allow the compiler to "see" other classes. Without importing other header files, you couldn't use that class' methods and properties without the compiler complaining.
2 . Is it ok to remove specifc children in the cleanup method? I thought that I would remove all child that are added in the
LoadLevelXXXData method to reduce the memory usage.
It makes no sense to remove children during cleanup. Cocos2D removes all children during cleanup automatically.
If that does not seem to be the case for you, you have a retain cycle somewhere that prevents child nodes from deallocating.
sorry for answering this but I have been experimenting and decided to:
not use the singleton pattern for GameScene
use, insteada a singleton object to keep all shared data
My implementation draft for the GameScene (now called ShooterScene) is the following (I followed some advices in a cocos2d-iphone forum post as well as this other one ):
#import "ShooterScene.h"
#import "LevelData.h"
#import "HudLayer.h"
#interface ShooterScene (PrivateMethods)
-(void) loadGameArtFile;
#end
#implementation ShooterScene
+ (id) sceneWithId:(int)sceneId
{
CCScene *scene = [CCScene node];
ShooterScene * shooterLayer = [ShooterScene node];
[scene addChild:shooterLayer];
[shooterLayer loadGameArtFile];
LevelData * levelData = [LevelData node];
[shooterLayer addChild:levelData];
switch (sceneId) {
case 1:
[levelData loadLevelDataOne];
break;
case 2:
[levelData loadLevelDataOne];
break;
default:
break;
}
HudLayer * hud = [HudLayer node];
[hud setUpPauseMenu];
[shooterLayer addChild:hud];
return scene;
}
I am using the update method of ShooterScene to manage the all the game events (E.g. spawning, checking collisions, moving the background layer). I haven't put the full implementation here as is still work in progress, but is just to have an idea of the type of answer I am finding useful.
I've recently added the CCLayerPanZoom cocos2d extension to my project and got my game scene zooming and scrolling just like I want. Now when the player takes certain actions, I want to be able to disable the pan/zoom temporarily while they perform an action but can't figure out how to do it. I searched around and found the following code in a forum but it doesn't work or I don't know how to use it.
Does anyone know how to do this properly either with different code or the code below?
-(void)enableTouches:(BOOL)enable {
if(enable) {
[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0];
_panZoomLayer.isTouchEnabled = YES;
CCLOG(#"LayerPanZoom enabled.");
} else {
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
_panZoomLayer.isTouchEnabled = NO;
CCLOG(#"LayerPanZoom disabled.");
}
}
I finally figured it out and figured I would post the answer back up here to share. The code I posted wasn't working because I was sending back self instead of the _panZoomLayer. So here are the steps to get this working yourself.
Implement the CCLayerPanZoom into your project as described by the documentation.
Add the following code as a method to call on your new CCLayerPanZoom class.
-(void)enableTouches:(BOOL)enable {
if(enable) {
[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:_panZoomLayer priority:0];
CCLOG(#"LayerPanZoom enabled.");
} else {
[[CCTouchDispatcher sharedDispatcher] removeDelegate:_panZoomLayer];
CCLOG(#"LayerPanZoom disabled.");
}}
NOTE: Make sure to put the instance of the parent class as the delegate to remove.
In order to re-enable and have it function properly, you have to remove all the entries from the array in the CCLayerPanZoom class before calling to re-register the delegate. I created a new method in the CCLayerPanZoom class as follows and just call it right before the addStandardDelegate method above.
-(void)removeTouchesFromArray {
[self.touches removeAllObjects];
}
Then it all works great! Took me a while to learn how to use this extension but it works perfect once you figure it all out. I can single finger pan, double finger zoom/pan, set center location for entire scene, limit panning past edges, and set min/max scales. I know people have had a lot of issues with this but it is a great extension, just takes some messing around with to understand it. Let me know if you have any questions. Hope this helps someone else.
Some breakpoints have pointed to the fact that this following line of code sends a CCLog that says removeChildByTag: child not found:
CCMenu* menu = [CCMenu menuWithItems:item1, item3, nil];
The actual creation of item1 and item3 do not cause any unusual CCLOGs but the creation of the menu itself does. Any idea why?
The only place where CCMenu or CCMenuItem send a removeChildByTag message is in the CCMenuItem method -(void)setSelectedIndex:(NSUInteger)index which in turn is called by the initWithTarget and activate methods.
I suppose as long as your menu is working this is nothing to worry about.