Cocos2d - Alternative for CCNode userData - cocos2d-iphone

I am using CCNode userData property to define whether a touched sprite has had a node built upon it (using Ray Wenderlich's - Tower Defence Tutorial).
The problem I am having is that for every new sprite position I touch and build upon, the previous ones are not retained in the sense that I am unable to sell the building built on it using my sell method.
What is the best way to save data to an individual CCSprite that is selected by touch, and defined by a property through a for loop?
I have read that subclassing CCSprite is an option, or creating a struct for userData etc. I have tried to implement these strategies but have been unsuccessful getting them to work.
touch method with sprite property:
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for(UITouch *touch in touches){
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
for(CCSprite *tb in self.BuildPositions){
//BuildPositions contains CCSprites from a class defining build positions and contained in a mutable array.
if(CGRectContainsPoint([tb textureRect],[tb convertToNodeSpace:location ])){
_selectedBuildPosition = tb;
[self buildMenuTapped];
}
}
}
}
build menu method:
-(void)buildMenuTapped{
[self removeChild:_buildSelectMenu cleanup:YES];
[self removeChild:_sellBuildingMenu cleanup:YES];
if (_selectedBuildPosition.userData == nil) {
CCMenuItem *buildOption = [CCMenuItemImage itemWithNormalImage:#"building.png" selectedImage:#"building.png" target:self selector: #selector(buildOption)];
buildOption.position = ccpAdd(_selectedBuildPosition.position,ccp(0,40));
_buildSelectMenu = [CCMenu menuWithItems:BuildOption, nil];
_buildSelectMenu.position = CGPointZero;
[self addChild:_buildSelectMenu z:100];
}else{
[self sellBuildingMenu];
}
}
build upon method:
-(void)buildOption{
if([self canBuyBuilding] && !_selectedBuildPosition.userData){
_playerEssence -= kBUILD_COST;
_lvl1BuildNodeInstance = [lvl1BuildNode nodeWithTheGame:self location:_selectedBuildPosition.position];
//method as seen in RW tutorial. Generates a lvl1BuildNode class node within the HelloWorldLayer.
_selectedBuildPosition.userData = (__bridge void*)(_lvl1BuildNodeInstance);
}
}
sell menu method:
-(void)sellBuildingMenu{
[self removeChild:_buildSelectMenu cleanup:YES];
[self removeChild:_sellBuildingMenu cleanup:YES];
CCMenuItem *buildingSell = [CCMenuItemImage itemWithNormalImage:#"sell.png" selectedImage:#"sell.png" target:self selector:#selector(sellBuilding)];
buildingSell.position = ccpAdd(_selectedBuildPosition.position,ccp(40,0));
_sellBuildingMenu = [CCMenu menuWithItems:buildingSell, nil];
_sellBuildingMenu.position = CGPointZero;
[self addChild:_sellBuildingMenu z:100];
}
sell method:
-(void)sellBuilding{
if (_selectedBuildPosition.userData == (__bridge void*)(_lvl1BuildNodeInstance)){
[self removeChild:_lvl1GemNodeInstance cleanup:YES];
[_buildings removeObject:_lvl1BuildNodeInstance];
_playerEssence += kBUILD_COST;
_selectedBuildPosition.userData = nil;
}else{
}
}
Thank you for your time.

Related

Individual buttons in cocos2d

Most talk about buttons for cocos2d seems to be directed to CCMenu, where CCMenu AFAIK is meant for having a row or column in center of screen.
I need to place buttons randomly on screen and have yet to find a simple out of the box solution for this. I did try CCControlButton but didn't get it to work (CCScale9Sprite spriteWithSpriteFrameName loads incorrectly from sprite atlas).
For now I try to use this. It uses only one button in each CCMenu. So my screen will have lots of CCMenu instances, one for each button.
+(CCMenu*)button:(NSString*)spriteframename at:(const CGPoint)POINT block:(void(^)(id sender))block {
CCSprite* sprite1 = [CCSprite spriteWithSpriteFrameName:spriteframename];
CCSprite* sprite2 = [CCSprite spriteWithSpriteFrameName:spriteframename];
CCMenuItem* menuitem = [CCMenuItemImage itemWithNormalSprite:sprite1 selectedSprite:sprite2 block:block];
CCMenu* menu = [CCMenu menuWithItems:menuitem, nil];
menu.contentSize = sprite1.contentSize;
menu.position = POINT;
return menu;
}
This is a better way which I changed to.
Put this in like init:
CCMenuItem* menuitemRetry = [[self class] buttonWithSpriteframenameOff:#"retry_off.png" on:#"retry_on.png" at:ccp(198, 184) block:^(id sender) {
// Do something
}];
CCMenuItem* menuitemMenu = [[self class] buttonWithSpriteframenameOff:#"menu_off.png" on:#"menu_on.png" at:ccp(362, 184) block:^(id sender) {
// Do something else
}];
CCMenu* menuLow = [CCMenu menuWithItems:menuitemMenu, menuitemRetry, nil];
menuLow.position = CGPointZero;
[self addChild:menuLow];
This also needed
+(CCMenuItem*)buttonWithSpriteframenameOff:(NSString*)spriteframenameOff on:(NSString*)spriteframeOn at:(const CGPoint)POINT block:(void(^)(id sender))block {
CCMenuItem* menuitem = [CCMenuItemImage itemWithNormalSprite:[CCSprite spriteWithSpriteFrameName:spriteframenameOff] selectedSprite:[CCSprite spriteWithSpriteFrameName:spriteframeOn] block:block];
menuitem.position = POINT;
return menuitem;
}

Code Refactoring issue

I have game ready and now I am trying to refactor code. I have derived Spider class from CCNode and used targeted delegate method CCTargetedTouchDelegate.
#interface Spider : CCNode<CCTargetedTouchDelegate> {
CCSprite* spiderSprite;
NSString * spiderKilled;
int killed;
AppDelegate *del;
}
+(id) spiderWithParentNode:(CCNode*)parentNode;
-(id) initWithParentNode:(CCNode*)parentNode;
#end
On Touch spider should be killed and here goes the code:
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint tch = [touch locationInView: [touch view]];
CGPoint touchLocation = [[CCDirector sharedDirector] convertToGL:tch];
// Check if this touch is on the Spider's sprite.
BOOL isTouchHandled = CGRectContainsPoint([spiderSprite boundingBox], touchLocation);
if (isTouchHandled)
{
j = j + 1;
killed ++;
[del setKilledScore:j];
[self removeChild:spiderSprite cleanup:YES];
}
return isTouchHandled;
}
I am adding 10 spiders in GameScene layer using: -
for(int i=0; i <10 ;i++){
[Spider spiderWithParentNode:self];
}
But, unfortunately I am not able to remove spiders and giving me EXC_BAD_ACCESS error on this line: [self removeChild:spiderSprite cleanup:YES];
Please help me overcome this error.
Thanks
Update --
Spider Init code
// Static autorelease initializer, mimics cocos2d's memory allocation scheme.
+(id) spiderWithParentNode:(CCNode*)parentNode
{
return [[[self alloc] initWithParentNode:parentNode] autorelease];
}
-(id) initWithParentNode:(CCNode*)parentNode
{
if ((self = [super init]))
{
[parentNode addChild:self];
del = [[UIApplication sharedApplication] delegate];
CGSize screenSize = [[CCDirector sharedDirector] winSize];
spiderSprite = [CCSprite spriteWithFile:#"spider.png"];
spiderSprite.position = CGPointMake(CCRANDOM_0_1() * screenSize.width, CCRANDOM_0_1() * screenSize.height);
[self addChild:spiderSprite];
// Manually add this class as receiver of targeted touch events.
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
}
return self;
}
If you manually add the class to the CCTouchDispatcher list, you should remove it from there after you're done using it.

how to do a function for a sprite or menu in cocos2d?

I created two CCLayers, one is gamelayer, another is howlayer. The code of gamelayer.m is
-(id)init{
if (self = [super init]) {
CCSprite *gamebg = [CCSprite spriteWithFile:#"bg.png"];
gamebg.anchorPoint = CGPointZero;
[self addChild:gamebg z:0 tag:1];
HowLayer *howLayer = [HowLayer node];
[self addChild:howLayer];
[self schedule:#selector(showthegamecontent:) interval:0.4];
}
return self;
}
the code of howlayer is
-(id)init{
if (self=[super init]) {
CCSprite *howbg = [CCSprite spriteWithFile:#"translucentbg.png"];
howbg.anchorPoint = CGPointZero;
[self addChild:howbg z:5 tag:1];
CCMenuItem *howmenu = [CCMenuItemImage itemFromNormalImage:#"how.png"
selectedImage:#"how.png"
target:self
selector:#selector(startgame:)];
CCMenu *ccMenuhowmenu = [CCMenu menuWithItems:howmenu, nil];
ccMenuhowmenu.position=ccp(517,384);
[self addChild:ccMenuhowmenu z:5 tag:2];
}
return self;
}
-(void)startgame:(id)sender{
[self removeAllChildrenWithCleanup:YES];
}
I want to do function like this:
When I click the menu on howlayer, the Howlayer will be removed (I have done), and then the game starts, calls the selector 'showthegamecontent', so how should I do?
Simple hack in your howlayer:
-(void)startgame:(id)sender{
gameLayer* parent = (gameLayer*) self.parent;
[parent showthegamecontent];
}
but it may leave you with a warning.. But it works..
The implementation without warning is that you have to store a reference to the parent with you init. Which i feel its unnecessary as you only need to reference it once.

Cocos2d Displaying a Layer Issue

I'm trying to display a pause game layer from the applicationDidEnterBackground: method and for some reason it does call the method but nothing happens.
Delegate
- (void)applicationDidEnterBackground:(UIApplication*)application {
ship = [[Ship alloc] init];
[ship pause];
Pause Method
- (void)pause
{
BOOL isPaused = [[CCDirector sharedDirector] isPaused];
if(!isPaused)
{
//Pause the game
ccColor4B c = {100,100,0,100};
PauseLayer *pauseLayer = [[[PauseLayer alloc] initWithColor:c] autorelease];
[self.leftMenuItem setIsEnabled:NO];
[self.rightMenuItem setIsEnabled:NO];
[self.fireMenuItem setIsEnabled:NO];
[self addChild:pauseLayer z:10 tag:100];
[[CCDirector sharedDirector] pause];
}
}
PauseLayer
+ (id)scene
{
CCScene *scene = [CCScene node];
PauseLayer *layer = [PauseLayer node];
[scene addChild:layer];
return scene;
}
- (id)initWithColor:(ccColor4B)color
{
if((self = [super initWithColor:color]))
{
self.isTouchEnabled = YES;
[CCMenuItemFont setFontName:#"Marker Felt"];
[CCMenuItemFont setFontSize:40];
CCMenuItemFont *resumeGameItem = [CCMenuItemFont itemFromString:#"Resume" target:self selector:#selector(resumeGame)];
CCMenuItemFont *menuGameItem = [CCMenuItemFont itemFromString:#"Menu" target:self selector:#selector(goToGameMenu)];
CCMenu *menu = [CCMenu menuWithItems:resumeGameItem,menuGameItem,nil];
[menu alignItemsVerticallyWithPadding:40.00];
[self addChild:menu];
}
return self;
}
Thanks!
If you init the ship in the delegate, it isn't added to any cocos layer that I can see. you would have to get a reference to the current scene and add the ship to it (assuming ship is a sub-class of Cocos node).

Rotating sprite with finger in Cocos2d

I need help figuring out the rotation of a sprite with my finger. The sprite is rotating fine but on the initial touch of my finger it somehow rotates by a few degrees by itself.
Also the rotations only work when the finger is rotated around the center of the sprite.
I am trying to simulate the bicycle wheel and have a gear sprite and a pedal sprite as a child of the gear sprite. I want the bicycle wheel to rotate as I touch the pedal and rotate it. I haven't got that far yet. Right now I am trying to figure out how to get rid of the initial shift the gear makes.
Here is the complete code
#import "BicycleScene.h"
#implementation BicycleScene
+(id) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
BicycleScene *layer = [BicycleScene node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
if ((self=[super init])) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
//place the bicycle sprite
bicycleSprite = [CCSprite spriteWithFile:#"bike_gear.png"];
bicycleSprite.position = ccp(bicycleSprite.contentSize.width/2 + 100, winSize.height/2);
[self addChild:bicycleSprite z:1];
//place the pedal sprite
pedalSprite = [CCSprite spriteWithFile:#"bike_pedal.png"];
[bicycleSprite addChild:pedalSprite z:1];
pedalSprite.position = ccp(150, 15);
//enable touch
self.isTouchEnabled = YES;
[self schedule:#selector(gameLoop:) interval:.1/100];
}
return self;
}
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"Touch begun");
}
-(void)gameLoop:(ccTime) dt{
bicycleSprite.rotation = cocosAngle;
}
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"Touch moved");
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
CGPoint previousLocation = [touch previousLocationInView:[touch view]];
CGPoint touchingPoint = [[CCDirector sharedDirector] convertToGL:location];
CGPoint previousTouchingPoint = [[CCDirector sharedDirector] convertToGL:previousLocation];
CGPoint vector = ccpSub(touchingPoint, bicycleSprite.position);
CGFloat rotateAngle = -ccpToAngle(vector);
previousAngle = cocosAngle;
cocosAngle = CC_RADIANS_TO_DEGREES( rotateAngle);
//bicycleSprite.rotation = cocosAngle;
}
#end
I am slightly confused if the line:
CGPoint vector = ccpSub(touchingPoint, bicycleSprite.position);
should actually be:
CGPoint vector = ccpSub(touchingPoint, previousTouchingPoint );
I tried that as well but it didn't work.
I have also uploaded my complete xcodeproj to 4shared for anyone who wants to take a look here: http://www.4shared.com/file/5BaeW4oe/Healthy.html
#interface MainScene : CCLayer {
CCSprite *dial;
CGFloat dialRotation;
}
+ (id)scene;
#end
---------------------------------
#implementation MainScene
+ (id)scene
{
CCScene *scene = [CCScene node];
CCLayer *layer = [MainScene node];
[scene addChild:layer];
return scene;
}
- (id)init
{
if((self = [super init])) {
CCLOG(#"MainScene init");
CGSize size = [[CCDirector sharedDirector]winSize];
dial = [CCSprite spriteWithFile:#"dial.png"];
dial.position = ccp(size.width/2,dial.contentSize.height/2);
[self addChild:dial];
self.isTouchEnabled = YES;
[self scheduleUpdate];
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)update:(ccTime)delta
{
dial.rotation = dialRotation;
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
//acquire the previous touch location
CGPoint firstLocation = [touch previousLocationInView:[touch view]];
CGPoint location = [touch locationInView:[touch view]];
//preform all the same basic rig on both the current touch and previous touch
CGPoint touchingPoint = [[CCDirector sharedDirector] convertToGL:location];
CGPoint firstTouchingPoint = [[CCDirector sharedDirector] convertToGL:firstLocation];
CGPoint firstVector = ccpSub(firstTouchingPoint, dial.position);
CGFloat firstRotateAngle = -ccpToAngle(firstVector);
CGFloat previousTouch = CC_RADIANS_TO_DEGREES(firstRotateAngle);
CGPoint vector = ccpSub(touchingPoint, dial.position);
CGFloat rotateAngle = -ccpToAngle(vector);
CGFloat currentTouch = CC_RADIANS_TO_DEGREES(rotateAngle);
//keep adding the difference of the two angles to the dial rotation
dialRotation += currentTouch - previousTouch;
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
EDIT: I am sorry I can't get this to stay in code mode. But this is the solution and it should be easy to implement in your code. cheers!
In ccTouchesBegan, use the same code as what you've got in ccTouchesMoved to determine the angle, then move to that angle using CCRotateTo. You will need some handling of the user moving his finger while CCRotateTo is active, perhaps stopping the current action and kicking off another one to move to the new angle.