Рекомендации по обращению с CCSprite с помощью cocos2d

Эй, все. Я только начал изучать библиотеку cocos2d. Я слышал, что это простая библиотека, в которую можно попасть, если вы привыкли к программированию в ActionScript, и я обнаружил, что многие концепции действительно похожи.

Я начал искать примеры проектов (примеры игр, связанных здесь были особенно полезны), и я увидел, что обработка касаний обычно не выполняется в CCSprite. Скорее, CCLayer, который запускает CCSprites, реагирует на событие касания и выполняет итерации через созданные им спрайты, чтобы обнаружить, какой CCSprite был затронут (если есть).

Я хочу, чтобы CCSprites справлялся с тем, были ли они затронуты сами, и вызывайте, чтобы сообщить, что он был затронут (если необходимо). Класс Paddle, найденный в /tests/TouchesTest, делает именно это - он сам обрабатывает касания.

Итак, у меня есть вопрос: что считается лучшей практикой для этого? Лучше ли прикасаться к центральному месту и проходить через детей, чтобы увидеть, что было затронуто? Или каждый ребенок должен обрабатывать собственные события касания? Или это не имеет значения?

Я бы предпочел, чтобы каждый ребенок обрабатывал свои собственные события касания, но я хотел бы следовать лучшим практикам в этом (если они существуют). Спасибо!

Ответ 1

Я думаю, что это вопрос предпочтения, но мне нравится обнаруживать спрайт, если он был затронут путем подкласса CCSprite. Я делаю метод getter в моем подклассе CCSprite, который извлекает переменную состояния из подкласса, а затем основная программа может действовать соответствующим образом.

Вот пример заголовочного файла для моего подкласса CCSprite "spuButton":

    #import "cocos2d.h"

    typedef enum tagButtonState {
        kButtonStatePressed,
        kButtonStateNotPressed
    } ButtonState;

    typedef enum tagButtonStatus {
        kButtonStatusEnabled,
        kButtonStatusDisabled
    } ButtonStatus;

    @interface spuButton : CCSprite <CCTargetedTouchDelegate> {
    @private
        ButtonState state;
        CCTexture2D *buttonNormal;
        CCTexture2D *buttonLit;
        ButtonStatus buttonStatus;

    }

    @property(nonatomic, readonly) CGRect rect;

    + (id)spuButtonWithTexture:(CCTexture2D *)normalTexture;

    - (void)setNormalTexture:(CCTexture2D *)normalTexture;
    - (void)setLitTexture:(CCTexture2D *)litTexture;
    - (BOOL)isPressed;
    - (BOOL)isNotPressed;

    @end

и вот пример файла .m:

    #import "spuButton.h"
    #import "cocos2d.h"

    @implementation spuButton

    - (CGRect)rect
    {
        CGSize s = [self.texture contentSize];
        return CGRectMake(-s.width / 2, -s.height / 2, s.width, s.height);
    }

    + (id)spuButtonWithTexture:(CCTexture2D *)normalTexture
    {
        return [[[self alloc] initWithTexture:normalTexture] autorelease];
    }

    - (void)setNormalTexture:(CCTexture2D *)normalTexture {
        buttonNormal = normalTexture;
    }
    - (void)setLitTexture:(CCTexture2D *)litTexture {
        buttonLit = litTexture;
    }

    - (BOOL)isPressed {
        if (state == kButtonStateNotPressed) return NO;
        if (state == kButtonStatePressed) return YES;
        return NO;
    }

    - (BOOL)isNotPressed {
        if (state == kButtonStateNotPressed) return YES;
        if (state == kButtonStatePressed) return NO;
        return YES;
    }

    - (id)initWithTexture:(CCTexture2D *)aTexture
    {
        if ((self = [super initWithTexture:aTexture]) ) {

            state = kButtonStateNotPressed;
        }

        return self;
    }

    - (void)onEnter
    {
        [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
        [super onEnter];
    }

    - (void)onExit
    {
        [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
        [super onExit];
    }   

    - (BOOL)containsTouchLocation:(UITouch *)touch
    {
        return CGRectContainsPoint(self.rect, [self convertTouchToNodeSpaceAR:touch]);
    }

    - (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
    {
        if (state == kButtonStatePressed) return NO;
        if ( ![self containsTouchLocation:touch] ) return NO;
        if (buttonStatus == kButtonStatusDisabled) return NO;

        state = kButtonStatePressed;
        [self setTexture:buttonLit];

        return YES;
    }

    - (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
    {
        // If it weren't for the TouchDispatcher, you would need to keep a reference
        // to the touch from touchBegan and check that the current touch is the same
        // as that one.
        // Actually, it would be even more complicated since in the Cocos dispatcher
        // you get NSSets instead of 1 UITouch, so you'd need to loop through the set
        // in each touchXXX method.

        if ([self containsTouchLocation:touch]) return;
        //if (buttonStatus == kButtonStatusDisabled) return NO;

        state = kButtonStateNotPressed;
        [self setTexture:buttonNormal];

    }

    - (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
    {

        state = kButtonStateNotPressed;
        [self setTexture:buttonNormal];


    }

@end

Надеюсь, это поможет и счастливое кодирование!

ДОБАВЛЕНИЕ метода и объяснения тика (для вопроса Стефана ниже):

Чтобы проверить статус кнопок, у меня есть метод tick: метод, который в основном запускает каждый кадр и проверяет состояние всех моих кнопок.

    -(void)tick:(ccTime)dt {

do my button checks here....

}

Я проверяю статус моих кнопок, вызывая функцию isPressed или isNotPressed, которая является частью моего класса spuButton.

for (spuButton *aButton in _fourButtonsArray) {
     if ([aButton isNotPressed]) continue; //this button is not pressed
     .....otherwise record that it is pressed.....
}

Затем я делаю такую ​​же проверку, чтобы убедиться, что она была выпущена и соответственно реагирует. Я делаю это так, потому что хочу иметь возможность реагировать на множественные комбинации клавиш, и я хочу что-то сделать, когда он нажимается, а затем что-то еще, когда он освобождается. Я использую ccTouchBegan и ccTouchEnded для изменения текстур (спрайт-изображение) и для изменения переменной состояния.

Ответ 2

просто добавляя к этому потоку. Марк также дает примеры того, как создать экземпляр spuButton, который полезен:

Проблема с cocos2d и изменения ориентации, текстуры деформированы

вы также можете изменить этот пример, чтобы передать как нормальные, так и освещенные кнопки, например:

+ (id)spuButtonWithTexture:(CCTexture2D *)normalTexture lit:(CCTexture2D *)litTexture

а затем выполните то же самое:

- (id)initWithTexture:(CCTexture2D *)normalTexture lit:(CCTexture2D *)litTexture

и внутри этого метода вы можете установить обе текстуры:

[self setNormalTexture:normalTexture];
[self setLitTexture:litTexture];

Ответ 3

Вот мое решение, основанное на CCSprite, надеюсь, что это будет полезно для кого-то

это протокол для контролируемого объекта (например, игрок или что-то еще):

@class AGSensitiveButton;

@protocol AGSensitiveButtonControlledObjectProtocol <NSObject>
@required
- (void)sensitiveButtonTouchDown:(AGSensitiveButton *)sButton;
- (void)sensitiveButtonTouchUp:(AGSensitiveButton *)sButton;
@optional
- (void)sensitiveTouchButtonKeepPressed:(AGSensitiveButton *)sButton forTime:(ccTime)pressTime;
@end

.h файл:

#import "CCSprite.h"
#import "cocos2d.h"
#import "AGSensitiveButtonControlledObjectProtocol.h"

typedef enum {
    AGSensitiveButtonStateNormal = 0,
    AGSensitiveButtonStateHighlighted,
    AGSensitiveButtonStateDisabled
} AGSensitiveButtonState;

@interface AGSensitiveButton : CCSprite <CCTargetedTouchDelegate>

@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
@property (nonatomic, assign) ccTime maximumTouchDuration;
@property (nonatomic, weak) id <AGSensitiveButtonControlledObjectProtocol> controlledObject;
@property (nonatomic, copy) void (^touchDownHandler)();
@property (nonatomic, copy) void (^touchUpHandler)();

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture;

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
              disabledtexture:(CCTexture2D *)disabledTexture;

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
             controllerObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject;

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
              disabledtexture:(CCTexture2D *)disabledTexture
             controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject;

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
              disabledtexture:(CCTexture2D *)disabledTexture
             controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject
             touchDownHandler:(void(^)(void))touchDownHandler
               touchUpHandler:(void(^)(void))touchUpHandler;

- (void)setTexture:(CCTexture2D *)texture forState:(AGSensitiveButtonState)state;

- (BOOL)isHighlighted;

@end

.m файл:

#import "AGSensitiveButton.h"

@interface AGSensitiveButton ()
@property (nonatomic, assign) AGSensitiveButtonState state;
@property (nonatomic, strong) NSDictionary *stateTextures;
@property (nonatomic, assign) ccTime currentTouchTime;
@end

@implementation AGSensitiveButton

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture {
    return [self buttonWithNormalTexture:normalTexture
                      highlightedTexture:highTexture
                        controllerObject:nil];
}

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
              disabledtexture:(CCTexture2D *)disabledTexture {
    return [self buttonWithNormalTexture:normalTexture
                      highlightedTexture:highTexture
                         disabledtexture:disabledTexture
                        controlledObject:nil];
}

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
             controllerObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject {
    return [self buttonWithNormalTexture:normalTexture
                      highlightedTexture:highTexture
                         disabledtexture:nil
                        controlledObject:controlledObject];
}

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
              disabledtexture:(CCTexture2D *)disabledTexture
             controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject {
    return [self buttonWithNormalTexture:normalTexture
                      highlightedTexture:highTexture
                         disabledtexture:disabledTexture
                        controlledObject:controlledObject
                        touchDownHandler:NULL
                          touchUpHandler:NULL];
}

+ (id)buttonWithNormalTexture:(CCTexture2D *)normalTexture
           highlightedTexture:(CCTexture2D *)highTexture
              disabledtexture:(CCTexture2D *)disabledTexture
             controlledObject:(id <AGSensitiveButtonControlledObjectProtocol>)controlledObject
             touchDownHandler:(void(^)(void))touchDownHandler
               touchUpHandler:(void(^)(void))touchUpHandler {
    AGSensitiveButton *button = [[self alloc] initWithTexture:normalTexture
                                                         rect:CGRectMake(0.0, 0.0, normalTexture.contentSize.width, normalTexture.contentSize.height)];
    [button setTexture:normalTexture forState:AGSensitiveButtonStateNormal];
    [button setTexture:highTexture forState:AGSensitiveButtonStateHighlighted];
    [button setTexture:disabledTexture forState:AGSensitiveButtonStateDisabled];
    button.controlledObject = controlledObject;
    button.touchDownHandler = touchDownHandler;
    button.touchUpHandler = touchUpHandler;
    return button;
}

- (void)setEnabled:(BOOL)enabled {
    [self setupNewState:enabled ? AGSensitiveButtonStateNormal : AGSensitiveButtonStateDisabled];
}

- (BOOL)isEnabled {
    return (self.state != AGSensitiveButtonStateDisabled);
}

- (BOOL)isHighlighted {
    return (self.state == AGSensitiveButtonStateHighlighted);
}

- (void)toggleTextureForCurrentState {
    CCTexture2D *textureToSet = [self.stateTextures objectForKey:[NSNumber numberWithInteger:self.state]];
    if (textureToSet) {
        self.texture = textureToSet;
        self.textureRect = CGRectMake(0.0, 0.0, textureToSet.contentSize.width, textureToSet.contentSize.height);
    }
}

- (void)setTexture:(CCTexture2D *)texture forState:(AGSensitiveButtonState)state {
    NSMutableDictionary *newStates = self.stateTextures.mutableCopy;
    if (texture) {
        [newStates setObject:texture forKey:[NSNumber numberWithInteger:state]];
    } else {
        [newStates removeObjectForKey:[NSNumber numberWithInteger:state]];
    }
    self.stateTextures = newStates.copy;
}

- (NSDictionary *)stateTextures {
    if (!_stateTextures) {
        _stateTextures = [[NSDictionary alloc] init];
    }
    return _stateTextures;
}

- (void)onEnter {
    [super onEnter];
    [self toggleTextureForCurrentState];
    [[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
    [self scheduleUpdate];
}

- (void)onExit {
    [super onExit];
    [self unscheduleUpdate];
    [[[CCDirector sharedDirector] touchDispatcher] removeDelegate:self];
}

- (void)update:(ccTime)dt {
    if ((self.state == AGSensitiveButtonStateHighlighted) && (self.maximumTouchDuration)) {
        self.currentTouchTime+=dt;
        if (self.currentTouchTime >= self.maximumTouchDuration) {
            [self ccTouchEnded:nil withEvent:nil];
    } else {
        if ([self.controlledObject respondsToSelector:@selector(sensitiveTouchButtonKeepPressed:forTime:)]) {
            [self.controlledObject sensitiveTouchButtonKeepPressed:self forTime:self.currentTouchTime];
        }
    }
    }
}

- (CGRect)rectForTouches {
    return CGRectMake(-self.contentSize.width/2, -self.contentSize.height/2,
                      self.contentSize.width, self.contentSize.height);
}

- (void)forwardTouchDownEventIntoHandlers {
    if ([self.controlledObject respondsToSelector:@selector(sensitiveButtonTouchDown:)]) {
        [self.controlledObject sensitiveButtonTouchDown:self];
    }
    if (self.touchDownHandler) {
        self.touchDownHandler();
    }
}

- (void)forwardTouchUpEventIntoHandlers {
    if ([self.controlledObject respondsToSelector:@selector(sensitiveButtonTouchUp:)]) {
        [self.controlledObject sensitiveButtonTouchUp:self];
    }
    if (self.touchUpHandler) {
        self.touchUpHandler();
    }
}

- (void)setupNewState:(AGSensitiveButtonState)state {
    if (self.state != state) {
        switch (state) {
            case AGSensitiveButtonStateHighlighted: {
                [self forwardTouchDownEventIntoHandlers];
                break;
            }
            default: {
                if (self.state == AGSensitiveButtonStateHighlighted) {
                    [self forwardTouchUpEventIntoHandlers];
                }
                break;
            }
        }
        self.state = state;
        [self toggleTextureForCurrentState];
    }
}

#pragma mark - CCTargetedTouchDelegate

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    if ((self.state != AGSensitiveButtonStateNormal) || (!CGRectContainsPoint([self rectForTouches], [self convertTouchToNodeSpaceAR:touch]))) {
        return NO;
    }
    self.currentTouchTime = 0.0;
    [self setupNewState:AGSensitiveButtonStateHighlighted];
    return YES;
}

- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
    if (self.state == AGSensitiveButtonStateHighlighted) {
        if (!CGRectContainsPoint([self rectForTouches], [self convertTouchToNodeSpaceAR:touch])) {
            [self ccTouchEnded:touch withEvent:event];
        }
    }
}

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
    if (self.state == AGSensitiveButtonStateHighlighted) {
        [self setupNewState:AGSensitiveButtonStateNormal];
    }
}

@end