[SCNKit ERROR] SCNPhysicsContactDelegate must at least implement one selector
I am receiving the above error after setting my viewController as the contactDelegate for my project. I cannot find what I have forgotten to implement, below is my code for loading the scene.
- (void)viewDidLoad {
[super viewDidLoad];
SCNView *scnView = (SCNView *) self.view;
//set the background colour
scnView.backgroundColor = [UIColor blackColor];
//setup the scene
SCNScene *scene = [self setupScene];
//present it
scnView.scene = scene;
//initial point of view
scnView.pointOfView = _cameraNode;
//plug game logic
scnView.delegate = self;
//physicsworld
scnView.scene.physicsWorld.gravity = SCNVector3Make(0, 0, 0);
scnView.scene.physicsWorld.contactDelegate = self;
[super viewDidLoad];
}
- (void)didBeginContact:(SCNPhysicsContact *)contact {
NSLog(#"contact");
}
.h
#interface GameViewController : UIViewController <SCNSceneRendererDelegate, SCNPhysicsContactDelegate>
#end
You're not implementing any methods from the protocol, so it's an error to declare conformance.
You probably want to implement physicsWorld:didBeginContact:, not just didBeginContact:.
Related
I am trying to get screen capture working on my iOS SpriteKit game using this code (by Aroth)
Basically I need to replace the SKView of my UIViewController with my own ScreenCaptureView (see the link). I changed the ScreenCaptureView in the example to derive from SKView instead of UIView.
I override loadView function in my game UIViewController class:
- (void)loadView
{
ScreenCaptureView *newView = [[ScreenCaptureView alloc] init];
newView.frame = CGRectMake(0, 0, 568, 320);//hard code for now
newView.clipsToBounds = NO;
self.view = newView;
[newView setNeedsDisplay];
NSLog(#"loadView %x", (int)newView);
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"viewDidLoad view %x %f x %f", (int)self.view ,self.view.frame.size.width, self.view.frame.size.height);
But its not working. The drawRect in ScreenCaptureView never gets called.
Any help is really appreciated.
I am trying to remove a SKScene and add a new one. The first presentScene: works fine, but the second one has messed up bounds, like this.
Here is my code.
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadLevel:1];
}
- (void)loadLevel:(int)level {
SKView * skView = (SKView *)self.view;
[skView presentScene:nil];
GameScene * gameScene;
switch (level) {
case 1:
gameScene = [Level1 sceneWithSize:skView.bounds.size];
break;
case 2:
gameScene = [Level2 sceneWithSize:skView.bounds.size];
break;
default:
break;
}
gameScene.scaleMode = SKSceneScaleModeAspectFill;
gameScene.gameViewController = self;
self.gameScene = gameScene;
[skView presentScene:gameScene transition:[SKTransition crossFadeWithDuration:1.0f]];
}
I didn't figure out why skView.bounds.size was changing between levels, but to correct it I hardcoded the size in. Let me know if there's a better way of doing it.
gameScene = [Level1 sceneWithSize:CGSizeMake(768, 1024)];
You run this in viewDidLoad, which means the view will be in portrait mode at that time:
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadLevel:1];
}
To fix this, you should present the first scene in viewWillLayoutSubviews:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (((SKView*)self.view).scene == nil)
{
[self loadLevel:1];
}
}
It's important to add the nil check because viewWillLayoutSubviews will be called repeatedly, for instance when rotating the device.
I have the following code in my View Controller:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
skView.showsFPS = NO;
skView.showsNodeCount = NO;
skView.showsDrawCount = NO;
// Create and configure the scene.
SKScene * scene = [MenuScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
self.canDisplayBannerAds = YES;
// Present the scene.
[skView presentScene:scene];
}
}
When I run the application it crashes immediately. I get the following error message:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView scene]: unrecognized selector sent to instance
I can't use SKView *skView = (SKView *)self.originalContentView; because the app is in landscape mode. Is there a way to display iAds in a Landscap Sprite Kit game?
EDIT:
I just added this code to the view controller, but I get the same results..
#pragma mark iAd Delegate Methods
-(void)bannerViewDidLoadAd:(ADBannerView *)banner {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1];
[banner setAlpha:1];
[UIView commitAnimations];
}
I ran into this issue and solved it via creating an ABBannerView property and adding that as a subView.
In my ViewController class :
adView = [[ADBannerView alloc] initWithFrame:CGRectZero];
adView.delegate = self;
[adView setFrame:CGRectMake(0, 0, 1024, 768)]; // set to your screen dimensions
[self.view addSubview:adView];
Important to NOT set the canDisplayBannerAds property of your view controller.
I believe what is happening is that if you do set the canDisplayBannerAds property to true, the view is modified and is no longer compatible with a SKView, and no longer has a scene property.
I did have to set the frame, so that the dimensions were correct otherwise it was portrait.
You can solve this without having to manually configure the ad banner. You do still have to use the original content view, but making it work correctly is just a matter of putting everything together in the right stages of the view controller's life cycle. All you have to do is enable canDisplayBannerAds: in awakeFromNib. Then set the SKView you create to either the view controller's view or originalContentView depending on the existence of the original content view.
- (void)awakeFromNib
{
[super awakeFromNib];
[self setCanDisplayBannerAds:YES];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
SKView *skView = nil;
if (self.originalContentView) {
skView = (SKView *)self.originalContentView;
}else{
skView = (SKView *)self.view;
}
[skView setShowsDrawCount:YES];
[skView setShowsFPS:YES];
[skView setShowsNodeCount:YES];
SKScene *scene = [MyScene sceneWithSize:skView.bounds.size];
[scene setScaleMode:SKSceneScaleModeFill];
[skView presentScene:scene];
}
None of the above answers works for me. Below code is tested in iOS 7 and 8 and works just fine.
Add below lines in header file
#import <iAd/iAd.h>
#interface GameViewController : UIViewController<ADBannerViewDelegate>{
//iAd
ADBannerView *adView;
}
In Implementation file .m add below code
#import "GameViewController.h"
#import "GameScene.h"
#implementation GameViewController
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
SKView *skView;
if (self.originalContentView) {
skView = (SKView *)self.originalContentView;
}
if (!skView.scene) {
//[skView setShowsDrawCount:YES];
//[skView setShowsFPS:YES];
//[skView setShowsNodeCount:YES];
//Improve Performance
skView.ignoresSiblingOrder = YES;
GameScene *scene = [GameScene sceneWithSize:skView.bounds.size];
[scene setScaleMode:SKSceneScaleModeFill];
[skView presentScene:scene];
}
}
- (void)awakeFromNib{
[super awakeFromNib];
CGRect screenRect = [[UIScreen mainScreen] bounds];
adView = [[ADBannerView alloc] initWithFrame:CGRectZero];
adView.frame = CGRectMake(0, 0, screenRect.size.width, adView.frame.size.height);
adView.delegate=self;
[self.view addSubview:adView];
}
//iAd
-(void)bannerViewDidLoadAd:(ADBannerView *)banner {
[adView setAlpha:1.0];
NSLog(#"Show Ad");
}
-(void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
[adView setAlpha:0];
NSLog(#"Hide Ad");
}
//
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskAllButUpsideDown;
} else {
return UIInterfaceOrientationMaskAll;
}
}
-(BOOL)prefersStatusBarHidden{
return YES;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
I have added iAds to my Sprite Kit game with the following code:
In the viewController.h file
#property (strong, nonatomic) IBOutlet ADBannerView * adBannerView;
In the viewController.m file
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (!skView.scene) {
// Create and configure the scene.
SKScene * scene = [MenuScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
_adBannerView = [[ADBannerView alloc] initWithFrame:CGRectZero];
_adBannerView.delegate = self;
[_adBannerView setFrame:CGRectMake(0, 0, 460, 320)];
[self.view addSubview:_adBannerView];
// Present the scene.
[skView presentScene:scene];
}
}
This shows the iAd in every scene. Is there a way to hide the iAd in some of the scenes?
Apple's iAd Programming Guide says:
Only create a banner view when you intend to display it to the user. Otherwise, it may cycle through ads and deplete the list of available advertising for your application.
Is this at all possible with scenes?
yes there is a way to hide the iAd in some of the scenes.
- (void)viewDidLoad
{
[super viewDidLoad];
//Add view controller as observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"hideAd" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"showAd" object:nil];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = NO;
skView.showsNodeCount = NO;
// Create and configure the scene.
SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
self.canDisplayBannerAds = YES;
adView = [[ADBannerView alloc] initWithFrame:CGRectZero];
adView.frame = CGRectOffset(adView.frame, 0, 0.0f);
adView.delegate=self;
[self.view addSubview:adView];
self.bannerIsVisible=NO;
}
//Handle Notification
- (void)handleNotification:(NSNotification *)notification
{
if ([notification.name isEqualToString:#"hideAd"]) {
[self hidesBanner];
} else if ([notification.name isEqualToString:#"showAd"]) {
[self showBanner];
}
}
And in your scene in which you want to hide banner...
[[NSNotificationCenter defaultCenter] postNotificationName:#"showAd" object:nil];
//Sends message to viewcontroller to show ad.
[[NSNotificationCenter defaultCenter] postNotificationName:#"hideAd" object:nil];
//Sends message to viewcontroller to hide ad.
Well, In your specific scene follow the Apple's guide(same place as your question) on this issue at the link below, look at the section that says "banner view best practices":
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/iAd_Guide/WorkingwithBannerViews/WorkingwithBannerViews.html#//apple_ref/doc/uid/TP40009881-CH4-SW3
In Summary they say: "remove the banner view from the view hierarchy, set its delegate to nil"
The most clean solution is to declare and implement a protocol to let the UIViewController know from the scene that it should hide the ad.
#protocol MySceneDelegate <NSObject>
- (void)hideAd;
#end
#interface MyScene : SKScene
#property (weak) id <MySceneDelegate> delegate;
#end
View controller that shows the scene should implement a hideAd method and set itself as a delegate of the scene. Example:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
MyScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Set the delegate
[scene setDelegate:self];
// Present the scene.
[skView presentScene:scene];
}
Then in the scene you can call the hideAd method of the view controller which was set as a delegate:
if ([_delegate respondsToSelector:#selector(closeScene)])
{
[_delegate performSelector:#selector(hideAd)];
}
And remove the banner in hideAd method.
To hide the banner view, you should:
Resize your banner view's frame to be offscreen Resize your content
view's frame to cover the space originally hosting the banner
Hope it helps.
I read two questions here about that, both with the same answer. The suggested solution was to create the UIScrollView object, and then add it to the parent view of the SKScene in the target scene's didMoveToView:, removing it in willMoveFromView:.
And that is the target scene code I implemented:
#interface MQSSMakerScene ()
#property BOOL contentCreated;
#property MQSSMakerWorkspaceLayer *workspaceLayer;
#property MQSSMakerDockLayer *dockLayer;
#property UIScrollView *scrollView;
#end
#implementation MQSSMakerScene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
CGSize layerSize = CGSizeMake(944, 140);
CGPoint layerPosition = CGPointMake(40, 768-(140+30));
CGRect viewFrame = CGRectMake(layerPosition.x, layerPosition.y, layerSize.width, layerSize.height);
_scrollView = [[UIScrollView alloc] initWithFrame:viewFrame];
_scrollView.contentSize = CGSizeMake(2000, 120);
_scrollView.scrollEnabled = YES;
_scrollView.showsHorizontalScrollIndicator = YES;
_scrollView.backgroundColor = [UIColor brownColor];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
if(!self.contentCreated) {
[self createSceneContents];
self.contentCreated = YES;
}
[self.view addSubview:_scrollView]; // --> WHERE IT'S BEING ADDED
}
- (void)willMoveFromView:(SKView *)view
{
[super willMoveFromView:view];
[_scrollView removeFromSuperview]; // --> WHERE IT'S BEING REMOVED
}
- (void)createSceneContents
{
self.backgroundColor = [UIColor colorWithHue:.237 saturation:.56 brightness:.46 alpha:1];
self.scaleMode = SKSceneScaleModeAspectFill;
[self addChild:[self newMakerLayout]];
}
- (SKNode *)newMakerLayout
{
SKNode *mainLayout = [[SKNode alloc] init];
self.workspaceLayer = [[MQSSMakerWorkspaceLayer alloc] init];
self.dockLayer = [[MQSSMakerDockLayer alloc] init];
[mainLayout addChild:self.workspaceLayer];
[mainLayout addChild:self.dockLayer];
MQSSStoryObject *ob1 = [[MQSSStoryObject alloc] initWithImageNamed:#"maker-1.png"];
[self.workspaceLayer addChild:ob1];
return mainLayout;
}
#end
Please note that the user should go to this scene from the home scene when clicking on a button. The problem now is that once I add the line:
[self.view addSubview:_scrollView];
to add the UIScrollView object to this scene, the application no more goes to this scene and instead add the UIScrollView to the home scene I am transitioning from. It also doesn't remove it when transitioning to another scene. Once I comment out this line, everything works just as expected, clearly without presenting the UIScrollView.
I am stuck. Any help or advice is highly appreciated. Thanks!
I finally figured out what the problem was for me. I was using the -(void)willLayoutSubviews method of the viewController to ensure I had the correct bounds, but I had forgotten to see if my SKScene was already presented. That resulted in a new welcomeScene being presented each time the view was laid out on top of the old one.
-(void)willLayoutSubviews
{
if(!self.didPresentScene)
{
// Present scene here
....
self.didPresentScene = YES;
}
}