Magical function in SpriteBuilder projects - cocos2d-iphone

In cocos2d tutorials people initialize sprites/physical nodes in the init method, but in the cocos2d + SpriteBuilder tutorials it's written in this method:
- (void)didLoadFromCCB {}
In cocos2d only tutorials there is a method like that,but there is a line of code, that says what a kind of method it is,right? :
- (void)onEnter{
[super onEnter];
}
So, 'didLoadFromCCB' is a magical method for SpriteBuilder projects?

The didLoadFromCCB method is sent to each node after the node has been loaded by CCBReader. It runs that method when it is implemented by that node's class. It behaves like a protocol method without having to implement a protocol (nor is there a protocol). See also this github issue.
The CCBReader method that runs didLoadFromCCB does so like this after loading is complete:
+ (void) callDidLoadFromCCBForNodeGraph:(CCNode*)nodeGraph
{
for (CCNode* child in nodeGraph.children)
{
[CCBReader callDidLoadFromCCBForNodeGraph:child];
}
if ([nodeGraph respondsToSelector:#selector(didLoadFromCCB)])
{
[nodeGraph performSelector:#selector(didLoadFromCCB)];
}
}
The onEnter method is defined by the CCNode class, so it is documented in the CCNode class reference and Xcode is able to suggest the method when starting to type it.

Related

How to use accelerometer in cocos2d 3.0?

I am programming with Cocos2d 3.0 now, In Cocos2d 2.0, we can use the following code to add accelerometer to app, but this example was based on class CCLayer which has deprecate in Cocos2d 3.0, and UIAccelerometer also replaced by CMMotionManager in IOS 5.0, so I am wondering how to do this in Cocos2d 3.0? I googled for a while, didn't find anything useful.
-(id) init
{
if ((self = [super init]))
{
// ...
self.isAccelerometerEnabled = YES;
// ...
}
}
-(void) accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration *)acceleration
{
// ...
}
===
We've written a tutorial on exactly this: https://www.makegameswith.us/gamernews/371/accelerometer-with-cocos2d-30-and-ios-7
You need to use the CoreMotion framework.
Well, there are two problems in the tutorial example given above.
Single instance of CMMotionManager.
Acceleration data become +Ve or -Ve according to the orientation of the device. You also need to add Scene as observer of device orientation change notification.
If you don't want handle these overheads, you can use CCAccelerometer class. It solves both the problems.
HOW TO USE
Add CoreMotion Framework in your project from Build Phases.
Copy CCAccelerometer.h and CCAccelerometer.m files in your project.
Import CCAccelerometer.h file in the Prefix.pch.
Implement the <CCSharedAccelerometerDelegate> in the CCScene where you want to use the accelerometer.
Create shared instance in init method by simply calling [CCAcceleroMeter sharedAccelerometer];
Start accelerometer in -(void)onEnterTransitionDidFinish by calling [CCAcceleroMeter sharedAccelerometer]startUpdateForScene:self];
Define delegate method -(void)acceleroMeterDidAccelerate:(CMAccelerometerData*)accelerometerData in your scene.
Stop accelerometer in -(void)onExitTransitionDidStart by calling [CCAcceleroMeter sharedAccelerometer]stopUpdateForScene:self];
You can find out the example project in GitHub.
Here is example:
Device::setAccelerometerEnabled(true);
auto accelerometerListener = EventListenerAcceleration::create([this](Acceleration* acc, Event* event)
{
});
getEventDispatcher()->addEventListenerWithSceneGraphPriority(accelerometerListener, this);
Also video tutorial https://www.youtube.com/watch?v=Xk6lXK6trxU

initWithSpriteFrameName not initializing class after upgrade to cocos2d v2.0

I have been building the SpaceViking project described in the book "Learning Cocos2D". I had got to chapter 10 on Box2D when I started to experience problems. I then attempted to upgrade cocos2D from version 1 to version 2.0. After making the necessary changes to fix deprecations, the code no longer works. Specifically, I have found that when game objects or enemy objects are created, their init methods are no longer being called. For example, when the following line is executed:
RadarDish *radarDish = [[RadarDish alloc] initWithSpriteFrameName:#"radar_1.png"];
the RadarDish init method is not called. I then looked into the execution of initWithSpriteFrameName in both version 1 and version 2 and found that CCSprite.m has change such that the init method is no longer called. In the version 2 source code, initWithSpriteFrameName method calls initWithSpriteFrame which calls initWithTexture which calls:
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect rotated:(BOOL)rotated
{
if( (self = [super init]) ) {
Consequently, RadarDish init method is not called. Instead, CCNode init method is called. However in version 1, initWithSpriteFrameName calls initWithSpriteFrame which calls initWithTexture which has this code:
// IMPORTANT: [self init] and not [super init];
if( (self = [self init]) ){
[self setTexture:texture];
[self setTextureRect:rect];
}
That allows the RadarDish init method to be called.
What can I do to resolve this? It seems unlikely that the book would need to change to support the upgrade to cocos2d v2.0, so I suspect I must be missing something else. But if I am wrong, then what would be the way to change this code to cause the RadarDish init method to be called?
Ok, I was wrong. "cocos2d 2.x is different in many details from v1.x so can't expect v1.x code to just work under 2.x". If you are going to follow the book "Learning Cocos2D", I would recommend loading cocos2d-iphone version 1.0.1. Otherwise, you will be faced with many incompatibilities as well as a lot of deprecations to fix.
But if you really want to use the latest cocos2d, then there are some things you should do. 1) Follow the instructions in this link. 2) You are going to have a lot of deprecations and changes to fix, so use this link to understand how to fix those deprecations and changes. 3) You'll need to update the Joystick classes also, so go to this link to get those changes, 4) you'll have to google the rest to find solutions.
Now as for the solution to the problem I mentioned here, there are likely multiple solutions but I will offer one below (thanks to Sylvan's answer above):
In each of the GameObjects, EnemyObjects, and PowerUps, I added a method to override initWithFrameName. This will circumvent the use of the objects init method. For example, for the RadarDish, I added the following:
-(id) initWithSpriteFrameName:(NSString*)frameName {
if( (self=[super init]) ) {
if ((self = [super initWithSpriteFrameName:frameName])) {
CCLOG(#"### RadarDish initialized");
[self initAnimations]; // 1
characterHealth = 100.0f; // 2
gameObjectType = kEnemyTypeRadarDish; // 3
[self changeState:kStateSpawning]; // 4
}
}
return self;
}
This allows the GameObject and GameCharacter init methods to run before the CCSprite's initWithSpriteFrameName method to run.
The Viking GameObject had to have a slightly different solution because it is initialized with initWithSpriteFrame rather than initWithSpriteFrameName. But the override implementation is basically the same as the example of RadarDish above.
(Aside from all the changes necessary to overcome the deprecations) The above change allowed everything else in the examples of "Learning Cocos2D" to remain intact.
Good Luck.
I think you are having trouble because you are keeping your init method as it was and not modifying it. You are calling a method initWithSpriteFrameName: on your RadarDish class but that class doesn't have that method name, so it looks to its superclass. I think you could have simply renamed your init method to initWithSpriteFrameName: and you would have been fine. Like this:
// RadarDish.m
-(id) initWithSpriteFrameName:(NSString*)frameName {
if ((self = [super initWithSpriteFrameName:frameName])) {
// init anything here
}
return self;
}
Just don't also have a method named init and expect it to get called.

Cocos2d with ARC: what is the best implementation of singleton pattern for GameScene when having multiple levels?

EDIT: I am using ARC
In my code (based on the ShootEmUp example in this book, which I highly reccomend, source code in chapter 8 available here) I often use the trick of accessing the GameScene via:
+(GameScene*) sharedGameScene;
which returns a reference to the static instance of GameScene and is used by all children of GameScene (e.g. ShipEntity, InputLayer.. etc..) to dialog with GameScene (EDIT: aka singleton instance of GameScene).
To create multiple levels I thought of implementing a method calledsceneWithId:(int) method where I load different level data each time.
EDIT: Code snippet of init method:
-(id) initWithId:(int)sceneId
{
CCLOG(#"scene With id");
if ((self = [super init]))
{
//All common initialization happens here..
switch (sceneId) {
case 1:
[self loadFirstLevelData];
break;
case 2:
[self loadSecondLevelData];
break;
default:
[self loadSecondLevelData];
break;
}
//Other common stuff..
[self setUpCounters];
[self setUpWeaponsMenu];
[self scheduleUpdate];
[self schedule:#selector(timerUpdate:) interval:1];
InputLayerButtons* inputLayer = [InputLayerButtons node];
[self addChild:inputLayer z:1 tag:GameSceneLayerTagInput];
}
EDIT: Is that init method ok? I have found this post which uses dispatch_once. Should I do the same?
Or should I pheraps create a GameScene class and then sublcass it?
E.g. FirstGameScene : GameScene
EDIT: I have followed the advice of #LearnCocos2D and used the cleanup method, and I used it to stop a singleton object music layer to play (the MusicLayer object is initialized in AppDelegate and I meant to use it to "manage" the music across all scenes - the problem was that without stopping it in dealloc it would have kept playing the music that was loaded at init time).
-(void) loadFirstLevelData{
//HERE WILL LOAD ALL SPECIFIC ELEMENTS: ENEMIES, BONUSES etc..
//AS WELL AS THE MUSIC FOR THE LEVEL
[[MusicLayer sharedMusicLayer] _loadMusic:#"1.mp3"];
[[MusicLayer sharedMusicLayer] playBackgroundMusicFile: #"1.mp3"];
}
-(void) cleanup
{
//Should I remove all child loaded in LoadLevelData??
CCLOG(#"cleanup GameScene");
[[MusicLayer sharedMusicLayer] stopAllMusic];
//MusicLayer is not a child of GameScene but of AppDelegate - the idea is to keep loading and unloading music files - sometimes I need to keep playing the file between scenes and hence I used the singleton pattern for this as well..
[super cleanup];
}
But I still have some doubts:
Is it ok to have several loadLevelData methods in GameScene class? Each method can be 200 lines long! I tried to sublcass GameScene but is a bit messy. I explain better. I imported "GameScene.h" in the header file of the subclass and by doing so I expected that if I had ovverriden only certain methods (e.g. init) I would have been able to see the various classes imported in GameScene (e.g. InputLayerButtons). It is not the case. So I probably don't understand how imports work in Objective-C
Is it ok to remove specifc children in the cleanup method? I thought that I would remove all child that are added in the LoadLevelXXXData method to reduce the memory usage.
I have set a bounty for this question but I will probably need to test the answer and re-edit as I don't have a clear enough understanding of the subject to be super precise in the question. Hope is ok.
PS: Would be great if someone would feel like sharing a UML style diagram of a Cocos2D Shooter Game where with various levels and GameScene using singleton pattern :).
I'll focus on the questions on the bottom:
Is it ok to have several loadLevelData methods in GameScene class? Each method can be 200 lines long! I tried to sublcass GameScene but
is a bit messy. I explain better. I imported "GameScene.h" in the
header file of the subclass and by doing so I expected that if I had
ovverriden only certain methods (e.g. init) I would have been able to
see the various classes imported in GameScene (e.g.
InputLayerButtons). It is not the case. So I probably don't understand
how imports work in Objective-C
There's nothing wrong with having long methods. However I suspect your loading methods perform very similar routines, so you should check if you can generalize these into subroutines. A good indicator is if several lines of code are absolutely identical except for the parameters or variable names. The best practice is to write identical code only once, and execute it many times with varying parameters.
The #import statement is used to allow the compiler to "see" other classes. Without importing other header files, you couldn't use that class' methods and properties without the compiler complaining.
2 . Is it ok to remove specifc children in the cleanup method? I thought that I would remove all child that are added in the
LoadLevelXXXData method to reduce the memory usage.
It makes no sense to remove children during cleanup. Cocos2D removes all children during cleanup automatically.
If that does not seem to be the case for you, you have a retain cycle somewhere that prevents child nodes from deallocating.
sorry for answering this but I have been experimenting and decided to:
not use the singleton pattern for GameScene
use, insteada a singleton object to keep all shared data
My implementation draft for the GameScene (now called ShooterScene) is the following (I followed some advices in a cocos2d-iphone forum post as well as this other one ):
#import "ShooterScene.h"
#import "LevelData.h"
#import "HudLayer.h"
#interface ShooterScene (PrivateMethods)
-(void) loadGameArtFile;
#end
#implementation ShooterScene
+ (id) sceneWithId:(int)sceneId
{
CCScene *scene = [CCScene node];
ShooterScene * shooterLayer = [ShooterScene node];
[scene addChild:shooterLayer];
[shooterLayer loadGameArtFile];
LevelData * levelData = [LevelData node];
[shooterLayer addChild:levelData];
switch (sceneId) {
case 1:
[levelData loadLevelDataOne];
break;
case 2:
[levelData loadLevelDataOne];
break;
default:
break;
}
HudLayer * hud = [HudLayer node];
[hud setUpPauseMenu];
[shooterLayer addChild:hud];
return scene;
}
I am using the update method of ShooterScene to manage the all the game events (E.g. spawning, checking collisions, moving the background layer). I haven't put the full implementation here as is still work in progress, but is just to have an idea of the type of answer I am finding useful.

Temporarily disable touch with CCLayerPanZoom

I've recently added the CCLayerPanZoom cocos2d extension to my project and got my game scene zooming and scrolling just like I want. Now when the player takes certain actions, I want to be able to disable the pan/zoom temporarily while they perform an action but can't figure out how to do it. I searched around and found the following code in a forum but it doesn't work or I don't know how to use it.
Does anyone know how to do this properly either with different code or the code below?
-(void)enableTouches:(BOOL)enable {
if(enable) {
[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0];
_panZoomLayer.isTouchEnabled = YES;
CCLOG(#"LayerPanZoom enabled.");
} else {
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
_panZoomLayer.isTouchEnabled = NO;
CCLOG(#"LayerPanZoom disabled.");
}
}
I finally figured it out and figured I would post the answer back up here to share. The code I posted wasn't working because I was sending back self instead of the _panZoomLayer. So here are the steps to get this working yourself.
Implement the CCLayerPanZoom into your project as described by the documentation.
Add the following code as a method to call on your new CCLayerPanZoom class.
-(void)enableTouches:(BOOL)enable {
if(enable) {
[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:_panZoomLayer priority:0];
CCLOG(#"LayerPanZoom enabled.");
} else {
[[CCTouchDispatcher sharedDispatcher] removeDelegate:_panZoomLayer];
CCLOG(#"LayerPanZoom disabled.");
}}
NOTE: Make sure to put the instance of the parent class as the delegate to remove.
In order to re-enable and have it function properly, you have to remove all the entries from the array in the CCLayerPanZoom class before calling to re-register the delegate. I created a new method in the CCLayerPanZoom class as follows and just call it right before the addStandardDelegate method above.
-(void)removeTouchesFromArray {
[self.touches removeAllObjects];
}
Then it all works great! Took me a while to learn how to use this extension but it works perfect once you figure it all out. I can single finger pan, double finger zoom/pan, set center location for entire scene, limit panning past edges, and set min/max scales. I know people have had a lot of issues with this but it is a great extension, just takes some messing around with to understand it. Let me know if you have any questions. Hope this helps someone else.

cocos2d scene question - where does init() get called within an app?

I am totally new to cocos2d and Objective C. I just started studying the HelloWorld example that came with cocos2d package, and just couldn't figure out where in the application the -init() function within HelloWorldScene.m is getting called.
Here is the tutorial that I was following:
http://www.bit-101.com/blog/?p=2123
Thanks in advance!
jtalarico is correct. I'd like to expand on his answer a bit.
In general, some form of [init] is called by convention whenever an object gets instantiated. For many objects, [init] is all that is needed, but some objects have more complex forms, such as [initWithSomething].
In Cocos2d, the init function is generally called by the [node] method, which is often used to construct an object in Cocos2d. For example, look in CCNode.m, and you will see this code:
+(id) node
{
return [[[self alloc] init] autorelease];
}
Other objects have other constructors, but this is the main example.
So, if you subclass CCNode, you can override the [init] method and do your own stuff when an object gets created. Just be sure to call [super init] so that CCNode can do its own initialization, too.
The init() method is being overridden in the scene. It is getting called within the base class when an instance of the scene is created. By overriding it, you get the opportunity to fire your own code.