Evening all,
I'll start this question in the time honoured tradition by saying that I've had a good old search on SO and also in the wider world but I'm not quite getting my head around this...
I've implemented a sneakyJoystick which works wonderfully (It moves my sprite around quite happily) however I've now done myself a mischief in thinking about it's positioning.
What I'd like to do is simply change it's position to a touch location and have it move my sprite around but this seems to be out of my knowledge pool. I might be being an idiot but I cannot work it out.
The touch events are already sorted in the sneakyjoystick classes (available on github https://github.com/0xPr0xy/sneaky-joystick-cocos2d). At the moment if I create the joystick during the init method in a class called controlsLayer then everything works fine; Joystick appears and it allows me to move to sprite around
-(id) init
{
if( (self=[super init]) ) {
myJoystickBase = [[[SneakyJoystickSkinnedBase alloc] init] autorelease];
myJoystickBase.backgroundSprite = [CCSprite spriteWithFile:#"dpad.png"];
myJoystickBase.thumbSprite = [CCSprite spriteWithFile:#"joystick.png"];
myJoystickBase.joystick = [[SneakyJoystick alloc] initWithRect:CGRectMake(0, 0, 128, 128)];
myJoystickBase.position = ccp(64, 64);
myJoystickBase.backgroundSprite.opacity = 100;
myJoystickBase.thumbSprite.opacity = 100;
[self addChild:myJoystickBase];
myJoystick = [myJoystickBase.joystick retain];
[self scheduleUpdate];
}
return self;
}
So to begin with I thought about looking at how I could simply get it to show and hide itself and set it's location. To do this I created a ccTouchesbegan method which contains pretty much the same code as my init method did before. This works fine up to a point (the joystick appears centred wherever I touch) but the issue now is that I cannot interact with it. The joystick appears where i want but it will not recognise my movements (the stick on the joystick does not move which in turn means that my sprite is not being told to move either)
if( (self=[super init]) ) {
self.isTouchEnabled = YES;
[self scheduleUpdate];
}
return self;
}
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
location = [self convertToNodeSpace:location];
myJoystickBase = [[[SneakyJoystickSkinnedBase alloc] init] autorelease];
myJoystickBase.backgroundSprite = [CCSprite spriteWithFile:#"dpad.png"];
myJoystickBase.thumbSprite = [CCSprite spriteWithFile:#"joystick.png"];
myJoystickBase.joystick = [[SneakyJoystick alloc] initWithRect:CGRectMake(0, 0, 128, 128)];
myJoystickBase.position = location;
myJoystickBase.backgroundSprite.opacity = 100;
myJoystickBase.thumbSprite.opacity = 100;
[self addChild:myJoystickBase];
myJoystick = [myJoystickBase.joystick retain];
}
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[self removeChild:myJoystickBase cleanup:YES];
}
So to my uninitiated brain this is saying that when I touch the controlsLayer class it happily does what I've asked it to but it will not then pass this touch to other classes.
In a nutshell can a touch event be passed to multiple classes at the same time? The sneaky joystick class uses CCTargetedTouchDelegate which worked fine when it was the only thing looking for a touch. However now that I've added a -(void)ccTouchesBegan: in another class it's not happy.
Can anyone tell me if the problem is with the way I'm handling touches or is it possibly an issue with the way that I allocate the joystick in the touch method? Should I be allocating the joystick in the init method and be doing something else in the touchesBegan method? Trial and error isn't getting me anywhere useful at the moment. Feel like I'm banging my head against a brick wall. I'm happy to post up the full class files if necessary. Does this make sense?
The problem is that you create the joystick during touch began, and remove it on touch ended. This does not allow the joystick to receive a touch began event, since the event is already being processed at the time the joystick is being created.
You're also leaking the joystick due to the unnecessary retain (use ARC, please!).
Try creating the joystick in init, disable & hide it until a touch began event is received. This is also faster than recreating the joystick on every touch.
Also, should you have multiple touches enabled, keep in mind that you can receive up to 5 touch began events (5 fingers) without any touch ended in between. That would create 5 joysticks, but only remove one! Every time!
Related
I'm using Xcode with Cocos2d version 3.0.
I want to drag sprites around the screen. I've done so successfully using the following code:
(void) touchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [touch locationInNode:self];
sprite1.position=touchLocation;
sprite2.position=touchLocation;
sprite3.position=touchLocation;
sprite4.position=touchLocation;
}
However, sometimes the sprites stop moving after a second. It's not a lag, because they never catch back up with my movement. They just stop! If I let go and start moving my touch again, the sprites start moving fine again / sometimes do the 'freeze thing' again.
Is it a memory issue?
Ok, I'm sure it must be memory. I copied this code onto a simple game with hardly any sprites and it worked perfectly.
Ok I've got it!
I had to unEnable the UISwipeGestureRecognizers while I moved the sprite.
The game was registering my touchesMoved movement as a swipe, and cancelling the touchesMoved commands.
I use this code to show game over menu after _hero sprite and _enemy sprite collide :
if (CGRectIntersectsRect(_hero.boundingBox,_enemy.boundingBox)) {
CCActionCallFunc *_callShowMenu=[CCActionCallFunc actionWithTarget:self selector:#selector(showMenu)];
[self runAction:_callShowMenu];
// EDIT : I also remove both sprites when collision happens.
[_hero removeFromParent];
[_enemy removeFromParent];
}
In _callShowMenu I just stop all actions and show a sprite with half transparent black background image and buttons.
Sometimes when collision happens, it seems to me, that _callShowMenu is called twice, because
background is completely black, like there is the same image behind. Has anyone had a similar problem? (Mostly background image is half-transparent, as it should be).
EDIT:
-(void)showMenu{
[[CCDirector sharedDirector] pause];
CCSprite *_halfTransparentBackground=[CCSprite spriteWithImageNamed:#"halfTransparentBackground.png"];
_halfTransparentBackground.position=ccp(160, 280);
[self addChild:_blackBack z:5];
}
I found a solution using BOOL. Actually everyone uses BOOL in this case, so I don't need to reinvent the wheel.
BOOL doNotCallMeTwice;
Somewhere in the didLoad method:
doNotCallMeTwice=NO;
In the collision detection method:
if (doNotCallMeTwice==NO) {
[self showMenu];
}
And finally:
-(void)showMenu{
doNotCallMeTwice=YES;
}
Possibly, showMenu was called twice(or much more times),because collision detection is in the update method.
I have initialized UITapGesture as follows:
tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
tapGestureRecognizer.cancelsTouchesInView = YES;
[[[CCDirector sharedDirector] view] addGestureRecognizer:tapGestureRecognizer];
As per code, if you tap on a sprite, it moves to new location using CCMoveTo action with duration = 0.3.
This works fine when a Sprite is tapped and you wait till it reaches new destination.
So far so good.
But if you tap on sprites fast enough to get an effect of multiple sprites moving to new destinations, I noticed some of the sprites are left inbetween and stop some random location.
Why this would be happening? Any thoughts?
I started my app using the base code found here in the menus tutorial. In this manner, all of my 'screens' (there are only 5 of them) are implemented as extensions of the CCLayer class, and I have a shared + Scene Manager which works by adding my layer classes as children to a new scene and then using the director to run or replace the currently playing scene:
+(void) goMenu{
// \/---------- Issue right here, next line:
CCLayer *layer = [MenuLayer node];
[SceneManager go: layer];
}
+(void) go: (CCLayer *) layer{
CCDirector *director = [CCDirector sharedDirector];
CCScene *newScene = [SceneManager wrap:layer];
if ([director runningScene]) {
[director replaceScene: newScene];
}else {
[director runWithScene:newScene];
}
}
+(CCScene *) wrap: (CCLayer *) layer{
CCScene *newScene = [CCScene node];
[newScene addChild: layer];
return newScene;
}
The problem I am having is as follows. Let's say I have 2 layers -- 'MenuLayer' and 'GameLayer'. I start off with MenuLayer and later on use [SceneManager go:[GameLayer node]] to transition over to GameLayer. From GameLayer, if I goMenu the app terminates with an 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again' where indicated. I assumed that this is happening because I am trying to add some child sprites in the layer's init code and I am re-adding them again.
My first attempt at debugging was to call [self removeAllChildrenWithCleanup:YES]; within the onExit code of all my layers. That didn't solve the issue. I added in some debugging logs and found out the last line that executes before the script blows up is the indicated one [myLayerClass node];. The init code inside the layer class does not get executed at all -- so it can't be one of the sprites or other children being added that is causing the issue. Again -- remember that everything works the first time I try to open any scene. It's moving back to an opened scene that is currently problematic.
So I tried a different approach -- the approach used in Cocos2D Hello world. I modified all my Layer Classes by adding a singleton +(id) scene method defined as follows:
+(id) scene
{
CCScene *scene = [CCScene node];
myLayerClass *layer = [myLayerClass node];
[scene addChild: layer];
return scene;
}
Added in a [super dealloc] in the dealloc, and encapsulated all my init code within:
-(id) init
{
if( (self=[super init] )) {
// All init code here....
}
}
Of course, all of the goLayerName singleton methods in the scene manager had their contents replaced to match this -- for example:
+(void) goMenu {
//CCLayer *layer = [MenuLayer node];
//[SceneManager go: layerMenu];
CCDirector *director = [CCDirector sharedDirector];
if ([director runningScene])
[director replaceScene:[MenuLayer scene]];
else
[director runWithScene:[MenuLayer scene]];
}
I figured this was the way Cocos2D's hello world worked, it must be good. Also, since each of my scenes would be created once and once alone (and thus every layer would be created once and once alone). No effect whatsoever! The first time I visit any scene it runs as expected. Whenever I try to navigate back to any existing scene, I get the error I mentioned above.
I tried poking around SO and found this, but I am unsure how to properly implement it; also I'm not entirely sure that would even solve the issue -- since the symptoms are different (he just gets a pink screen, whereas my app terminates).
Any assistance would be appreciated.
For anyone who was interested in this question, I was able to resolve the issue.
In summary, any child of a child you add, will not be released with releaseAllChildrenWithClenup. That function will only release the immediate children, so you are responsible to release any nested children yourself.
In my game, I had some characters and their eyes were nested in the head sprites (so that I could just move / rotate the heads). The eyes had their own animation loop but I wanted them to transform with the head. All that worked, but since I had a line of code in init which basically [headSprite addChild:eyeSprite];, I had to also add [headSprite removeChild:eyeSprite]; in the onExit code to dissociate it, followed by releaseAllChildrenWithClenup to remove all of the n=1 level children as well.
Once that was handled, everything worked as advertised.
I was playing around with a Joystick moving a Sprite around in one layer. Now, according to best practice, the Joystick and the Sprite have to be in different Layers of the same scene. I have managed to separate these, yet I am now completely stuck having absolutely no clue whatsoever how to pass joystick commands from one layer to another? What is the recommended way to do this?
Scene
Game Play Layer
Sprite
Game Control Layer
Joystick
When the joystick is manipulated I need to pass this info to the GamePlayLayer to control the sprite.
Well, I got a great Cocos2d book, written by Rod Strougo and Ray Wenderlich, by the name "Learning Cocos2d". In their book, they implement a game, which has a joystick implemented and all, using your initial setup. The GamePlayLayer contains both the joyStick and the hero sprite. (See book page 40).
I don't believe they would use bad practices in their book, given they are very talented!
...
With that being said, I have possible solutions, if you wish to implement them on separate layers:
GameScene
|
|__>GameLayer
|
|__>ControlLayer
That's your basic setup. But, intuitively, what is the purpose of the control layer? Control the game layer's content! So, I would suggest you hold a (weak) reference to the GameLayer within the ControlLayer. That way, using a simple:
#property (nonatomic, assign) CCSprite* hero;
you now have access to the hero from the ControlLayer!
Extra (if needed):
//GameScene init:
- (id)init {
....
gameLayer = [GameLayer node];
controlLayer = [ControlLayer node];
[controlLayer setGameLayerRef:gameLayer];
...
}
// Control layer:
#property (nonatomic, assign) GameLayer* gameLayerRef;
Even though I just suggested that way, I don't use it in my games :)
What I normally do is:
Make the GameScene class a "Semi-Singleton". (I learned this method from "Learn iPhone and iPad Game Dev" By Itterheim (aka gaming horror, Kobold2d publisher ... etc).
Then, inside the control layer, I would call the GameScene object:
[[GameScene sharedScene] doSomethingToTheGameLayer];
Yeah, the gameScene has simplistic methods that just relies what the control need to update in the game layer.
Edit:
Implementing the Semi-singleton pattern, as described by Itterheim in his book.
But, what is semi-singleton?
It has the singleton pattern's property: you can access the object instance from anywhere using a static call.
[GameScene sharedScene];
However, singleton objects are usually retained, after being created for the first time, till the end of the application's life. In the Semi-singleton pattern, this is not the case.
Once you create the instance, you cannot create another instance before destroying the old one, BUT once you are done with the instance, you destroy it (dealloc). Creating another one when necessary.
Recap:
Semi-Singleton: Create many object from it, but only one at any given time. Only recreate after destroying the old.
Implementation:
Of course, as you do with any singleton class, you first declare a static variable of the same type of the class:
//In GameScene.m
static GameScene* instance = nil;
#implementation
//Here we have the static method to access the instance:
+(GameScene*)sharedScene {
//you must have created one before trying to access it "Globally".
///Typically, created where you transition the GameScene using CCDirector's replaceScene.
NSAssert(instance != nil, #"GameScene not instantiated!!");
return instance;
}
-(id)init {
if((self = [super init])) {
//Make sure you don't have another instance of the class created
//NOTE: Possible race condition that I wouldn't worry about, since it should never happen.
NSAssert(instance == nil, #"Another instance is already created!!");
instance = self;
...
}
return self;
}
//Don't forget!! Dealloc!!
- (void)dealloc {
//the only instance we had is dealloc'd! We are back to zero. Can create a new one later! :D
instance = nil;
[super dealloc];
}
Edit2:
So, the timeline:
CCScene* scene = [GameScene node];
[[CCDirector sharedDirector] replaceScene:scene];
...
[[GameScene sharedScene] doSomething];
...
[[CCDirector sharedDirector] replaceScene:otherScene];
//After the new scene replaces GameScene, dealloc will be called, implicitly. Making instance = nil;
instance = nil;
[super dealloc];
...
//Loop again
CCScene* scene = [GameScene node];
[[CCDirector sharedDirector] replaceScene:scene];
...