Sound effect stops midway when multiple sounds are being played? - cocos2d-iphone

I have a charging up sound effect that is 8 seconds long, which is triggered to play only at the end of each level in my game.
The problem is, the sound effect stops midway through when there are other sounds playing at the same time (lasers, explosions, etc). I was reading elsewhere that you can have up to 32 simultaneous sounds playing at the same time, but the game is maybe playing up to 7 or 8 sound effects at the same time max. But the charging up sound effect still cuts out.
If I don't do any shooting in the game and let the charging up effect just play, it plays all the way through.
I preload my sounds at start up like so:
-(void)setupSound
{
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:#"Kickshock.mp3" loop:YES];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"explosion_large.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"explosion_small.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"laser_ship.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"powerup.mp3"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"railgun.mp3"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"metal.mp3"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"count.mp3"];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"charge_up.mp3"];
}
-(id)init
{
if( (self = [super init]) )
{
[self setupSound];
// Rest of other code..
}
return self;
}
and I play them when needed (shooting laser, end of level, things exploding, etc):
[[SimpleAudioEngine sharedEngine] playEffect:#"charge_up.mp3" pitch:1.0 pan:0.0 gain:0.4];
I'm guessing that the charge up sound is losing its "slot" since other sounds are playing? Like mentioned above, I only have 7 or 8 sounds max playing simultaneously when the charging up sound effect cuts off.
If my sound is losing its "slot", is there a way that I can lock in a particular sound effect so that it won't lose its "slot"?
Any idea on what I'm doing wrong or what I can do to remedy this? Any help is greatly appreciated! :D Thanks for looking.

To play multiple sounds, I'd use CDAudioManager with CDSoundEngine instead of SimpleAudioEngine.
CDSoundEngine allows multiple channels (up to 32), which can play sounds at the same time.
Example:
// create engine
CDSoundEngine * engine = [CDAudioManager sharedManager].soundEngine;
// assign groups
NSArray * groups = [NSArray arrayWithObjects:
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:1], nil];
[engine defineSourceGroups:groups];
[CDAudioManager initAsynchronously:kAMM_FxPlusMusicIfNoOtherAudio];
// load requests
NSMutableArray * requests = [[[NSMutableArray alloc] init] autorelease];
NSString * plist_sounds = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:#"Sounds.plist"]];
if (![[NSFileManager defaultManager] fileExistsAtPath:plist_sounds]) {
plist_sounds = [[NSBundle mainBundle] pathForResource:#"Sounds" ofType:#"plist"];
}
NSDictionary * dictionary_sounds = [[NSDictionary dictionaryWithContentsOfFile:plist_sounds] objectForKey:#"Sounds"];
if (dictionary_sounds != nil) {
for (id key in dictionary_sounds) {
[requests addObject:[[[CDBufferLoadRequest alloc] init:[key intValue] filePath:[dictionary_sounds objectForKey:key]] autorelease]];
}
}
[engine loadBuffersAsynchronously:requests];
Then to play a sound:
- (ALuint)play:(NSInteger)sound group:(NSInteger)group loop:(BOOL)forever volume:(float)volume {
[[CDAudioManager sharedManager].soundEngine playSound:sound sourceGroupId:group pitch:1.0f pan:0.0f gain:volume loop:forever];
}

Ok I got it figured out after watching the tutorial from:
http://bobueland.com/cocos2d/?p=200
I didn't realize that SimpleAudioEngine is meant for short sound effects.
For those of you who are stuck at this point, here's what I did:
In the interface:
CDLongAudioSource *rightChannel;
In my init or setupSound method:
rightChannel = [[CDAudioManager sharedManager] audioSourceForChannel:kASC_Right];
[rightChannel load:#"charge_up.mp3"];
And when I want to play it:
[rightChannel play];
I'm sure this will only work if you have only one long sound effect. If you have multiple long sound effects, I'm sure you have to approach it differently. Unless I just create more instances of CDLongAudioSource? Does anyone have any links / tutorials for that? Thanks!

sorry for my simple english...
simple engine have max 32 audio channels at moment
if(SoundId == 0)
{
// when you say playEffect engine capture one channel for effect
// do playEffect once and save result SoundID for Stop or Resume effect
SoundId = SimpleAudioEngine::sharedEngine()->playEffect("audio.mp3");
}
// effect loaded, channel captured, say resume for play effect again, nothing more
else
{
SimpleAudioEngine::sharedEngine()->resumeEffect(SoundId);
}
// if you say effect nothing will happen with channel try
SimpleAudioEngine::sharedEngine()->stopEffect(SoundID);
In this case, the channel of the first effect will not be replaced by a new effect,
if the number of runs more than 32

Related

iAd reducing FPS?

Does iAd banner reduces FPS?
Indeed, since I added iAd my FSP is shit. I was 59 all game and now it's variable from 35-50 FPS.
Any ideas please because my game is almost not playable with this banner.
Thank for help.
You should implement the iAd logic in your UINavigationController subclass that was set in the appDelegate. At mine code it look like:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create the main window
window_ = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
CCGLView *glView = [CCGLView viewWithFrame:[window_ bounds]
pixelFormat:kEAGLColorFormatRGB565
depthFormat:0
preserveBackbuffer:NO
sharegroup:nil
multiSampling:NO
numberOfSamples:0];
// Enable multiple touches
[glView setMultipleTouchEnabled:YES];
director_ = (CCDirectorIOS*) [CCDirector sharedDirector];
director_.wantsFullScreenLayout = YES;
// Display FSP and SPF
[director_ setDisplayStats:YES];
// set FPS at 60
[director_ setAnimationInterval:1.0/60];
//FEW LINES LATER
// Create a Navigation Controller with the Director
navController_ = [[MyNavigationController alloc] initWithRootViewController:director_];
navController_.navigationBarHidden = YES;
[self checkDeviceType];
// setup iAds bannerView
[self setupIADs];
// for rotation and other messages
[director_ setDelegate:navController_];
// set the Navigation Controller as the root view controller
[window_ setRootViewController:navController_];
// make main window visible
[window_ makeKeyAndVisible];
return YES;
}
- (void)setupIADs {
CGSize size = [[CCDirector sharedDirector] winSize];
float bannerHeight = _isiPadRunning ? 66.0f : 50.0f;
ADAdType bannerType = _isiPadRunning ? ADAdTypeMediumRectangle : ADAdTypeBanner;
_bannerView = [[ADBannerView alloc] initWithAdType:bannerType];
_bannerView.frame = CGRectMake(0, navController_.view.frame.size.height, size.width, bannerHeight);
CGSize newBannerSize = [_bannerView sizeThatFits:CGSizeMake(size.width, bannerHeight)];
_bannerView.frame = CGRectMake(0, navController_.view.frame.size.height,
newBannerSize.width, newBannerSize.height);
_bannerView.delegate = self;
//get view
UIView *mainView = [navController_ view];
[mainView addSubview:_bannerView];
[mainView setNeedsLayout];
}
As you can see I use navController_ to place an iAd banner view. No FPS drops! :)
Please, try it out. For me it helped and I have no FPS drops or delay. Everything works great.

How to take screenshot of a user defined rectangular area in cocos2d

I need to take the screenshot in my cocos2d application. I've searched a lot, even in stack overflow. Then I found the following code:
+(UIImage*) screenshotWithStartNode:(CCNode*)stNode
{
[CCDirector sharedDirector].nextDeltaTimeZero = YES;
CGSize winSize = [[CCDirector sharedDirector] winSize];
CCRenderTexture* renTxture =
[CCRenderTexture renderTextureWithWidth:winSize.width
height:winSize.height];
[renTxture begin];
[stNode visit];
[renTxture end];
return [renTxture getUIImage];
}
Now,
Problem: The above code gives me the entire screenshot. But I am in need of the screenshot of a custom are, such as, within a CGRect(50,50,100,200).
Can anyone please help me..? Thanks...
Wow... found out what I need. I've changed the above method like:
-(UIImage*) screenshotWithStartNode:(CCNode*)startNode
{
[CCDirector sharedDirector].nextDeltaTimeZero = YES;
CGSize winSize = [CCDirector sharedDirector].winSize;
CCRenderTexture* rtx =
[CCRenderTexture renderTextureWithWidth:winSize.width
height:winSize.height];
[rtx begin];
[startNode visit];
[rtx end];
UIImage *tempImage = [rtx getUIImage];
CGRect imageBoundary = CGRectMake(100, 100, 300, 300);
UIGraphicsBeginImageContext(imageBoundary.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// translated rectangle for drawing sub image
CGRect drawRect = CGRectMake(-imageBoundary.origin.x, -imageBoundary.origin.y, tempImage.size.width, tempImage.size.height);
// clip to the bounds of the image context
CGContextClipToRect(context, CGRectMake(0, 0, imageBoundary.size.width, imageBoundary.size.height));
// draw image
[tempImage drawInRect:drawRect];
// grab image
UIImage* subImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return subImage;
}

Why is my main menu image wrong sized on launch but not after a game?

I have an image that loads on a MainMenu scene and appears as wrong sized when the game launches but then after finishing a level returns you to the MainMenu again the image appears right sized.
The first time the image appears to be three quarters or 4/5 of the size of the screen from left to right. The image has a white background around it so I can see a black tall rectangle in the right side of the screen on the main menu's first launch. But after a game it is sized properly and the white background image is sized properly.
Anybody ever have this happen?
Here is the init code for the MainMenuLayer:
-(id)init {
if( (self=[super initWithColor:ccc4(255,255,255,255)]) ) {
[[GameManager sharedGameManager] playBackgroundTrack:BACKGROUND_TRACK_MAIN_MENU];
CGSize screenSize = [CCDirector sharedDirector].winSize;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
CCSprite *background = [CCSprite spriteWithFile:#"MainMenu-iPad.png"];
[background setPosition:ccp(screenSize.width/2,screenSize.height/2)];
[self addChild:background];
[self displayMainMenu];
} else {
CCSprite *background = [CCSprite spriteWithFile:#"MainMenu.png"];
[background setPosition:ccp(screenSize.width/2,screenSize.height/2)];
[self addChild:background];
[self displayMainMenu];
}
-
It works fine on the simulator though...
Print screen position, if position is wrong then put you code in onEnter instead of init.
See this thread in Stack overflow.
-(id)init {
if( (self=[super initWithColor:ccc4(255,255,255,255)]) ) {
}
return self;
}
-(void)onEnter {
[super onEnter];
[[GameManager sharedGameManager] playBackgroundTrack:BACKGROUND_TRACK_MAIN_MENU];
CGSize screenSize = [CCDirector sharedDirector].winSize;
CCSprite *background = [CCSprite spriteWithFile:#"MainMenu.png"];
[background setPosition:ccp(screenSize.width/2,screenSize.height/2)];
[self addChild:background];
[self displayMainMenu];
}
The image was apparently being cached even after clean and build! I ended up replacing the image by removing the original and adding the same one but with some markers just to make sure it got replaced.

Cocos2d: How to play a video in the background of a CCLayer

I want the video play in the background, and the text label in the front, run the following code, video is playing, but text label does not show!
-(id) init {
if(!(self=[super init])) {
return nil;
}
CGSize size = [[CCDirector sharedDirector] winSize];
// MP4
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"test" ofType:#"m4v"]];
moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
[moviePlayer respondsToSelector:#selector(setFullscreen:animated:)];
moviePlayer.controlStyle = MPMovieControlStyleNone;
moviePlayer.shouldAutoplay = YES;
moviePlayer.repeatMode = MPMovieRepeatModeOne;
moviePlayer.view.frame = CGRectMake(0, 0, size.height, size.width);
[viewController.view addSubview:moviePlayer.view];
[viewController.view sendSubviewToBack:moviePlayer.view];
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Hello World" fontName:#"Marker Felt" fontSize:64];
label.position = ccp( size.width /2 , size.height/2 );
[self addChild: label];
return self;
}
I found the answer:
First in AppDelegate.m replace kEAGLColorFormatRGB565 with kEAGLColorFormatRGBA8, Second, as the following code, the last 4 lines is important:
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"video" ofType:#"mp4"]];
_moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayBackDidFinish)
name:MPMoviePlayerPlaybackDidFinishNotification
object:_moviePlayer];
[_moviePlayer respondsToSelector:#selector(setFullscreen:animated:)];
_moviePlayer.controlStyle = MPMovieControlStyleNone;
_moviePlayer.shouldAutoplay = YES;
_moviePlayer.repeatMode = MPMovieRepeatModeOne;
_moviePlayer.view.frame = CGRectMake(0, 0, 300, 300);
UIView* glView = [CCDirector sharedDirector].openGLView; // attention
[glView.superview insertSubview:_moviePlayer.view atIndex:0]; // attention
glView.opaque = NO; // attention
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // attention
CCVideo player in one of the best options to play video in cocos2D project
[CCVideoPlayer setDelegate: self];
[CCVideoPlayer playMovieWithFile: #"bait.m4v"];

Cocos2d 2.0 - Ignoring touches to transparent areas of layers/sprites

I have an app where I have several layers created from PNG images with transparency. These layers are all on the screen over each other. I need to be able to ignore touches given to transparent areas of layers and just be able to detect as touches, when the user taps on a non-transparent area of a layer... see pic...
How do I do that? thanks.
Here you have a possible solution.
Implement an extension on CCLayer and provide this method:
- (BOOL)isPixelTransparentAtLocation:(CGPoint)loc
{
//Convert the location to the node space
CGPoint location = [self convertToNodeSpace:loc];
//This is the pixel we will read and test
UInt8 pixel[4];
//Prepare a render texture to draw the receiver on, so you are able to read the required pixel and test it
CGSize screenSize = [[CCDirector sharedDirector] winSize];
CCRenderTexture* renderTexture = [[CCRenderTexture alloc] initWithWidth:screenSize.width
height:screenSize.height
pixelFormat:kCCTexture2DPixelFormat_RGBA8888];
[renderTexture begin];
//Draw the layer
[self draw];
//Read the pixel
glReadPixels((GLint)location.x,(GLint)location.y, kHITTEST_WIDTH, kHITTEST_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
//Cleanup
[renderTexture end];
[renderTexture release];
//Test if the pixel's alpha byte is transparent
return (pixel[3] == 0);
}
if Lio's solution doesn't work, you may add add transparent sprite as a child you yours, place it just under your non-transparent area with size of this non-tranparent area and resive all touches by this new transparent sprite, but not by original sprite.
Here is my solution to your requirement, let me know if it works or not
Create a Category on CCMenu with Name Transparent
File CCMenu+Tranparent.h
#import "CCMenu.h"
#interface CCMenu (Transparent)
#end
File CCMenu+Tranparent.m
#import "CCMenu+Transparent.h"
#import "cocos2d.h"
#implementation CCMenu (Transparent)
-(CCMenuItem *) itemForTouch: (UITouch *) touch{
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
CCMenuItem* item;
CCARRAY_FOREACH(children_, item){
UInt8 data[4];
// ignore invisible and disabled items: issue #779, #866
if ( [item visible] && [item isEnabled] ) {
CGPoint local = [item convertToNodeSpace:touchLocation];
/*
TRANSPARENCY LOGIC
*/
//PIXEL READING 1 PIXEL AT LOCATION
CGRect r = [item rect];
r.origin = CGPointZero;
if( CGRectContainsPoint( r, local ) ){
if([NSStringFromClass(item.class) isEqualToString:NSStringFromClass([CCMenuItemImage class])]){
CCRenderTexture* renderTexture = [[CCRenderTexture alloc] initWithWidth:item.boundingBox.size.width * CC_CONTENT_SCALE_FACTOR()
height:item.boundingBox.size.height * CC_CONTENT_SCALE_FACTOR()
pixelFormat:kCCTexture2DPixelFormat_RGBA8888];
[renderTexture begin];
[[(CCMenuItemImage *)item normalImage] draw];
data[3] = 1;
glReadPixels((GLint)local.x,(GLint)local.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);
[renderTexture end];
[renderTexture release];
if(data[3] == 0){
continue;
}
}
free(data);
return item;
}
}
}
return nil;
}
#end
This will check for pixel for returning the CCMenuItem.
Its working fine here.. let me know if you face any issues
-Paresh Rathod
Cocos2d Lover
The solution that worked great for me was using Sprite sheets. I use TexturePacker to create sprite sheets. Steps to create sprite sheet using TexturePacker:
1. Load all the image (.png) files into TexturePacker.
2. Chose data format as coco2d and choose PVR as the texture format.
3. Load the sprite sheet into your code and extract images from your sprite sheet.
Detailed description can be found here.