Question

sorry if the question is too dull but I have been trying to understand as much as possible from Itterheim's book book's code example and can't figure out how this works.

In my "HelloWorldLayer.m" I create an instance of a class named "MusicLayer" that extends CCLayer The inspiration of this was that in the example named "ShootEmUp" (Chapter 8 of 1) there is an InputLayer. I thus thought that I would create a MusicLayer to deal with the various music files I have in my game and got some problems (will explain at the end of the post). I got most of the music related code code from RecipeCollection02, Ch6_FadingSoundsAndMusic of the Cocos2d Cookbook .

But first I would like to introduce some of my background thoughts that might have lead to this problem with the hope to have some clear reference on this.

I am used to a "model view controller" (MVC wikipedia link) approach as I come from a C++ and Java background and never coded a game before and it does seem to me that the gaming paradigms are a bit different from "the classic" Software Engineering University approach (don't take me wrong, I am sure that in many Universities they don't teach this stuff anymore :)).

The point is that it seems to me that the "classic" Software Engineering approach is lost in those examples (for instance in the ShootEmUp code the instance of InputLayer has a reference of the class GameScene -see below- and at the same time in InputLayer there is a reference to the static shared instance of GameScene -see below-).

//From GameScene
+(id) scene
{
CCScene* scene = [CCScene node];
GameScene* layer = [GameScene node];
[scene addChild:layer z:0 tag:GameSceneLayerTagGame];
InputLayer* inputLayer = [InputLayer node];
[scene addChild:inputLayer z:1 tag:GameSceneLayerTagInput];
return scene;
}


//From InputLayer
-(void) update:(ccTime)delta
{

totalTime += delta;

// Continuous fire
if (fireButton.active && totalTime > nextShotTime)
{
    nextShotTime = totalTime + 0.5f;

    GameScene* game = [GameScene sharedGameScene];
    ShipEntity* ship = [game defaultShip];

           //Code missing
}
}

I was told that static instances are things to avoid. Despite this I understand the benefit of having a GameScene staic instance but I still get confused on the biderectional reference (from GameScene to InputLayer and viceversa)..:

Is this common practice in Game programming?

Is there a way to avoid this approach?

I will now paste my code and I admit it, is probably rubbish and there will be probably some obvious error as I am trying to do a very simple thing and I do not manage.

So here we are. That's the MusicLayer class (it contains commented out code as I had previously tried to make it a shared class instance):

//
//  MusicLayer.h
//  ShootEmUp
//

//  Copyright 2012 __MyCompanyName__. All rights reserved.
//

//Enumeration files should be the way those are referred from outside the class..
enum music_files {
    PLAYER_STANDARD = 1,
};

enum sound_files{
    A = 0,
    B = 1,
    C = 2,
    ABC = 4,
    AAC = 5,
    ACA = 6,
    //And so on and so forth..
};

#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "CDXPropertyModifierAction.h"

@interface MusicLayer : CCLayer {
    SimpleAudioEngine *sae;
    NSMutableDictionary *soundSources;
    NSMutableDictionary *musicSources;
}

//In this way you can load once the common sounds on the sharedMusicLayer, have the benefit of keep tracks playing on scene switching as well as being able to load tracks and sounds before each level
//+(MusicLayer*) sharedMusicLayer;

/** API **/
//PLAY  - sound undtested
-(void) playBackgroundMusic:(enum music_files) file;
-(void) playSoundFile:(enum sound_files) file;
-(void) loadPopLevel;

/** Private methods **/
//Utilities methods

-(NSString *) _NSStringFromMusicFiles: (enum music_files) file;
-(NSString *) _NSStringFromSoundFiles: (enum sound_files) file;

//LOAD - Meant to be private methods
-(CDLongAudioSource*) _loadMusic:(NSString*)fn;
-(CDSoundSource*) _loadSoundEffect:(NSString*)fn;

//FADE - sound undtested
-(void) _fadeOutPlayingMusic;
-(void) _fadeInMusicFile:(NSString*)fn;
-(void) _playSoundFile:(NSString*)fn;

@end

And here we are with the .m file:

#import "MusicLayer.h"

@implementation MusicLayer

/**
static MusicLayer* instanceOfMusicLayer;

+(MusicLayer*) sharedMusicLayer
{
    NSAssert(instanceOfMusicLayer != nil, @"MusicLayer instance not yet initialized!");
    return instanceOfMusicLayer;
}**/


-(id) init
{
    CCLOG(@"In Init");
    if ((self = [super init]))
    {
        CCLOG(@"Inside Init");
        //instanceOfMusicLayer = self;

        //Initialize the audio engine
        sae = [SimpleAudioEngine sharedEngine];

        //Background music is stopped on resign and resumed on become active
        [[CDAudioManager sharedManager] setResignBehavior:kAMRBStopPlay autoHandle:YES];

        //Initialize source container
        soundSources = [[NSMutableDictionary alloc] init];
        musicSources = [[NSMutableDictionary alloc] init];


    }

    return self;
}

-(void) loadPopLevel{
    CCLOG(@"In loadPopLevel");
    [self _loadSoundEffect:[self _NSStringFromSoundFiles:A]];
    [self _loadSoundEffect:[self _NSStringFromSoundFiles:B]];
    [self _loadSoundEffect:[self _NSStringFromSoundFiles:C]];
    [self _loadSoundEffect:[self _NSStringFromSoundFiles:ACA]];
    [self _loadSoundEffect:[self _NSStringFromSoundFiles:ABC]];
    [self _loadSoundEffect:[self _NSStringFromSoundFiles:AAC]];

    [self _loadMusic:[self _NSStringFromMusicFiles:PLAYER_STANDARD]];
    [self _loadMusic:[self _NSStringFromMusicFiles:PLAYER_STANDARD]];
    [self _loadMusic:[self _NSStringFromMusicFiles:PLAYER_STANDARD]];
    [self _loadMusic:[self _NSStringFromMusicFiles:PLAYER_STANDARD]];
}


/** UTILITIES METHODS **/
//This function is key to define which files we are going to load..
-(NSString *) _NSStringFromMusicFiles: (enum music_files) file{
    CCLOG(@"In _NSStringFromMusicFiles");

    switch (file) {
        case PLAYER_STANDARD:
            return  @"a.mp3"; 
        default:
            NSAssert(0 == 1, @"Invalid argument");
            break;
    }
}

-(NSString *) _NSStringFromSoundFiles: (enum sound_files) file{
    CCLOG(@"In _NSStringFromSoundFiles");
    switch (file) {
        case A:
            return  @"shot.caf";
        case B:
            return  @"shot.caf";
        case C:
            return  @"shot.caf";
        case AAC:
            return  @"Wow.caf";
        case ABC:
            return  @"Wow.caf";
        case ACA:
            return  @"Wow.caf";
        default:
            NSAssert(0 == 1, @"Invalid argument");
            break;
    }
}

/** PLAY METHODS **/
-(void) _playSoundFile:(NSString*)fn {
    CCLOG(@"In _playSoundFile");
    //Get sound
    CDSoundSource *sound = [soundSources objectForKey:fn];
    sound.looping = YES;

    //Play sound
    if(sound.isPlaying){
        [sound stop];
    }else{
        [sound play];
    }
}

-(void) _fadeInMusicFile:(NSString*)fn {
    CCLOG(@"In _fadeInMusicFile");
    //Stop music if its playing and return
    CDLongAudioSource *source = [musicSources objectForKey:fn];
    if(source.isPlaying){
        [source stop];
        return;
    }

    //Set volume to zero and play
    source.volume = 0.0f;
    [source play];

    //Create fader
    CDLongAudioSourceFader* fader = [[CDLongAudioSourceFader alloc] init:source interpolationType:kIT_Linear startVal:source.volume endVal:1.0f];
    [fader setStopTargetWhenComplete:NO];

    //Create a property modifier action to wrap the fader
    CDXPropertyModifierAction* fadeAction = [CDXPropertyModifierAction actionWithDuration:1.5f modifier:fader];
    [fader release];//Action will retain
    [[CCActionManager sharedManager] addAction:[CCSequence actions:fadeAction, nil] target:source paused:NO];
}

-(void) _fadeOutPlayingMusic {
    CCLOG(@"In _fadeOutPlayingMusic");
    for(id m in musicSources){
        //Release source
        CDLongAudioSource *source = [musicSources objectForKey:m];
        if(source.isPlaying){
            //Create fader
            CDLongAudioSourceFader* fader = [[CDLongAudioSourceFader alloc] init:source interpolationType:kIT_Linear startVal:source.volume endVal:0.0f];
            [fader setStopTargetWhenComplete:NO];

            //Create a property modifier action to wrap the fader
            CDXPropertyModifierAction* fadeAction = [CDXPropertyModifierAction actionWithDuration:3.0f modifier:fader];
            [fader release];//Action will retain
            CCCallFuncN* stopAction = [CCCallFuncN actionWithTarget:source selector:@selector(stop)];
            [[CCActionManager sharedManager] addAction:[CCSequence actions:fadeAction, stopAction, nil] target:source paused:NO];
        }
    }
}

/** LOADING METHODS **/
-(CDLongAudioSource*) _loadMusic:(NSString*)fn {
    CCLOG(@"In _loadMusic" );
    //Init source
    CDLongAudioSource *source = [[CDLongAudioSource alloc] init];
    source.backgroundMusic = NO;  
    [source load:fn];

    //Add sound to container
    [musicSources setObject:source forKey:fn];

    return source;
}

-(CDSoundSource*) _loadSoundEffect:(NSString*)fn {
    CCLOG(@"In _loadSoundEffect" );

    //Pre-load sound
    [sae preloadEffect:fn];

    //Init sound
    CDSoundSource *sound = [[sae soundSourceForFile:fn] retain];

    //Add sound to container
    [soundSources setObject:sound forKey:fn];

    return sound;
}

/** Public methods **/
//Play music callback
-(void) playBackgroundMusicNumber:(enum music_files) file {
    CCLOG(@"In playBackgroundMusic");
    switch (file) {
        case PLAYER_STANDARD:
            [self _fadeOutPlayingMusic];
            [self _fadeInMusicFile: [self _NSStringFromMusicFiles:PLAYER_STANDARD]];
            break;
        default:
            break;
    }         
}

-(void) playSoundFile:(enum sound_files) file {
    CCLOG(@"In playSoundFile");
    switch (file) {
        case A:
            [self _playSoundFile:[self _NSStringFromSoundFiles:A]];
            break;
        case B:
            [self _playSoundFile:[self _NSStringFromSoundFiles:B]];
            break;
        case C:
            [self _playSoundFile:[self _NSStringFromSoundFiles:C]];            
            break;
        case AAC:
            [self _playSoundFile:[self _NSStringFromSoundFiles:AAC]];
            break;
        case ABC:
            [self _playSoundFile:[self _NSStringFromSoundFiles:ABC]];
            break;
        case ACA:
            [self _playSoundFile:[self _NSStringFromSoundFiles:ACA]];
            break;
        default:
            break;
    }
}

/** Dealloc ! **/
-(void) dealloc {
    [sae stopBackgroundMusic];

    for(id s in soundSources){
        //Release source
        CDSoundSource *source = [soundSources objectForKey:s];
        if(source.isPlaying){ [source stop]; }
        [source release];
    }

    [soundSources release];

    for(id m in musicSources){
        //Release source
        CDLongAudioSource *source = [musicSources objectForKey:m];
        if(source.isPlaying){ [source stop]; }
        [source release];
    }

    [musicSources release];

    //End engine
    [SimpleAudioEngine end];
    sae = nil;

    //instanceOfMusicLayer = nil;
    [super dealloc];
}
@end

Now, I created a new Ccoos2d Helloworld template and added the following to the .m class to test the code:

#import "cocos2d.h"
#import "MusicLayer.h"

enum tags {
    MUSICLAYERTAG = 99,
    };

// HelloWorldLayer
@interface HelloWorldLayer : CCLayer
{

}

+(CCScene *) scene;

@end


//
//  HelloWorldLayer.m
//  MusicFadingTest
//

// Import the interfaces
#import "HelloWorldLayer.h"

// HelloWorldLayer implementation
@implementation HelloWorldLayer

+(CCScene *) scene
{
    CCLOG(@"In helloworld scene");
    // 'scene' is an autorelease object.
    CCScene *scene = [CCScene node];

    // 'layer' is an autorelease object.
    HelloWorldLayer *layer = [HelloWorldLayer node];
    // add layer as a child to scene
    [scene addChild: layer];

    MusicLayer *musicLayer = [MusicLayer node];
    [musicLayer loadPopLevel];

    [scene addChild:musicLayer z:-1 tag:MUSICLAYERTAG];

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
-(id) init
{
    // always call "super" init
    // Apple recommends to re-assign "self" with the "super" return value
    if( (self=[super init])) {


        // ask director the the window size
        CGSize size = [[CCDirector sharedDirector] winSize];

        //Add menu items
        [CCMenuItemFont setFontSize:20];

        CCMenuItemFont *music0Item = [CCMenuItemFont itemFromString:@"Song A" target:self selector:@selector(play:)];
        music0Item.tag = 0;
        CCMenuItemFont *music1Item = [CCMenuItemFont itemFromString:@"Song B" target:self selector:@selector(play:)];
        music1Item.tag = 1;
        CCMenuItemFont *music2Item = [CCMenuItemFont itemFromString:@"Song C" target:self selector:@selector(play:)];
        music2Item.tag = 2;
        CCMenuItemFont *music3Item = [CCMenuItemFont itemFromString:@"Song D" target:self selector:@selector(play:)];
        music3Item.tag = 3;
        CCMenuItemFont *music4Item = [CCMenuItemFont itemFromString:@"Sound A" target:self selector:@selector(play:)];
        music2Item.tag = 4;
        CCMenuItemFont *music5Item = [CCMenuItemFont itemFromString:@"Sound B" target:self selector:@selector(play:)];
        music3Item.tag = 5;

        //Create our menus
        CCMenu *menu0 = [CCMenu menuWithItems:music0Item, music1Item,  music2Item,  music3Item,  music4Item,  music5Item, nil];
        [menu0 alignItemsInColumns: [NSNumber numberWithUnsignedInt:6], nil];
        menu0.position = ccp(240,240);
        [self addChild:menu0];
    }
    return self;
}

//Play music callback
-(void) play:(id)sender {

    CCNode* node = [self getChildByTag:MUSICLAYERTAG];
    NSAssert([node isKindOfClass:[MusicLayer class]], @"not a MusicLayer");

    MusicLayer *musicLayer = (MusicLayer*)node;

    CCMenuItem *item = (CCMenuItem*)sender;

    switch (item.tag ) {
        case 1:
            [musicLayer playBackgroundMusic:PLAYER_STANDARD];
            break;
        case 2:
            [musicLayer playBackgroundMusic:PLAYER_STANDARD];            
            break;

        case 3:
            [musicLayer playBackgroundMusic:PLAYER_STANDARD];            
            break;
        case 4:
            [musicLayer playBackgroundMusic:PLAYER_STANDARD];
            break;
        case 5:
            [musicLayer playSoundFile:A];
            break;
        case 6:
            [musicLayer playSoundFile:B];            
            break;
        default:
            break;
    }

 }

- (void) dealloc
{
     [super dealloc];
} 
@end

But here is the error message that I get when I the simulator I try to press any button and hence trigger the play method in HelloWorld.m

2012-04-23 10:39:18.493 MusicFadingTest[1474:10a03] In _loadMusic
2012-04-23 10:39:18.510 MusicFadingTest[1474:10a03] cocos2d: Frame interval: 1
2012-04-23 10:39:19.405 MusicFadingTest[1474:10a03] *** Assertion failure in -[HelloWorldLayer play:], /Users/user/Desktop/MusicFadingTest/MusicFadingTest/HelloWorldLayer.m:76
2012-04-23 10:39:19.406 MusicFadingTest[1474:10a03] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'not a MusicLayer'
*** First throw call stack:
(0x17f6022 0x1987cd6 0x179ea48 0x11c62cb 0xc264a 0x175c4ed 0x175c407 0x3a3b5 0x3ad73 0x37772 0x17f7e99 0x92821 0x9336f 0x95221 0x8573c0 0x8575e6 0x83ddc4 0x831634 0x27b1ef5 0x17ca195 0x172eff2 0x172d8da 0x172cd84 0x172cc9b 0x27b07d8 0x27b088a 0x82f626 0xc107f 0x2955)

The code above kind of mimics what happenes in the ShootEmUp example but I am missing something. As I cannot get the child by tag..

I asked this question as despite the answer will be probably trivial I hope to get some clarification on the general NON-ModelViewController approach in game programming and the usage of static variables.

I imagine using my MusicLayer in the MainMenu class and in the various layers implementing my levels. I would preload various music files according to the level the player is playing and keep the files that are not level specific preloaded (obviously taking care on the maximum number of sound files supported by the AudioEngine).

The other approach would have been to have a different instance of MusicLayer for each level and initializing them with different music files. The disadvantage of this approach is that the sharedAudioEngine is only one and when you want to have files keep playing between one scene and the other there is the risk of not having a full control on the track numbers used in the sharedAudioEngine (I recall should be 32 caf files for sound effect and few background tracks).

I thus understand why static instances are beneficial in game programming, but still, would love to hear what you think about the biderectional reference in 1 ShootEmUp code.

Also, would like to clarify that I encourage buying 1 as it has been a good starting point.

Thank you very much!

Was it helpful?

Solution

Oh, how much code and letters... First of all, for your question about "static instance". This is not static instance. This is static constructor. So, you can use smth like

CCScene* myScene = [GameScene scene];

instead of

CCScene* myScene = [[GameScene alloc] init];
// doing smth
[myScene release];

So, you will just create an autoreleased instance of your node (in that case, of your scene).

About the music question. You have add your music layer to the scene, but self in your play method will be HelloWorldLayer instance. So if you wanna get your music layer, you can try smth like this

MusicLayer* musicLayer = [[self parent] getChildByTag:MUSICLAYERTAG];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top