Should the following two code blocks be equivalent? My intuition says yes, but they produce different behaviors.
The first code block works the way I expect, the second does not. I really want to use the second style, because it makes the actual code (some of which was omitted here) much shorter and easier on the eye.
if (expanding) {
radius += delta;
if (radius > maxRadius)
expanding = false;
} else {
radius -= delta;
if (radius < minRadius)
expanding = true;
}
versus
if (expanding && (radius += delta) > maxRadius) {
expanding = false;
} else if ((radius -= delta) < minRadius) {
expanding = true;
}
If expanding is true but radius + delta <= maxRadius, the first version increments radius by delta and is done; the second version increments radius by delta then fails the first conditional and then decrements radius by delta, leaving it where it started.
So: No, the two blocks should not be expected to behave the same way. Even if you added ! expanding && to the else conditional, the first version is much clearer, in my opinion.
How about something like this:
radius += expanding ? delta : -delta;
if (expanding && radius > maxRadius)
expanding = false;
if (!expanding && radius < minRadius)
expanding = true;
This separates the functions of incrementing the radius and toggling expansion, but still simplifies the code from its original verison.
Related
In my app, I have created a circular, continuous slidable CircularSlider (this one). It has a minimumValue of 1.0 and a maximumValue of 10. Now, I'd like my users to be able to just keep sliding in circles to increase an NSInteger used somewhere in the app.
I tried doing that by writing the code below.
- (void)sliderChanged:(UICircularSlider *)slider {
int val = (int)slider.value;
slider.value = val;
int delta = val - self.oldValue;
if (self.oldValue == self.circularSlider.maximumValue && val == 0) {
delta = 1;
}
self.number += delta;
self.oldValue = val;
}
This works, but really sketch. Sometimes the value will drop by 10, caused by the slider giving me value of 10 and right afterwards a value of 0. It also doesn't work if the users starts scrubbing backwards and numbers start decreasing. I was wondering if there's a better way to achieve the same thing. Any ideas?
I think a better approach is to first figure out what direction the user moved. This can be done by checking which direction has the closest distance between the new and the old position. Care must be taken to check for passing the border. Then it is a simple matter of adjusting the delta depending on direction.
My solution code assumes the the slider starts at zero, which I would recommend you to use instead of 1, since calculations are easier. If you really want to start at 1 it can be adjusted. I also have defined the maximum value as a constant SLIDER_MAX_VALUE, which you could change to a variable instead. Finally, I changed self.number to a CGFloat, so do the cast when using the number instead. This is important, otherwise you get rounding errors when sliding. If you really want an integer, use two variables, and assign the integer variable from the float.
#define SLIDER_MAX_VAL 10
- (void)sliderChanged:(UICircularSlider *)slider {
CGFloat delta = slider.value - self.oldValue;
if ((delta > 0 && delta < SLIDER_MAX_VAL / 2) ||
(delta < 0 && delta < -SLIDER_MAX_VAL / 2)) {
// Moving forward
if (delta < 0)
delta += SLIDER_MAX_VAL;
} else {
// Moving backward
if (delta > 0)
delta -= SLIDER_MAX_VAL;
}
self.number += delta; // Change to CGFloat
self.oldValue = slider.value;
}
I have an NSMutableArray in my game, in which the array stores "cloud" objects. When spawning the cloud, I iterate through the array and check whether there is a cloud that is nearby, if there is, then I do not spawn the cloud. Here is the code:
BOOL isCloudInRange = NO;
float distance;
do {
//Horizontal Position
isCloudInRange = NO;
if (self.sprite.physicsBody.velocity.dx > 0) {
cloud.position = CGPointMake(self.sprite.position.x + HW*16/5, 0);
}
else if (self.sprite.physicsBody.velocity.dx <0) {
cloud.position = CGPointMake(self.sprite.position.x-HW*16/5, 0);
}
else {
cloud.position = CGPointMake(self.sprite.position.x, 0);
}
//Vertical Position
int offset = arc4random() % (int) 2*self.frame.size.height;
offset -= (int) (self.frame.size.height);
if (self.sprite.physicsBody.velocity.dy > 0) {
cloud.position = CGPointMake(cloud.position.x, self.sprite.position.y + offset + self.sprite.physicsBody.velocity.dy);
}
else if (self.sprite.physicsBody.velocity.dy <0) {
cloud.position = CGPointMake(cloud.position.x, self.sprite.position.y - offset - self.sprite.physicsBody.velocity.dy);
}
else {
cloud.position = CGPointMake(cloud.position.x, self.sprite.position.y + 16*HW/5);
}
if (cloud.position.y <= 300) {
cloud.position = CGPointMake(cloud.position.x, 100 + arc4random() %200);
}
// THIS IS WHERE THE ERROR HAPPENS
for (SKNode *myNode in arrayOfClouds) {
float xPos = myNode.position.x;
float yPos = myNode.position.y;
distance = sqrt((cloud.position.x - xPos) * (cloud.position.x - xPos) + (cloud.position.y - yPos) * (cloud.position.y - yPos));
if (distance < 300.0f) {
NSLog(#"%f",distance);
isCloudInRange = YES;
}
}
} while (isCloudInRange);
If the bottom piece of code is changed to if (distance < 150.0f) everything works fine. If the distance is kept at 300.0f, however, in a couple seconds or runtime, the game starts iterating forever. Here is an example of a typical log file with this code:
![hola][1]
Click this link if above image doesn't appear (I don't know why it isn't): http://i.stack.imgur.com/qX8h7.png
The logged floats are the distances between the cloud and whatever cloud is nearby. None of these distances seem to match (I don't have a million clouds spawning every second, they're set to spawn every second or so), and since it freezes with these logs as soon as the game starts, I know there cannot be that many clouds. What is happening? Please help.. Thanks!
The main issue I can see here is the following:
You have a do..while loop running checking your cloud distance. Once a cloud is in range, you mark it as YES and re-run the loop. The cloud's X position is never changed in the loop which means it will never move out of range again (infinite loop).
Ideally this is a check that should happen once per game loop (remove the do while).
Also it will be a little more efficient if you put a break; in your for loop. Once a cloud has been found in range there is no need to check the others so you may as well end you loop here.
for (SKNode *myNode in arrayOfClouds) {
float xPos = myNode.position.x;
float yPos = myNode.position.y;
distance = sqrt((cloud.position.x - xPos) * (cloud.position.x - xPos) + (cloud.position.y - yPos) * (cloud.position.y - yPos));
if (distance < 300.0f) {
NSLog(#"%f",distance);
isCloudInRange = YES;
break; // <--drop out of the for each loop now
}
}
You say "When spawning the cloud, I iterate through the array and check whether there is a cloud that is nearby, if there is, then I do not spawn the cloud" but to me it looks like your doing the exact opposite. If a cloud is in range (<300) you set isCloudInRange to yes and repeat. Once there are enough clouds it always finds a cloud in range it should loop indefinitely. The more clouds you spawn the harder and harder this is to every get out of the loop ( noting you set it to no at top)
If you are moving clouds and checking to create them on the same thread ( same run loop of code or function calls that are synchronous), you can try moving this code to a background thread, using dispatch_asynch(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), your code block here); and see if that helps.
Info on how to set up concurrency with dispatch_asynch is here:
https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ConcurrencyProgrammingGuide.pdf
and blocks are explained:
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW1
I'm making an app that (among other things) displays a simplified compass image that rotates according to the device's rotation. The problem is that simply doing this:
float heading = -1.0f * M_PI * trueHeading / 180.0f; //trueHeading is always between 0 and 359, never 360
self.compassNeedle.transform = CGAffineTransformMakeRotation(heading);
inside CLLocationManager's didUpdateHeading method makes the animation ugly and choppy.
I have already used Instruments to find out whether its simply my app not being able to render at more than 30-48 fps, but that's not the case.
How can I smooth out the image view's rotation so that it's more like Apple's own Compass app?
Instead of using the current instant value, try using the average of the last N values for the true heading. The value may be jumping around a lot in a single instant but settle down "in the average".
Assuming you have a member variable storedReadings which is an NSMutableArray:
-(void)addReading(float):newReading
{
[storedReadings addObject:[NSNumber numberWithFloat:newReading]];
while([storedReadings count] > MAX_READINGS)
{
[storedReadings removeObjectAtIndex:0];
}
}
then when you need the average value (timer update?)
-(float)calcReading
{
float result = 0.0f;
if([storedReadings count] > 0)
{
foreach(NSNumber* reading in storedReadings)
{
result += [reading floatValue];
}
result /= [storedReadings count];
}
return result;
}
You get to pick MAX_READINGS a priori.
NEXT LEVEL(S) UP
If the readings are not jumping around so much but the animation is still choppy, you probably need to do something like a "smooth" rotation. At any given time, you have the current angle you are displaying, theta (store this in your class, start it out at 0). You also have your target angle, call it target. This is the value you get from the smoothed calcReading function. The error is defined as the difference between the two:
error = target-theta;
Set up a timer callback with a period of something like 0.05 seconds (20x per second). What you want to do is adjust theta so that the error is driven towards 0. You can do this in a couple of ways:
thetaNext += kProp * (target - theta); //This is proportional feedback.
thetaNext += kStep * sign(target-theta); // This moves theta a fixed amount each update. sign(x) = +1 if x >= 0 and -1 if x < 0.
The first solution will cause the rotation to change sharply the further it is from the target. It will also probably oscillate a little bit as it swings past the "zero" point. Bigger values of kProp will yield faster response but also more oscillation. Some tuning will be required.
The second solution will be much easier to control...it just "ticks" the compass needle around each time. You can set kStep to something like 1/4 degree, which gives you a "speed" of rotation of about (1/4 deg/update) * (20 updates/seconds) = 5 degrees per second. This is a bit slow, but you can see the math and change kStep to suit your needs. Note that you may to "band" the "error" value so that no action is taken if the error < kStep (or something like that). This prevents your compass from shifting when the angle is really close to the target. You can change kStep when the error is small so that it "slides" into the ending position (i.e. kStep is smaller when the error is small).
For dealing with Angle Issues (wrap around), I "normalize" the angle so it is always within -Pi/Pi. I don't guarantee this is the perfect way to do it, but it seems to get the job done:
// Takes an angle greater than +/- M_PI and converts it back
// to +/- M_PI. Useful in Box2D where angles continuously
// increase/decrease.
static inline float32 AdjustAngle(float32 angleRads)
{
if(angleRads > M_PI)
{
while(angleRads > M_PI)
{
angleRads -= 2*M_PI;
}
}
else if(angleRads < -M_PI)
{
while(angleRads < -M_PI)
{
angleRads += 2*M_PI;
}
}
return angleRads;
}
By doing it this way, -pi is the angle you reach from going in either direction as you continue to rotate left/right. That is to say, there is not a discontinuity in the number going from say 0 to 359 degrees.
SO PUTTING THIS ALL TOGETHER
static inline float Sign(float value)
{
if(value >= 0)
return 1.0f;
return -1.0f;
}
//#define ROTATION_OPTION_1
//#define ROTATION_OPTION_2
#define ROTATION_OPTION_3
-(void)updateArrow
{
// Calculate the angle to the player
CGPoint toPlayer = ccpSub(self.player.position,self.arrow.position);
// Calculate the angle of this...Note there are some inversions
// and the actual image is rotated 90 degrees so I had to offset it
// a bit.
float angleToPlayerRads = -atan2f(toPlayer.y, toPlayer.x);
angleToPlayerRads = AdjustAngle(angleToPlayerRads);
// This is the angle we "wish" the arrow would be pointing.
float targetAngle = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
float errorAngle = targetAngle-self.arrow.rotation;
CCLOG(#"Error Angle = %f",errorAngle);
#ifdef ROTATION_OPTION_1
// In this option, we just set the angle of the rotated sprite directly.
self.arrow.rotation = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
#endif
#ifdef ROTATION_OPTION_2
// In this option, we apply proportional feedback to the angle
// difference.
const float kProp = 0.05f;
self.arrow.rotation += kProp * (errorAngle);
#endif
#ifdef ROTATION_OPTION_3
// The step to take each update in degrees.
const float kStep = 4.0f;
// NOTE: Without the "if(fabs(...)) check, the angle
// can "dither" around the zero point when it is very close.
if(fabs(errorAngle) > kStep)
{
self.arrow.rotation += Sign(errorAngle)*kStep;
}
#endif
}
I put this code into a demo program I had written for Cocos2d. It shows a character (big box) being chased by some monsters (smaller boxes) and has an arrow in the center that always points towards the character. The updateArrow call is made on a timer tick (the update(dt) function) regularly. The player's position on the screen is set by the user tapping on the screen and the angle is based on the vector from the arrow to the player. In the function, I show all three options for setting the angle of the arrow:
Option 1
Just set it based on where the player is (i.e. just set it).
Option 2
Use proportional feedback to adjust the arrow's angle each time step.
Option 3
Step the angle of the arrow each timestep a little bit if the error angle is more than the step size.
Here is a picture showing roughly what it looks like:
And, all the code is available here on github. Just look in the HelloWorldLayer.m file.
Was this helpful?
Im using a technique to control a sprite by rotating left/right and then accelerating forward. I have 2 questions regarding it. (The code it pasted together from different classes due to polymorphism. If it doesn't make sense, let me know. The movement works well and the off screen detection as well.)
When player moves off screen i call the Bounce method. I want the player not to be able to move off screen but to change direction and go back. This works on top and bottom but left and right edge very seldom. Mostly it does a wierd bounce and leaves the screen.
I would like to modify the accelerate algorithm so that i can set a max speed AND a acceleration speed. Atm the TangentalVelocity does both.
float TangentalVelocity = 8f;
//Called when up arrow is down
private void Accelerate()
{
Velocity.X = (float)Math.Cos(Rotation) * TangentalVelocity;
Velocity.Y = (float)Math.Sin(Rotation) * TangentalVelocity;
}
//Called once per update
private void Deccelerate()
{
Velocity.X = Velocity.X -= Friction * Velocity.X;
Velocity.Y = Velocity.Y -= Friction * Velocity.Y;
}
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1;
SoundManager.Vulture.Play();
}
//screen edge detection
public void CheckForOutOfScreen()
{
//Check if ABOVE screen
if (Position.Y - Origin.Y / 2 < GameEngine.Viewport.Y) { OnExitScreen(); }
else
//Check if BELOW screen
if (Position.Y + Origin.Y / 2 > GameEngine.Viewport.Height) { OnExitScreen(); }
else
//Check if RIGHT of screen
if (this.Position.X + Origin.X / 2 > GameEngine.Viewport.Width) { OnExitScreen(); }
else
//Check if LEFT of screen
if (this.Position.X - Origin.X / 2 < GameEngine.Viewport.X) { OnExitScreen(); }
else
{
if (OnScreen == false)
OnScreen = true;
}
}
virtual public void OnExitScreen()
{
OnScreen = false;
Bounce();
}
Let's see if I understood correctly. First, you rotate your sprite. After that, you accelerate it forward. In that case:
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1; //I THINK THIS IS THE PROBLEM
SoundManager.Vulture.Play();
}
Let's suposse your sprite has no rotation when it looks up. In that case, if it's looking right it has rotated 90º, and its speed is v = (x, 0), with x > 0. When it goes out of the screen, its rotation becomes -90º and the speed v = (-x, 0). BUT you're pressing the up key and Accelerate method is called so immediately the speed becomes v = (x, 0) again. The sprite goes out of the screen again, changes its velocity to v = (-x, 0), etc. That produces the weird bounce.
I would try doing this:
private void Bounce()
{
Rotation = Rotation * -1;
SoundManager.Vulture.Play();
}
and check if it works also up and bottom. I think it will work. If not, use two different Bounce methods, one for top/bottom and another one for left/right.
Your second question... It's a bit difficult. In Physics, things reach a max speed because air friction force (or another force) is speed-dependent. So if you increase your speed, the force also increases... at the end, that force will balance the other and the speed will be constant. I think the best way to simulate a terminal speed is using this concept. If you want to read more about terminal velocity, take a look on Wikipedia: http://en.wikipedia.org/wiki/Terminal_velocity
private void Accelerate()
{
Acceleration.X = Math.abs(MotorForce - airFriction.X);
Acceleration.Y = Math.abs(MotorForce - airFriction.Y);
if (Acceleration.X < 0)
{
Acceleration.X = 0;
}
if (Acceleration.Y < 0)
{
Acceleration.Y = 0;
}
Velocity.X += (float)Math.Cos(Rotation) * Acceleration.X
Velocity.Y += (float)Math.Sin(Rotation) * Acceleration.Y
airFriction.X = Math.abs(airFrictionConstant * Velocity.X);
airFriction.Y = Math.abs(airFrictionConstant * Velocity.Y);
}
First, we calculate the accelartion using a "MotorForce" and the air friction. The MotorForce is the force we make to move our sprite. The air friction always tries to "eliminate" the movement, so is always postive. We finally take absolute values because the rotation give us the direction of the vector. If the acceleration is lower than 0, that means that the air friction is greater than our MotorForce. It's a friction, so it can't do that: if acceleration < 0, we make it 0 -the air force reached our motor force and the speed becomes constant.
After that, the velocity will increase using the acceleration. Finally, we update the air friction value.
One thing more: you may update also the value of airFriction in the Deccelarate method, even if you don't consider it in that method.
If you have any problem with this, or you don't understand something (sometimes my English is not very good ^^"), say it =)
I've got a player sprite that moves by rotation and its rotation is constantly changing but I also need to work out if a target is to the left or right of it and not within 45 degrees of the front or rear rotation.
I've written this code which I think should work but it seems only occasional pick up one side and slightly more on the other.
public void GrappleCheck(AsteroidSprite target)
{
float targetTragectory = (float)Math.Atan2(Position.Y - target.Position.Y, Position.X - target.Position.X);
if (targetTragectory < 0)
targetTragectory += (float)(Math.PI * 2);
if (Rotation < 0)
Rotation += (float)(Math.PI * 2);
if ((targetTragectory > Rotation + (float)(MathHelper.PiOver4 / 2)) && (targetTragectory < Rotation + (float)(Math.PI - (MathHelper.PiOver4 / 2))))
{
target.Distance = Vector2.Distance(Position, target.Position);
if (RightTarget != null)
{
if (RightTarget.Distance > target.Distance)
{
RightTarget.isTarget = false;
RightTarget = target;
RightTarget.ColorTint = Color.Blue;
RightTarget.isTarget = true;
}
}
else
{
RightTarget = target;
RightTarget.ColorTint = Color.Blue;
RightTarget.isTarget = true;
}
}
else if ((targetTragectory < Rotation - (float)(MathHelper.PiOver4 / 2)) && (targetTragectory > Rotation - (float)(Math.PI - (MathHelper.PiOver4 / 2))))
{
target.Distance = Vector2.Distance(Position, target.Position);
if (LeftTarget != null)
{
if (LeftTarget.Distance > target.Distance)
{
LeftTarget.isTarget = false;
LeftTarget = target;
LeftTarget.ColorTint = Color.Red;
LeftTarget.isTarget = true;
}
}
else
{
LeftTarget = target;
LeftTarget.ColorTint = Color.Red;
LeftTarget.isTarget = true;
}
}
else
{
target.isTarget = false;
}
if (controlInput.IsHeld(Keys.X))
{
Speed = Speed;
}
Working with angles can be quite annoying. Here are some ways to solve your problems without using angles:
First, we need the direction to the target and the movement direction:
var targetDirection = target.Positon - Position;
// Update this to match the actual direction. The following line assumes that
// a rotation of 0 results in the right direction.
var movementDirection = new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation));
The first problem you want to solve is determining, if the target is within a 45° cone. You can calculate the actual angle with the following formula:
var dot = Vector2.Dot(myDirection, targetDirection);
//if dot is negative, then target is behind me, so just use the absolute value
var cos = Math.Abs(dot) / myDirection.Length() / targetDirection.Length();
var angle = Math.Acos(cos);
if(angle < MathHelper.PiOver4 / 2) //45° opening angle
; // within cone
else
; // outside cone
Your second problem is determining, if the target is on the left or right side. We use a vector that is ortogonal to myDirection and points to the left for this:
//assuming that +x is the right axis and +y is the down axis
var normal = new Vector2(myDirection.Y, -myDirection.X);
dot = Vector2.Dot(normal, targetDirection);
if(dot > 0)
; // target is on the left side
else
; // target is on the right side
I hope that makes cleaning up your code a bit easier and more comprehensible. You should consider extracting some code in separate methods to make it more readable.
Okay I've solved it, the player rotation can be from 0 to 2 x PI + or -, to keep it + though I put in
if (Rotation < 0)
Rotation += (float)Math.PI * 2;
the rotation to the target can be 0-PI or 0 - Negative PI depending on the way you declare the atan2 and where the player is.
//This works out the difference from the targets rotation to the players rotation.
RotationDif = TargetRotation - PlayerRotation;
//If the difference is greater than PI then when we check to see if its is within
//the range 0-PI or 0-Negative PI it will be missed whilst still possibly being on
//either side of the player, adding PI * 2 to the targets rotation basically spins
//it past the player so the Difference will be the shortest angle.
if(Math.Abs(RotationDif) > Math.PI)
RotationDif = TargetRotation + (float)(Math.PI * 2) - PlayerRotation;
//Next we check to see if the target is left(negative) or
//the right(positive), the negative/positive assignment will depend
//on which way round you did the atan2 to get the target rotation.
if ((RotationDif > 0) && (RotationDif < (float)Math.PI))
//Insert right target code here
else if ((RotationDif < 0) && (RotationDif > -(float)Math.PI))
//Insert left target code here
else
//Insert no target code here to cover all basis
And that's it, I've made the If (RotationDif > 0) etc differently so the a 45 degree angle front and back is ignored by making it
If ((RotationDif > (float)(Math.PI / 8) &&
(RotationDif < (float)(Math.PI - (Math.PI / 8)))
and the opposite for the other side, hope this helps someone else as it took me nearly 2 sodding weeks to work out :/