CocosDenshion music fade out - objective-c

I'm using cocos denshion for the music in my game.
I'm currently playing background music with the code:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:#"backSong.mp3"];
However, when the game ends, I need the background music to fade out gradually. How can I fade out the background music, is there a way to do this? Thanks in advance!
Additionally, is ObjectAL any better than CocosDenshion? If so, what are the differences/advantages?

Try this:
float currentVolume = [SimpleAudioEngine sharedEngine].backgroundMusicVolume;
id fadeOut = [CCActionTween actionWithDuration:1 key:#"backgroundMusicVolume" from:currentVolume to:0.0f];
[[[CCDirector sharedDirector] actionManager] addAction:fadeOut target:[SimpleAudioEngine sharedEngine] paused:NO];
Hope that helps!

The only way i found of doing that is to schedule a method for execution and change the volume setting accordingly, kind of as follows:
-(void) fadeOutBackgroundMusic{
if (!currentBackgroundMusic_) {
CCLOG(#"GESoundServicesProviderImpl<fadeOutBackgroundMusic> : No background music at this time, ignoring.");
return;
}
fadeOutActionTickerCount_=0;
[self schedule:#selector(tickMusicFadeOut:)];
}
-(BOOL) isPlayingBackgroundMusic{
return isPlayingBackgroundMusic_;
}
#pragma mark sequencing stuff
-(void) tickMusicFadeOut:(ccTime) dt{
static float fadeTime;
static float volume;
static float maxVolume;
fadeOutActionTickerCount_++;
if (1==fadeOutActionTickerCount_) {
isPerformingFadeOutAction_ =YES;
fadeTime=0.0f;
volume=0.0f;
maxVolume=audioSettings_.masterVolumeGain*audioSettings_.musicCeilingGain;
} else {
fadeTime+=dt;
volume=MAX(0.0f, maxVolume*(1.0 - fadeTime/K_MUSIC_FADE_TIME));
[self setMusicVolume:volume];
if (fadeTime>K_MUSIC_FADE_TIME) {
volume=0.0f; // in case we have a .000000231 type seting at that moment.
}
if (volume==0.0f) {
CCLOG(#"GESoundServicesProviderImpl<tickMusicFadeOut> : background music faded out in %f seconds.",fadeTime);
[self setMusicVolume:0.0f];
[sharedAudioEngine_ stopBackgroundMusic];
self.currentBackgroundMusic=nil;
isPlayingBackgroundMusic_=NO;
isPerformingFadeOutAction_=NO;
[self unschedule:#selector(tickMusicFadeOut:)];
}
}
}
this is a simplified (edited) sample from my sound services provider implementation class (not tested as shown here). The general idea is to schedule yourself a method that will gradually fade out the music over a period of time (here an app-wide constant, K_MUSIC_FADE_TIME).

Related

Completion Blocks Objective-C

I'm trying to write a completion handler in IOS with a block and am not sure exactly why it's not working.
This is what I have so far
typedef void(^myCompletion)(BOOL);
-(void) showAnswer {
[self showAnswer:^(BOOL finished) {
if (finished) {
LnbScene *scene = (LnbScene *)gameScene.lnbScene;
//once the tiles position have been updated then move to the next riddle
[scene nextRiddle];
}
}];
}
// This method should update the position of tiles on the scene
-(void) showAnswer:(myCompletion) compblock{
LnbScene *scene = (LnbScene *)gameScene.lnbScene;
NSArray *tilesArray = [scene.tilesBoundary children];
for (Tile *tile in tilesArray) {
if (tile.positionInAnswer != 17) {
[tile removeFromParent];
[scene.spacesBoundary addChild:tile];
CGPoint targetPoint = CGPointMake(tile.targetPoint.x, tile.targetPoint.y + 6);
tile.position = targetPoint;
}
}
compblock(YES);
}
And I am calling the showAnswer method from a Scene as follows:
GameViewController *gVC = (GameViewController *)self.window.rootViewController;
[gVC showAnswer];
As I step through the code I don't encounter any errors and the sequence proceeds as expected ie. The tile positions are changed and then the completion handler triggers to the nextRiddle method. Only problem is that none of the interface is updated. Is there something I am missing?
I've tested that the tile repositioned code works by taking out the completion block and I view it in the interface and get the desired results. So I'm pretty sure the problem lies in how I'm implementing the blocks. I've never written a block before so I'm really in the dark.
-(void) showAnswer {
LnbScene *scene = (LnbScene *)gameScene.lnbScene;
NSArray *tilesArray = [scene.tilesBoundary children];
for (Tile *tile in tilesArray) {
if (tile.positionInAnswer != 17) {
[tile removeFromParent];
[scene.spacesBoundary addChild:tile];
CGPoint targetPoint = CGPointMake(tile.targetPoint.x, tile.targetPoint.y + 6);
tile.position = targetPoint;
}
}
}
I think the problem seems to be a conceptual mistake - that the "flow" of code
in the -(void) showAnswer:(myCompletion) comp block method truthfully represents the flow of UI elements in time in the presentation layer.
However in practice it is not that straightforward. The UI layer is rendered with 60 Hz framerate. That means you see 60 (frames) screens per second and contents of each of those screens needs to be calculated in advance, processed, flattened, rendered.
That means 1 frame (screen) cycle/pass lasts approx. 16 milliseconds. If I remember correctly you as a programmer have 11 milliseconds to provide all the relevant code so that it can be processed into visual info. This chunk of code is processed all at once which is the answer to our problem.
Imagine if you had a method that would say
{
//some other UI code...
self.view.backgroundColor = [UIColor greenColor];
self.view.backgroundColor = [UIColor yellowColor];
//some other UI code...
}
Now imagine we are inside 1 such 16 millisecond pass.
This piece of code would be processed in advance before any rendering happens and the resultative background colour will be yellow. Why? Because in the cycle preparation phase this code is processed and the framework interprets the green colour as pointless because the value is immediately overwritten by a yellow one.
Thus instructions for the renderer will contain only the yellow background information.
Now back to your actual code. I think all your code is processed fast enough to fit into that 11 milisec window, and the problem is that you do not really wait for the swapping of tiles because there is that block with YES parameter as part of the method and that one already slides to whatever is next.
So the rendered does get instructions to simply move on the the next thing. And it does not animate.
Try putting this "tile swapping" into the "animation" block
[UIview animateWithDuration:....completion] method..and put your block into the completion block. Yes you will have a block in a block but thats fine.
[UIView animateWithDuration:0.4f
animations:^{
//tile swapping code
}
completion:^(BOOL finished)
{
complBlock(YES);
}];
I am not completely sure if it will work but let's try.
The UI relate method should call on main thread. U can try this:
[self showAnswer:^(BOOL finished) {
if (finished) {
LnbScene *scene = (LnbScene *)gameScene.lnbScene;
dispatch_async(dispatch_get_main_queue(), ^{
[scene nextRiddle];
});
}
}];

How to trick an OS X app into thinking the mouse is a finger?

I'm writing a Mac app that contains a collection view. This app is to be run on a large touchscreen display (55" EP series from Planar). Due to hardware limitation, the touchscreen doesn't send scroll events (or even any multitouch events). How can I go about tricking the app into thinking a "mousedown+drag" is the same as a "mousescroll"?
I got it working halfway by subclassing NSCollectionView and implementing my own NSPanGestureRecognizer handler in it. Unfortunately the result is clunky and doesn't have the feeling of a normal OS X scroll (i.e., the velocity effect at the end of a scroll, or scroll bounce at the ends of the content).
#implementation UCTouchScrollCollectionView
...
- (IBAction)showGestureForScrollGestureRecognizer:(NSPanGestureRecognizer *)recognizer
{
CGPoint location = [recognizer locationInView:self];
if (recognizer.state == NSGestureRecognizerStateBegan) {
touchStartPt = location;
startOrigin = [(NSClipView*)[self superview] documentVisibleRect].origin;
} else if (recognizer.state == NSGestureRecognizerStateEnded) {
/* Some notes here about a future feature: the Scroll Bounce
I don't want to have to reinvent the wheel here, but it
appears I already am. Crud.
1. when the touch ends, get the velocity in view
2. Using the velocity and a constant "deceleration" factor, you can determine
a. The time taken to decelerate to 0 velocity
b. the distance travelled in that time
3. If the final scroll point is out of bounds, update it.
4. set up an animation block to scroll the document to that point. Make sure it uses the proper easing to feel "natural".
5. make sure you retain a pointer or something to that animation so that a touch DURING the animation will cancel it (is this even possible?)
*/
[self.scrollDelegate.pointSmoother clearPoints];
refreshDelegateTriggered = NO;
} else if (recognizer.state == NSGestureRecognizerStateChanged) {
CGFloat dx = 0;
CGFloat dy = (startOrigin.y - self.scrollDelegate.scrollScaling * (location.y - touchStartPt.y));
NSPoint scrollPt = NSMakePoint(dx, dy);
[self.scrollDelegate.pointSmoother addPoint:scrollPt];
NSPoint smoothedPoint = [self.scrollDelegate.pointSmoother getSmoothedPoint];
[self scrollPoint:smoothedPoint];
CGFloat end = self.frame.size.height - self.superview.frame.size.height;
CGFloat threshold = self.superview.frame.size.height * kUCPullToRefreshScreenFactor;
if (smoothedPoint.y + threshold >= end &&
!refreshDelegateTriggered) {
NSLog(#"trigger pull to refresh");
refreshDelegateTriggered = YES;
[self.refreshDelegate scrollViewReachedBottom:self];
}
}
}
A note about this implementation: I put together scrollScaling and pointSmoother to try and improve the scroll UX. The touchscreen I'm using is IR-based and gets very jittery (especially when the sun is out).
In case it's relevant: I'm using Xcode 6 beta 6 (6A280e) on Yosemite beta (14A329r), and my build target is 10.10.
Thanks!
I managed to have some success using an NSPanGestureRecognizer and simulating the track-pad scroll wheel events. If you simulate them well you'll get the bounce from the NSScrollView 'for free'.
I don't have public code, but the best resource I found that explained what the NSScrollView expects is in the following unit test simulating a momentum scroll. (See mouseScrollByWithWheelAndMomentumPhases here).
https://github.com/WebKit/webkit/blob/master/LayoutTests/fast/scrolling/latching/scroll-iframe-in-overflow.html
The implementation of mouseScrollByWithWheelAndMomentumPhases gives some tips on how to synthesize the scroll events at a low level. One addition I found I needed was to actually set an incrementing timestamp in the event in order to get the scroll-view to play ball.
https://github.com/WebKit/webkit/blob/master/Tools/WebKitTestRunner/mac/EventSenderProxy.mm
Finally, in order to actually create the decaying velocity, I used a POPDecayAnimation and tweaked the velocity from the NSPanGestureRecognizer to feel similar. Its not perfect but it does stay true to NSScrollView's bounce.
I have a (dead) project on Github that does this with an NSTableView, so hopefully it will work well for an NSCollectionView.
Disclaimer: I wrote this while I was still learning GCD, so watch for retain cycles... I did not vet what I just posted for bugs. feel free to point any out :) I just tested this on Mac OS 10.9 and it does still work (originally written for 10.7 IIRC), not tested on 10.10.
This entire thing is a hack to be sure, it looks like it requires (seems to anyway) asynchronous UI manipulation (I think to prevent infinite recursion). There is probably a cleaner/better way and please share it when you discover it!
I havent touched this in months so I cant recall all the specifics, but the meat of it surely is in the NBBTableView code, which will paste snippets of.
first there is an NSAnimation subclass NBBScrollAnimation that handles the "rubber band" effect:
#implementation NBBScrollAnimation
#synthesize clipView;
#synthesize originPoint;
#synthesize targetPoint;
+ (NBBScrollAnimation*)scrollAnimationWithClipView:(NSClipView *)clipView
{
NBBScrollAnimation *animation = [[NBBScrollAnimation alloc] initWithDuration:0.6 animationCurve:NSAnimationEaseOut];
animation.clipView = clipView;
animation.originPoint = clipView.documentVisibleRect.origin;
animation.targetPoint = animation.originPoint;
return [animation autorelease];
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
typedef float (^MyAnimationCurveBlock)(float, float, float);
MyAnimationCurveBlock cubicEaseOut = ^ float (float t, float start, float end) {
t--;
return end*(t * t * t + 1) + start;
};
dispatch_sync(dispatch_get_main_queue(), ^{
NSPoint progressPoint = self.originPoint;
progressPoint.x += cubicEaseOut(progress, 0, self.targetPoint.x - self.originPoint.x);
progressPoint.y += cubicEaseOut(progress, 0, self.targetPoint.y - self.originPoint.y);
NSPoint constraint = [self.clipView constrainScrollPoint:progressPoint];
if (!NSEqualPoints(constraint, progressPoint)) {
// constraining the point and reassigning to target gives us the "rubber band" effect
self.targetPoint = constraint;
}
[self.clipView scrollToPoint:progressPoint];
[self.clipView.enclosingScrollView reflectScrolledClipView:self.clipView];
[self.clipView.enclosingScrollView displayIfNeeded];
});
}
#end
You should be able to use the animation on any control that has an NSClipView by setting it up like this _scrollAnimation = [[NBBScrollAnimation scrollAnimationWithClipView:(NSClipView*)[self superview]] retain];
The trick here is that the superview of an NSTableView is an NSClipView; I dont know about NSCollectionView, but I suspect that any scrollable control uses NSClipView.
Next here is how the NBBTableView subclass makes use of that animation though the mouse events:
- (void)mouseDown:(NSEvent *)theEvent
{
_scrollDelta = 0.0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (_scrollAnimation && _scrollAnimation.isAnimating) {
[_scrollAnimation stopAnimation];
}
});
}
- (void)mouseUp:(NSEvent *)theEvent
{
if (_scrollDelta) {
[super mouseUp:theEvent];
// reset the scroll animation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSClipView* cv = (NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([cv documentVisibleRect].origin.y - _scrollDelta));
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
[anim setCurrentProgress:0.0];
anim.targetPoint = newPoint;
[anim startAnimation];
});
} else {
[super mouseDown:theEvent];
}
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSClipView* clipView=(NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([clipView documentVisibleRect].origin.y - [theEvent deltaY]));
CGFloat limit = self.frame.size.height;
if (newPoint.y >= limit) {
newPoint.y = limit - 1.0;
} else if (newPoint.y <= limit * -1) {
newPoint.y = (limit * -1) + 1;
}
// do NOT constrain the point here. we want to "rubber band"
[clipView scrollToPoint:newPoint];
[[self enclosingScrollView] reflectScrolledClipView:clipView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
anim.originPoint = newPoint;
});
// because we have to animate asyncronously, we must save the target value to use later
// instead of setting it in the animation here
_scrollDelta = [theEvent deltaY] * 3.5;
}
- (BOOL)autoscroll:(NSEvent *)theEvent
{
return NO;
}
I think that autoscroll override is essential for good behavior.
The entire code is on my github page, and it contains several other "touch screen" emulation tidbits, if you are interested, such as a simulation for the iOS springboard arrangeable icons (complete with "wiggle" animation using NSButtons.
Hope this helps :)
Edit: It appears that constrainScrollPoint: is deprecated in OS X 10.9. However, It should fairly trivial to reimplement as a category or something. Maybe you can adapt a solution from this SO question.

Time based sprite movement when coming back from background

I'm pretty new working with SpriteKit. I have a working example of an game I want to develop. I have some time based movements of sprites, with these I mean:
SKSpriteNode * track = (SKSpriteNode *) node;
CGPoint trackVelocity = CGPointMake(0, -objectVelocity);
CGPoint amtToMove = CGPointMultiplyScalar(trackVelocity,_dt);
track.position = CGPointAdd(track.position, amtToMove);
where "_dt" is:
-(void)update:(CFTimeInterval)currentTime {
if (_lastUpdateTime)
{
_dt = currentTime - _lastUpdateTime;
}
else
{
_dt = 0;
}
_lastUpdateTime = currentTime;
}
The problem I'm having is that when the user goes to background and comes back to the app a long time later that _dt is HUGE, so all the sprites that are moved with the _dt variable are gone from screen and never come back... I can't find a way of setting this _dt to a correct value.
How can I achieve this?
Thanks a lot!!
I already have in the AppDelegate the following added:
- (void)applicationWillResignActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = NO;
}
And it pauses the game, but the _dt that is set in the update is still being changed to a HUGE number and all my sprites go out of screen.
Here is the output of when going to background and coming back:
NSLog(#"%f %f",_lastUpdateTime,_dt);
2014-06-30 08:34:00.988 TestingSpriteKit[26303:60b] 224490.130906 0.033162
2014-06-30 08:34:19.761 TestingSpriteKit[26303:60b] 224508.904119 18.773212
2014-06-30 08:34:19.804 TestingSpriteKit[26303:60b] 224508.947477 0.043359
You can set a maximum _dt value in your update method such as
if _dt > 1 {
_dt = 1.0 / 60.0
}
This is the logic you will see in the Adventure demo app from Apple. Look for kMinTimeInterval to see how they do it.
I think this is a wise solution as there could be additional scenarios, aside from coming back from background, that result in a large delta time.
Without going into too many options, it sounds like you need to set a BOOL and manage your _dt based on it.
so in applicationWillResignActive:(UIApplication *)application set the bool property in the scene to:
theMainScene.appWentIntoBackground = YES;
and then at the beginning of your update method, manage your time interval based on the bool. I don't know whether you want a default amount, or just to skip the entire gap, etc, but something like this maybe:
if (_appWentIntoBackground == YES) {
_appWentIntoBackground = NO;
_lastTime = _currentTime;
}
You should pause your SKScene and it's SKView when app goes to background.
When SKView is paused, update: method doesn't get called.

Fading CCLayerColor does not work

I would like to fade a CCLayerColor, which seems to be pretty straightforward, but it just does not work ... Here is what I do :
LoadingScreen.h
#interface LoadingScreen : CCLayerColor{
}
-(id)init;
-(void)setOpacity:(GLubyte)opacity;
#end
LoadingScreen.m
#implementation LoadingScreen
-(id)init{
if ((self=[super initWithColor:ccc4(66 , 66, 66, 255)])){
self.isTouchEnabled = NO;
}
return self;
}
-(void)setOpacity:(GLubyte)opacity{
for( CCNode *node in [self children] )
{
if( [node conformsToProtocol:#protocol(CCRGBAProtocol)] )
{
NSLog(#"conforms to protocol!");
[(id<CCRGBAProtocol>) node setOpacity: opacity];
}
}
}
#end
GameLayer.m
-(id)init{
timer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:#selector(checkIfLoaded) userInfo:nil repeats:YES];
}
-(void)checkIfLoaded{
NSLog(#"still doing stuff");
if(doneInitializing ==YES){
NSLog(#"done");
[timer invalidate];
NSLog(#"FADE NOW !!!!");
id fade = [CCFadeOut actionWithDuration:2.5];
[loadingLayer runAction:fade];
[self performSelector:#selector(removeLoadingLayer) withObject:nil afterDelay:3.5];
}
if(loadingScreenPushed == NO && doneInitializing == NO){
NSLog(#"adding Layer");
loadingScreenPushed = YES;
loadingLayer = [LoadingScreen node];
[self addChild:loadingLayer z:10];
}
}
As far as I remember, fade actions calculates on each tick new opacity according to the current opacity value. You replace standard CCLayerColor setOpacity method with new one. Try to add
[super setOpacity: opacity];
to your setOpacity: method.
ok...a few things:
1: why make a different class called LoadingScreen ...it seems a bit redundant, what it's best is this: declare a variable type CCColorLayer in GameLayer and that's it and just initialize it with.. loadingScreen=[CCColorLayer layerWithColor...etc]..it's better for optimization, and it's autoreleased.
2nd: replace
id fade = [CCFadeOut actionWithDuration:2.5];
[loadingLayer runAction:fade];
with: [loadingLayer runAction:[CCFadeOut actionWithDuration:2.5]]; (it's faster)
3rd: try this:
if(loadingScreenPushed == NO && doneInitializing == NO){
//stuff
}else if(doneInitializing ==YES){
//stuff
}
What i think you're doing there is checking if layer is added on the screen and the remove it...but you can't have the layer on the screen..because you add it much later...so yea..invert those and everything should work.
I've done a bit of research of my own on this for the same reason that the_critic asked the question in the first place and the results I get are surprising to say the least. I'm building for iPad iOS 8.4:
1) It seems that 'super setOpacity' on a subclass of a subclass of CALayer does NOT work! (I've attacked this from every direction imaginable).
2) GETting the opacity value of a subclass of a subclass of CALayer DOES return the correct value when the setOpacity call failed.
i.e:
#interface IntermediateLayerType : CALayer.....
#interface FinalLayerType : IntermediateLayerType....
In some method of FinalLayerType...
[self setOpacity:1.0f];
[super setOpacity:0.0f];
NSLog(#"The opacity set was: %f", [self opacity]);
Yields the answer: "The opacity set was: 1.0"
I think you've hit the same bug!
My workaround was as follows:
In the same 'some method'....
CALayer *myBaseInstance = (CALayer *)self;
[myBaseInstance setOpacity:0.0f];
This worked for me after it had cost me a day and a half of lost development time.
I hope this helps other iPad dev people. VV.

MPMoviePlayerViewController can't set current playback time

I am trying to set currentPlaybackTime property of the MPMoviePlayerController in MPMoviePlayerViewController to make it resume playing video (HLS stream) from the time, it was stopped when the app resigned active. Here's my code:
//the functinon that sets playback time
- (void)setCurrentPlayTime:(NSNumber *)time {
if (self.moviePlayer.currentPlaybackTime < [time floatValue] - 10.0) {
[self.moviePlayer setCurrentPlaybackTime:(NSTimeInterval)[time floatValue]];
}
}
//app did become active callback
- (void) applicationDidBecomeActiveNotification:(NSNotification*)notification {
if (!isnan(_curPlayTime) && _curPlayTime > 0.0) {
[self performSelector:#selector(setCurrentPlayTime:) withObject:[NSNumber numberWithFloat:_curPlayTime] afterDelay:0.1];
}
}
//player load state did change callback
-(void)playerLoadStateDidChange:(NSNotification *)notification {
MPMoviePlayerController *player = notification.object;
MPMovieLoadState loadState = player.loadState;
if (loadState & MPMovieLoadStatePlaythroughOK) {
if (!isnan(_curPlayTime) && _curPlayTime > 0.0) {
[self performSelector:#selector(setCurrentPlayTime:) withObject:[NSNumber numberWithFloat:_curPlayTime] afterDelay:0.1];
_curPlayTime = 0.0;
}
}
When I just tap Home button and then reopen the app, and also if I get incoming call but decline it, it works. But if I answer an incoming call, after I finish the call, playing starts from the 0.0 ignoring setCurrentPlaybackTime method call. Does anybody know, where's the problem and may be any example how it should be done to work correct?
Not sure if this is the issue but the selector you are searching for is setCurrentPlayTime not setCurrentPlay*back*Time.