How can I have the same sprite in multiple locations dynamically? - objective-c

How can I have the same sprite in multiple locations dynamically? I have already seen the other question, but, you can only do that with three sprites. I want to have a dynamic number of sprites. My objective is that I am trying to make, instead of shooting only one bullet, I want it to shoot three or more. I have all of the math done, but, I need to draw the three sprites in a for-loop. Here is what I have so far.
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint pointOne = [touch locationInView:[touch view]];
CGSize size = [[CCDirector sharedDirector] winSize];
CGPoint position = turret.position;
CGFloat degrees = angleBetweenLinesInDegrees(position, pointOne);
turret.rotation = degrees;
pointOne.y = size.height-pointOne.y;
CCSprite *projectile = [CCSprite spriteWithFile:#"projectile.png"];
projectile.position = turret.position;
// Determine offset of location to projectile
int angle = angleBetweenLinesInDegrees(position, pointOne);
int startAngle = angle-15;
int shots = 3;
NSMutableArray *projectiles = [[NSMutableArray alloc] initWithCapacity:shots];
// Ok to add now - we've double checked position
for(int i = 0;i<shots;i++) {
[self addChild:projectile z:1];
int angleToShoot = angle;
int x = size.width;
int y = x*tan(angle);
CGPoint realDest = ccp(x,y);
projectile.tag = 2;
if (paused==0 ) {
[_projectiles addObject:projectile];
// Move projectile to actual endpoint
[projectile runAction:
[CCSequence actions:
[CCMoveTo actionWithDuration:1 position:realDest],
[CCCallBlockN actionWithBlock:^(CCNode *node) {
[_projectiles removeObject:node];
[node removeFromParentAndCleanup:YES];
}],
nil]];
}
}
}
This gives me the error: 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again'

you need to create 3 different sprite and add all 3 of them as a child.
usually for doing stuff like this is better to use a CCBatchNode (take a look to the cocos doc).
With a batchnode you get all the childs be drawn in 1 draw call with the only constrain that all the childs of the batchnode needs to have the texture on the same spriteSheet (or in your case if they have the same "filename")
for just 3 projectiles you wont have performance problems but its the correct way to design it, if you will need to have dozens of projectiles on screen without using a batchnode the game wont run smooth.
to recap:
create a ccbatchnode,
add the batchnode as a child of self (i suppose its ur layer or main node)
create 3 sprites and add them as a child of the batchnode

Related

Make CCSprite move towards other CCSprite

I know this has been asked a lot before, but I can't find the solution. I have a CCSprite on the screen, the player, that is steered with the accelerometer. Then on top of the screen other CCSprites are spawned every 2 seconds, the enemies. I want all the enemies to follow the player, if the player moves the player the enemies should change direction and go towards that CCSprite. This is my code this far:
- (void)spawnEnemies
{
monsterTxt = [[CCTexture2D alloc] initWithCGImage:[UIImage imageNamed:#"obj.png"].CGImage resolutionType:kCCResolutionUnknown];
monster = [CCSprite spriteWithTexture:monsterTxt];
...
//random spawn position etc.
CCMoveTo *movemonster = [CCMoveTo actionWithDuration:7.0 position:ccp(_rocket.boundingBox.origin.x, _rocket.boundingBox.origin.y)];
[monster runAction:[CCSequence actions:movemonster, nil]];
[_monsters addObject:monster]; //adds the sprite to a mutable array
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
...
//determines new position and move sprite there
[monster stopAllActions];
CCMoveTo *movemonster = [CCMoveTo actionWithDuration:7.0 position:ccp(_rocket.boundingBox.origin.x, _rocket.boundingBox.origin.y)];
[monster runAction:[CCSequence actions:movemonster, nil]];
}
Now when I start the game the sprites are going towards the player, but when the player moves the enemies doesn't update their destination, they just continue down and stops at the y-coordinate of the player. And after a while the app crashes. What am I doing wrong?
My guess is that the problem may be in that section of code that you did not post.
In v2.1 of Cocos2d, to receive accelerometer measurements in your layer:
Do this in your init or onEnterTransitionDidFinish call:
self.accelerometerEnabled = YES;
And overload the is method:
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
CCLOG(#"Acceleration (%f,%f,%f)",
acceleration.x,
acceleration.y,
acceleration.z);
// Do something meaningful...you probably want to send a notification to
// some other scene/layer/logic. It should cache that the acceleration value
// and then do something on an update(...) call with it. Then reset it so that
// it does not get used the next update cycle untila new value comes in (here).
}
It seems you have this...and you have indicated you are receiving them...so I'm a bit confused.
SO (SANITY CHECK TIME)...
I created an example project (here) which has a single player sprite being chased by multiple monster sprites. When the user touches the screen (I did not use the accelerometer), the player sprite changes location and the sprites "chase" him. I added some random offset to their target positions so they would not all cluster on top of the player.
Here is the code for (the most important parts of) the file, which is a modified version of the HellowWorldLayer that comes with a cocos2d v2.1 project (I created it from the template):
-(void)createPlayer
{
playerMoved = NO;
if(self.player != nil)
{
[self removeChild:player];
self.player = nil;
}
CGSize scrSize = [[CCDirector sharedDirector] winSize];
CCSprite* playerSprite = [CCSprite spriteWithFile:#"Icon.png"];
playerSprite.scale = 2.0;
playerSprite.position = ccp(scrSize.width/2,scrSize.height/2);
[self addChild:playerSprite];
self.player = playerSprite;
}
-(void)createMonsters
{
const int MAX_MONSTERS = 4;
if(self.monsters.count > 0)
{ // Get rid of them
for(CCSprite* sprite in monsters)
{
[self removeChild:sprite];
}
[self.monsters removeAllObjects];
}
CGSize scrSize = [[CCDirector sharedDirector] winSize];
for(int idx = 0; idx < MAX_MONSTERS; idx++)
{
CCSprite* sprite = [CCSprite spriteWithFile:#"Icon.png"];
float randomX = (arc4random()%100)/100.0;
float randomY = (arc4random()%100)/100.0;
sprite.scale = 1.0;
sprite.position = ccp(scrSize.width*randomX,scrSize.height*randomY);
[self addChild:sprite];
[monsters addObject:sprite];
}
}
-(void)update:(ccTime)delta
{
if(playerMoved)
{ // Modify all the actions on all the monsters.
CGPoint playerPos = player.position;
for(CCSprite* sprite in monsters)
{
float randomX = (1.0)*(arc4random()%50);
float randomY = (1.0)*(arc4random()%50);
CGPoint position = ccp(playerPos.x+randomX,playerPos.y+randomY);
[sprite stopAllActions];
[sprite runAction:[CCMoveTo actionWithDuration:3.0 position:position]];
}
playerMoved = NO;
}
}
I have a retained array of CCSprite* objects (monsters) and a CCSprite* for the player. I have a flag to tell me if the player has changed position (player moved). In the update loop, if the player has moved, stop all the actions on the monsters and update them.
On the screen it looks like this:
And then when I move the main sprite...
They follow it...
Was this helpful?

Setting the bounding box of a sprite in cocos2d

I have three sprites on my scene. I have to make a touch event wherein the player can drag one of the sprites. My problem is these sprites are very thin and whenever I try to drap one of them, the bounding box appear to be very big so even if I touch the empty space and drag, the sprite would move. Here's how I was trying to do it.
NSSet *allTouch = [event allTouches];
UITouch *touch = [[allTouch allObjects]objectAtIndex:0];
CGPoint loc = [touch locationInView:[touch view]];
loc = [[CCDirector sharedDirector]convertToGL:location];
//Swipe Detection - Beginning point
beginTouch = location;
for(int i = 0; i < [objArray count]; i++)
{
CCSprite *sprite = (CCSprite *)[objArray objectAtIndex:i];
CGRect spriteRect = CGRectMake(sprite.position.x - (sprite.contentSize.width/2), sprite.position.y - (sprite.contentSize.height/2), sprite.contentSize.width, sprite.contentSize.height);
if(CGRectContainsPoint(spriteRect, location))
{
//actions here
}
}
How do I set the bounding box to be exactly the size of the sprite?
You can do it like this:
CCSprite * sprite = [CCSprite spriteWithFile:#"sprite.png"];
CGRect boundingBox = sprite.boundingBox;
However if your image file contains transparent space around your sprite, that will also be part of the bounding box.

How to remove a sprite when it reaches a certain point or it is touched?

I am trying to make a game in cocos2d wherein I have sprites dropping from the top of the screen. Now, once the sprite is tapped, it should move back up. This works for me just fine but, there are some instances wherein the sprite would go back down after it goes off the screen. Also, my sprites don't disappear once they reach a certain y-axis position on the screen. Here's part of my code
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint loc = [[CCDirector sharedDirector]convertToGL:[touch locationInView:[touch view]]];
CGRect rect = CGRectMake(sprite.position.x - (sprite.contentSize.width/2), sprite.position.y - (sprite.contentSize.height/2), sprite.contentSize.width, sprite.contentSize.height);
if(CGRectContainsPoint(rect, loc))
{
[sprite runAction:[CCCallFuncN actionWithTarget:self selector:#selector(spriteCaught)]];
}}
-(void)spriteCaught
{
//currentPos is an integer to get the current position of the sprite
id moveUp = [CCMoveTo actionWithDuration:1 position:ccp(currentPos, 500)];
[sprite runAction:[CCSequence actions:moveUp, nil]];
if(sprite.position.y >= 480)
{
[self removeChild:sprite cleanup:YES];
}}
Also, I'm not sure if my syntax is correct but my conditional statement (The one that checks the position in the y-axis of the sprite) doesn't work either. How do I go about fixing this? Any help and suggestion is greatly appreciated
Instead of manual rect, use sprite's bounding box.
if(CGRectContainsPoint([sprite boundingBox], loc))
Also update spriteCaught function.
-(void)spriteCaught
{
CGSize s = [[CCDirector sharedDirector] winSize];
float dest_y = s.height+sprite.contentSize.height*0.5f; //assumed ur sprite's anchor y = 0.5
//currentPos is an integer to get the current position of the sprite
id moveUp = [CCMoveTo actionWithDuration:1 position:ccp(sprite.position, dest_y)];
id calBlokc = [CCCallBlockN actionWithBlock:^(CCNode *node)
{
//here node = sprite tht runs this action
[node removeFromParentAndCleanup:YES];
}];
id sequence = [CCSequence actions:moveUp, calBlokc, nil];
[sprite runAction:sequence];
}

Need CCRenderTexture to render faster ios

I'm making a drawing app, and I'm having the users draw with CCRenderTexture. It basically keeps rendering a picture of a black circle to simulate drawing. When I move my finger slowly, it works really well since the circles come together to form a line. However, when I move my finger quickly, it ends up just being a bunch of circles that aren't connected (http://postimage.org/image/wvj3w632n/). My question is how I get the render texture to render the image faster or have it fill in the blanks for me.
Also, I'm not completely sold on this method, but it's what I've found while looking around. Feel free to suggest whatever you think would be better. I was originally using ccdrawline but it really killed my performance. Thanks!
The gaps between start point and the end points need to be sorted out. I am pasting code that might help you to resolve the situation you showed in the link.
in .h file
CCRenderTexture *target;
CCSprite* brush;
in the init method of .m file
target = [[CCRenderTexture renderTextureWithWidth:size.width height:size.height] retain];
[target setPosition:ccp(size.width/2, size.height/2)];
[self addChild:target z:1];
brush = [[CCSprite spriteWithFile:#"brush_i3.png"] retain];
add the touches method I am showing the touchesMoved code.
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint start = [touch locationInView: [touch view]];
start = [[CCDirector sharedDirector] convertToGL: start];
CGPoint end = [touch previousLocationInView:[touch view]];
end = [[CCDirector sharedDirector] convertToGL:end];
printf("\n x= %f \t y= %f",start.x,start.y);
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[target begin];
[brush setColor:ccc3(0, 255, 0)];
brush.opacity = 5;
[brush visit];
[target end];
}
}
}
Hopefully it would work for you.
Its not that CCRenderTexture draws too slow its that the event only fires so often. You do need to fill in the gaps between the touch points you receive.
There is a great tutorial here about it which you may have already seen, http://www.learn-cocos2d.com/2011/12/how-to-use-ccrendertexture-motion-blur-screenshots-drawing-sketches/#sketching

Best way to save/load stroke data of the sample GLPaint app?

I noticed that by default the sample app GLPaint comes with a recorded data in binary and it loads up on start, it is what is used to display the "Shake me" text.
I'm creating a similar painting app and I was just wondering what is the best way to actually record these strokes and load them up next time.
Currently I tried saving the location of every vertex using Core Data, then upon start it reads and renders all the points again, however I found this too slow.
Is there a better/more efficient method of doing this? Can the entire viewBuffer be saved as binary and then loaded back up?
If you look at the Recording.data you will notice that each line is its own array. To capture the ink and play it back you need to have an array of arrays. For purposes of this demo - declare a mutable array - writRay
#synthesize writRay;
//init in code
writRay = [[NSMutableArray alloc]init];
Capture the ink
// Handles the continuation of a touch.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGRect bounds = [self bounds];
UITouch* touch = [[event touchesForView:self] anyObject];
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
if (firstTouch) {
firstTouch = NO;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
/******************* create a new array for this stroke's points **************/
[writRay addObject:[[NSMutableArray alloc]init]];
/***** add 1st point *********/
[[writRay objectAtIndex:[writRay count] -1]addObject:[NSValue valueWithCGPoint:previousLocation]];
} else {
location = [touch locationInView:self];
location.y = bounds.size.height - location.y;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
/********* add additional points *********/
[[writRay objectAtIndex:[writRay count] -1]addObject:[NSValue valueWithCGPoint:previousLocation]];
}
// Render the stroke
[self renderLineFromPoint:previousLocation toPoint:location];
}
Playback the ink.
- (void)playRay{
if(writRay != NULL){
for(int l = 0; l < [writRay count]; l++){
//replays my writRay -1 because of location point
for(int p = 0; p < [[writRay objectAtIndex:l]count] -1; p ++){
[self renderLineFromPoint:[[[writRay objectAtIndex:l]objectAtIndex:p]CGPointValue] toPoint:[[[writRay objectAtIndex:l]objectAtIndex:p + 1]CGPointValue]];
}
}
}
}
For best effect shake the screen to clear and call playRay from changeBrushColor in the AppController.