Keep Rotating sprite from going off screen, but bounce back. - xna

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 =)

Related

SpriteKit stop spinning wheel in a defined angle

I have a spinning wheel rotating at an angular speed ω, no acceleration involved, implemented with SpriteKit.
When the user push a button I need to slowly decelerate the wheel from the current angle ∂0 and end-up in a specified angle (lets call it ∂f).
I created associated to it a mass of 2.
I already tried the angularDamping and the SKAction.rotate(toAngle: duration:) but they do not fit my needs because:
With the angularDamping I cannot specify easy the angle ∂f where I want to end up.
With the SKAction.rotate(toAngle: duration:) I cannot start slowing down from the current rotation speed and it doesn't behave natural.
The only remaining approach I tried is by using the SKAction.applyTorque(duration:).
This sounds interesting but I have problems calculating the formula to obtain the correct torque to apply and especially for the inertia and radius of the wheel.
Here is my approach:
I'm taking the starting angular velocity ω as:
wheelNode.physicsBody?.angularVelocity.
I'm taking the mass from wheelNode.physicsBody?.mass
The time t is a constant of 10 (this means that in 10 seconds I want the wheel decelerating to the final angle ∂f).
The deceleration that I calculated as:
let a = -1 * ω / t
The inertia should be: let I = 1/2 * mass * pow(r, 2)*. (see notes regarding the radius please)
Then, finally, I calculated the final torque to apply as: let t = I * a (taking care that is opposite of the current angular speed of the wheel).
NOTE:
Since I don't have clear how to have the radius of the wheel I tried to grab it both from:
the wheelNode.physicsBody?.area as let r = sqrt(wheelNode.physicsBody?.area ?? 0 / .pi)
by converting from pixel to meters as the area documentation says. Then I have let r = self.wheelNode.radius / 150.
Funny: I obtain 2 different values :(
UNFORTUNATLY something in this approach is not working because so far I have no idea how to end up in the specified angle and the wheel doesn't stop anyway as it should (or the torque is too much and spins in the other direction, or is not enough). So, also the torque applied seems to be wrong.
Do you know a better way to achieve the result I need? Is that the correct approach? If yes, what's wrong with my calculations?
Kinematics makes my head hurt, but here you go. I made it to where you can input the amount of rotations and the wheel will rotate that many times as its slowing down to the angle you specify. The other function and extension are there to keep the code relatively clean/readable. So if you just want one giant mess function go ahead and modify it.
• Make sure the node's angularDampening = 0.0
• Make sure the node has a circular physicsbody
// Stops a spinning SpriteNode at a specified angle within a certain amount of rotations
//NOTE: Node must have a circular physicsbody
// Damping should be from 0.0 to 1.0
func decelerate(node: SKSpriteNode, toAngle: CGFloat, rotations: Int) {
if node.physicsBody == nil { print("Node doesn't have a physicsbody"); return } //Avoid crash incase node's physicsbody is nil
var cw:CGFloat { if node.physicsBody!.angularVelocity < CGFloat(0.0) { return -1.0} else { return 1.0} } //Clockwise - using int to reduce if statments with booleans
let m = node.physicsBody!.mass // Mass
let r = CGFloat.squareRoot(node.physicsBody!.area / CGFloat.pi)() // Radius
let i = 0.5 * m * r.squared // Intertia
let wi = node.physicsBody!.angularVelocity // Initial Angular Velocity
let wf:CGFloat = 0 // Final Angular Velocity
let ti = CGFloat.unitCircle(node.zRotation) // Initial Theta
var tf = CGFloat.unitCircle(toAngle) // Final Theta
//Correction constant based on rate of rotation since there seems to be a delay between when the action is calcuated and when it is run
//Without the correction the node stops a little off from its desired stop angle
tf -= 0.00773889 * wi //Might need to change constn
let dt = deltaTheta(ti, tf, Int(cw), rotations)
let a = -cw * 0.5 * wi.squared / abs(dt) // Angular Acceleration - cw used to determine direction
print("A:\(a)")
let time:Double = Double(abs((wf-wi) / a)) // Time needed to stop
let torque:CGFloat = i * a // Torque needed to stop
node.run(SKAction.applyTorque(torque, duration: time))
}
func deltaTheta(_ ti:CGFloat, _ tf:CGFloat, _ clockwise: Int, _ rotations: Int) -> CGFloat {
let extra = CGFloat(rotations)*2*CGFloat.pi
if clockwise == -1 {
if tf>ti { return tf-ti-2*CGFloat.pi-extra }else{ return tf-ti-extra }
}else{
if tf>ti { return tf-ti+extra }else{ return tf+2*CGFloat.pi+extra-ti }
}
}
}
extension CGFloat {
public var squared:CGFloat { return self * self }
public static func unitCircle(_ value: CGFloat) -> CGFloat {
if value < 0 { return 2 * CGFloat.pi + value }
else{ return value }
}
}

Cummulative Circle rotation

I have a rotation of a circle that goes from 0 to 360 an back to 0 to 360 and so on. Now i want to now the cumulative degree so that i get 0 to 360 an to 720 and so on.
so i have a 2 variables
var rotation = <goes from 0-360>
var cumulativeRotation = <should go from 0-+360>
rotation is know, cumulativeRotation should be calculated.
My first thought was to remember the previousRotation and substract it from rotation and add that to the cumulativeRotation, but when you go from 360 back to 0, you break the circle
is there a easy way to overcome that issue?
Why don't you simply iterate the cumulativeRotation, and calculate rotation from it, by dividing it modulo 360?
What's the problem with having a separate property? That sounds fine, as long as you strictly manage it.
- (void)setCumulativeRotation:(NSInteger)cumulativeRotation
{
if (cumulativeRotation > 719 || cumulativeRotation < 0)
cumulativeRotation = 0;
_cumulativeRotation = cumulativeRotation;
}
You can also override the setter for .rotation to keep track of how many times it passes through 0 (or 360), although there's no way to robustly handle situations where the rotation is set to an arbitrary value:
- (void)setRotation:(NSInteger)rotation
{
if ( _rotation > rotation)
{
// counter-clockwise
if (rotation < 0)
self.numberOfRevolutions--;
} else if (_rotation < rotation)
{
// clockwise
if (rotation > 719)
self.numberOfRevolutions++;
}
_rotation = rotation;
}
Something like that?

How to simulate Gravity in z-axis in a 2D game with Sprite Kit

I'm writing a 2D ball game with sprite kit on iOS 7 and currently struggling on one physic simulation.
To explain the expected behavior: if a ball is dropped into a tea cup, it will circle around, loosing speed and finally stand still in the center of the cup.
I've tried to archive this with gravity, but gravity in sprite kit only applies to vertical X and Y axis, not Z-axis. I also tried to use level gravity by switching gravity values with small physic bodies on beginContact depending on the current ball position in the tea cup. But some contacts are dropped and the result is far away to look realistic.
I think I need to solve this in the update: method, but I have no idea which way to go.
Any advice greatly welcome and I need to mention that I'm not an expert on math, please explain your path to go. :-)
Since there's no built-in support for this kind of behavior in SpriteKit, rather than trying to hack existing functions to get what you want, you're probably better off integrating some published 2D physics formulas in your x,y 2D world. I would think that something like simulating magnetic or a homing behavior might be right for this.
A simple example would be something like (in the scene's -update: method):
CGFloat strength = 0.5; //(some scaling value)
CGPoint ballLocation = ball.position;
CGPoint cupLocation = cup.position;
[ball.physicsBody applyForce:CGVectorMake((cupLocation.x - ballLocation.x) * strength,
(cupLocation.y - ballLocation.y) * strength)];
following Joshd great idea, I have created an NSArray with like explained in my comment above. Hope this snippets does help some others...
The result could be found on youtube: http://youtu.be/Uephg94UH30
Sorry for the bad Airplay frame rate, it runs perfectly smooth on my iPad
The -update: functions does the work but only triggered if _meditationIsActive. This bool is set in -didBeginContact: when any ball gets in contact with a hole.
if (_lastCheck > 0.005)
{
if (_meditationIsActive)
{
CGFloat strength = 0.1; //(some scaling value)
CGPoint ballLocation;
CGPoint holeLocation;
for (MeditationHole * holeObj in _meditationHoles)
{
if (holeObj.connectedMeditationBall != nil)
{
ballLocation = holeObj.connectedMeditationBall.position;
holeLocation = holeObj.position;
[holeObj.connectedMeditationBall.physicsBody applyForce:CGVectorMake(
(holeLocation.x - ballLocation.x) * strength, (holeLocation.y - ballLocation.y) * strength)];
}
}
_meditationIsActive = [self doesMeditationApplies];
}
_lastCheck = 0;
}
At the end I'm checking if there is a valid ball out of the array in contact with a hole to avoid checking during every update. This is done with the following function where position check +/- 48 detects a ball close to a hole and +/-1 ball stands still
- (bool)doesMeditationApplies
{
bool isInArea = NO;
int perfectMatchCount = 0;
for (MeditationHole * holeObj in _meditationHoles)
{
if (holeObj)
{
if (holeObj.connectedMeditationBall != nil)
{
MeditationBall * ballObj = holeObj.connectedMeditationBall;
if ((ballObj.position.x >= holeObj.position.x - 48) &&
(ballObj.position.x <= holeObj.position.x + 48) &&
(ballObj.position.y >= holeObj.position.y - 48) &&
(ballObj.position.y <= holeObj.position.y + 48))
{
isInArea = YES;
}
else
{
holeObj.connectedMeditationBall = nil;
}
if ((ballObj.position.x >= holeObj.position.x - 1) &&
(ballObj.position.x <= holeObj.position.x + 1) &&
(ballObj.position.y >= holeObj.position.y - 1) &&
(ballObj.position.y <= holeObj.position.y + 1))
{
perfectMatchCount++;
isInArea = YES;
}
}
}
}
if (perfectMatchCount == _oxydStonesMax)
{
if (_sound)
{
self.pauseMusicPlaybackBlock(YES);
NSLog(#"PlaySound Meditation");
[OxydScene PlaySystemSound:#"Win2"];
}
isInArea = NO;
[self showPauseScreenWithWin:YES andPauseOnly:NO];
}
return isInArea;
}

Animating rotation changes of UIImageView

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?

XNA how to implement gravity?

I've tried following the example on XNA Development website but when the character jumps, they cant be controlled/cant stop the jump movement until its completed.
How do I get around that? Here is my jump code
private void Jump()
{
if (mCurrentState != FoxState.Jumping)
{
mCurrentState = FoxState.Jumping;
mStartingPosition = Position;
Direction.Y = Fox_vSpeed;
Speed = new Vector2(Fox_Speed, Fox_Speed);
}
}
Gravity is simply a force that affects the acceleration of an object. Acceleration changes the velocity of an object so you can do just that: Speed -= Vector2.UnitY * -2; You can then check collision with an object on the ground and once such collision happens you can zero the Y component of Speed (and possibly incur damage on the poor fox if the Y component is too high in magnitude (absolute value)).
Something like:
Update(GameTime gt)
{
if(mCurrentState == FoxState.Jumping)
{
Speed -= Vector2.UnitY * -2;
Position += Speed;
if(Position.Y > GroundLevel)
{
Position.Y = 0;
mCurrentState = FoxState.Walking;
}
}
}
Modify your question with details if you need more information.

Resources