How to let a sprite call the sprite owner (layer) to remove the sprite when the sprite finishes its task? - objective-c

I have a custom sprite derived from CCNode, and I want the sprite to be removed from the layer after the sprite detecting a signal, what technique should I use?
Is it safe to do [self removeFromParentAndCleanup:YES] inside the sprite to kill itself? In such way I can do what I want, but it is safe?

Yes, it is generally safe to call removeFromParentAndCleanup from within the sprite object. When you do this, nothing particularly special happens to the sprite - the one thing that might cause problems is the fact that the self.parent will be nullified, so you should avoid calling on the sprite's parent after using removeFromParentAndCleanup.

Related

Accessing CCSprite From Different Scene

So basically what I am trying to do is when the player of my game completes a level (for example level 1), it switches scenes back to the level select scene, and swaps the sprite picture of level 1 to a different one (for example one that has a check mark over it). I can replace the scene but I don't know how to change the sprite in the new scene, specifically when the scene change occurs after the level is completed. So I am assuming I would use a singleton class, am I right? If so, how would I go about using it?
Singletons are ok, don't be afraid to use them. Many components of cocos2d are singletons.
I think what you need is some sort of structure that keeps track of the state of the game. (How many levels are completed/What should be the next level/etc). When your level select scene is loaded it should look up that 'state of the game' object (be it a singleton, plist, etc ) and displays itself accordingly.
I would stay away from passing information directly from one scene to another, this makes reordering them a headache later on.
First, let me make sure I understand the problem correctly.
You have a scene (A) with a sprite in it.
You transition to another scene (B) for the game play.
The game ends and you transition back to scene A.
When scene A redisplays, you want to change the image displayed by the sprite.
If I've got this right, then regardless of whether singletons are good or bad, you don't need one for this.
If, like me, you've created your sprite using a display frame from the CCSpriteFrameCache, then you can simply change the frame you want the sprite to use when "A" is redisplayed.
Some sample code demonstrating this can be seen in another question:
How to switch the image of a CCSprite
(Certainly, if I've got this right, then feel free to just dupe this)

Multiple threads to draw in NSView

In my code, I subclassed NSView and in its drawRect method, I am spawning three threads to perform the drawing.
-(void)drawRect:(NSRect)dirtyRect
{
[[self window] setAllowsConcurrentViewDrawing:YES];
[self setCanDrawConcurrently:YES];
[NSThread detachNewThreadSelector:#selector(DrawText) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawRectangle) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawGradient) toTarget:self withObject:nil];
//Wherease these functions DrawText, DrawRectangle and DrawGradient performs their task as suggested by name.
//In DrawText, DrawRectangle, and DrawGradient lockFocus and unlockFocus is being
// used for multithreaded drawing.
}
When I run the same program from Xcode, it is running fine. Output is shown below.
But when I run it from the outside, there is problem and output is shown below.
First, I would like to know is it right way to draw from a secondary thread? Or what is another way to draw from a secondary thread?
What is the reason behind this problem?
Ken Aspeslagh is somewhat incorrect about drawing from secondary threads (he is correct it is generally a bad idea). From what I can see of your code you don't have a good use case for drawing on a secondary thread. Can you explain why you want to do this?
you yourself have already discovered setCanDrawConcurrently: which explicitly talks of invoking drawRect: from a background thread. Note that the views window must have allowsConcurrentViewDrawing set to YES for this to work (it is the default).
Apples own Cocoa Drawing Guide has a section on drawing from secondary threads. I have highlighted some portions I think are relevant to you.
The Application Kit maintains a unique graphics context for each window and thread combination. Because each thread has its own graphics context object for a given window, it is possible to use secondary threads to draw to that window. There are some caveats, however.
During the normal update cycle for windows, all drawing requests are sent to your application’s main thread for processing. The normal update cycle happens when a user event triggers a change in your user interface. In this situation, you would call the setNeedsDisplay: or setNeedsDisplayInRect: method (or the display family of methods) from your application’s main thread to invalidate the portions of your view that require redrawing. You should not call these methods from any secondary threads.
If you want to update a window or view from a secondary thread, you must manually lock focus on the window or view and initiate drawing yourself. Locking focus configures the drawing environment for that window's graphics context. Once locked, you can configure the drawing environment, issue your drawing commands as usual, and then flush the contents of the graphics context to the window buffer.
In order to draw regularly on a secondary thread, you must notify the thread yourself. The simplest way to send regular notifications is using an NSTimer or NSAnimation object. For more information on how to animate content, see “Advanced Drawing Techniques.”
The Cocoa Threading Programming Guide also says this:
If you want to use a thread to draw to a view, bracket all drawing code between the lockFocusIfCanDraw and unlockFocus methods of NSView
Just an aside, GCD block invocation is probably a much nicer method for performing small sets of operations in the background than NSThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// you can put each of these calls in their own queue if you want
[self DrawText];
[self DrawRectangle];
[self DrawGradient];
});
However, that likely has nothing to do with your problem; I mention it only because I think it will serve you better to use GCD queues.
You should only be drawing to the screen from the main thread.
Edit: It is apparently really complicated so you're better off drawing to the screen from the main thread. ;)
If you need to render something that takes too much time to avoid blocking the main thread, consider using a thread to do the drawing to an offscreen context, and then copy that context to the screen on the main thread.
I read about NSGraphicsContext Restriction at Thread guide.
Here, I found the following line:
If you do any drawing from a secondary thread, you must flush your drawing calls manually. Cocoa does not automatically update views with content drawn from secondary threads, so you need to call the flushGraphics method of NSGraphicsContext when you finish your drawing. If your application draws content from the main thread only, you do not need to flush your drawing calls.
After calling flushGraphics, it works fine.

How do I properly use CCSpriteFrameCache and CCSpriteBatchNode?

I do not understand what I do exactly when I add a CCSpriteFrameCache or CCSpriteBatchNode to my cocos2d application. Can somebody please explain the following points (it would be helpful if you could explain a few; please write the corresponding letter in front of your answer according to which question you are answering):
[all questions imply the achievement of best performance and lowest memory-use]
a) Is it crucial to create spritesheets for every single layer ? (For example: Menu - own spritesheet, GameLayer - own spritesheet...)
b) Can somebody explain why I have to add sprites to the batch node, and what a batch node generally is ?
b1)So, why can't I just do something like:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"menusprites.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"menusprites.png"];
[self addChild:spriteSheet];
And then just add sprites to my layer by calling
CCSprite *mySprite = [CCSprite spriteWithSpriteFrameName:#""];
[self addChild:mySprite];
without adding them to the batch node ? Because from what I understand it works like this :
I add my spritesheet with all the sprites on it to the screen. My app then goes into the plist and looks for the coordinates of the sprite I want to display and then places it on the screen. So why should I call
[spriteSheet addChild:mySprite];
?
c) How do I then get rid of the spritesheet for memory purposes when I do not need it anymore ?
a) It is best to create as few spritesheets (CCSpriteBatchNodes) as is possible. Sprite batching reduces draw calls. Draw calls are expensive. Still, every batch node creates one draw call. So you want to use as few as possible because the ultimate goal is to keep draw calls as low as possible.
b) The CCSpriteBatchNode renders all of its children in one go, in one batched draw call. That's why you need to add sprites to the batch node so it can render them all together. Only sprites using the same texture as the batch node can be added to a batch node, because you can only batch draw from the same texture. Whenever the engine has to switch from one texture to another, it issues a new draw call.
b1) You can't do this because the batch node renders its children. If you add the sprites to any other node, each sprite draws itself, which means one additional draw call per sprite. And the sprite batch node has nothing to do.
c) The CCSpriteBatchNode is just a regular node. You can remove it from the scene like any other node. The texture and sprite frames are cached in the CCTextureCache and CCSpriteFrameCache singleton classes. If you want to remove the textures and sprite frames from memory, you have to do it through the cache classes.
a) no
b) batchNode increases your performance when you need to draw many sprites at the same time, in case of small number of sprites (10, 20. etc.) i dont think that you will notice any performance increasing. batchNode is much faster because opengl should draw only it to see all content. in other case opengl will draw all your objects separately. that is - if you will have 500, 600, 700 sprites, the draw() and visit() methods will be called for each one. if they all will be placed to the batchNode, it will be only one draw() call and one visit() call
c) you can purge cached data manually to force memory freeing by calling these methods:
[CCTextureCache purgeSharedTextureCache];
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
[CCAnimationCache purgeSharedAnimationCache];

How to use CCSpriteBatchNode properly?

I add my sprite frames to CCSpriteFrameCache. Then I create a CCSpriteBatchNode with my desired image file.
This is what I don't quite understand:
When I make a CCSprite, if I want to take advantage of the CCSpriteBatchNode, I need to initialize the CCSprite with [CCSprite spriteWithBatchNode: rect:]? But if that's the case, I don't see how am I taking advantage of CCSpriteFrameCache to get the frames, since now I would be manually making the rect.
So I guess I use [CCSprite spriteWithSpriteFrameName:] and then I add this sprite to the batch node. But I am still unsure.
You should use:
CCSprite *sp = [CCSprite spriteWithSpriteFrameName:#"monster.png"];
The .plist that you specified in the SpriteFrameCache will take care of the frames for you.
Then you create the sprite and add to the batch.
If you create the batchnode with a file called "myArt.png", you CAN ONLY add a sprite to it that is contained inside "myArt.png".
Hope it helps!
According to what I've learned of cocos2d. SpriteFrameCache and SpriteBatchNode have the same result but are used differently and can notice a slight performance difference if your game is very big...
CCSpriteFrameCache loads your frames according to when they are called by their named according to the plist file it was given. The atlas associated with the plist has to be added to the project as well or else the frames will be called but nothing will be found to be drawn. The Plist is like the address of where the image is located inside the image atlas.
The good part of CCSpriteFrameCache is that the code is neater, and smaller than CCSpriteBatchNode method, at the cost that for every call of that frame, it goes to that specific atlas and draws it.
CCSpriteBatchNode, on the other hand, loads the atlas and loads it in one draw call. This is efficient because it reduces the amount of times the draw has to be done per need in the game. The only difficulty here is that you need to do math for the rectangles of each sprite in the atlas. This is because lets say your atlas is of 2 actions of a character, the atlas image file has a size of 1024x1024, and each sprite has a size of 128x128. so you would do the math to get each rectangle for the whole jump action for example.(This is where .plist come in handy to avoid doing such math)
The code gets complicated as you can see but it will only do one call, making it performance-wise your best call.
Another way to use CCSpriteBatchNode is to have different static sprites and you would just do one draw call for those multiple static images or sprites.
If you need example code just ask, I would be more than happy to provide it.
Update: Adding Link for SpriteBatchNode and an Example of my own.
SpriteBatchNode:
Example using SpriteBatchNode with Ray Wenderlich
I believe in this guy, and I have learned alot of Cocos2d from his tutorials. I would suggest you to read other of his tutorials.
In a nutshell, CCSpriteBatchNode is the exact same process we did below with the CCSpriteFrameCache the ONLY difference and its that you add the Sprite Child Node to the CCSpriteBatchNode and not the Layer, BUT you do Add the CCSpriteBatchNode to the Layer.
This is the hard concept that new comers to Cocos2d get entangled at.
SpriteFrameCache:
The SpriteFrameCache I couldn't find a good example so here is one simple one.
//By doing this your sprites are now in the cache ready to be used
//by their names declared in the .plist file.
-(void) loadingSprites:(NSString*) plistName {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:plistName];
}
-(id)initGameLayer {
//CCSprite accepts CCSpriteFrame and your img is now ready to be displayed.
//However is still not drawn yet.
CCSprite * mySprite = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:framename];
//set a position if desired
//20 pixels to the right and 0 pixels to the top.
mySprite.position = CGPointMake(20,0);
//Now the Image has been drawn, making 1 draw call.
[self addChild:mySprite];
}
It is noteworthy to point out that CCSpriteBatchNode makes just 1 drawcall, HOWEVER all the sprites being added to the batchnode have to be part of the same SpriteAtlas.
And using SpriteFrameCache only its easier and simpler, but for every child added to the layer it means +1 draw call is being done.(This is the downside, performance)
So if you add 10 Sprites to the layer with SpriteFrameCache you will have 10 drawcalls.
However if you implement the SpriteBatchNode and add those 10 Sprites in the CCSpriteBatchNode instead and just add that CCSpriteBatchNode to the layer, you will have the same 10 sprites added but only ONE draw call will be done. Hence the Performance difference(for the best) will be significant in larger games.
Hope it helps, Cheers!

Fitting Cocoa Animation into MVC/OOP patterns

MVC/OOP design patterns say you don't set a property, per se, you ask an object to set its property. Similarly, in Cocoa you don't tell an object when to draw itself. Your object's code has detailed HOW it will draw itself so we trust the frameworks to decide when (for the most part) it should draw.
But, when it comes to animation in Cocoa (specifically Cocoa-Touch) it seems that we now must take control of when the object draws itself from within the objects view controller. I can't send a message to a UIView subclass asking it to change some value and then leave it alone knowing it will slowly (duration = X) animate itself to a new position, alpha, rotation, etc. depending on the property changes. Or can I?
Basically, I'm looking for a way to set the property and then walk away. Instead, it seems, I need to wrap the code that calls the object asking it to change its property with an animation block of some sort "[UIView beginAnimations:nil context:NULL]; ... [UIView commitAnimations];"
I'm ending up with lots and lots of animation blocks in my view controllers and none in my view objects...I guess I'm just looking for someone to verify that this is how things are done and I'm not overlooking something. I haven't gotten much farther than the UIView animations within Cocoa-Touch, so maybe that's my problem and it's time to dig deeper?!?
You are correct that UIView does not animate its property changes by default the way CALayer does, but I don't think this indicates a break in MVC. It is appropriate for a Controller to instruct a View in how it should transform. That is the role of a Controller class as surely as it is appropriate for the Controller to know the correct frame for the View and even manage layout. I agree that it's a little weird that you call -beginAnimations:context: on the UIView class rather than on an instance, but in practice it does actually work much better that way since you may want to animate many views together.
That said, if you had a UIView subclass that managed the layout of its subviews, there would be nothing wrong with allowing that UIView to manage the animation rather than relying on a UIViewController to do it. So this is something that could go either place, but in practice it generally goes in the Controller as you've discovered.
I am using "MVC" here in the typical Cocoa sense. You're correct that this might not be appropriate in a SmallTalk program, but then SmallTalk Controllers have a much more limited role (management of user input events). Cocoa significantly expands the role of Controllers in MVC and I think it's an improvement, even if it means there are now some functions that could go in either the Controller or the View (and this is one of them).