I have a collection of six seat objects (UIViews with the alpha property set to 0) on my screen and I have player objects basically placed on top of them. The seats may or may not have a player on top of it. What I have right now is I've programmed the player's touchesMoved event so that when I drag a player on top of a seat object the seat's alpha property will go from 0 to 0.6. And then while still dragging the player, if I drag him off the seat the alpha property will go back to 0.
Instead, is there a built in UIView animation that could instead cause the alpha property to kind of fluctuate back and forth between .6 and .2? Kind of a throbbing effect? Would this require core animation or something more advanced?
I'm using the following in the Player's touchesMoved method to drag a player and to detect if it's above a seat:
UITouch *aTouch = [touches anyObject];
self.center = [aTouch locationInView:[self.superview]];
Seat *seat = [controller seatAtPoint:[aTouch locationInView:self.superview]];
if (seat) {
self.hoverSeat = seat;
seat.alpha = .6;
} else {
self.hoverSeat.alpha = 0;
}
The seatAtPoint method in my controller is as follows:
- (Seat *) seatAtPoint:(CGPoint)point {
NSMutableArray seats = [NSMutableArray arrayWithCapacity:6];
for (int i = 1; i <= 6; i++) {
Seat *aSeat = (Seat*)[self.view viewWithTag:i];
[seats addObject:aSeat];
}
for (Seat *seat in seats) {
if (CGRectContainsPoint([seat frame], point)) {
return seat;
}
}
return nil;
}
I use a hoverSeat ivar to hold the seat above which the player is hovering. And then if the seat returned is nil then it sets that seat's alpha to 0.
A bug I'm seeing with this code is if I move the player around the screen a little too quickly sometimes the alpha property won't go back to 0. Can anyone think of a more effective way to ensure that it goes back to 0?
Thank you for any suggestions.
I would look into using CoreAnimation; it's not that hard to use. Here's what it might look like for a fade:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.5];
seat.alpha = 1;
[UIView commitAnimations];
You can look into the callbacks that methods like these use to create some kind of pulsing animation ( Trigerring other animation after first ending Animation (Objetive-C) ) when combined with a condition you can probably set it up to loop forever. Also of note is that UIView Animations disable user input when they're running ( Infinitely looping animation ).
As for the bug you mentioned, if it's still happening, you could set up a timer that when fired, iterates through all the seats and checks for ones that aren't being displayed correctly. You might want to set this interval to be pretty infrequent so that it doesn't interrupt or impact the performance of your other animations.
Related
There are a lot of similar questions but they all differ from this one.
I have UIScrollView which I could both scroll and stop programmatically.
I scroll via the following code:
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [self.scrollView scrollRectToVisible:newPageRect animated:NO]; }];
And I don't know how to stop it at all. In all the cases it won't stop or will stop but it also jumps to newPageRect (for example in the case of removeAllAnimations).
Could you suggest how to stop it correctly? Should I possibly change my code for scrolling to another one?
I think this is something you best do yourself. It may take you a few hours to create a proper library to animate data but in the end it can be very rewarding.
A few components are needed:
A time bound animation should include either a CADispalyLink or a NSTimer. Create a public method such as animateWithDuration: which will start the timer, record a current date and set the target date. Insert a floating value as a property which should then be interpolated from 0 to 1 through date. Will most likely look something like that:
- (void)onTimer {
NSDate *currentTime = [NSDate date];
CGFloat interpolation = [currentTime timeIntervalSinceDate:self.startTime]/[self.targetTime timeIntervalSinceDate:self.startTime];
if(interpolation < .0f) { // this could happen if delay is implemented and start time may actually be larger then current
self.currentValue = .0f;
}
else if(interpolation > 1.0f) { // The animation has ended
self.currentValue = 1.0f;
[self.displayLink invalidate]; // stop the animation
// TODO: notify owner that the animation has ended
}
else {
self.currentValue = interpolation;
// TODO: notify owner of change made
}
}
As you can see from the comments you should have 2 more calls in this method which will notify the owner/listener to the changes of the animation. This may be achieved via delegates, blocks, invocations, target-selector pairs...
So at this point you have a floating value interpolating between 0 and 1 which can now be used to interpolate the rect you want to be visible. This is quite an easy method:
- (CGRect)interpolateRect:(CGRect)source to:(CGRect)target withScale:(CGFloat)scale
{
return CGRectMake(source.origin.x + (target.origin.x-source.origin.x)*scale,
source.origin.y + (target.origin.y-source.origin.y)*scale,
source.size.width + (target.size.width-source.size.width)*scale,
source.size.height + (target.size.height-source.size.height)*scale);
}
So now to put it all together it would look something like so:
- (void)animateVisibleRectTo:(CGRect)frame {
CGRect source = self.scrollView.visibleRect;
CGRect target = frame;
[self.animator animateWithDuration:.5 block:^(CGFloat scale, BOOL didFinish) {
CGRect interpolatedFrame = [Interpolator interpolateRect:source to:target withScale:scale];
[self.scrollView scrollRectToVisible:interpolatedFrame animated:NO];
}];
}
This can be a great system that can be used in very many systems when you want to animate something not animatable or simply have a better control over the animation. You may add the stop method which needs to invalidate the timer or display link and notify the owner.
What you need to look out for is not to create a retain cycle. If a class retains the animator object and the animator object retains the listener (the class) you will create a retain cycle.
Also just as a bonus you may very easily implement other properties of the animation such as delay by computing a larger start time. You can create any type of curve such as ease-in, ease-out by using an appropriate function for computing the currentValue for instance self.currentValue = pow(interpolation, 1.4) will be much like ease-in. A power of 1.0/1.4 would be a same version of ease-out.
Here's my setup: I want to programmatically...
Raise the built-in keyboard (by giving focus to a text field)
Programmatically animate a finger graphic moving over the keyboard.
Fake a tap on the keyboard and have the keyboard respond to that tap.
The use-case is to create a video showing "look, you can type, and this is what happens...", but with a stylized and partially transparent finger-image, rather than somebody's actual finger blocking the view.
Related: I can map the X/Y coordinates for my video easily enough, but bonus points if anyone can help me get the position of a given key on the keyboard, thus simplifying my writing of the routine fakeTypeText: #"Hello World!" to move the point of the finger to the correct location.
Thanks!
EDIT: Use case sample code...
- (void) fakeTypeText: (NSString*) text
{
int len = (int) text.length;
for (int ii = 0 ; ii < len ; ++ii)
{
char oneChar = [text characterAtIndex: ii];
// *** BONUS POINTS ***
CGPoint kbPoint = CGPointZero; // ?!?! Point on keyboard of the oneChar-key.
[UIView animateWithDuration: duration
animations: ^{
self.fingerView.center = kbPoint;
}
completion: ^(BOOL finished) {
// *** MAIN QUESTION ***
[self fakeTapEventOnKeyboard: oneChar]; // ?!?! How to do this?
}
];
}
}
EDIT: To clarify further, the behaviour I want is, when I call fakeTapEventOnKeyboard, I want iOS to behave as if the user had tapped that key. Specifically, the key should highlight (including the little popup-key graphic), make the key-tapped sound and type the key into the firstResponder field, if any.
I'm having trouble getting a repeatable background to work in my game menu.
The user can slide a finger across the screen to select a character to play.
I have a parallax effect working with various backgrounds as the characters slide into view.
Sample below.
- (void)didMoveToView:(SKView *)view
{
self.pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(dragScene:)];
self.pan.minimumNumberOfTouches = 1;
self.pan.delegate = self;
[self.view addGestureRecognizer:self.pan];
}
- (void)dragScene:(UIPanGestureRecognizer *)gesture
{
CGPoint trans = [gesture translationInView:self.view];
SKAction *moveSky = [SKAction moveByX:trans.x*0.03 y:0 duration:0];
[_skyBackground runAction:moveSky];
}
I would like to repeat the backgrounds. I know how to do this with automatically scrolling backgrounds but I can't seem to get it to work here. It needs to repeat in both directions, left and right.
Thanks for any help!
You can create two more background nodes - one to the left of your current background node and one to the right. Move them aswell any time you move your existing _skyBackground node.
Then, in the update method, check if any of the three nodes needs to be "shifted" - either to behind the other two or in front. You're basically swapping the three nodes' positions if needed.
-(void)update:(NSTimeInterval)currentTime {
//get the left background node (or if using an ivar just use _leftNode)
SKSpriteNode *leftNode = (SKSpriteNode*)[self childNodeWithName:#"leftNode"];
//my positioning might be off but you'll get the idea
if (leftNode.position.x < -leftNode.size.width*2)
{
leftNode.position = CGPointMake(leftNode.size.width, leftNode.position.y);
}
if (leftNode.position.x > leftNode.size.width*2)
{
leftNode.position = CGPointMake(-leftNode.size.width, leftNode.position.y);
}
//repeat the same for _skyBackground and _rightNode
}
You may need more than 3 images if there's a slight gap between images as they're shifted.
I've been developing a game in Cocos2D for about 3 years which utilizes a transparent background to show a UIView. The reason for this is to have the parallax background still run as Cocos2D does scene transitions.
I'm having a new issue that started when I updated to iOS 7. Slow down occurs in combination of these circumstances:
-ONLY if the parallax background's frame position has changed.
-If I destroy an enemy which emits small sprites and a particle effect.
So it's the combination of those two things and it only happens sometimes. The debug value of the frame rate does not dip when the slow down happens. If I load a new scene it goes back to normal. Sometimes when I destroy another enemy the slow down disappears as well.
I have code in my parallax UIView that runs just about every frame of in-gameplay. I summed down the issue to one line:
-(void)updateImagePosWithPos:(CGPoint)pos{ // in game
// create vel based on last currentPos minus new pos
CGPoint vel = CGPointMake(currentPos.x-pos.x, currentPos.y-pos.y);
// init variables tmpVel and tempTotalImages
CGPoint tmpVel = CGPointZero;
int tmpTotalImages = 0;
// create indexLayerArr
NSMutableArray *indexLayerArr = [NSMutableArray array];
// for every parallax layer, add the number of images horizontally minus 1 to indexLayerArr
for (int j=0; j<totalLayers; ++j){
[indexLayerArr addObject:[NSNumber numberWithInt:[[totalImagesArr objectAtIndex:j] intValue]-1]];
}
int i = 0;
for (UIImageView *imageView in self.subviews) {
CGRect tmpRect = CGRectZero;
NSMutableArray *tmpRectArr = [rectContainer objectAtIndex:imageView.tag];
float speed = 0.00;
tmpTotalImages = [[totalImagesArr objectAtIndex:imageView.tag] intValue];
speed = [[speedArr objectAtIndex:imageView.tag] floatValue];
tmpVel = CGPointMake(vel.x*speed, vel.y*speed);
i = [[indexLayerArr objectAtIndex:imageView.tag] intValue];
tmpRect = [[tmpRectArr objectAtIndex:i] CGRectValue];
if(tmpRect.origin.x - tmpVel.x > wins.width){
tmpRect.origin.x -= (tmpTotalImages)*tmpRect.size.width;
}
else if(tmpRect.origin.x - tmpVel.x < -tmpRect.size.width){
tmpRect.origin.x += (tmpTotalImages)*tmpRect.size.width;
}
tmpRect.origin.x -= tmpVel.x;
tmpRect.origin.y += tmpVel.y;
[tmpRectArr replaceObjectAtIndex:i withObject:[NSValue valueWithCGRect:tmpRect]];
imageView.frame = [[tmpRectArr objectAtIndex:i] CGRectValue]; // <-- slow down cause
i--;
[indexLayerArr replaceObjectAtIndex:imageView.tag withObject:[NSNumber numberWithInt:i]];
}
currentPos = CGPointMake(pos.x, pos.y);
}
See commented line imageView.frame = [[tmpRectArr objectAtIndex:i] CGRectValue];
So if I comment that line out, the problem will never happen. If I keep the line and as well as don't change the values of tempRect, the problem also won't happen.
It looks like there's an issue in iOS 7 in changing the UIImageView's frame position, but only sometimes. Just wondering what other alternatives could I use? Or am I doing something definitely wrong in iOS 7?
Not a solution to your problem but workarounds. I'll start with the one that's probably requires the most code changes.
You don't actually have to have a UIView in order to keep background with transitions. Instead if you implement the background entirely in cocos2d (as part of the scene), you can achieve the same effect if instead of replacing scenes you transition layers in and out. Scene transitions for the most part use the same actions that also work on nodes.
Implement the background using cocos2d nodes, and have one parent node acting as the container (ie "layer") of the background nodes. You can do one of two things with that node:
a. Edit CCDirectorIOS's code and add a reference to your background node. Update the node before all other nodes in the drawScene method, by calling visit on the background node just before [_runningScene visit].
b. When transitioning to a new scene, either remove the background node from the current scene and add it to the new scene, or create a copy of the background with all the same settings and add it to the new scene. Ensure the copy starts with the exact same state as the original. Though this won't work with most transitions due to the nature of their animation (ie move/flip/zoom).
If you need the background to animate while a transition is running, there's a simple trick. Schedule update on a non-CCNode object that has global lifetime (ie AppDelegate). Then manually send the update to all nodes that should continue to update their state during a transition, and only during a transition.
You can register updates on non-node objects like this:
[_director.scheduler scheduleUpdateForTarget:self priority:0 paused:NO];
This update method will be called even during scene transitions. Alternatively it should also be possible to continue updating nodes by changing their paused state and thus resuming scheduler and actions, either by overriding the paused property or by explicitly unpausing specific nodes when a transition occurs.
I'm having a problem with side scrolling in Cocos2d. What the situation is, is that i have a sprite that contains multiple other sprites know as actions. The user can swipe back and forth horizontally to scroll through the multiple actions. Whats happening now is that it is very jerky and seems to lag and not a smooth scroll but just very choppy. Not sure what the problem is, I've tried to change the time of the animation but that doesn't seem to work.
- (void)translateInventoryForSwipe:(int)xTranslationValue {
NSArray* tempArray = [NSArray arrayWithArray:self.slotsCenterCoordinates];
[self.slotsCenterCoordinates removeAllObjects];
for (NSNumber* i in tempArray) {
NSNumber* newXCoordinate = [NSNumber numberWithInt:[i intValue] + xTranslationValue];
[self.slotsCenterCoordinates addObject:newXCoordinate];
}
[self updatePositionOfActionsInInventory];
}
this method takes in the delta x of the two touches from the parent view. (current touch minus previous touch) This sets the centre coord of all the actions in the scrolling view.
- (void)updatePositionOfActionsInInventory {
for (int inventoryCounter = 0; inventoryCounter < self.inventorySize; inventoryCounter++) {
FFAction* action = [self.actions objectAtIndex:inventoryCounter];
if (action != self.actionBeingDragged)
[self placeAction:action atIndex:inventoryCounter];
}
self.tempAction = nil;
}
- (void)placeAction:(FFAction*)action atIndex:(int)index {
const float yCenterCoordinate = self.boundingBox.size.height/2;
NSNumber* xCenterCoordinate = [self.slotsCenterCoordinates objectAtIndex:index];
CGPoint centerPointForActionAtIndex = ccp([xCenterCoordinate floatValue], yCenterCoordinate);
CCAction* updatePositionAction = [CCMoveTo actionWithDuration:0.03f position:centerPointForActionAtIndex];
if ([action.view numberOfRunningActions] == 0 || self.tempAction == action) {
[action.view runAction:updatePositionAction];
[action.view released];
}
}
this part is from the parent sprite that handles the touch:
CGPoint currentTouch = [self convertTouchToNodeSpace:touch];
CGPoint previousTouch = [touch previousLocationInView:[touch view]];
int translationPoint = currentTouch.x - previousTouch.x;
[self.inventory translateInventoryForSwipe:translationPoint withPoint:currentTouch];
this then sets the action coordinate mimicking a scrolling effect. I'm not sure where its causing the jerky motion but if anyone has any help on the situation it would be awesome!
Assuming all of the complexity in your code is not required, there are several aspects to consider here, I'll go through them one by one.
First, memory allocation is expensive and a lot of it is done in every call of translateInventoryForSwipe:. A whole new NSArray is created and the self.slotsCenterCoordinates is repopulated. Instead, you should iterate the action sprites and reposition them one by one.
This brings us to the second aspect, which is the use of CCAction to move the sprites. A new CCAction is created for every sprite, again causing delay because of the memory allocation. The CCAction is created, even if it would not be used. Also, the use of actions might be the main cause of the lag as a new action won't be accepted until the previous has finished. A better way to do this would be to directly reposition the sprites by delta instead of assigning actions for repositioning. The action is not required to get smooth movement as the frequency of calls to translateInventoryForSwipe: will be high.
You should also consider using float to send the delta value to the method instead of int. The touch coordinates are floats and especially on retina devices this matters as the distance of two pixels is 0.5f.
Based on these aspects, here is a template of what a fixed method could look like. This is not tested, so there may be errors. Also, I assumed that action.view is the actual sprite, as the actions are assigned there.
- (void)translateInventoryForSwipe:(float)xTranslationValue {
for (FFAction *action in self.actions) {
if (action == self.actionBeingDragged)
continue;
// Position the items manually
float xCoordinate = action.view.position.x + xTranslationValue;
float yCoordinate = self.boundingBox.size.height/2;
action.view.position = ccp(xCoordinate, yCoordinate);
}
}