开发者

Problem creating touchable CCNodes whose CCSprites are children of a CCBatchNode

This may take a bit of explaining but here goes, would really appreciate any insight.

The short version: How can I create a TouchableButton (that detects touches on its own) whose CCSprite image is a child of CCSpriteBatchNode in another class? (Typically the CCSprite would be a child of the TouchableButton itself).

The long version:

I'm building a game using Cocos2d. The game focuses on a landscape (class EnvironmentView: CCLayer) filled with agents (class AgentView: CCNode) that run around and interact with one another.

The EnvironmentView maintains a list of AgentView objects and creates/destroys them as needed (depending on how they're interacting).

Each AgentView has a CCSprite @property that's added as a child of a CCBatchNode (an @property of EnvironmentView) which is added as child of the EnvironmentView.

I'm trying to implement a feature where users can touch the agents and move them from one place to another in the landscape.

Because there are many many agents moving around in the EnvironmentView I don't want to use the standard approach of getting the touch location and looping through all AgentView CCSprites to see if the touch hits one of them (this would slow the framerate considerably, please: not interested in answers promoting this approach).

Instead I would like to make each AgentView into a touchable node (a node that knows when it's touched rather than a node that's told when it's touched (approach mentioned above)).

Basically I'd like to replace or augment each AgentView's CCSprite with some sort of TouchableButton object.

I am using a class (let's call it TouchableButton) that uses this approach for the UI-related buttons in my game, they know when they're touched without implementing any CCTouchesBegan methods in their parent layer. But I've been unable to adapt TouchableButton for this use case, here's why:

TouchableButtons take a CCSprite as an init parameter. This CCSprite is set to be the touchable portion of the button and is added as a child of the button itself. Because I'm also adding the CCSprite as a child of the CCSpriteBatchNode in EnvironmentView I get an error (can't add something as a child twice to two different parent objects). How can I structure things to avoid this conflict?

开发者_运维百科

Thanks in advance for any help!


The short answer: You can't.

The long answer: You can get the same effect, but not in the same way.

CCSpriteBatchNode works by drawing all of its CCSprite children in a single glDrawElements call with a common texture (sprite sheet), which is what gives it such good performance. But as a result, every child MUST be a sprite, and if you add a child to a sprite, it will be ignored.

So, your only recourse at this point is to subclass CCSprite as a button and duplicate a lot of functionality, like so:

ButtonSprite.h:

//
//  ButtonSprite.h
//  TestButtonSprite
//
//  Created by Karl Stenerud on 9/1/11.
//

#import "cocos2d.h"

@class ButtonSprite;

typedef void (^ButtonPressCallback)(ButtonSprite* button);

/**
 * A sprite that can respond to touches.
 * Most of this code was taken from CCLayer.
 */
@interface ButtonSprite : CCSprite <CCStandardTouchDelegate, CCTargetedTouchDelegate>
{
    BOOL touchEnabled_;
    int touchPriority_;
    BOOL swallowTouches_;
    BOOL registeredWithDispatcher_;

    BOOL touchInProgress_;
    BOOL buttonWasDown_;

    ButtonPressCallback onButtonPressedCallback_;
}

/** Priority position in which this node will be handled (lower = sooner) */
@property(nonatomic,readwrite,assign) int touchPriority;

/** If true, no other node will respond to touches this one responds to */
@property(nonatomic,readwrite,assign) BOOL swallowTouches;

/** If true, this node responds to touches. */
@property(nonatomic,readwrite,assign) BOOL touchEnabled;

/** Called whenever a full touch completes */
@property(nonatomic,readwrite,copy) ButtonPressCallback onButtonPressedCallback;

/** Called when a button press is detected. */
- (void) onButtonPressed;

/** Called when a button is pushed down. */
- (void) onButtonDown;

/** Called when a button is released. */
- (void) onButtonUp;

- (BOOL) touchHitsSelf:(UITouch*) touch;

- (BOOL) touch:(UITouch*) touch hitsNode:(CCNode*) node;

@end

ButtonSprite.m:

//
//  ButtonSprite.m
//  TestButtonSprite
//
//  Created by Karl Stenerud on 9/1/11.
//

#import "ButtonSprite.h"


@interface ButtonSprite ()

- (void) registerWithTouchDispatcher;
- (void) unregisterWithTouchDispatcher;

@end

@implementation ButtonSprite

@synthesize touchEnabled = touchEnabled_;
@synthesize touchPriority = touchPriority_;
@synthesize swallowTouches = swallowTouches_;
@synthesize onButtonPressedCallback = onButtonPressedCallback_;

- (id) init
{
    if(nil != (self = [super init]))
    {
        touchPriority_ = 0;
        swallowTouches_ = YES;
        touchEnabled_ = YES;

        self.isRelativeAnchorPoint = YES;
        self.anchorPoint = ccp(0.5, 0.5);
    }
    return self;
}

- (void) dealloc
{
    [self unregisterWithTouchDispatcher];
    [onButtonPressedCallback_ release];

    [super dealloc];
}

- (void) registerWithTouchDispatcher
{
    [self unregisterWithTouchDispatcher];

    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:self.touchPriority swallowsTouches:self.swallowTouches];
    registeredWithDispatcher_ = YES;
}

- (void) unregisterWithTouchDispatcher
{
    if(registeredWithDispatcher_)
    {
        [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
        registeredWithDispatcher_ = NO;
    }
}

- (void) setSwallowTouches:(BOOL) value
{
    if(swallowTouches_ != value)
    {
        swallowTouches_ = value;

        if(isRunning_ && touchEnabled_)
        {
            [self registerWithTouchDispatcher];
        }
    }
}

- (void) setTouchPriority:(int) value
{
    if(touchPriority_ != value)
    {
        touchPriority_ = value;
        if(isRunning_ && touchEnabled_)
        {
            [self registerWithTouchDispatcher];
        }
    }
}

-(void) setTouchEnabled:(BOOL)enabled
{
    if( touchEnabled_ != enabled )
    {
        touchEnabled_ = enabled;
        if( isRunning_ )
        {
            if( touchEnabled_ )
            {
                [self registerWithTouchDispatcher];
            }
            else
            {
                [self unregisterWithTouchDispatcher];
            }
        }
    }
}

- (void)cleanup
{
    self.touchEnabled = NO;
}

#pragma mark TouchableNode - Callbacks
-(void) onEnter
{
    // register 'parent' nodes first
    // since events are propagated in reverse order
    if (self.touchEnabled)
    {
        [self registerWithTouchDispatcher];
    }

    // then iterate over all the children
    [super onEnter];
}

-(void) onExit
{
    if(self.touchEnabled)
    {
        [self unregisterWithTouchDispatcher];
    }

    [super onExit];
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    if([self touchHitsSelf:touch])
    {
        touchInProgress_ = YES;
        buttonWasDown_ = YES;
        [self onButtonDown];
        return YES;
    }
    return NO;
}

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{   
    if(touchInProgress_)
    {
        if([self touchHitsSelf:touch])
        {
            if(!buttonWasDown_)
            {
                [self onButtonDown];
            }
        }
        else
        {
            if(buttonWasDown_)
            {
                [self onButtonUp];
            }
        }
    }
}

-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{   
    if(buttonWasDown_)
    {
        [self onButtonUp];
    }
    if(touchInProgress_ && [self touchHitsSelf:touch])
    {
        touchInProgress_ = NO;
        [self onButtonPressed];
    }
}

-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
    if(buttonWasDown_)
    {
        [self onButtonUp];
    }
    touchInProgress_ = NO;
}

- (void) onButtonDown
{
    buttonWasDown_ = YES;
}

- (void) onButtonUp
{
    buttonWasDown_ = NO;
}

- (void) onButtonPressed
{
    self.onButtonPressedCallback(self);
}


- (BOOL) touchHitsSelf:(UITouch*) touch
{
    return [self touch:touch hitsNode:self];
}

- (BOOL) touch:(UITouch*) touch hitsNode:(CCNode*) node
{
    CGRect r = CGRectMake(0, 0, node.contentSize.width, node.contentSize.height);
    CGPoint local = [node convertTouchToNodeSpace:touch];

    return CGRectContainsPoint(r, local);
}

@end

Use it like so:

    ButtonSprite* myButton = [ButtonSprite spriteWithFile:@"button_image.png"];
    myButton.onButtonPressedCallback = ^(ButtonSprite* button)
    {
        NSLog(@"Pressed!");
    };
    [self addChild: myButton];

Note that if you use this class in a batch node, it MUST NOT have any children of its own!


I've wrestled with this a lot, myself, and my conclusion is that this is not possible and you have to use a separate image file for each button.

I was hoping this feature would appear in 1.0 but I don't think it did.

Hoping I'm wrong, though! :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜