Wednesday, January 5, 2011

Creating a Slider Control in cocos2d

I wanted to add a little slider control to allow the user to set the background music level in a game I am working on. So I created this little CCSliderControl class that I think is cute and useful.

The CCSliderControl is sub-classed from CCLayer. Here is the .h file for it:

 @protocol CCSliderControlDelegate  
 - (void) valueChanged: (float) value tag: (int) tag;  
 @end  
 @interface CCSliderControl : CCLayer {  
      float value;  
      id<CCSliderControlDelegate> delegate;  
      float minX;  
      float maxX;  
 }  
 @property (nonatomic, assign) float value;  
 @property (nonatomic, retain) id<CCSliderControlDelegate> delegate;  
 @end  

In the init of this class instance, I add two sprites - one for the background, and one for the slider thumb:

 -(id) init  
 {  
      if ((self = [super init]))  
      {  
           CCLOG(@"init %@", self);  
           self.isTouchEnabled = YES;  
           value = 0;  
           // add the slider background  
           CCSprite *bg = [CCSprite spriteWithFile:@"slider_background.png"]; //- TODO: add this to some texture atlas  
           [self setContentSize:[bg contentSize]];  
           bg.position = CGPointMake([bg contentSize].width / 2, [bg contentSize].height / 2);  
           [self addChild:bg];  
           // add the slider thumb  
           CGSize thumb_size;  
           CCSprite *thumb = [CCSprite spriteWithFile:@"slider_thumb.png"]; //- TODO: add this to some texture atlas  
           thumb_size = [thumb contentSize];  
           minX = thumb_size.width / 2;  
           maxX = [self contentSize].width - thumb_size.width / 2;  
           thumb.position = CGPointMake(minX, [self contentSize].height / 2);  
           [self addChild:thumb];  
      }  
      return self;  
 }  

The setValue method that sets the value property of the instance also updates the thumb sprite position:

- (void) setValue:(float) newValue
{
    if (newValue < 0) newValue = 0;
    if (newValue > 1.0) newValue = 1.0;
    value = newValue;
    CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
    CGPoint pos = thumb.position;
    pos.x = minX + newValue * (maxX - minX);
    thumb.position = pos;
    
}

Next step is to register for touch events:

-(void) registerWithTouchDispatcher
{
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
}


and then handle the touch events:

-(CGPoint) locationFromTouch:(UITouch *)touch
{
    CGPoint touchLocation = [touch locationInView: [touch view]];
    touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
    CGRect bbox = [self boundingBox];
    touchLocation.x -= bbox.origin.x;
    touchLocation.y -= bbox.origin.y;
    return touchLocation;
}

-(bool) isTouchForMe:(CGPoint)touchLocation
{
    CCSprite *bg = (CCSprite *)[[self children] objectAtIndex:0];
    return CGRectContainsPoint([bg boundingBox], touchLocation);
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    CGPoint location = [self locationFromTouch:touch];
    bool isTouchHandled = [self isTouchForMe:location];
    if (isTouchHandled) {
        CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
        thumb.color = ccYELLOW;
        CGPoint pos = thumb.position;
        pos.x = location.x;
        thumb.position = pos;
    }
    return isTouchHandled; // YES for events I handle
}

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
    CGPoint location = [self locationFromTouch:touch];
    if ((location.x < minX) || (location.x > maxX))
        return;

    CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
    CGPoint pos = thumb.position;
    pos.x = location.x;
    thumb.position = pos;
}

-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
    CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
    thumb.color = ccWHITE;
    value = (thumb.position.x - minX) / (maxX - minX);
    [delegate valueChanged:value tag:self.tag];
}


And the result looks like this:



You can get the source code from bitbucket by using mercurial:


$ hg clone https://iroth_net@bitbucket.org/iroth_net/ccslider

8 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Sorry i got the finger to move correctly once i properly looked through your project :)

    One thing though how would i actually implement this so i can changed the volume of my music? My music is being played like this:

    "if(![[CDAudioManager sharedManager]isBackgroundMusicPlaying])
    {
    [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TileMap.caf"];
    }"

    Thanks in advance.

    ReplyDelete
  3. Sorry got it fixed no more messages,

    if anyone else has the problem i had i used this code :

    "- (void) valueChanged: (float) value tag: (int) tag
    {
    if (tag == 1) // music volume
    [self updateLabel:value];
    [CDAudioManager sharedManager].backgroundMusic.volume = value;

    CCLOG (@"Unknown slider");
    }"

    ReplyDelete
  4. Hello!

    Thank you for sharing CCSliderControl. I changed it a little:
    added Mac OS support and changed hardcoded behavior to CCSprite as background and CCMenuItem as a thumb at init.

    Grab it here: https://github.com/psineur/CCSlider

    Best Regards,
    Stepan

    ReplyDelete
  5. Hello. First of all I would like to thank you for this control, it has come in handy for an app I am building. However, I would like to know if it is possible to use the control vertically rather than horizontally as I see it is configured.

    Thanks.

    ReplyDelete
  6. Hi Cenobyte - sure, just change the orientation of the graphics and in the ccTouchMoved method compute the value based on y rather than x...

    ReplyDelete