0

对不起,如果这个问题太无聊了,但我一直试图从Itterheim 的书的代码示例中尽可能多地理解,但 无法弄清楚它是如何工作的。

在我的“HelloWorldLayer.m”中,我创建了一个名为“MusicLayer”的类的实例,它扩展了 CCLayer。灵感来自于在名为“ShootEmUp”的示例(第 8 章,共1章)中有一个 InputLayer。因此我想我会创建一个 MusicLayer 来处理我在游戏中拥有的各种音乐文件并遇到了一些问题(将在帖子末尾解释)。我从Cocos2d Cookbook 的 RecipeCollection02、Ch6_FadingSoundsAndMusic 中获得了大部分与音乐相关的代码。

但首先我想介绍一些可能导致这个问题的背景想法,希望对此有一些明确的参考。

我习惯于“模型视图控制器”(MVC 维基百科链接)方法,因为我来自 C++ 和 Java 背景,以前从未编写过游戏,在我看来,游戏范例与“经典”有点不同软件工程大学的方法(不要误会我的意思,我确信在许多大学中他们不再教这些东西了:))。

关键是在我看来,这些示例中丢失了“经典”软件工程方法(例如,在 ShootEmUp 代码中,InputLayer 的实例具有 GameScene 类的引用 - 见下文 - 同时在 InputLayer有对 GameScene 的静态共享实例的引用-见下文-)。

//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
}
}

有人告诉我静态实例是要避免的。尽管如此,我了解拥有 GameScene 静态实例的好处,但我仍然对双向参考感到困惑(从 GameScene 到 InputLayer,反之亦然)..:

这是游戏编程中的常见做法吗?

有没有办法避免这种方法?

我现在将粘贴我的代码,我承认它可能是垃圾,并且可能会出现一些明显的错误,因为我正在尝试做一件非常简单的事情并且我无法管理。

所以我们在这里。那是 MusicLayer 类(它包含注释掉的代码,因为我之前试图使它成为一个共享类实例):

//
//  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

在这里,我们使用 .m 文件:

#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

现在,我创建了一个新的 Ccoos2d Helloworld 模板并将以下内容添加到 .m 类中以测试代码:

#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

但这是我在模拟器中尝试按下任何按钮并因此触发 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)

上面的代码有点模仿 ShootEmUp 示例中发生的事情,但我遗漏了一些东西。因为我不能通过标签得到孩子..

我问了这个问题,因为尽管答案可能很简单,但我希望对游戏编程中的一般 NON-ModelViewController 方法和静态变量的使用有一些澄清。

我想象在 MainMenu 类和实现我的关卡的各个层中使用我的 MusicLayer。我会根据播放器正在播放的级别预加载各种音乐文件,并保留预加载不特定级别的文件(显然要注意 AudioEngine 支持的最大声音文件数量)。

另一种方法是为每个级别设置不同的 MusicLayer 实例,并使用不同的音乐文件对其进行初始化。这种方法的缺点是 sharedAudioEngine 只有一个,当您想让文件在一个场景和另一个场景之间继续播放时,存在无法完全控制 sharedAudioEngine 中使用的曲目编号的风险(我记得应该是32个caf文件用于音效和少量背景音轨)。

因此,我理解为什么静态实例在游戏编程中是有益的,但仍然很想听听您对1 ShootEmUp 代码中的双向引用的看法。

另外,想澄清一下,我鼓励购买1,因为这是一个很好的起点。

非常感谢你!

4

1 回答 1

1

哦,多少代码和字母......首先,关于“静态实例”的问题。这不是静态实例。这是静态构造函数。所以,你可以使用 smth like

CCScene* myScene = [GameScene scene];

代替

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

因此,您只需创建节点的自动发布实例(在这种情况下,就是您的场景)。

关于音乐问题。您已将音乐图层添加到场景中,但您的播放方法中的 self 将是 HelloWorldLayer 实例。所以如果你想得到你的音乐层,你可以像这样尝试

MusicLayer* musicLayer = [[self parent] getChildByTag:MUSICLAYERTAG];
于 2012-04-23T16:52:55.877 回答