I'm making a particle system for my game, which basically is smoke coming out from rockets in my game. All particles are in a ccArray called smoke.
ccArray *smoke = ccArrayNew(0);
I have a class called Smoke, which is a subclass of CCSprite, with the addition of an ivar called __opacity.
When I add a new smoke to the ccArray I do like this:
ccArrayAppendObject(smoke, [Smoke spriteWithFile: #"smoke.png"]);
[smoke->arr[smoke->num - 1] setupWithTouch: touch andOpacity: 255.0f];
[self addChild: smoke->arr[smoke->num - 1]];
Which doesn't lag at all, and is fast,
And this is how I handle the smoke every frame:
if(smoke->num > 0)
{
for(NSUInteger i = 0; i < smoke->num; i++)
{
Smoke *s = smoke->arr[i];
s.__opacity = s.__opacity - 255.0f * delta;
[s setOpacity: s.__opacity];
if(s.__opacity <= 0.0f)
{
[self removeChild: s cleanup: YES];
ccArrayFastRemoveObjectAtIndex(smoke, i);
}
}
}
When opacity is less than 0, we remove the smoke from the scene, and then
remove it from the array -- which is the part that slows the game down, removing it from the Array. It goes from 60 FPS to like 15-20 FPS, when there's like, 60 smoke particles on the scene.
Any ideas how I can speed this up?
Also, reason I'm using ccArray instead of NSMutableArray is because I read ccArray is faster.
removing object from the middle or the beginning of an array (any array) will recreate the array, and the operation is very slow (alloc+copy of members), if you have datastruct with many removes that not at the end you probably should use a linked-list
here some implementation i found in the internet (haven't tested it but it looks decent)
http://www.cocoadev.com/index.pl?DoublyLinkedList
Related
I've been working on a game, and at a point I didn't have issues removing the enemy game objects (right now they are subclassed from CCSprite and I know that's not the best way)
But I'm not sure what I changed to make it crash when the program attempts to removeChild from _targets after they have been added to targetsToDelete.
I tried moving things around, I just don't know how I am adding or editing the array while its being created... Any help or advice would be great!
And actually if you had any pointers on how best to create game enemies, do you subclass NSObject or CCNode? I heard to divide them into component classes but I had no clue what they meant.
//Projectile Target collision
-(void)update:(ccTime)dt {
for (spygot *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
//Collision Detection Player
CGRect playerRect2 = CGRectMake(
_controlledSprite.position.x - (_controlledSprite.contentSize.width/2),
_controlledSprite.position.y - (_controlledSprite.contentSize.height/2),
_controlledSprite.contentSize.width,
_controlledSprite.contentSize.height);
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (Projectile *projectile in _projectiles)
{
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
BOOL monsterHit = FALSE;
if (CGRectIntersectsRect(projectileRect, targetRect))
{
NSLog(#"hit");
target.mhp = target.mhp - 1;
monsterHit = TRUE;
if (target.mhp <= 0)
{
[targetsToDelete addObject:target];
}
}
for (spygot *target in targetsToDelete)
{
[self removeChild:target cleanup:YES];
[_targets removeObject:target];
}
if (monsterHit)
{
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (Projectile *projectile in projectilesToDelete)
{
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
It looks like the code that you've pasted is all from within a for loop iterating over _targets. How does the variable target get initialized?
Usually when I get this sort of error it's because I have the code in a block or am in some other way on a nebulous thread. How sure are you that this bit of code is not running more than once at the same time?
You could try wrapping it in the following:
dispatch_async(dispatch_get_main_queue(), ^{
// do everything here.
});
As for advice about using CCSprite for your game enemy objects, my advice is fix it when it becomes a problem. Are you seeing issues with it right now? Premature optimization is almost as bad as doing it wrong in the first place. You'll know better at the end of the project how you should have done it earlier. ;)
I guess you know that you cannot remove elements from the array while you iterate over it. That is why you have targetsToDelete array.
But it looks to me that you do remove targets to soon.
Try this:
finish iterating the main loop and finish collecting targets to the targetsToDelete array and only after main loop is done remove the targets.
I have an Array of CCSprites that being displayed all at once.
Every sprite has a movement path, a movement path is a random point on screen.
All the sprites are moving all at once to random points on screen.
What I want to do is to detect collision between the sprites and then change their movement path.
Is it possible?
Iterate through every CCSprite in your array (call it A), and for every iteration iterate again through every CCSprite in the array (excluding A itself of course) (call this one B). Now, use CGRectIntersectsRect along with boundingBox to find a collision between them. It goes something like this:
for (CCSprite *first in mySprites) {
for (CCSprite *second in mySprites) {
if (first != second) {
if (CGRectIntersectsRect([first boundingBox], [second boundingBox])) {
// COLLISION! Do something here.
}
}
}
}
Edit: But of course, it is possible that if two sprites collide, the "collision event" will occur twice (first from the point of view of sprite A, and then from the point of view of sprite B).
If you only want the collision event to trigger once every check, you will need to memorize the pairs so that you can ignore collisions that already did happen on that check.
There are countless ways you could check for that, but here's an example (updated code):
Edited again:
NSMutableArray *pairs = [[NSMutableArray alloc]init];
bool collision;
for (CCSprite *first in mySprites) {
for (CCSprite *second in mySprites) {
if (first != second) {
if (CGRectIntersectsRect([first boundingBox], [second boundingBox])) {
collision = NO;
// A collision has been found.
if ([pairs count] == 0) {
collision = YES;
}else{
for (NSArray *pair in pairs) {
if ([pair containsObject:first] && [pair containsObject:second]) {
// There is already a pair with those two objects! Ignore collision...
}else{
// There are no pairs with those two objects! Add to pairs...
[pairs addObject:[NSArray arrayWithObjects:first,second,nil]];
collision = YES;
}
}
}
if (collision) {
// PUT HERE YOUR COLLISION CODE.
}
}
}
}
}
[pairs release];
Have a look at this S.O. answers.
You can do simple collision detection using CGRectIntersectsRect and the node boundingBox. If you need more advanced features, have a look at a physics engine like chipmunk or Box2D.
Ray Wenderlich has written a good tutorial about using Box2D just for collision detection, if you're interested in going that way. http://www.raywenderlich.com/606/how-to-use-box2d-for-just-collision-detection-with-cocos2d-iphone
Check first that your sprites can be approximated by rectangles. If so then #Omega's answer was great. If they can't be, perhaps because they contain a lot of transparency or for some other reason, you might need to approximate your sprites with polys and work with those.
I am wondering if someone can explain me how to re-use animations in cocos2d? I was used to so-called usual way: I create a 'game object', load animations and after that simply call my animations like gameObj->idle(); or gameObj->walkToPoint();...
I understand that these helper methods should be defined by myself, and I also studied a lot of guides about CC2d and using animations in different ways, but these guides are too simple and don't shot real cases.
So at the moment I wrote method which loads animation from plist and png texture, adds this texture to parent layer and assigns first sprite to the 'Game Object' sprite. But I have some questions - I still can't find the way to play animations.
Situation is more difficult for me (but I know that this is usual case :) - animations should be assigned to 'actions' and there should be different types of actions. For example - idle action plays forever, death plays once and stays at the last frame and shoot action plays once and restores previous frame - idle's one.
To make things simple I'll show some basic code w\o complex checks and for..in loops (I wondering who decided store all frames in cc2d animation format as Array and load them with frame_%d, not as Dictionary with structure Animations->Idle->Frames Animations->Shoot->Frames, but this not main point :))
//These things are global
CCAnimation *idleAnim;
CCAction * idleAction; // idle animation should be played forever
CCAnimation *deathAnim;
CCAction * deathAction; // death animation should be played once and stop at last frame
CCAnimation *shootAnim;
CCAction * shootAction; // shoot animation should be played once and idle frame restored
// loading animations with simple for loops
- (id)init {
self = [super initWithColor:ccc4(255,255,255,255)];
if (self) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
#"player.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode
batchNodeWithFile:#"player.png"];
[self addChild:spriteSheet];
NSMutableArray *idleAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 20; ++i) {
[idleAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_idle01_%04i_Layer-%i.png", i-1, i]]];
}
idleAnim = [CCAnimation animationWithFrames:idleAnimFrames delay:0.1f];
idleAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:idleAnim restoreOriginalFrame:NO]];
NSMutableArray *deathAnimFrames = [NSMutableArray array];
for(int i = 0; i <= 30; ++i) {
[deathAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_death01_%04i_Layer-%i.png", i, i+1]]];
}
deathAnim = [CCAnimation animationWithFrames:deathAnimFrames delay:0.1f];
deathAction = [CCAnimate actionWithAnimation:deathAnim restoreOriginalFrame:NO];
NSMutableArray *shootAnimFrames = [NSMutableArray array];
for(int i = 0; i <= 19; ++i) {
[shootAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_idleshoot02_%04i_Layer-%i.png", i, i+1]]];
}
shootAnim = [CCAnimation animationWithFrames:shootAnimFrames delay:0.1f];
shootAction = [CCAnimate actionWithAnimation:shootAnim restoreOriginalFrame:YES];
self.player = [CCSprite spriteWithSpriteFrameName:#"hero_idle01_0000_Layer-1.png"];
self.player.position = ccp(20, self.size.height/2);
[self.player runAction:shootAction];
[spriteSheet addChild:self.player];
[self.player runAction:[CCRotateTo actionWithDuration:0.0f angle:45]];
[self schedule:#selector(gameLogic:) interval:1.0];
}
return self;
}
This code runs correctly (loads idle animation and plays it forever) but I can't find the way to switch between actions and animatinos nicely. I've seen approaches when for example when someone wan't shoot animation, he deletes original sprite takes first sprite from shoot, plays shoot, deleted shoot sprite, restores original sprite. But what if sprite was rotated? One should pass all the params from original sprite such as flip, rotation, scale, etc. back and forward between sprites? Usually Game Objects are inherited from CCSprite, so this method generally means that one should delete and restore main game object all the time? This is not clear for me.
I tried next things, but they didn't work for me, program just halts and nothing is written to debug (I guest it is because error happens in other thread, the one which is responsible for touch handling).
// somewhere after touch happened in [shootToPoint:(CGPoing)point] method
[idleAction stop];
[shootAction startWithTarget:self.player];
[self.player runAction:[CCSequence actions:
shootAction,
[CCCallFuncN actionWithTarget:self selector:#selector(shooted)],
nil]];
And in shooted method I call again something like this:
[shootActino stop]; // or [self.player stopAllActions] - doesn't matter I guess
[idleAction startWithTarget:self.player];
CCSprite *sprite = (Bullet *)sender; // remove bullet sprite from memory
[self removeChild:sprite cleanup:YES]; // remove bullet sprite from memory
// other routines...
So can someone explain me how to create game objects with helper methods to switch animations? Please don't send me to cc2d docs because for it is unclear how to solve this simple problem from cc2d docs or other tutorial on the web.
Since you are creating your instances with helper methods instead of alloc/initWith... they are getting autoreleased, that's why you get a crash.
For example your idle animation should be created like this:
NSMutableArray *idleAnimFrames = [[NSMutableArray alloc] init];
for(int i = 1; i <= 20; ++i) {
[idleAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_idle01_%04i_Layer-%i.png", i-1, i]]];
}
CCAnimation *idleAnimation = [[CCAnimation alloc] initWithFrames:idleAnimFrames delay:0.1f];
[idleAnimFrames release];
CCAnimate *idleAnimate = [[CCAnimate alloc] initWithAnimation:idleAnimation restoreOriginalFrame:NO];
[idleAnimation release];
idleAction = [[CCRepeatForever alloc] initWithAction:idleAnimate];
[idleAnimate release];
That way you can use this action multiple times until you release it (which you should do in your dealloc method, or somewhere else when you're done with it).
I am making a game in Cocos2d. Everything is okay, so far. I used Ray Wenderlich's tutorial to get collision detection to work. It works, but whenever an 'enemy' spawns where a bullet was deleted (because the bullet that was deleted hit a target, therefore, was deleted), the enemy is automatically deleted, too. I think it's because it doesn't remove the rect that was declared for the sprite. Note, it also can go through more than one enemy, even though the bullet is deleted. Any help is appreciated. Thanks!
EDIT:
I found out what the problem was. I had the shoot method set in a schedule:#selector method, with no set interval. That meant that it would fire bullets 60fps fast. So I was getting TWO bullets with ONE click. They were so close together, that it took me a while to notice it. I won't make that mistake again!!!
Are you using the following code? (from How To Make A Simple iPhone Game with Cocos2D Tutorial)
- (void)update:(ccTime)dt {
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
if (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
}
}
for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
if (targetsToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
whenever an 'enemy' spawns where a bullet was deleted, the enemy is automatically deleted, too.
It sounds that the bullet is removed from the layer, but it is not removed from _projectiles array.
[_projectiles removeObject:projectile];
Are you sure that this code works?
Rect is not a seperate entity from your bullet. Rect is the property associated with the bullet. As soon as your "bullet is deleted" the rect will no longer be valid.
What you should be looking at is your collision checking code.
You probably want to surround your bullet collision check code with a condition like:
if(bullet exists)
{
check for collision
}
Since you haven't posted code I could only post pseudo code here. Maybe if you post your collision checking code I could show you in more detail.
I'm making a game with cocos2d and I have a bunch of sprites that I would like to delete. For example I might have a bunch of characters on the screen but when my game is over I would like to clean them up. Right now I have created a special effect (particle system) as a distraction, but because it is transparent and does not cover all of the screen you can see through and watch the sprites disappear as I remove them from the layer.
Also because the instructions execute so fast to the user it appears as if the sprites disappear before the particle effect begins!
Any suggestions on my 2 problems? Thanks.
NSMutableArray *toRemove = [[NSMutableArray alloc] init]; // array of sprites that I collect to remove
spriteCount = 0;
if([self findAllSprites:parent forRemoval:toRemove] > 0){ // is there is at least one sprite to delete. If not then don't do anything
[self specialEffect]; // runs for maybe 3 seconds.
// how can I stall here so that the sprites aren't removed "instantaneously"?
for (Character* aCharacter in toRemove) {
[aCharacter.parent remove:aCharacter];
}
}
You can delay the removal action using performSelector:withObject:afterDelay:. For example:
NSMutableArray *toRemove = [[NSMutableArray alloc] init]; // array of sprites that I collect to remove
spriteCount = 0;
if([self findAllSprites:parent forRemoval:toRemove] > 0){ // is there is at least one sprite to delete. If not then don't do anything
[self specialEffect]; // runs for maybe 3 seconds.
[self performSelector:#selector(removeSprites:) withObject: toRemove afterDelay:1.0];
}
[toRemove release];
- (void) removeSprites: (NSArray*) toRemove
{
for (Character* aCharacter in toRemove) {
[aCharacter.parent remove:aCharacter];
}
}
Note that performSelector:withObject:afterDelay: will retain the toRemove object and keep it alive until after it calls removeSprites, so you don't have to do anything special with toRemove (except that you still need to release it as shown since you own it as well).
You need to do your 'special effect' in a thread, so that it runs alongside your sprite remove. Lookup NSThread for more information, but this will enable you to synchronize the two processes.