I want to load a Cocos3D Scene inside a UIViewController. Currently I have integrate Cocos3D in to my normal iOS project. How can I load Cocos3D scene on to RootViewController's view.
I found a way of using EAGLView. But I do not have a clue about EAGLView and CCDirector. Heres the code currently Im trying.
Is there any other way to do it? Please let me know
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CCDirector * director = [CCDirector sharedDirector];
[self.view addSubview:[director openGLView]];
cc3Layer = [LR3DLayer layerWithColor:ccc4(100, 100, 100, 255) width:768 height:960];
[cc3Layer scheduleUpdate];
world = [LR3DScene scene];
cc3Layer.cc3Scene = world;
scene = [CCScene node];
[scene addChild: (ControllableCCLayer*)cc3Layer];
if([director runningScene])
[director replaceScene:scene];
else
[director runWithScene:scene];
}
Follow the examples in the AppDelegate implementations in the cocos3d CC3DemoMashUp demo app, or the cocos3d hello, world template app.
cocos3d requires you to use a subclass of CC3UIViewController. The controller will automatically create and use the required CC3GLView view.
Related
Since xCode was updated to version 6.0, the default method for SpriteKit in "GameScene" (the default scene created) changes to:
-(void) didMoveToView:(SKView *) view {
/* Scene set up in here */
}
as opposed to the older method:
-(id) initWithSize:(CGSize) size {
if (self = [super initWithSize:size]{
/* Scene set up in here */
}
}
I understand that the new method (as with the changes in the view controller) is used to help manage the import from the .sks file that is new in xCode 6. However, I was curious if I didn't want to use the new "storyboard"-type format that is the .sks file, should I still use the new method? Or should I change the method back to the initWithSize method and just delete the .sks file?
Init methods should be used for initializing but keep in mind that inside init, view is always nil. So any code that needs the view has to be moved to didMoveToView method (it's called immediately after a scene is presented by a view).
About initWithSize in Xcode 6... By default, scene is loaded from .sks file. Because of this, initWithSize is never called actually. initWithCoder is called instead:
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
// do stuff
}
return self;
}
So initializing anything inside initWithSize won't have any effect. If you decide to delete .sks file and create a scene in "old" way, you can do something like this in view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = YES;
// Create and configure the scene.
GameScene *scene = [GameScene sceneWithSize:self.view.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
After that, you can use initWithSize for initialization.
Note that in viewDidLoad the final size of a view may not be known yet, and using viewWillLayoutSubviews instead could be a right choice. Read more here.
And proper implementation of viewWillLayoutSubviews for scene initialization purpose would be:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = YES;
//viewWillLayoutSubviews can be called multiple times (read about this in docs ) so we have to check if the scene is already created
if(!skView.scene){
// Create and configure the scene.
GameScene *scene = [GameScene sceneWithSize:self.view.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
}
Swift code:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.showsPhysics = true
skView.showsDrawCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
if(skView.scene == nil){
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
}
I think that, for all intents and purposes, there isn't much of a difference between the two methods when it comes to actually setting up your scene and using it. (initWithSize is called when the scene is initialized, while didMoveToView is always called right when the scene is presented by the view). I guess if you really prefer to see the initialization then you could just use the init method without any trouble at all.
Regarding the .sks file:
take a look at your view controller implementation file. Right above the v.controller's methods you'll see:
#implementation SKScene (Unarchive)
+ (instancetype)unarchiveFromFile:(NSString *)file {
/* Retrieve scene file path from the application bundle */
NSString *nodePath = [[NSBundle mainBundle] pathForResource:fileofType:#"sks"];
/* Unarchive the file to an SKScene object */
NSData *data = [NSData dataWithContentsOfFile:nodePath
options:NSDataReadingMappedIfSafe
error:nil];
NSKeyedUnarchiver *arch = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[arch setClass:self forClassName:#"SKScene"];
SKScene *scene = [arch decodeObjectForKey:NSKeyedArchiveRootObjectKey];
[arch finishDecoding];
return scene;
}
#end
This is the part that handles the sks. Using the file is totally optional, you aren't forced to do it and it really has no impact on you setting up your scene. However, if you do want to work with it, then you'll find that you'll need to work with this code snippet.
Both options are correct. Said that, the init method is not actually the same than the didMoveToView: method, as this last one will be called once the SKScene is presented in a SKView.
Okay so my app run perfectly fine in the simulator but when I plug in my device and try to run it on the real thing, I get this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[_NSPlaceholderData
initWithContentsOfFile:options:error:]: nil file argument'
and its due to this method:
+ (instancetype)unarchiveFromFile:(NSString *)file {
/* Retrieve scene file path from the application bundle */
NSString *nodePath = [[NSBundle mainBundle] pathForResource:file ofType:#"sks"];
/* Unarchive the file to an SKScene object */
NSData *data = [NSData dataWithContentsOfFile:nodePath
options:NSDataReadingMappedIfSafe
error:nil];
NSKeyedUnarchiver *arch = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[arch setClass:self forClassName:#"SKScene"];
SKScene *scene = [arch decodeObjectForKey:NSKeyedArchiveRootObjectKey];
[arch finishDecoding];
return scene; }
More specifically it is caused by the "pathForResource:file ofType:#"sks"
but I dont understand why this causes the error, or how else I am supposed to load my scene and use my app. If it helps, I am using GameScene.h to programatically make my scene, not the scene designer in xcode
It looks like that you want to access a file, which doesn't exists on the real device. Maybe you're accessing a file in a directory which the iOS-simulator can get(in Application Support-folder) but the real iOS device can't.
Check if there are any hard-coded paths in your code which could make this behaviour happen.
I was able to fix this by avoiding the use of the unarchiving method all together. Previously the scene was created with:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = YES;
// Create and configure the scene.
GameScene *scene = [GameScene unarchiveFromFile:#"GameScene"];
scene.scaleMode = SKSceneScaleModeResizeFill;
// Present the scene.
[skView presentScene:scene];
}
Now I changed that to this and it works fine on the real device:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [GameScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeResizeFill;
// Present the scene.
[skView presentScene:scene];
}
So I have added iAds to my application and I have done this very simply. By importing iAds into the ViewController.h and putting the following code into the ViewController.m file:
self.canDisplayBannerAds = YES;
This works, however, when I the add loads the screen is squished. Where all of the images and sprites become shorter.
Is there any way the add can just be overlaid on top of the SKScene? so that is does not affect the elements below – so that it stops the squishing?
Also, is there a way to only have the banner ads run in certain SKScenes or when a BOOL is changed from YES to NO?
UPDATED
Code from my project:
#import "XYZViewController.h"
#import "XYZMenuScene.h"
#import <iAd/iAd.h>
#implementation XYZViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [XYZMenuScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
//iAds Enabled
self.canDisplayBannerAds = YES;
// Present the scene.
[skView presentScene:scene];
}
This is all I have in terms of the iAds. It makes the entire scene push upwards to make room for the ad banner. Where I want it to just be over the scene and not push anything. So it covers, instead of pushes.
To overlay you can set the Z position of the banner higher than the rest of the content by default it's 0.
eg:
banner.layer.zPosition=2;
but I believe the iAd framework has built in method thats you can override to control when the ads must display and when they shouldn't and give you much more control.
Specific methods are called when the ad is available,and like you said you make the ads appear only when a boolean value is 1 or 0.
eg:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
if(showADS==1)
{
if (!self.bannerIsVisible)
{
[UIView beginAnimations:#"animateAdBannerOn" context:NULL];
// Assumes the banner view is just off the bottom of the screen.
banner.frame = CGRectOffset(banner.frame, 0, -banner.frame.size.height);
[UIView commitAnimations];
self.bannerIsVisible = YES;
}
}
else
{NSlog(#"No ads");}
}
check apple's documentation regarding this,they are really helpful
https://developer.apple.com/library/ios/documentation/userexperience/Conceptual/iAd_Guide/WorkingwithBannerViews/WorkingwithBannerViews.html
But let give a complete example.
The banner instance is only available in you iAd Methods not outside them.
In your view did load method:
- (void)viewDidLoad
{
self.canDisplayBannerAds=YES;
}
This method is called when the ad is loaded,in this method specify the position of the ad
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
banner.layer.zPosition=2;
/*do all the additional like checking is the boolean is 1 or 0 etc.*/
}
If you are working with iAd and sprite kit,you need to call methods in your view controller from the scene class to do all the ad work.
Suppose you have a method named checkAD in your view controller that checks if the ad should shown or not etc..
You can call this method from your scene class by using this code
if([self.delegate respondsToSelector:#selector(checkAD)]){
[self.delegate checkAD];
}
Add the SKSCene in a container View that might help. From my experience any Apple subclasses of UIViewController can do weird stuff when iAds are added through canDisplayBannerAds.
In my application, I would like to show a movie on the first screen (the first time user opens an application) and when movie is finished running, the login screen should show.
I have two questions:
The way I am trying to do it, is to show the video on a separate controller and when movie is finished, perform segue to the login controller. Is this the correct way to do it?
I am doing performSegueWithIdentifier when I receive MPMoviePlayerPlaybackDidFinishNotification. The problem is that nothing happens. The video remains on the screen and the controller is not changed. I don't see any errors in the console. Is there a problem to perform segue from NSNotificationCenter notification?
A possible way to do this would be to reverse the viewController hierarchy.
Start off with the loginViewController, and have a flag that
determines whether it is the first login. If that is the case, then
have an introVideoViewController presented over the
loginViewController with the loginViewController as its delegate.
Place the code to initiate and present your introVideoViewController
in the viewDidLoad of loginViewController, so it appears that the app
has directly started in the introVideoViewController
With MPMoviePlayerPlaybackDidFinishNotification, perform something like: [self.delegate introVideoDidFinishPlaying];. This will transfer control back to loginViewController, which can then dismiss the presentedViewController.
I'm not familiar how to do it with storyboards, but this approach definitely works with nibs.
As I commented, in your .h file of your loginViewController...
#import <UIKit/UIKit.h>
#import "cocos2d.h"
#import "cocos2d.h"
#interface VPViewController : UIViewController<CCDirectorDelegate>
#property (nonatomic, strong) IBOutlet CCGLView *glView;
In your .m loginViewController...
#synthesize glView=glView_;
In your viewDidLoad method
CGRect glViewFrame = self.glView.frame;
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == IPHONE_5_SCREEN_SIZE) {
glViewFrame.size.height = 578;
}else{
glViewFrame.size.height = 490;
}
[self.glView setFrame:glViewFrame];
CCDirector *director = [CCDirector sharedDirector];
[director setDisplayStats:NO];
[director setView:glView_];
// Enables High Res mode (Retina Display) on iPhone 4 and maintains low res on all other devices
if( ! [director enableRetinaDisplay:YES] )
CCLOG(#"Retina Display Not supported");
// turn on multiple touches
[glView_ setMultipleTouchEnabled:YES];
CCScene *scene = [CCScene node];
glClearColor(0,0,0,0);
[scene setRotation:90.0f];
if([director enableRetinaDisplay:YES] && [self returnMaxTextureSize] == 2048){
// this is a device with 2.0 scale factor and a max texture size of 2048 pixels
NSLog(#"2048 only");
[scene setScale:2.0f];
}else{
[scene setScale:1.5f];
}
CCSprite * bg = [CCSprite spriteWithFile:#"login_background_white.png"];
[bg setRotation:-90.0f];
[bg setScale:0.8f];
[bg setPosition:ccp(155, 270)];
[scene addChild:bg z:-1];
[scene addChild: [VPAnimation node]];
[director pushScene:scene];
[director startAnimation];
And finally, in your xib...
I want to add a CCSprite on my UIView, is it possible to do that? What I found were the ways to add UIKit on Cocos2D, but I want the its reverse. I want to add Cocos2D on UIKit.
Any help will be appreciated. Thanks!
You can do this by setting following method in your uiviewcontroller class
- (void)setupCocos2D {
CCGLView *glView = [CCGLView viewWithFrame:CGRectMake(100, 100, 200, 200)
pixelFormat:kEAGLColorFormatRGB565 // kEAGLColorFormatRGBA8
depthFormat:0 // GL_DEPTH_COMPONENT16_OES
];
glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view insertSubview:glView atIndex:0];
[[CCDirector sharedDirector] setView:glView];
CCScene *scene = [StarVideoView scene];
[[CCDirector sharedDirector] runWithScene:scene];
}
and calling this method in your viewDidLoad method as
[self setupCocos2D];
here startVideoView is my ccscene class and ofcourse you have to add cocos2d.h library in your uiviewcontroller class :)
Cocos2D does its drawings inside of a EAGLView class, which is just a wrapper for a CAEAGLLayer. So, you might try creating a director with its associated EAGLView, thendo this to add the cocos2d open gl layer to your view:
[myView.layer addSublayer:[[CCDirector sharedDirector] openGLView].layer];