Повторное назначение протокола делегата SKScene

Поэтому я создаю протокол делегата и соответствующее свойство в моей сцене. В моем контроллере вида, который содержит все мои сцены, я установил делегат сцен себе, чтобы я мог вызвать полноэкранное объявление (из контроллера вида) в меню, которое является первой вызванной игровой сценой. Это прекрасно работает, я могу назвать метод. Но после того, как я перехожу к сценам уровня, я возвращаюсь к сцене меню, вызывая мой метод для показа полноэкранной рекламы, но ничего не происходит. Я предполагаю, потому что делегат, установленный для себя, был потерян. Как я могу вернуть делегата себе на игровую сцену? Вот как я это делаю внутри контроллера View.M

myScene = [GameScene unarchiveFromFile:@"GameScene"];
((GameScene *)myScene).mySceneDelegate = self; // i need to be able to do this within the scene
myScene.scaleMode = SKSceneScaleModeResizeFill;

// Present the scene.
[skView presentScene:myScene];

Как я вызываю метод fullScreen, который есть в моем ВК из сцены меню...

[self.mySceneDelegate showFullScreen:self];

... так что в моем коде, когда я перехожу со сцены LevelOne, я пытаюсь переназначить делегата с помощью следующего кода, но ничего не происходит

-(void)goBackToMenu{

    GameScene *newscene = [GameScene sceneWithSize:self.size];
    [self.view presentScene:newscene transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
    ((GameScene *)newscene).mySceneDelegate = self;
}

Я также попытался установить атрибут свойства Strong вместо слабого, но тоже не повезло

********************** ВСЕ КОДЫ ************************** ************

Больше кода.. мой Viewcontroller.h

@protocol TCAMySceneDelegate;

@interface GameViewController : UIViewController<TCAMySceneDelegate>

@property(nonatomic, weak) id<TCAMySceneDelegate> mySceneDelegate;

@end

мой взгляд controller.m

- (void)viewDidLoad
{
    [super viewDidLoad];

    SKView * skView = (SKView *)self.view;

    myScene = [GameScene unarchiveFromFile:@"GameScene"];

    ((GameScene *)myScene).mySceneDelegate = self;

    // Present the scene.
    [skView presentScene:myScene];
}


-(void)showFullScreen:(GameScene *)gameScene{

    NSLog(@"Show Full Screen");

    [revmobFS loadWithSuccessHandler:^(RevMobFullscreen *fs) {
        [fs showAd];
        NSLog(@"Ad loaded");
    } andLoadFailHandler:^(RevMobFullscreen *fs, NSError *error) {
        NSLog(@"Ad error: %@",error);
    } onClickHandler:^{
        NSLog(@"Ad clicked");
    } onCloseHandler:^{
        NSLog(@"Ad closed");
    }];
    [RevMobAds startSessionWithAppID:@"547251235f2a043608a66f1a"
                  withSuccessHandler:^{
                      NSLog(@"Session started with block");
                      // Here, you should call the desired ad unit
                      revmobFS = [[RevMobAds session] fullscreen];
                      [revmobFS showAd];
                      // [RevMobAds session].testingMode = RevMobAdsTestingModeWithAds;
                      revmobFS.delegate = self;
                      [revmobFS loadAd];

                  } andFailHandler:^(NSError *error) {
                      NSLog(@"Session failed to start with block");
                  }];
}

мой игровой файл scene.h..

@protocol TCAMySceneDelegate;

@interface GameScene : SKScene

@property (nonatomic, weak) id<TCAMySceneDelegate> mySceneDelegate;

@end

@protocol TCAMySceneDelegate <NSObject>

-(void)showFullScreen:(GameScene *)gameScene;
@end

файл моей игры scene.m..

-(void)didMoveToView:(SKView *)view {

    //Show Ad

    [self.mySceneDelegate showFullScreen:self];

}

теперь вышеприведенный showFullScreen: вызывается, но в режиме онлайн, когда gameScene (мое игровое меню) представляется в первый раз... после перехода к другим сценам в играх, таким как мои уровни, затем возвращайтесь в мое меню (gamescene), которое никогда не вызывается. Надеюсь, теперь это стало понятнее

как я переключаюсь на другую сцену из моей игровой сцены.

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];
    SKNode *node = [self nodeAtPoint:location];
    NSLog(@"Level Selected: %@", node.name);
    int x = [node.name intValue];

    switch (x) {
        case 1:{
                LevelOne *newscene = [LevelOne sceneWithSize:self.size];
                [self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
            break;
        }
        case 2:{

                LevelTwo *newscene = [LevelTwo sceneWithSize:self.size];
                [self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
            }
            break;
        }
        case 3:{
                LevelThree *newscene = [LevelThree sceneWithSize:self.size];
                [self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
            }
            break;
        }
//..........

        default:
            break;
    }
}

Код LevelOne.h...

#import <SpriteKit/SpriteKit.h>
#import "DrawCanvas.h"
#import "GameScene.h"

@interface LevelOne : SKScene<SKPhysicsContactDelegate>

@end

1 ответ

Решение

Лично я нашел ваше описание, а также код, который вы выложили, немного недостоверным, а также немного запутанным. При предоставлении кода вы, вероятно, получите лучшую / более быструю помощь, если предоставите больше деталей. Например, к какому файлу принадлежит goBackToMenu?

Как / в каком контексте

[self.mySceneDelegate showFullScreen:self];

называется? Я бы предположил, что это с какой-то триггерной точки в GameScene. И что такое метод подписи showFullScreen?

А так как вы не указываете тип mySceneDelegate, вы заставляете читателей угадывать, что вы имеете в виду.

Итак, пытаясь расшифровать то, что вы просите...

Предположим, у вас есть какой-то делегат, который называется GameSceneDelegate. Я также собираюсь предположить, что вы уже знаете, что SKSceneDelegate существует и уже используете его, но просто не опубликовали этот код или не должны его использовать.

Ваш GameViewController будет иметь свой интерфейс, определенный как:

@interface GameViewController : UIViewController<GameSceneDelegate>

И ваша GameScene будет иметь свойство, определенное следующим образом:

@property (nonatomic, weak) id<GameSceneDelegate> mySceneDelegate;

Обратите внимание, что вы должны использовать слабый здесь. Потому что, если ваш GameViewController когда-либо имеет сильную ссылку на вашу GameScene, то вы только что создали цикл сохранения.

У вас есть несколько вариантов, когда устанавливать mySceneDelegate. Если я знаю, что должен определить делегата, я часто добавляю его в качестве аргумента инициализаторам. Таким образом, ясно, что у него должен быть делегат. Итак, если вы используете методы класса для генерации экземпляров, вы должны сделать что-то вроде этого:

@interface GameScene : SKScene

@property (nonatomic, weak) id<GameSceneDelegate> mySceneDelegate;

+ (instancetype)unarchiveFromFile:(NSString *)file mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate;

+ (instancetype)sceneWithSize:(CGSize)size mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate;

@end

И вам эти методы будут выглядеть так:

+ (instancetype)unarchiveFromFile:(NSString *)file mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate
{
    GameScene *scene = [super unarchiveFromFile:file];

    scene.mySceneDelegate = mySceneDelegate;

    return scene;
}

+ (instancetype)sceneWithSize:(CGSize)size mySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate
{
    GameScene *scene = [super sceneWithSize:size];

    scene.mySceneDelegate = mySceneDelegate;

    return scene;
}

Обратите внимание, чтобы это работало, вам нужно определить это в GameViewController.h (или, если хотите, в отдельном файле для категории)

@interface SKScene (Unarchive)

+ (instancetype)unarchiveFromFile:(NSString *)file;

@end

Альтернативой является немедленная установка mySceneDelegate сразу после создания экземпляра сцены.

Реально, если вы делаете это, это должно работать, и ваш делегат должен оставаться в такте. FWIW, я на самом деле делаю это в моей игре. Так что я знаю, что это работает.

Ладно, вот в чем вопрос в вашем вопросе. Вы указываете, что делегат "потерян". Единственный способ, которым делегат может быть "потерян", - это если кто-то явно установит его в ноль. Или другая возможность, я полагаю, заключается в том, что ваша GameScene освобождается, а вы этого не понимаете.

Я бы настроил две вещи, чтобы помочь отладить это.

В GameScene добавьте

- (void)dealloc
{
    NSLog(@"I'm being dealloc'd");
}

- (void)setMySceneDelegate:(id<GameSceneDelegate>)mySceneDelegate
{
    _mySceneDelegate = mySceneDelegate;

    NSLog(@"Setting mySceneDelegate");
}

Так что теперь вы можете попытаться определить, где он "теряется". Если какой-либо из них выводится на консоль в тот момент, когда он потерян, просто установите точку останова в этих методах, чтобы увидеть, кто его вызывает.

Еще одна информация, которая также поможет

  • Что вы имеете в виду, назначая делегата себе? Я понятия не имею, что это значит.

  • Вы упомянули, что предполагаете, что делегат потерян. Зачем? У вас есть доказательства или это просто предположение?

Если вы можете дать некоторые ответы и более подробную информацию, мы, вероятно, сможем выяснить, что происходит не так.

Хорошо, теперь, глядя на ваш код, я думаю, что ваша проблема здесь, в GameScene.m:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];
    SKNode *node = [self nodeAtPoint:location];
    NSLog(@"Level Selected: %@", node.name);
    int x = [node.name intValue];

    switch (x) {
        case 1:{
                LevelOne *newscene = [LevelOne sceneWithSize:self.size];
                newScene.mySceneDelegate = self.mySceneDelegate;
                // Or, if you can make it work, newscene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
                [self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
            break;
        }
        case 2:{

                LevelTwo *newscene = [LevelTwo sceneWithSize:self.size];
                newScene.mySceneDelegate = self.mySceneDelegate;
                // Or, if you can make it work, newscene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
                [self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
            }
            break;
        }
        case 3:{
                LevelThree *newscene = [LevelThree sceneWithSize:self.size];
                newScene.mySceneDelegate = self.mySceneDelegate;
                // Or, if you can make it work, newscene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
                [self.view presentScene:newscene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
            }
            break;
        }
//..........

        default:
            break;
    }
}

Здесь я назначаю mySceneDelegate новой сцены уже используемой.

Вы можете очистить этот код, чтобы сделать его более читабельным.

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];
    SKNode *node = [self nodeAtPoint:location];
    NSLog(@"Level Selected: %@", node.name);
    int x = [node.name intValue];
    GameScene *newScene = nil;

    switch (x) {
        case 1:
            newScene = [LevelOne sceneWithSize:self.size];
            // Or, if you can make it work, newScene = [LevelOne sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
            break;
        case 2:
            newScene = [LevelTwo sceneWithSize:self.size];
            // Or, if you can make it work, newScene = [LevelTwo sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
            break;
        case 3:
            newScene = [LevelThree sceneWithSize:self.size];
            // Or, if you can make it work, newScene = [LevelThree sceneWithSize:size mySceneDelegate:self.mySceneDelegate;
            break;

        // other cases..........

        default:
            break;
    }
    if (newScene) {
        newScene.mySceneDelegate = self.mySceneDelegate;
        [self.view presentScene:newScene transition:[SKTransition doorsOpenVerticalWithDuration:1]];
    }
}

Для ваших уровней вам нужно будет сделать следующее:

#import <SpriteKit/SpriteKit.h>
#import "DrawCanvas.h"
#import "GameScene.h"

@interface LevelXYZ : GameScene<SKPhysicsContactDelegate>

@end

Где XYZ - уровень (например, один). Делая это, вы наследуете mySceneDelegate от GameScene. Если по какой-то причине вы настаиваете, что сцены уровня не наследуются от GameScene, то добавьте базовый класс, который содержит делегата. Так что это будет что-то вроде:

@interface MyBaseScene : SKScene

@property(nonatomic, weak) id<TCAMySceneDelegate> mySceneDelegate;

@end

И тогда GameScene будет такой:

@interface GameScene : MyBaseScene

И ваш уровень сцены будет

@interface LevelXYZ : MyBaseScene<SKPhysicsContactDelegate>

Обратите внимание на пару вещей, которые я сделал.

  • Изменена новостная сцена на новую. В iOS вы обычно используете came case. Вы делали это для mySceneDelegate. Согласованность является ключевым.

  • Первоначально объявлен newScene как ноль и назначен как необходимость. Затем, в конце коммутатора, если newScene существует, назначьте делегата и представьте. Таким образом, вы делаете это только в одном месте. Это облегчает жизнь. Например, если вы хотите изменить продолжительность каждого из них, что тогда? Вы должны изменить случай. Обратите внимание, что это имеет место. Если вы хотели, чтобы на уровне была особая продолжительность, то это не совсем работает. Но вы можете изменить его, сделав переменные продолжительности и типа перехода.

Кстати, я бы настоятельно не рекомендовал, чтобы ваш класс сцены содержал код, который выполняет фактический переход. Я мог бы либо вызвать делегата для этого, либо найти другой способ (например, использовать уведомление о необходимости изменения сцены). Например, в моей текущей игре мой контроллер вида должен быть контролером всех изменений сцены. Преимущество этого в том, что вы также можете вызывать переходы сцены от других внешних сил (и не делать так, чтобы ваша сцена была той, которая управляет ею).

Наконец, вы можете надеяться, что единственный способ обнаружить проблему - это представить правильный код. Я согласен, трудно знать, какой код представить, потому что, если кода слишком много, люди могут его не читать. Это тонкая грань, но немного пищи для размышлений.

Другие вопросы по тегам