I want to create a CALayer animation that gives sort of a 'flashy' effect. For that I'm trying to animate the 'opacity' property, but my problem is that I have no idea where to start and how to do it.
Here is a graphical explanation of the animation:
opacity
| ___
1 | | |
| | | * repeatCount
0 |___| |_ . . .
-------------------------> time
|______|
duration
The opacity starts at 0, then animates to 1, then to 0 again (this 0-to-1-to-0 animation takes a number of seconds equal to duration). Then this process is repeated 'repeatCount' times.
Here's some background on the code:
float duration = ...; // 0.2 secs, 1 sec, 3 secs, etc
int repeactCount = ...; // 1, 2, 5, 6, ect
CALayer* layer = ...; // I have a CALayer from another part of the code
layer.opacity = 0;
// Animation here
done = YES; // IN THE END of the animation set this ivar to yes
What is the best way to accomplish this? I have never used CALayers before, so this is also a good opportunity to learn how their animation system works. By the way, I have searched the docs and I understand how you add one or two simple animations, but I have no idea how to do this particular one.
The best way to accomplish this is to use an explicit animation (see guide) by creating an instance of CABasicAnimation and adding it to the layer.
The code would look something like this:
CABasicAnimation *flash = [CABasicAnimation animationWithKeyPath:#"opacity"];
flash.fromValue = [NSNumber numberWithFloat:0.0];
flash.toValue = [NSNumber numberWithFloat:1.0];
flash.duration = 1.0; // 1 second
flash.autoreverses = YES; // Back
flash.repeatCount = 3; // Or whatever
[layer addAnimation:flash forKey:#"flashAnimation"];
If you want to know when the animation is done you can set a delegate and implement the animationDidStop:finished: method, however it's best to use a completion block as that allows all the code to be in the same place. If you are writing for iOS 4 or OS X then you can use the excellent CAAnimationBlocks category to accomplish this.
Trojanfoe's answer is excellent. I just want to add that if you want more control over the "timeline" (how long should it take to fade out? how long should we then wait? then how long should it take to fade in? and so on) you're going to want to combine multiple CABasicAnimations into a CAAnimationGroup.
You might want to read my book chapter on this topic, the last part of which constitutes a tutorial on CAAnimation and its offspring:
http://www.apeth.com/iOSBook/ch17.html#_core_animation
Note that my discussion is directed at iOS; on Mac OS X, if that's where you are, the view/layer architecture is a little different, but what it says about CAAnimation is still correct.
Related
I was looking up the different abilities of Cocos2d, and don't quite understand what property is being modified here:
id rot = [CCPropertyAction actionWithDuration:2 key:#"rotation" from:0 to:-270];
id rot_back = [rot reverse];
id rot_seq = [CCSequence actions:rot, rot_back, nil];
Specifically, the "rotation" of what sprite? Rotation is a property of CCSprite, but I see no CCSprite here, so I'm very confused.
Furthermore, it seems to have existed in an earlier version of Cocos2d, so what has happened to it?
I've never seen CCPropertyAction used... I'll show you how I would do what you exampled.
// Create rotate action
CCActionRotateBy *rotateAction = [CCActionRotateBy actionWithDuration:2.0f angle:-270];
// Create a copy of that action and reverse it
id reverseAction = [[rotateAction copy] reverse];
// Create a sequence of actions in order
CCActionSequence *sequenceAction = [CCActionSequence actions:rotateAction,reverseAction, nil];
// mySprite will run actions in order
[mySprite runAction:sequenceAction];
In previous versions of cocos2d the actions were CCRotateBy and CCSequence, they just changed names for the sake of confusion.
All cocos2d actions will have CCAction... before them to classify them as actions.
I'm a little stumped by this weird occurrence:
I have a UIButton, which once tapped either sets a loop for an audio player, or resets it to 0 (no loop). Here is the method -
-(void)changeLoopValueForPlay:(int)tag toValue:(bool)value{
AVAudioPlayer *av = [self.playerArray objectAtIndex:tag];
if(value){
[av setNumberOfLoops:100];
[av prepareToPlay];
}
else{
[av setNumberOfLoops:0];
}
}
Now for some reason, the loop will only take effect after the player plays through the audio one time, meaning that the looping value doesn't take affect immediately, but the "numberOfLoops" value of the player is in fact set to 100 when I check its value before playing. I'm assuming this has something to do with the initialization or loading of the player, but I don't re-initialize it between those two plays (one without loop, the other with). Any idea why this is happening? If you want to see any other code please let me know.
This fixed the problem, however I feel as if this is a work-around instead of a direct solution. What I did is just create a new AVAudioPlayer with the numberOfLoops value set to whatever it is I wanted and replace that player with the existing player, instead of changing the value of the already existing player.
I workaround the issue by abandoning numberOfLoops altogether and doing my own logic instead.
First, set the delegate of the AVAudioPlayer:
self.audioPlayer.delegate = self;
Next, implement -audioPlayerDidFinishPlaying:successfully: of the delegate:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag
{
if(flag && <#(bool)i_want_to_repeat_playing#>)
{
[self.audioPlayer play];
}
}
Just replace <#(bool)i_want_to_repeat_playing#> with your desired logic, e.g., check if a counter has reached a certain threshold.
I have the following code:
CCProgressTimer *aTimer;
-(void) generateDungeon {
srand (time(NULL));
[self initDungeonArray];
int numRooms = RNDM(10,100);
for (int a=0; a< numRooms; a++) {
[self makeRandomRoom];
aTimer.percentage += 100/numRooms;
}
[self connectTheRooms];
[self placeStairs];
}
The problem is that during the loop the timer does not get updated on screen, then suddenly (after the loop finishes I think) fills up to nearly full. I don't understand why this is happening. I thought that when you change the percentage, the image would update.
Can anyone help me understand what I should be understanding?
Thanks.
Your image(in this case, you progress timer), updates on the screen in draw() and visit() methods. They are called every tick. Here you change it's value in one tick. So during previous drawing the timer value was 0 percent, and on the next drawing it's value will be 100
you make it in the single thread. so your timer will not be update in this case. to create updatable timer, you can try to move your long working code to the separate thread. then you will be able to update timer.
I have been working my way through the code pretty quickly with the help of the pre 0.99 migration guide -
http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:migrating_to_0_9
- but have got stuck with the conversion of AtlasSpriteManager, AtlasSprite to CCSPrite and other objects.
I am not clear enough on what is being done or how the current system works when it invovles a Board object as well. (the examples I found didnt include it). I do know I need to convert the AtlasSprite instances into CCSprite.
Edit: Board is an implementation of CCLayer
This is an example of the code I need to upgrade:
Board *board = [Board sharedBoard];
AtlasSpriteManager *backgroundManager = [AtlasSpriteManager spriteManagerWithFile:#"cloudBoard.png" capacity:200];
[board addChild:backgroundManager z:0 tag:BLOCK_KEY];
background = [AtlasSprite spriteWithRect:CGRectMake(0, 0, 480, 320) spriteManager:backgroundManager];
background.anchorPoint = ccp(0.0, 0.0);
background.position = ccp(0, 0);
[backgroundManager addChild:background];
thanks in advance!
I havne't finished the upgrade so I verify this with a sucessful runtime, however AtlasSpriteManager and AtlasSprite are both replaced with instances of CCSprite
Edit: yes this is the answer.
EDIT: Updating my own question with the answer I figured out months later. Short answer is no, MPMusicPlayerController forwards all calls to the main thread. But it uses a CPDistributedMessagingCenter to actually handle all operations, so one can very easily write a replacement controller that makes asynchronous calls (but it won't work for a sandboxed App Store app, as far as I know - and if it did, Apple would promptly reject it).
I'm making a simple app to control iPod playback, so I've been using an MPMusicPlayerController, which Apple states can only be used in the main thread. However, I've been experiencing some frustrating performance issues in the UI. Switching to the next or previous song is triggered by a swipe, which moves the entire display (of the song info) with it, and then updates the display for the next song when the switch is triggered. The trouble is that once the song has been changed, the UI hangs for up to a second while the song info is retrieved from the MPMusicPlayerController. I've tried most everything I can think of to optimize the code, but it seems to me that the only way to fix it is to move the MPMusicPlayerController code on to a background thread, despite Apple's instructions not to.
The code to update the display, for reference:
// Called when MPMusicPlayerControllerNowPlayingItemDidChangeNotification is received
- (void) nowPlayingDidChange {
if ([iPodMusicPlayer nowPlayingItem]) {
// Temp variables (necessary when updating the subview values in the background)
title = [[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyTitle];
artist = [[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyArtist];
album = [[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyAlbumTitle];
artwork = [[[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyArtwork] imageWithSize:CGSizeMake(VIEW_HEIGHT - (2*MARGINS), VIEW_HEIGHT - (2*MARGINS))];
length = [[[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration] doubleValue];
if (updateViewInBackground)
[self performSelectorInBackground:#selector(updateSongInfo) withObject:nil];
else
[self updateSongInfo];
}
else
[self setSongInfoAsDefault];
}
- (void) updateSongInfo {
// Subviews of the UIScrollView that has performance issues
songTitle.text = title;
songArtist.text = artist;
songAlbum.text = album;
songLength.text = [self formatSongLength:length];
if (!artwork) {
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2.00)
songArtwork.image = [UIImage imageWithContentsOfFile:
#"/System/Library/Frameworks/MediaPlayer.framework/noartplaceholder#2x.png"];
else
songArtwork.image = [UIImage imageWithContentsOfFile:
#"/System/Library/Frameworks/MediaPlayer.framework/noartplaceholder.png"];
}
else
songArtwork.image = artwork;
title = nil;
artist = nil;
album = nil;
artwork = nil;
length = 0.0;
}
Is there anything I'm missing here (ie. performance optimization when updating the UIScrollView subviews)? And if not, would it be such a bad idea to just use the MPMusicPlayerController in a background thread? I know that can lead to issues if something else is accessing the iPodMusicPlayer (shared instance of the iPod in MPMusicPlayerController), but are there any ways I could potentially work around that?
Also, this is a jailbreak tweak (a Notification Center widget), so I can make use of Apple's private frameworks if they would work better than, say, the MPMusicPlayerController class (which is fairly limited anyways) for my purposes. That also means, though, that my app will be running as a part of the SpringBoard process, so I want to be sure that my code is as safe and stable as possible (I experience 2 minute hangs whenever my code does something wrong, which I don't want happening when I release this). So if you have any suggestions, I'd really appreciate it! I can provide more code / info if necessary. Thanks!