Decelerate Update of UiLabel - ios

I've spent a few hours tonight trying to get two UILabels I have that are already being refreshed as a gesture is recognized (based on the translation.y and velocity.y). In the
if (recognizer.state == UIGestureRecognizerStateEnded)
I want to animate a deceleration of the refresh of the UILabels after the gesture is complete. The UILabels are refreshed just by calling a
[self refreshLabels];
I've spent a lot of time tonight trying to do this with an infinite scrollView and tracker of the .contentOffset.y which failed miserably. I also tried to do a for and while loop in the above if statement with an animation block which also didn't work.
Does anyone have any suggestions/done this before?

How about a recursive call of of refreshLabels using performSelector:withObject:afterDelay:?
When the gesture ends, set a private counter and call refreshLabels. Within refreshLabels, calculate a simple deceleration curve using the counter as the "time taken" variable and use the resulting value as the delay for the next recursive call.
Example code:
int counter; // Set this value to 1 when the gesture completes
- (void)refreshLabels:(id)sender{
// Update the labels using whatever code you have
// This code will execute the recursive deceleration
if (counter > -1){
float somereasonablenumber = 0.1F;
float delaytime = (1.0-0.1)/counter; // deceleration = (finaltime-initialtime)/timetaken
counter++;
if (delaytime > somereasonablenumber){
[self performSelector:#selector(refreshLabels:) withObject:nil afterDelay:delaytime];
} else {
counter = -1;
}
}
}
You might need to play with the values I used for finaltime and initialtime to get the curve you want. Right now, this code would execute on a delay curve like:
0.9s
0.45s
0.3s
0.225s
0.18s
0.15s
0.128571429s
0.1125s

Related

Xamarin SpriteKit running action with key

I am using SpriteKit with Xamarin. I'm currently making a game similar to flappy bird, except the pipes can also 'chomp' down and kill you like that. So at any given time my pipe sprites can be running two actions, one for movement along the X axis (when the player is moving) and one for movement along the Y axis (closing the pipes). When the player stops moving along the X axis I want to stop the running action of the pipes that moves them horizontally, but still keep the chomping action going. I'm having trouble setting up a key for my action though. Currently, this is what I have.
public override void TouchesEnded (NSSet touches, UIEvent evt)
{
base.TouchesEnded (touches, evt);
//TODO: Optimize by removing action by key instead of all of them. Figure out how to do this and impliment.
//This needs to be done before refining chomp as it will flow into the chomp logic
//by not stopping a chomp mid-way because the player lifted their finger.
poletop.RemoveAllActions();
polebottom.RemoveAllActions();
pole2top.RemoveAllActions ();
pole2bottom.RemoveAllActions ();
background.RemoveAllActions ();
//restarting the chomping action if it was stopped before.
chomped_return (false);
chomped_return (true);
}
So I'm basically stopping all actions, then restarting only the chomp if it was running (gets restarted in chomped_return).
This isn't very efficient and also causes some lag in-game as it stops and starts.
Code for starting the action along the X axis (player movement). This is the SKAction that I want to have the key so I can stop it and it alone. resetpoles is the completion function.
SKAction movebottompole;
SKAction movetoppole;
movebottompole = SKAction.MoveToX (edgebottom.X, flTime);
movetoppole = SKAction.MoveToX (edgetop.X, flTime);
polebottom.RunAction (movebottompole, resetpoles);
poletop.RunAction(movetoppole, resetpoles);
Chomping down on the player is an instant teleport of the pipes, but here's the code that runs to start the action of the pipes returning to their original position along the Y axis. This is setting up the action that I don't want to stop until it has completed.
public void chomped_return(bool blFirstPole)
{
//define our two actions
SKAction topreturn;
SKAction botreturn;
//define our floats for the time calculation
float flTime = 0;
float flMoveSpeed = 750;
float flDistance = 0.0f;
if (blFirstPole == true)
{
flDistance = (float)polebottom.Position.Y;
}
else if (blFirstPole == false)
{
flDistance = (float)pole2bottom.Position.Y;
}
//calculate time based on distance and vector units/second desired.
flTime = flDistance / flMoveSpeed;
//setup our moveto actions and use the time calculated above. Start the action.
topreturn = SKAction.MoveToY (750.0f, flTime);
botreturn = SKAction.MoveToY (0.0f, flTime);
if (blFirstPole == true)
{
poletop.RunAction (topreturn);
polebottom.RunAction (botreturn);
}
else if (blFirstPole == false)
{
pole2top.RunAction (topreturn);
pole2bottom.RunAction (botreturn);
}
return;
}
The variable blFirstPole is used to determine whether or not we are running the action on the first set of poles or second set (as there can be two on screen at once)
Would really appreciate any assistance, if you need any more information please do let me know!
Thanks
You can use RemoveActionForKey to remove an action with an certain key.
https://developer.xamarin.com/api/member/MonoTouch.SpriteKit.SKNode.RemoveActionForKey/p/System.String/
polebottom.RunAction(movebottompole, "movebottompole");
// remove it
polebottom.RemoveActionForKey("movebottompole");
But now you don't have your completion handler anymore. You can solve it by combining these two actions into one Sequence.
var poleactionWithCompletion = SKAction.Sequence(movebottompole, restpoles);
polebottom.RunAction(poleactionWithCompletion, "movebottompole");
If you need this more often, you can implement an extension method like:
public static class SKNodeExtension
{
public static void RunAction(this SKNode node, SKAction action, SKAction completion, string key)
{
var sequence = SKAction.Sequence(action, completion);
node.RunAction(sequence, key);
}
}
and then run your action like:
polebottom.RunAction(movebottompole, restpoles, "movebottompole");

Alternative to `CACurrentMediaTime()` for scheduling events in SpriteKit

I'm relatively new to SpriteKit and am wondering what alternatives there are to using the CACurrentMediaTime() for scheduling events. For example, I may implement an algorithm that prevents a player from firing too many times by adding the last time they fired with some 'cool down' period, comparing it to the current media time:
BOOL canFire = self.lastFireInterval + self.coolPeriod < CACurrentMediaTime();
The problem I have run into is that if I decide to alter the speed of a node, or even the entire scene, this logic falls apart. For example, if I gave the player a speed-up power up, I could slow down all other nodes except for the player, but the timings would be messed up for enemies firing.
What other alternatives are there for CACurrentMediaTime() that factor in the speed of the node?
2 options come to mind regarding the timed to fire:
1) Create an ivar/property BOOL readyNextMove; for your player class. Then when your player shoots set the readyNextMove to false and add this code:
SKAction *wait = [SKAction waitForDuration:2.0];
SKAction *block0 = [SKAction runBlock:^{
readyNextMove = true;
}];
[self runAction:[SKAction sequence:#[wait, block0]]];
2) You can use the update:(CFTimeInterval)currentTime method to see how much time has elapsed from, for example you player shooting, and set readyNextMove accordingly.
It's a matter of taste. But for all games I keep an internal frame based clock which handles everything. I firmly believe this is the best way, but of course it depends on the scope of your project. This method is very rigid and guarantees continuity. If you use timers and your game lags, your continuity is thrown off (in some cases a shooter might shoot more times per frame than other parts of your game gets to move).
In aScene.h
long _currentTime = 0;
In aScene.m
-(void) update {
_currentTime++;
for(shooter * aShooter in self.shooters) {
[aShooter updateWithTime:_currentTime];
}
}
In your shooter.m
-(BOOL) tryShoot:(long)globalTime {
if(_lastShootingTime<globalTime-shootingCooldown) {
_lastShootingTime = globalTime;
[self fireBullet];
return YES;
}
return NO;
}
-(void) updateWithTime:(long)globalTime {
//...perform shooters action for that time frame
_lastUpdatedTime = globalTime;
}

Set A bool to true for two seconds then change back to false

As the question states, in spritekit I am trying to find a way to change my value of a bool from false, too true for x amount of seconds then change it back to false.
the logic is i have this var, isStunned, when my player comes in contact with x sprite i call it and my joystick is disabled since the player is "stunned"
if (controlButtonDown) { // If i pressed joystick
if (!isStunned) { // IF i am not stunned
//move player
When my player touches the glue, i set isStunned to yes and the joystick is disabled, however I am wondering of a way to only disable my joystick or not move my sprite for x amount of seconds. I know of SKAction and the wait sequence, but those are actions and I dont think they will apply here. Any help is appreciated.
Thank you
Since you ask specifically with a spritekit tag.
Don't use NSTimer or actions to work with time in a game. Instead use the update function. If you're running at 60fps that means the update function will be called 120 times for two seconds. Using the frames instead of seconds to keep your game updated will avoid the gameplay being changed by lag. Imagine for instance that the players game lags a little, in other words runs slower. 2 seconds is still 2 seconds regardless of game lag, so now he is affected less by the glue than a person who has no lag. So...
First off you should have a variable keeping track of in game time: int _time;
In every update cycle you add one to time: _time++;
You should have a variable keeping track of when the user touched the glue: int _glueTouchedAtTime;
When the user clicks glue you set: _glueTouchedAtTime = time;
You should have a constant defining how long the glue is active: #define GLUE_TIME 120;
When you initiate the game set _glueTouchedAtTime to: _glueTouchedAtTime = -GLUE_TIME; To prevent the glue from being on when (_time == 0)
Testing if the user has touched glue now works like this:
if(_glueTouchedAtTime+GLUE_TIME>time) {
// Glue is active
} else {
// Glue is not active
}
Different glues for different sprites
To have different glues for different sprites I would suggest first doing a general game object (GameObject) as a subclass of either SKNode or SKSpriteNode depending on your needs. Then create a subclass of GameObject called GameObjectGluable. This should have a property called: #property int gluedAtTime;
You glue the glueable game object by: aGameObjectGluable.gluedAtTime = time;
Now you can test:
if(aGameObjectGluable.gluedAtTime +GLUE_TIME>time) {
// Game Object is glued
} else {
// Game object is not glued
}
Set your value-of-interest to true and then fire a NSTimer after two seconds to set it back to false.
For an explanation of how you might use NSTimer, see this SO thread: How do I use NSTimer?
You can use SKAction also. The runBlock method let's you execute blocks of code as actions.
SKAction *wait = [SKAction waitForDuration:2.0];
SKAction *reenableJoystick = [SKAction runBlock:^{
joystick.userInteractionEnabled = TRUE;
}];
SKAction *sequence = [SKAction sequence:#[wait, reenableJoystick]];
[self runAction:sequence];

How do I calculate the speed of which a user drags an image?

I have an image that the user can drag to the right and it will spring back when the user releases it. I want to execute some code when a user drags it quickly and releases it. Now I have a very awkward requirement that the user can drag the image, then keep it still for any length of time (for example 5 seconds), then drag it quickly and release it. As long as the image is moving above a certain speed when it is released, it will execute the code. If it falls below the minimum speed, it executes some different code. So that means I can't calculate the length of time between the beginning of the gesture and the end and execute the code depending on the length of time. What can I do? I guess I somehow need to know the speed at which the image is moving in it's last 500 milliseconds before the gesture ends. However I've hit a brick wall figuring out how to do that. Any help would be greatly appreciated.
Can you please include an explanation and possible example code with your answer as that would be a great help.
If you get the start X,Y coordinates of when the image is dragged, and the X,Y coordinates for when the mouse is released, you can use pythagoras' theorm to calculate the distance between the two points: http://en.wikipedia.org/wiki/Pythagorean_theorem
Also, if you start a timer when the mouse is moved (and mouse button is down), and stop it in the mouseup event, you can calculate the speed using the time and distance (speed = distance / time)
edit following comments:
point delayedMousePos;
point previousMousePos;
bool secondDrag = false;
bool isStopped = false;
var timeFirstStopped;
var positionCount = 0;
array previousMousePositions[3];
// timer which monitors mouse position (set to an interval of say, 10ms)
function timerMonitorMousePos_Elapsed() {
point currentMousePos = getMousePos();
if (isStopped == false) {
if (positionCount >= 2) {
array_shift(previousMousePositions); // remove the first element of the array and move everything down to reindex numerical array to start counting from zero
positionCount = 2; // keep positionCount within array bounds
}
previousMousePositions[positionCount] = currentMousePos; // add the new position to the end of the 'stack'
positionCount++;
}
if (currentMousePos == previousMousePos) { // start check for stationary
isStopped = true;
if (timeFirstStopped == null) {
timeFirstStopped = NOW();
} else {
if (NOW() - timeFirstStopped >= 500) { // we have been stopped for at least 500ms (assumes time is counted in milliseconds)
secondDrag = true;
// previousMousePositions[0] = the mouse position 30ms before the mouse stopped
}
}
} else {
isStopped = false;
timeFirstStopped = null;
}
previousMousePos = currentMousePos;
}
I wouldn't use a timer. I would just save the starting date/time along with x,y position when the dragging starts.
When the dragging has ended, save the ending date/time and position. From those information, I can calculate the distance in pixel and duration in milliseconds.
After searching some more on the internet, I finally answered my own question.
I worked out what I needed to do:
My UIPanGestureRecognizer:
- (IBAction)handlePan3:(UIPanGestureRecognizer *)recognizer3
Get the velocity of the users finger moving across the screen:
CGPoint vel = [recognizer velocityInView:self.myView];
Then:
if (vel.x > /*value*/) {
// Your code
}
I was about to give up, but no! I got there in the end. Thanks for everyones help. I've upvoted one or two answers because they were helpful. bobnoble actually gave the suggestion to use velocityInView and I found this other stack overflow question which gave me the info I needed: iOS - Making sense of velocityInView on UIPanGesture

How to detect when pan gesture moves really quickly, rather than finely, to adjust control accordingly?

In my app I have the option for the user to pan upward to adjust a control, but it's a little slow when they pan up really quick, which I'd like to jump a lot more.
// If user is panning upwards or downwards, adjust WPM every 8 translations in either direction
if (translation.y<-8 || translation.y>8) {
// Reset translation so we can see when it exceeds 8 again
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
// Figure out direction, if pan down, decrease by 5, if up, increase by 5
int sign = (translation.y > 0) ? -1 : 1;
WPM = #([WPM intValue] + (sign * 5));
if ([WPM intValue] >= 200 && [WPM intValue] <= 1500) {
self.WPMLabel.text = [WPM stringValue];
self.wordsPerMinute = WPM;
[[NSUserDefaults standardUserDefaults] setObject:WPM forKey:#"WPM"];
}
}
How would I go about changing this to account for faster acceleration?
Use the velocity property on the gesture recognizer.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPanGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/instm/UIPanGestureRecognizer/velocityInView:
Speed involves time. So each time your code runs, you need to save in an instance variable what time it is on the event's timestamp. That way, the next time your code runs, you can compare both the translation change and the time elapsed to the previous change.
What I would do is experiment with saving three or four previous times and three or four previous locations in an array so that you can take a moving average and damp out speed variations.

Resources