2013年2月19日 星期二

CCSprite的touch 與 透明部份的檢查

因為要做一個CCSprite的圖 並播動畫, 之後還要可以點擊 做一些事.
還要能判斷是不是點到圖的透明部份..


首先是 CCSprite的 touch event 部份
自訂一個MySprite的類別.



MySprite.h

@interface MySprite : CCSprite < CCTargetedTouchDelegate > {

}
@end
加上 < CCTargetedTouchDelegate > 就好..

然後
MySprite.h
@implementation MySprite

- (void)onEnter{
    [[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:1 swallowsTouches:NO];
    [super onEnter];
}

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

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
    CCLog(@"touch ");
    return YES;
}

@end

加上onEnter跟 onExit中的設定, 就可以取得touch event.
之後就可以在對應的函式中接收到點擊事件 做想做的事.

OnEnter中的設定 . :
priority 設定這個物件的優先度 .
swallowsTouches 設定是否要吃掉touch events 不往下傳.
(這跟- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event 的回傳值也有關)

---------

再來就是檢查是不是在sprite範圍內,並檢查點擊位置的alpha值.

//判段是否在sprite範圍, 並檢查alpha值,是否透明.

- (BOOL)containsTouchLocation:(UITouch *)touch
{

    CGPoint touchLocation = [touch locationInView: [touch view]];

    touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
   
    CGPoint local = [self convertToNodeSpace:touchLocation];
   
    CGRect r = [self rect];
   
    r.origin = CGPointZero;
   
    BOOL isTouched = CGRectContainsPoint( r, local );
   
    if (isTouched) {
       
        //在sprite上的座標 , 由GL系統轉成UI系統.
        CGPoint uilocal = CGPointMake(local.x, self.boundingBox.size.height-local.y);
       
        int alpha = [self getPixelColorAtLocation:uilocal];

        if (alpha < 85) { //小於85 表示透明,不攔截事件.
            return NO;
        }
       
        return isTouched;
    }
   
    return isTouched;
}

-(CGRect) rect
{
    return CGRectMake( position_.x - contentSize_.width*anchorPoint_.x,
                      position_.y - contentSize_.height*anchorPoint_.y,
                      contentSize_.width, contentSize_.height);
}

以上是判斷是否在sprite範圍內 的code.

接下來用getPixelColorAtLocation: 取得sprite上的某點的color中的alpha

//取得某pixel的color 的alpha值.
- (int)getPixelColorAtLocation:(CGPoint)point
{
   
    CGContextRef cgctx = [self createARGBBitmapContextFromImage];
    if (cgctx == NULL) { return -1; /* error */ }

   
    size_t w = [self boundingBox].size.width;
    size_t h = [self boundingBox].size.height;
   
    CGRect rect = {{0,0},{w,h}};


    //取得的offset不是原始texture上對應的資料,所以自己算.
    int oriOffsetX = self.offsetPosition.x - (w-self.textureRect.size.width )/2 ;
    int oriOffsetY = self.offsetPosition.y - (h-self.textureRect.size.height)/2 ;
    if (self.flipX) {
        oriOffsetX *= -1;
    }
    if (self.flipY) {
        oriOffsetY *= -1;
    }

    CGPoint oriOffset = CGPointMake(oriOffsetX , oriOffsetY);



    //使用CCSpriteFrame 從目前的CCSprite中建立新的CCSprite .
    //解決CCSprite從spritesheet產生會造成texture是整張大圖的問題.
    CCSpriteFrame *sprFrame = [CCSpriteFrame frameWithTexture:[self texture] rectInPixels:self.textureRect rotated:self.textureRectRotated offset:self.offsetPosition originalSize:CGSizeMake(w, h)];
   
    CCSprite *spr = [CCSprite spriteWithSpriteFrame:sprFrame];
     
  //做跟目前圖形對應的翻轉,這樣取值才正確.
    [spr setFlipX:self.flipX];
    [spr setFlipY:self.flipY];

    CGContextDrawImage(cgctx, rect, [[UIImage convertSpriteToImage:spr] CGImage]);
   
    unsigned char* data = CGBitmapContextGetData (cgctx);
   
    int alpha;
   
    if (data != NULL) {
       
        @try {
            int offset = 4*((w*round(point.y))+round(point.x));
           
            alpha =  data[offset];
        }
        @catch (NSException * e) {
        }
        @finally {
           
        }  
    }
   
    CGContextRelease(cgctx);
   
    if (data) { free(data); }
   
    return alpha;
}

- (CGContextRef)createARGBBitmapContextFromImage
{
   
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
   
    size_t pixelsWide = [self boundingBox].size.width;
    size_t pixelsHigh = [self boundingBox].size.height;
   
    bitmapBytesPerRow   = (pixelsWide * 4);
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
   
    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
        return nil;
   
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    {
        CGColorSpaceRelease( colorSpace );
        return nil;
    }
   
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst);
   
    if (context == NULL)
    {
        free (bitmapData);
        fprintf (stderr, "Context not created!");
    }
   
    CGColorSpaceRelease( colorSpace );
   
    CGContextSetBlendMode(context, kCGBlendModeCopy);
   
    return context;
}

其中會用到
+(UIImage *) convertSpriteToImage:(CCSprite *)sprite
{
    CGPoint p = sprite.anchorPoint;
    [sprite setAnchorPoint:ccp(0,0)];
    CCRenderTexture *renderer = [CCRenderTexture renderTextureWithWidth:sprite.contentSize.width height:sprite.contentSize.height];
    [renderer begin];
    [sprite visit];
    [renderer end];
    [sprite setAnchorPoint:p];
   
    return [renderer getUIImage];
}
這個 convertSpriteToImage 去將sprite轉成UIImage, 然後再從UIImage 取CGImage 再去處理.
取得對應某一個點的ARGB 的 color[4] , 將alpha 值回傳就是判斷透明度會需要的.

1 則留言:

  1. 加上offset計算 跟flip 處理.

    offset的部份是因為做spritesheet時 如果開 Trim , 那在讀texture時的 offset 跟從ccsprite物件中再去取得的offsetpostion不一樣.
    所以自己算一個原本的正確讀texture用的offset來取圖.

    flip則是如果ccsprite有翻轉的話, 那產出檢查用的圖也要翻轉, 這樣取得的點才正確.

    回覆刪除