Sprite touch detection - ios

Have a question here. I created a couple sprites (with tags) in my (id)init function, and then merely trying to detect which sprite was touched? A snippet of the code from my init function is pasted below.
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"blue_sheet.plist"];
//create a sprite batch node
CCSpriteBatchNode *TrainerSprites = [CCSpriteBatchNode batchNodeWithFile:#"blue_sheet.png"];
[self addChild:TrainerSprites z:1];
//create a sprite from that node
CCSprite *Horse = [CCSprite spriteWithSpriteFrameName:#"horse_blue.png"];
[TrainerSprites addChild:Horse z:1 tag:1];
//Horse.position = ccp(winSize.width/5, winSize.height/2);
[Horse setScaleX: 138.5/Horse.contentSize.width];
[Horse setScaleY: 80/Horse.contentSize.height];
//create a sprite from that node
CCSprite *Cow = [CCSprite spriteWithSpriteFrameName:#"cow_blue.png"];
[TrainerSprites addChild:Cow z:1 tag:2];
//Cow.position = ccp(winSize.width/2, winSize.height/2);
[Cow setScaleX: 126/Cow.contentSize.width];
[Cow setScaleY: 100/Cow.contentSize.height];
Horse.position = ccp(4*winSize.width/5, winSize.height/2);
Cow.position = ccp(winSize.width/5, winSize.height/2);
CGRect pos1 = CGRectMake(Cow.position.x, Cow.position.y, 200, 100);
CGRect pos2 = CGRectMake(Horse.position.x, Horse.position.y, 200, 100);
self.touchEnabled = YES;
All looks fine... and the sprites appear where they are supposed to. When I touch anywhere on screen my ccTouchBegan function fires. Not seeing anything happen with the CGRect and I suppose I need to determine which one fired by the assigned tag. Yes indeedy, I know I'm missing code, I just cannot locate good solid documentation anywhere how to do this seemingly basic ios functionality. I assume the "sprite touch detection" code should reside inside the ccTouchBegan function? Any help or guidance sincerely appreciated. :)

Another approach could be to subclass CCSprite and implement the TargetedTouchDelegate.
Something like:
#interface AnimalSprite:CCSprite<CCTargetedTouchDelegate>
The advantage of this approach is that you wont have to do a lot of "If" checks in the layer where you are adding the sprites. The link provides the method that you must implement in orer to implement the protocol and where and how you could register with the touch dispatcher.

to detect the sprite touch you can use this
declare CCSprite *Cow in your .h section
and in .m section use this
in init method
//create a sprite from that node
Cow = [CCSprite spriteWithSpriteFrameName:#"cow_blue.png"];
[TrainerSprites addChild:Cow z:1 tag:2];
//Cow.position = ccp(winSize.width/2, winSize.height/2);
[Cow setScaleX: 126/Cow.contentSize.width];
[Cow setScaleY: 100/Cow.contentSize.height];
in touches began method
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch =[touches anyObject];
CGPoint location=[touch locationInView:[touch view]];
location =[[CCDirector sharedDirector] convertToGL:location];
if (CGRectContainsPoint( [Cow boundingBox], location)) {
/* CCScene *scene = [CCScene node];
[scene addChild:[ClassicScene node]];
[[CCDirector sharedDirector] replaceScene:scene];*/
}
}

Related

Sprite kit moving sprite

I have made this sprite that i can grab and move around.
My issue is that i want to be able to "throw" the sprite. Meaning, when i release the sprite i want it to continue in the direction that i moved it. Just like throwing a ball.
What should i do?
#implementation NPMyScene
{
SKSpriteNode *sprite;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
self.scaleMode = SKSceneScaleModeAspectFit;
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"GreenBall"];
sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
sprite.physicsBody.velocity = self.physicsBody.velocity;
sprite.physicsBody.affectedByGravity = false;
sprite.physicsBody.dynamic = true;
sprite.physicsBody.friction = 0;
[sprite.physicsBody isDynamic];
[sprite.physicsBody allowsRotation];
[self addChild:sprite];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[sprite runAction:[SKAction moveTo:[[touches anyObject] locationInNode:self]duration:0.21]];
}
If you are talking about applying physics to the ball, then you need to think deeper about how Sprite Kit deals with physics.
When you specify the 'moveTo' action, you're not actually using Sprite kit's physics engine at all. You're simply specifying a point to animate that sprite to.
What you should be doing to achieve the effect you're looking for is attaching the sprite's position to your finger as it's moved around on the screen like so:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
sprite.position = [[touches anyObject] locationInNode:self];
}
Then when the finger is lifted, you need to calculate the direction and speed in which your finger just moved and apply the appropriate amount of force to the sprite's physics body using the applyForce method:
[sprite.physicsBody applyForce:calculatedForce];
You will need to figure out how to calculate the force to be applied, but play around with the applyForce method and look at what information you are able to get back from the touchedMoved: event to help you apply that force. Hope that helps.

Only one sprite on the scene at once Cocos2D 3.x

The sprite appears every time the screen is touched, then it shoots to the desired area. How would i make it that only one sprite will be on the scene at a time until it exits the scene or hits an object? (even though the screen is touched multiple times)
This is the projectile code
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// 1
CGPoint touchLocation = [touch locationInNode:self];
// 2
CGPoint offset = ccpSub(touchLocation, _player.position);
float ratio = offset.y/offset.x;
int targetX = _player.contentSize.width/2 + self.contentSize.width;
int targetY = (targetX*ratio) + _player.position.y;
CGPoint targetPosition = ccp(targetX,targetY);
// 3
CCSprite *projectile = [CCSprite spriteWithImageNamed:#"projectile.png"];
projectile.position = _player.position;
projectile.physicsBody = [CCPhysicsBody bodyWithCircleOfRadius:projectile.contentSize.width/2.0f andCenter:projectile.anchorPointInPoints];
projectile.physicsBody.collisionGroup = #"playerGroup";
projectile.physicsBody.collisionType = #"projectileCollision";
[_physicsWorld addChild:projectile];
// 4
CCActionMoveTo *actionMove = [CCActionMoveTo actionWithDuration:1.5f position:targetPosition];
CCActionRemove *actionRemove = [CCActionRemove action];
[projectile runAction:[CCActionSequence actionWithArray:#[actionMove,actionRemove]]];
[[OALSimpleAudio sharedInstance] playEffect:#"pew-pew-lei.caf"];
}
If I understood your point correctly , you could simply add a flag , to be able to notice when there's already a sprite on the scene. Just declare on your class a
BOOL isSpritePresent;
Initialize it on your class custom id method.
-(id)init {
self=[super init];
isSpritePresent=NO;
return self; }
And then on the start of TouchBegan add something like
if(isSpritePresent){
return; //As there's already an sprite on the scene.
}
And at the end
isSpritePresent=YES;
And finally when the arrow or w/e it is reached its target , call a method to reset the Boolean.
Or... If you're looking forward to doing things simpler and you believe you have an specific time to let the user shoot again just add a delay after the other actions as ..
CCActionDelay *delay = [CCActionDelay actionWithDuration:1.2f];
[projectile runAction:[CCActionSequence actionWithArray:#[actionMove,actionRemove,delay]]];

Sprites and Bounding Box issues

I am making an IOs game for an assignment using cocos2D. So far I am just trying to get all of the menus done, but I am having some issues with bounding box when setting up some of my buttons.
The game starts with the cocos2D logoscreen, then transits into a splash screen for my game, then into the main menu. In the game menu I have 2 buttons: Begin, and Options. Both buttons are working perfectly fine, each makinga transicion to the correct scene.
I am currently working on the options menu scene, where I have a button to take care of volume, a butto to set game difficulty, and a button to go back to main menu. I have been trying to make the back button work, but is not triggering when touched, I placed an NSLog for debugging, and the touches are being detected, just the if statement that checks whether the sprite was touched oris never true. I am puzzled by this, since I am using the same method I used in my main menu (which works). I searched for similar issues before posting, but everything I tried, like altering the parent coordinate system and/or the sprites boundingbox.origin, haven't worked at all.
This is the code for my Options Scene:
#import "OptionsMenu.h"
#implementation OptionsMenu
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
OptionsMenu *layer = [OptionsMenu node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
if( (self=[super init]) ) {
self.touchEnabled = YES;
self.accelerometerEnabled = YES;
CGSize winSize = [[CCDirector sharedDirector] winSize];
CCSprite *background = [CCSprite spriteWithFile:#"Menu0.png"];
background.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:background z:0];
CCSprite *volumeBar = [CCSprite spriteWithFile:#"VolumeBar.png"];
volumeBar.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:volumeBar z:1];
CCSprite *volumeButton = [CCSprite spriteWithFile:#"VolumeButton.png"];
volumeButton.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:volumeButton z:1];
CCSprite *difficultyButton = [CCSprite spriteWithFile:#"EasyButton.png"];
difficultyButton.position = ccp(winSize.width/2, winSize.height/2-55);
[self addChild:difficultyButton z:1];
CCSprite *backButton = [CCSprite spriteWithFile:#"BackButton.png"];
backButton.position = ccp(winSize.width/12, winSize.height/8);
[self addChild:backButton z:1];
[self scheduleUpdate];
}
return self;
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
CCActionInterval *actionInterval = [CCActionInterval actionWithDuration:0.1];
if (CGRectContainsPoint([self.backButton boundingBox], location)) {
[self.backButton setTexture:[[CCTextureCache sharedTextureCache] addImage:#"BackButton.png"]];
id actionCallFunc = [CCCallFunc actionWithTarget:self selector:#selector(goToMainMenu:)];
[self.background runAction: [CCSequence actions: actionInterval, actionCallFunc, nil]];
}
else if (CGRectContainsPoint([self.difficultyButton boundingBox], location)) {
[self.volumeButton setTexture:[[CCTextureCache sharedTextureCache] addImage:#"VolumeButton.png"]];
}
}
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
NSLog(#"Touch");
//missileButton* missileButton;
location = [[CCDirector sharedDirector] convertToGL: location];
if (CGRectContainsPoint([self.backButton boundingBox], location))
{
[self.backButton setTexture:[[CCTextureCache sharedTextureCache]addImage:#"BackButtonPressed.png"]];
NSLog(#"BackButtonTouched");
}
else if (CGRectContainsPoint([self.volumeButton boundingBox], location))
[self.volumeButton setTexture:[[CCTextureCache sharedTextureCache]addImage:#"VolumeButtonPressed.png"]];
}
}
-(void) goToMainMenu:(id) sender
{
[[CCDirector sharedDirector] replaceScene: [CCTransitionSlideInR transitionWithDuration:2 scene:[MainMenu node]]];
}
#end
I would appreciate any help in solving this issue.
Your self.backButton is never defined. Change your init function to the following (I'm assuming all of your CCSprite defines are actually properties:
-(id) init
{
if( (self=[super init]) ) {
self.touchEnabled = YES;
self.accelerometerEnabled = YES;
CGSize winSize = [[CCDirector sharedDirector] winSize];
_background = [CCSprite spriteWithFile:#"Menu0.png"];
_background.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:_background z:0];
_volumeBar = [CCSprite spriteWithFile:#"VolumeBar.png"];
_volumeBar.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:_volumeBar z:1];
_volumeButton = [CCSprite spriteWithFile:#"VolumeButton.png"];
_volumeButton.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:_volumeButton z:1];
_difficultyButton = [CCSprite spriteWithFile:#"EasyButton.png"];
_difficultyButton.position = ccp(winSize.width/2, winSize.height/2-55);
[self addChild:_difficultyButton z:1];
_backButton = [CCSprite spriteWithFile:#"BackButton.png"];
_backButton.position = ccp(winSize.width/12, winSize.height/8);
[self addChild:_backButton z:1];
[self scheduleUpdate];
}
return self;
}
Another approach would be to use CCMenu and CCMenuItemSprite for all of your buttons. These classes make simple buttons for menus much easier to implement. Most/all of the touch handling is done for you and there are variants that use selectors or blocks for handling the button press.
This would save you a lot of code, and trouble.

How to detect touch on a sprite that is a child of another sprite

I searched and found some question like mine, but there's no answer right what I need.I want to do something like this in my app. When I touch in the content space of a shape, 4 red dots will appear, then allow to resize(transform) it like in photoshop when we do ctrl+T.
This is how it looks
I don't know how to detect whenever touch on a red dot and then transform it.
Anyone explain me how to do this or example code.
I want to make it in cocos2d or kobold2d
This is how I make my shape
DragSprite *sprite = [DragSprite spriteWithFile:#"SpriteBGAlpha1.png" rect:CGRectMake(point.x, point.y, 100, 100)];
sprite.position = point;
sprite.color = ccRED;
[shapeArray addObject:sprite];
[self addChild:sprite z:0 tag:1];
CCSprite *s = [CCSprite spriteWithFile:#"Shape-Icon_Elevation-Triangle.png"];
s.position = ccp(sprite.boundingBox.size.width/2, sprite.boundingBox.size.height/2);
[sprite addChild:s];
dotBlueArea1 = [CCSprite spriteWithFile:#"bluedotimage.png"];
dotBlueArea1.position = ccp(0, 0);
[shapeArray addObject:dotBlueArea1];
[sprite addChild:dotBlueArea1 z:10 tag:1];
try this one..
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
CGPoint convertedNodeSpacePoint = [aMainSpr convertToNodeSpace:location];
if (CGRectContainsPoint([child_Sprite boundingBox],convertedNodeSpacePoint))
{
NSLog(#"Touch");
}

Draw a Line Sprite Between Two Points made by Sprites in Cocos2d

I've been trying to draw a sprite line between 2 points made by sprites with mouse events on Xcode.
I have been following the steps given on a forum in this link:
cocos2d forums
But when i run the code, i get the line going all the way of the simulator. just like this.
snapshot1
The line should stop by the second mouse sprite generated code, but it doesn't and keeps going all the way.
My Scene is something like this.
My .h class
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "Constants.h"
#import "SceneManager.h"
#interface EscenaInfo : CCLayer{
CGPoint lastTouchPoint;
CCSprite * background;
}
#property (nonatomic, assign) BOOL iPad;
#end
My .mm
#import "EscenaInfo.h"
#implementation EscenaInfo
#synthesize iPad;
- (void)onBack: (id) sender {
/*
This is where you choose where clicking 'back' sends you.
*/
[SceneManager goMenuPrincipal];
}
- (void)addBackButton {
if (self.iPad) {
// Create a menu image button for iPad
CCMenuItemImage *goBack = [CCMenuItemImage itemFromNormalImage:#"Arrow-Normal-iPad.png"
selectedImage:#"Arrow-Selected-iPad.png"
target:self
selector:#selector(onBack:)];
// Add menu image to menu
CCMenu *back = [CCMenu menuWithItems: goBack, nil];
// position menu in the bottom left of the screen (0,0 starts bottom left)
back.position = ccp(64, 64);
// Add menu to this scene
[self addChild: back];
}
else {
// Create a menu image button for iPhone / iPod Touch
CCMenuItemImage *goBack = [CCMenuItemImage itemFromNormalImage:#"Arrow-Normal-iPhone.png"
selectedImage:#"Arrow-Selected-iPhone.png"
target:self
selector:#selector(onBack:)];
// Add menu image to menu
CCMenu *back = [CCMenu menuWithItems: goBack, nil];
// position menu in the bottom left of the screen (0,0 starts bottom left)
back.position = ccp(32, 32);
// Add menu to this scene
[self addChild: back];
}
}
- (id)init {
if( (self=[super init])) {
// Determine Screen Size
CGSize screenSize = [CCDirector sharedDirector].winSize;
//Boton en la Interfaz del iPad
self.iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
// Put a 'back' button in the scene
[self addBackButton];
///
self.isTouchEnabled = YES;
lastTouchPoint = ccp(-1.0f,-1.0f);
///
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB565];
background = [CCSprite spriteWithFile:#"background.png"];
background.anchorPoint = ccp(0,0);
[self addChild:background z:-1];
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_Default];
}
return self;
}
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if( touch ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
CCLOG(#"location(%f,%f)", location.x, location.y);
if( CGPointEqualToPoint(lastTouchPoint, ccp(-1.0f,-1.0f) ) )
{
lastTouchPoint = ccp(location.x, location.y);
CCSprite *circle = [CCSprite spriteWithFile:#"circle.png"];
[circle setPosition:lastTouchPoint];
[self addChild:circle];
CCLOG(#"initial touchpoint set. to (%f,%f)", lastTouchPoint.x, lastTouchPoint.y);
}
else {
CCLOG(#"lastTouchPoint is now(%f,%f), location is (%f,%f)", lastTouchPoint.x, lastTouchPoint.y, location.x, location.y);
CGPoint diff = ccpSub(location, lastTouchPoint);
float rads = atan2f( diff.y, diff.x);
float degs = -CC_RADIANS_TO_DEGREES(rads);
float dist = ccpDistance(lastTouchPoint, location);
CCSprite *line = [CCSprite spriteWithFile:#"line.png"];
[line setAnchorPoint:ccp(0.0f, 0.5f)];
[line setPosition:lastTouchPoint];
[line setScaleX:dist];
[line setRotation: degs];
[self addChild:line];
CCSprite *circle = [CCSprite spriteWithFile:#"circle.png"];
[circle setPosition:location];
[self addChild:circle];
// lastTouchPoint = ccp(location.x, location.y);
lastTouchPoint = ccp(-1.0f,-1.0f);
}
}
}
#end
Does anyone knows how to work this out? i have been trying lots of things but nothing has worked for me, or maybe point out my mistake. i would really appreciate this.
I've not run the code but it looks fairly straightforward. The cause of the problem lies in this section:
float dist = ccpDistance(lastTouchPoint, location);
CCSprite *line = [CCSprite spriteWithFile:#"line.png"];
[line setAnchorPoint:ccp(0.0f, 0.5f)];
[line setPosition:lastTouchPoint];
[line setScaleX:dist];
This code calculates the distance between the two touch points in points (pixels), creates a new sprite (that will become the line) and sets the anchor point to the right hand side, centred vertically. It positions this at the point of the last touch and then sets the scale of the sprite's width based on the distance calculated earlier. This scaling factor will ensure the sprite is 'long' enough to reach between the two points.
Your issue:
This isn't taking into account the initial dimensions of the image you are loading (line.png). If this isn't a 1x1 dimension png then the setScale is going to make the resulting sprite too large - the overrun you are experiencing.
The Solution
Make line.png a 1 x 1 pixel image. Your code will work perfectly, though you will have a very thin line that is not aesthetically pleasing.
Or, for best results, calculate the scale for the sprite by taking into account the width of line.png. This way the sprite can be more detailed and won't overrun.
Change thesetScaleX line to this:
[line setScaleX:dist / line.boundingBox.size.width];
Using Cocos2D v3.x this works:
in -(void)update:(CCTime)delta{} you do this:
[self.drawnode drawSegmentFrom:ccp(50,100) to:ccp(75, 25) radius:3 color:self.colorDraw];
The self.drawnode and self.colorDraw properties are initialized like this, maybe inside -(void)onEnter{} :
self.drawnode = [CCDrawNode node];
self.colorDraw = [CCColor colorWithCcColor3b:ccRED];
[self addChild:self.drawnode];
I think you can use core graphics here :
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context,4);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context,startPoint.x , startPoint.y);
CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
CGContextStrokePath(context);
}
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touchPoint = [touches anyObject];
startPoint = [touchPoint locationInView:self];
endPoint = [touchPoint locationInView:self];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
endPoint=[touch locationInView:self];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
endPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
I think this will help you.

Resources