I currently have a "Console" CClayer, which is handling touch detection for sprites that have been added to it. However, I also have some sprites that I want to do touch detection on that are not part of the Console layer... They are currently children of a class that inherits from CCNode.
My understanding is, the more cocos objects have the "isTouchEnabled" property set to true, the more performance will be affected, so I am curious how I should approach this?
Should I:
A) Have the console's touchesBegan method perform detection of the sprites belonging to the CCNode?
B) Just implement isTouchEnabled on the CCNode object
C) Some other approach?
well, for starters, you should only concern yourself about performance if you are concerned i.e. you are seeing or measuring (on DEVICES not a simulator) some inappropriate response times.
I would avoid detecting touches that concern another node - it could get messy, software wise. I tend to return YES (from a ccTouchBegan) strictly when the touch is at a location of an object of concern to the detecting node. When you return NO, the dispatcher will pass on the touch to other handlers ('under' the console), until one such CCNode takes the bite. Kind of as follows:
- (void) onEnter{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[super onEnter];
}
- (void) onExit{
[[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self];
[super onExit];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
if (!_visible || !_enabled) {
return NO;
}
CGPoint loc = [touch locationInView:touch.view];
loc = [[CCDirector sharedDirector] convertToGL:loc];
if ([self containsPoint:loc]) {
// do your thing here !
return YES;
}
return NO;
}
-(BOOL) containsPoint:(CGPoint) location {
// determine here whether this node should be handling
// this touch.
}
Related
First try at Chipmunk.
Not getting collision detection registering is the problem.
My code:
#implementation MainPlayScene
{
CCPhysicsNode *_physics;
CCNode *MyPhysicsBody;
CCNode *bottomBody;
}
+ (instancetype)scene
{
return [[self alloc] init];
}
- (instancetype)init
{
// Apple recommend assigning self with supers return value, and handling self not created
self = [super init];
if (!self) return(nil);
_physics = [CCPhysicsNode node];
_physics.debugDraw = YES;
[self addChild:_physics z:1];
/// BOTTOM
CGRect bottomRect = CGRectMake(0, 0, [CCDirector sharedDirector].viewSize.width, 10);
bottomBody = [CCNode node];
bottomBody.physicsBody = [CCPhysicsBody bodyWithPolylineFromRect:bottomRect cornerRadius:0];
bottomBody.physicsBody.collisionCategories = #[#"Bottom"];
bottomBody.physicsBody.type = CCPhysicsBodyTypeStatic;
[_physics addChild:bottomBody];
/// MyBody to bounce around
MyPhysicsBody = [CCSprite spriteWithImageNamed:#"MyBody-64x64-24.png"];
MyPhysicsBody.position = ccp((self.contentSize.width/2),(self.contentSize.height/2));
MyPhysicsBody = [CCNode node];
MyPhysicsBody.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, MyPhysicsBody.contentSize.height,MyPhysicsBody.contentSize.width} cornerRadius:0];
MyPhysicsBody.physicsBody.collisionCategories = #[#"MyBody"];
[_physics addChild:MyPhysicsBody z:150];
self.userInteractionEnabled = YES;
return self;
}
Detecting touch events and applying force to physics body to have it bounce up and down onto bottom body
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CCLOG(#"Touch Detected");
[MyPhysicsBody.physicsBody applyImpulse:ccp(0, 300.f)];
}
Now I try to detect a collision on “Bottom” but nothing is being registered even though I see debug lines of 2 objects touch.
/// try onCollisionEnter first ... nothing
-(void)onCollisionEnter:(CCNode *)entity collisionPair:(CCPhysicsCollisionPair *)pair
{
if ([entity.physicsBody.collisionCategories isEqual: #"Bottom"]) {
CCLOG(#"Hit bottomBody");
}
}
/// try ccPhysicsCollisionBegin pair ... nothing
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair MyBody:(CCNode *) MyBody Botton:(CCNode *)Bottom
{
CCLOG(#"Hit bottomBody");
return TRUE;
}
Obviously I’m missing something critical here …
Any help is VERY appreciated!
Thanks
I am finding it difficult to see the relevant code with all the things you posted.
I will give you an example of 2 CCSprite objects collision detection. One is called _arrowand the other one is _obstacle.
First step is to define the collision types like so :
_arrow.physicsBody.collisionType = #"arrow";
_obstacle.physicsBody.collisionType = #"obstacle";
Second step is to define the callback
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair arrow:(CCNode *)arrow obstacle:(CCNode *)obstacle
{
// Do some cool stuff
return TRUE;
}
Notice the naming, arrow and obstacle according to the collision type of theses sprites.
And third, the one that you forgot to do is set the delegate so you actually get these methods called on your physicsNode object
_physics.collisionDelegate = self;
And self (which is typically just your scene) should implement the CCPhysicsCollisionDelegate protocol.
Tibor has the correct answer, but there's an important clue that I was missing which was driving me crazy. From the comments in CCPhysicsNode.h section on the CCPhysicsCollisionDelegate:
The final two parameter names (typeA/typeB) should be replaced
with names used with CCPhysicsBody.collisionType or
CCPhysicsShape.collisionType.
If both final parameter names are "default" then the collision
method is called when a more specific method isn't found.
So I was incorrectly trying to implement something like this:
-(BOOL) ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair typeA:(CCNode *)nodeA typeB:(CCNode *)nodeB
{
// Compare if we're ignored collisions
if (([nodeA.physicsBody.collisionType isEqualToString: #"foo"])
|| ([nodeB.physicsBody.collisionType isEqualToString: #"foo"]) )
{
return NO;
}
return YES;
}
But that's WRONG!!! The correct thing I wanted to implement was this:
-(BOOL) ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair foo:(CCNode *)nodeA foo:(CCNode *)nodeB
{
return NO;
}
Note how the ignored collision type is part of the method signature.
Whew!
EDIT:
For completeness, don't forget to do:
_physics.collisionDelegate = self;
You are setting collision categories which along with collisions mask allow you to discard certain collisions. The collision type property is what works with the delegate methods.
I want to know how to disappear certain sprite(star) once the MainPlayer touches the Star.
Thanks. By the way, I'm new to Cocos2d and I'm doing it just for fun and educational purpose.
Thanks.
If you want to be able to detect touches in cocos2d, you need to set the isTouchEnabled property to YES in your init method. You can now take advantage of touch events.
Next, create the new method:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//This method gets triggered every time a tap is detected
NSSet *allTouches = [event allTouches]; //Get all the current touches in the event
UITouch *aTouch = [allTouches anyObject]; //Get one of the touches, multitouch is disabled, so there is only always going to be one.
CGPoint pos = [aTouch locationInView:touch.view]; //Get the location of the touch
CGPoint ccPos = [[CCDirector sharedDirector] convertToGL:pos]; //Convert that location to something cocos2d can use
if (CGRectContainsPoint(yourSprite.boundingBox, ccPos)) //Method to check if a rectangle contains a point
{
yourSprite.visible = NO; //Make your sprite invisible
}
}
Other methods that you may want to take advantage of eventually are the following:
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
Hope this helped.
I've been studying this for hours (2 days, actually) and just cannot figure out what is wrong. The touches are accepted and processing, but the isTouchHandled test is triggering TRUE prematurely; as if a different bounding box was touched than the one that is touched. I have several non-overlapping CCSprite buttons, with each pointed to in the levelButtons array. Iterate through to see which one is touched; but it's always the wrong one.
Does the CGRectContainsPoints method get thrown off if these sprites are in their own layer, which is then in another layer? In other words, is CGRectContainsPoints using raw equality of pixel locations as reported by position? If so, a sprite's position relative to the entire screen is different than its reported position if it is a child, which is relative to the parent. Maybe this has something to do with it? My array and the tags of its contents are correctly lining up, I've logged and checked that many times; it appears to be the bounding box check.
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CCLOG (#"levelButtons size:%i",[self.levelButtons count]);
BOOL isTouchHandled = NO;
for (int i=0;i<25;i++){
CCSprite*temp=(CCSprite*)[self.levelButtons objectAtIndex:i];
CCLOG(#"iteration temp.tag: %i for object: %i",temp.tag,i);
isTouchHandled= CGRectContainsPoint([temp boundingBox], [[CCDirector sharedDirector] convertToGL:[touch locationInView: [touch view]]]);
if (isTouchHandled) {
CCLOG(#"level touched: %i",temp.tag);
break;
}
}
return isTouchHandled;
}
UPDATE: Incidentally, I also just subclassed CCSprite and add the touche methods to the individual sprites in this fashion ,taking my array of sprites out of the picture. The results were the same, so I suspect the CGRectContainsPoints is not properly working when your rect is a child of other children, the coordinates are not being reported correctly, I suspect.
I think it may be problem with an array that you are getting sprites. Any way , this is how i am using the code for getting the sprite tag.
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
for(int i1=0;i1<=25;i1++)
{
CCSprite *sprite1 = (CCSprite *)[self getChildByTag:i1];
if(CGRectContainsPoint([sprite1 boundingBox], location))
{
//Your Code
break;
}
}
I solved this by creating a new CGRect for the CGRectContainsPoint test, and translating the bounding box into the actual onscreen rectangle; the bounding box test will not work on its own if it is located on a child sprite (or layer). It returns its local position only, relative to the parent.
There is a scene in my application which has only two labels and a menu item. When I load this scene using replaceScene method it stays for 3-4 seconds and then gets disappeared or released. I want to keep it until cancel button is pressed. How can I do it? code is:
#implementation MyLayer
+ (id)myScene {
CCScene *aScene = [CCScene node];
MYLayer *myLayer = [MyLayer node];
[aScene addChild:myLayer];
return aScene;
}
- (id) init {
if (self = [super init]) {
//labels and menu here
}
return self;
}
And I am calling it from another scene like this:
[[CCDirector sharedDirector] replaceScene: [MyLayer myScene]];
Maybe the problem is that it's your first scene. Then you should use runWithScene method of CCDirector.
did you try replacing that scene with a "empty" init function to see if it still releases itself? It might be because of the amount of textures you are putting into memory
I did have sort of similar problems before because the images used in the new scene is too big and got auto purged by my app delegate, thus returning me an empty scene sometimes
I am trying to preload an animation in the init method of my layer. I then call the animation if the screen is touched. The app crashes with no error message as soon as I touch the screen and seems it is to do with calling the preloaded animation. I would like to do it this way as it seems expensive to create the animation every time the screen is touched - which does seems to work though. Any tips greatly appreciated.
Sample Code:
In my header:
#interface Test : CCLayer {
NSMutableArray *wake;
CCSprite* ani;
CCAnimate *animate;
}
#end
In my implementation:
-(id) init {
if( (self=[super init])) {
// enable touches
self.isTouchEnabled = YES;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"ani.plist" texture:[[CCTexture2D alloc] initWithImage:[UIImage imageNamed:#"ani.png"]]];
ani = [CCSprite spriteWithSpriteFrameName:#"ani1.png"]; //comes from .plist file
ani.anchorPoint=ccp(0,0);
ani.position = ccp(700,65);
[self addChild:ani z:30];
wake = [NSMutableArray array];
for(int i = 1; i <= 4; i++) {
[wake addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"ani%d.png",i]]];
}
animate = [CCAnimate actionWithAnimation:[CCAnimation animationWithFrames:wake delay:1.0f] restoreOriginalFrame:FALSE];
}
return self;
}
Handling the touch:
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// run the animation
[ani runAction:animate];
}
Animations in Cocos2d are not designed to be reused. You need to create a new one every time.
Problem solved by creating properties for the array and animation on the class using nonatomic,retain.
You only need to retain the animation but the array can be local.
self.myAnimation = [[CCAnimation animationWithFrames:myAniFramesArray delay:0.1f] retain];
Remember to make the property nonatomic, retain as stated by Chev and to release any objects you retain in the appropriate dealloc method.