XNA 2D rotated sprite, with realative rotated position (Lunar Lander Exhaust Placement) - xna

I am making a little Lunar Lander clone, and its working quite ok, now i have added particle effects to the lander, so when the thrust is engange, the particle effect is created, just in the middle of my ship.
What i would like to have happen is that the Particles are created, where the ship exhaust is on the sprite. And this has me stumped. I know i should be able to calculate it, as i both have the rotation angle and the current location, so i should be able to get the rotated location of any pixel within my 64x64 sprite.
Im interested in calculating the Lander.exhaust.X and Lander.exhaust.Y values. Could anyone point me in the right direction.
//this is part of the code, im sure i dont need all of it :)
Lander.acceleration.X = Lander.acceleration.X * (0.01f * gameTime.ElapsedGameTime.Seconds);
Lander.acceleration.Y = Lander.acceleration.Y * (0.01f * gameTime.ElapsedGameTime.Seconds);
Lander.velocity.Y = Lander.velocity.Y + (0.05f + Lander.velocity.Y * gameTime.ElapsedGameTime.Seconds);
Lander.oldvelocity.X = Lander.velocity.X;
Lander.oldvelocity.Y = Lander.velocity.Y;
Lander.exhaust.X = (float)Math.Cos(Lander.RotationAngle) * 0.1f + Lander.Position.Y ;
Lander.exhaust.Y = (float)Math.Sin(Lander.RotationAngle) * 0.1f + Lander.Position.X ;
Lander.Position.Y = Lander.velocity.Y + Lander.Position.Y;
Lander.Position.X = Lander.velocity.X + Lander.Position.X;
//if (Lander.Position.Y >= groundlevel + (Lander.mSpriteTexture.Height / 2))
if (Lander.Position.Y >= groundlevel)
{
Lander.Position.Y = groundlevel;
Lander.velocity.X = 0f;
Lander.oldvelocity.X = 0f;
}
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;
Lander.RotationAngle = RotationAngle;
RotationAngledegrees = MathHelper.ToDegrees(RotationAngle);
if (keyState.IsKeyDown(Keys.Space))
{
Lander.acceleration.X = (float)Math.Cos(Lander.RotationAngle) * 0.1f + Lander.acceleration.X;
Lander.acceleration.Y = (float)Math.Sin(Lander.RotationAngle) * 0.1f + Lander.acceleration.Y;
Lander.velocity.X = Lander.oldvelocity.X + Lander.acceleration.X;
Lander.velocity.Y = Lander.oldvelocity.Y + Lander.acceleration.Y;
particleEngine.EmitterLocation = new Vector2(Lander.exhaust.X, Lander.exhaust.Y);
-lasse

Lander.exhaust.X = (float)Math.Cos(RotationAngle) * 32 + Lander.Position.X;
Lander.exhaust.Y = (float)Math.Sin(RotationAngle) * 32 + Lander.Position.Y;
You may have to subtract or add PI/2 to the angle depending on the initial angle of the sprite the emitter will be 32 pixels away from the position of the Lander.
On a side note, it would probably be a good idea to put each part of the game in its own class, it will make changing stuff later a LOT easier.
And another thing, when you add the velocity to the position, you can do this:
Lander.Position += Lander.velocity;
It basically does the same thing.

Related

Cocos2d-x Parallax with Accelerometer (How to stop smoothly when reaching the edges and when changing direction)

I am creating a game that has 3 layers of background. They are added to a CCParallaxNode and it's moved by tilting the device to the right, left, up and down. I am using this code to move the CCParallaxNode (accelerometer delegate method - didAccelerate):
void SelectScreen::didAccelerate(cocos2d::CCAcceleration *pAccelerationValue)
{
float deceleration = 0.1f, sensitivity = 30.0f, maxVelocity = 200;
accelX = pAccelerationValue->x * sensitivity;
accelY = pAccelerationValue->z * sensitivity;
parallaxMovementX = parallaxMovementX * deceleration + pAccelerationValue->x * sensitivity;
parallaxMovementX = fmaxf(fminf(parallaxMovementX, maxVelocity), -maxVelocity);
float offset = -calibration * sensitivity;
parallaxMovementY = (parallaxMovementY * deceleration + pAccelerationValue->z * sensitivity) + offset;
}
Then, in the update method:
void SelectScreen::update(float dt)
{
CCNode* node = getChildByTag(100);
float maxX = (Data::getInstance()->getWinSize().width * 2) + 100;
float minX = node->getContentSize().width - 100;
float maxY = Data::getInstance()->getWinSize().height * 0.1f;
float minY = -200;
float diffX = parallaxMovementX;
float diffY = parallaxMovementY;
float newX = node->getPositionX() + diffX;
float newY = node->getPositionY() + diffY;
newX = MIN(MAX(newX, minX), maxX);
newY = MIN(MAX(newY, minY), maxY);
if(isUpdating)
node->setPositionX(newX);
if(isUpdatingY)
node->setPositionY(newY);
}
The movement is nicely done, however, when reaching any of the 4 edges it stops abruptly. Also, when changing direction (eg. moving to the right then moving to the left) it does it abruptly.
Question: How can I do a smooth stop and a smooth direction change (maybe some little bouncing effect)? I think this is also related to the accelerometer data (when going fast it must bounce longer that it should when going slow).
Thanks in advance.
You need some math to smooth the movements.
Try checking the code here:
http://www.nscodecenter.com/preguntas/10768/3d-parallax-con-accelerometer

Calculus to convert Y coordinates for purpose of updating bpm in a metronome

I'm in the course of developing a metronome for iPad. I'm using CGAffineTransformRotate for the metronomeArm animation, NSTimer(I'm not interested in great precision) for sound and a UIPanGestureRecognizer for dragging the metronomeWeight on the metronomeArm.
My problem is that I don't know how to update the bpm by dragging the weight using the pan. For now I have this : metronomeWeight.center.y is 240 and the default bpm for this position is 80.The weight goes from top 140 to a maximum of 450. I have implemented this method but it is not correct :
-(void)updateBPM
{
CGFloat weightYPosition = metronomeWeight.center.y;
NSUInteger newBPM = (weightYPosition/3);
self.bpm = newBPM;
}
and the selector for the pan is this :
-(void)handlePan:(UIPanGestureRecognizer*)gesture
{
CGPoint translation = [gesture translationInView:metronomeArm];
CGPoint location = [gesture locationInView:metronomeArm];
NSLog(#"miscarea pe oy are valoare de: %f", location.y);
CGPoint newCenter = CGPointMake(metronomeArm.frame.size.width/2, gesture.view.center.y + translation.y );
if (newCenter.y >= 140 && newCenter.y <= 450)
{
gesture.view.center = newCenter;
[gesture setTranslation:CGPointZero inView:metronomeArm];
[self updateBPMFromWeightLocation];
tempoLabel.text = [NSString stringWithFormat:#"%d", self.bpm];
NSLog(#"metronomeWeight position : %f ",metronomeWeight.center.y);
}
}
The sound and animation update but not as desired, meaning that the lower limit bpm should be 225 and the upper one should be 1. In my case they are 150 and 46 respectively.
My calculations are not good, so it will be fantastic if you can help me solve this problem... I have looked at apple's metronome project for days and can't understand how they do this...
Thanks
The new updateBPM method thanks to #zimmryan suggestion
-(void)updateBPMFromWeightLocation
{
CGFloat weightYPosition = metronomeWeight.center.y;
float lengthInM = ((weightYPosition - 140) * 0.00041333);
float time = 2 * M_PI * sqrt(lengthInM / 9.8);
NSUInteger newBPM = floor(60.0 / time);
self.bpm = newBPM;
}
From my understanding of physics and calculus, the equation for the period of a pendulum is T=2pi sqrt(l/g) where T is time in seconds, l is length in meters, and g is gravity.
You are picking a base point of 290 (pixels) and a BPM of 120. A BPM of 120 converts to a period of .5 seconds. So T = .5. Solving the equation you get .062 for l, or 6.2cm.
But your length is not in cm it is in pixels s now you have to convert it. Since your range is from 140 to 350, your zero point is 350. So first you take 350 - 390 to get an offset of 60. Now create your equation of 60pixels * k = .062 so your k = .001033
Your final function should read
-(void)updateBPM
{
CGFloat weightYPosition = metronomeWeight.center.y;
float lengthInM = ((350 - weightYPosition) * .001033);
float time = 2 * M_PI * sqrt(lengthInM / 9.8);
NSUInteger newBPM = floor(60 / time);
self.bpm = newBPM;
}
or
-(void)updateBPM
{
self.bpm = floor(60 / (2 * M_PI * sqrt(((350 - metronomeWeight.center.y) * .001033) / 9.8)));
}

Animation with an image in iPad

Hi I have an image like a round top of a table.
I want to move it clockwise when ever user swipes from left to right and counter clockwise when user swipes from right to left.
Like moving a round table top in real time.
How can I do this in the app?
I am using the following code for rotation. Its from the TrackBall example.
The problem I am having is the when ever the image spins, it changes its position.
- (CATransform3D)rotationTransformForLocation:(CGPoint)location
{
CGFloat trackBallCurrentPoint[3] = {location.x - trackBallCenter.x, location.y - trackBallCenter.y, 0.0f};
if(fabs(trackBallCurrentPoint[0] - trackBallStartPoint[0]) < kTol && fabs(trackBallCurrentPoint[1] - trackBallStartPoint[1]) < kTol)
{
return CATransform3DIdentity;
}
CGFloat dist = trackBallCurrentPoint[0] * trackBallCurrentPoint[0] + trackBallCurrentPoint[1] * trackBallCurrentPoint[1];
if(dist > trackBallRadius * trackBallRadius)
{
// outside the center of the sphere so make it zero
trackBallCurrentPoint[2] = 0.0f;
}
else
{
trackBallCurrentPoint[2] = sqrt(trackBallRadius * trackBallRadius - dist);
}
// cross product yields the rotation vector
CGFloat rotationVector[3];
rotationVector[0] = trackBallStartPoint[1] * trackBallCurrentPoint[2] - trackBallStartPoint[2] * trackBallCurrentPoint[1];
rotationVector[1] = -trackBallStartPoint[0] * trackBallCurrentPoint[2] + trackBallStartPoint[2] * trackBallCurrentPoint[0];
rotationVector[2] = trackBallStartPoint[0] * trackBallCurrentPoint[1] - trackBallStartPoint[1] * trackBallCurrentPoint[0];
// calc the angle between the current point vector and the starting point vector
// use arctan so we get all eight quadrants instead of just the positive ones
// cos(a) = (start . current) / (||start|| ||current||)
// sin(a) = (||start X current||) / (||start|| ||current||)
// a = atan2(sin(a), cos(a))
CGFloat startLength = sqrt(trackBallStartPoint[0] * trackBallStartPoint[0] + trackBallStartPoint[1] * trackBallStartPoint[1] + trackBallStartPoint[2] * trackBallStartPoint[2]);
CGFloat currentLength = sqrt(trackBallCurrentPoint[0] * trackBallCurrentPoint[0] + trackBallCurrentPoint[1] * trackBallCurrentPoint[1] + trackBallCurrentPoint[2] * trackBallCurrentPoint[2]);
CGFloat startDotCurrent = trackBallStartPoint[0] * trackBallCurrentPoint[0] + trackBallStartPoint[1] * trackBallCurrentPoint[1] + trackBallStartPoint[2] * trackBallCurrentPoint[2]; // (start . current)
// start X current we have already calcualted in the rotation vector
CGFloat rotationLength = sqrt(rotationVector[0] * rotationVector[0] + rotationVector[1] * rotationVector[1] + rotationVector[2] * rotationVector[2]);
CGFloat angle = atan2(rotationLength / (startLength * currentLength), startDotCurrent / (startLength * currentLength));
// normalize the rotation vector
rotationVector[0] = rotationVector[0] / rotationLength;
rotationVector[1] = rotationVector[1] / rotationLength;
rotationVector[2] = rotationVector[2] / rotationLength;
CATransform3D rotationTransform = CATransform3DMakeRotation(angle, rotationVector[0], rotationVector[1], rotationVector[2]);
return CATransform3DConcat(baseTransform, rotationTransform);
}
Thanks in advance.
Take a look at a question I posed... you might be trying to do the same thing (I don't think the question covered it, but after getting rotation working I implemented pan gesture to allow the user to spin the disc in either direction)
How to rotate a flat object around its center in perspective view?

Algorithm for creating a circular path around a center mass?

I am attempting to simply make objects orbit around a center point, e.g.
The green and blue objects represent objects which should keep their distance to the center point, while rotating, based on an angle which I pass into method.
I have attempted to create a function, in objective-c, but it doesn't work right without a static number. e.g. (It rotates around the center, but not from the true starting point or distance from the object.)
-(void) rotateGear: (UIImageView*) view heading:(int)heading
{
// int distanceX = 160 - view.frame.origin.x;
// int distanceY = 240 - view.frame.origin.y;
float x = 160 - view.image.size.width / 2 + (50 * cos(heading * (M_PI / 180)));
float y = 240 - view.image.size.height / 2 + (50 * sin(heading * (M_PI / 180)));
view.frame = CGRectMake(x, y, view.image.size.width, view.image.size.height);
}
My magic numbers 160, and 240 are the center of the canvas in which I'm drawing the images onto. 50 is a static number (and the problem), which allows the function to work partially correctly -- without maintaining the starting poisition of the object or correct distance. I don't know what to put here unfortunately.
heading is a parameter that passes in a degree, from 0 to 359. It is calculated by a timer and increments outside of this class.
Essentially what I would like to be able to drop any image onto my canvas, and based on the starting point of the image, it would rotate around the center of my circle. This means, if I were to drop an image at Point (10,10), the distance to the center of the circle would persist, using (10,10) as a starting point. The object would rotate 360 degrees around the center, and reach it's original starting point.
The expected result would be to pass for instance (10,10) into the method, based off of zero degrees, and get back out, (15,25) (not real) at 5 degrees.
I know this is very simple (and this problem description is entirely overkill), but I'm going cross eyed trying to figure out where I'm hosing things up. I don't care about what language examples you use, if any. I'll be able to decipher your meanings.
Failure Update
I've gotten farther, but I still cannot get the right calculation. My new code looks like the following:
heading is set to 1 degree.
-(void) rotateGear: (UIImageView*) view heading:(int)heading
{
float y1 = view.frame.origin.y + (view.frame.size.height/2); // 152
float x1 = view.frame.origin.x + (view.frame.size.width/2); // 140.5
float radius = sqrtf(powf(160 - x1 ,2.0f) + powf(240 - y1, 2.0f)); // 90.13
// I know that I need to calculate 90.13 pixels from my center, at 1 degree.
float x = 160 + radius * (cos(heading * (M_PI / 180.0f))); // 250.12
float y = 240 + radius * (sin(heading * (M_PI / 180.0f))); // 241.57
// The numbers are very skewed.
view.frame = CGRectMake(x, y, view.image.size.width, view.image.size.height);
}
I'm getting results that are no where close to where the point should be. The problem is with the assignment of x and y. Where am I going wrong?
You can find the distance of the point from the centre pretty easily:
radius = sqrt((160 - x)^2 + (240 - y)^2)
where (x, y) is the initial position of the centre of your object. Then just replace 50 by the radius.
http://en.wikipedia.org/wiki/Pythagorean_theorem
You can then figure out the initial angle using trigonometry (tan = opposite / adjacent, so draw a right-angled triangle using the centre mass and the centre of your orbiting object to visualize this):
angle = arctan((y - 240) / (x - 160))
if x > 160, or:
angle = arctan((y - 240) / (x - 160)) + 180
if x < 160
http://en.wikipedia.org/wiki/Inverse_trigonometric_functions
Edit: bear in mind I don't actually know any Objective-C but this is basically what I think you should do (you should be able to translate this to correct Obj-C pretty easily, this is just for demonstration):
// Your object gets created here somewhere
float x1 = view.frame.origin.x + (view.frame.size.width/2); // 140.5
float y1 = view.frame.origin.y + (view.frame.size.height/2); // 152
float radius = sqrtf(powf(160 - x1 ,2.0f) + powf(240 - y1, 2.0f)); // 90.13
// Calculate the initial angle here, as per the first part of my answer
float initialAngle = atan((y1 - 240) / (x1 - 160)) * 180.0f / M_PI;
if(x1 < 160)
initialAngle += 180;
// Calculate the adjustment we need to add to heading
int adjustment = (int)(initialAngle - heading);
So we only execute the code above once (when the object gets created). We need to remember radius and adjustment for later. Then we alter rotateGear to take an angle and a radius as inputs instead of heading (this is much more flexible anyway):
-(void) rotateGear: (UIImageView*) view radius:(float)radius angle:(int)angle
{
float x = 160 + radius * (cos(angle * (M_PI / 180.0f)));
float y = 240 + radius * (sin(angle * (M_PI / 180.0f)));
// The numbers are very skewed.
view.frame = CGRectMake(x, y, view.image.size.width, view.image.size.height);
}
And each time we want to update the position we make a call like this:
[objectName rotateGear radius:radius angle:(adjustment + heading)];
Btw, once you manage to get this working, I'd strongly recommend converting all your angles so you're using radians all the way through, it makes it much neater/easier to follow!
The formula for x and y coordinates of a point on a circle, based on radians, radius, and center point:
x = cos(angle) * radius + center_x
y = sin(angle) * radius + center_y
You can find the radius with HappyPixel's formula.
Once you figure out the radius and the center point, you can simply vary the angle to get all the points on the circle that you'd want.
If I understand correctly, you want to do InitObject(x,y). followed by UpdateObject(angle) where angle sweeps from 0 to 360. (But use radians instead of degrees for the math)
So you need to track the angle and radius for each object.:
InitObject(x,y)
relative_x = x-center.x
relative_y = y-center.y
object.radius = sqrt((relative_x)^2, (relative_y)^2)
object.initial_angle = atan(relative_y,relative_x);
And
UpdateObject(angle)
newangle = (object.initial_angle + angle) % (2*PI )
object.x = cos(newangle) * object.radius + center.x
object.y = sin(newangle) * object.radius + center.y
dx=dropx-centerx; //target-source
dy=-(dropy-centery); //minus = invert screen coords to cartesian coords
radius=sqrt(dy*dy+dx*dx); //faster if your compiler optimizer is bad
if dx=0 then dx=0.000001; //hackpatchfudgenudge*
angle=atan(dy/dx); //set this as start angle for the angle-incrementer
Then go with the code you have and you'll be fine. You seem to be calculating radius from current position each time though? This, like the angle, should only be done once, when the object is dropped, or else the radius might not be constant.
*instead of handling 3 special cases for dx=0, if you need < 1/100 degree precision for the start angle go with those instead, google Polar Arctan.

AS2: Tween around ellipse

I have 7 movieclips on stage I want to tween around an ellipse from different start points. I am having lots of trouble doing this.... I used a circle formula at first and then divided the y value by the width of the ellipse over the height. This sort of worked but after every rotation the y value was a little of. That code is:
this._x += (Math.cos(angle * Math.PI/180) * radius);
this._y += (Math.sin(angle * Math.PI/180) *radius)/1.54;
I also have trouble finding the angle of the start point, if it is off they won't travel in the same ellipse but they all have different starting angles.
Any clues?
Calculate the incidvidual offsets using this snippet:
// assuming you have your buttons in an array called buttons
for (var i:Number = 0; i < buttons.length; i++){
buttons[i].angleOffset = 360 / buttons.length * i;
}
Set the position each update instead of moving, that way you wont get any drift.
Update each object using this code, incrementing the angle var to get it to spin.
this._x = offsetX + Math.sin((angle + angleOffset) * Math.PI/180) * radius;
this._y = offsetY + Math.cos((angle + angleOffset) * Math.PI/180) * radius / 1.54;
This is almost soved, this piece of script will take the items of the array buttons (can add as many as you want), space them around the ellipse you set (origin + radius), and tween them around it according to the speed you set. The only problem is the spacing isn't even and some are close and some far apart and I don't understand why.
var angle:Number = 0;
var originX:Number = 200;
var originY:Number = 200;
var radiusX:Number = 267.5;
var radiusY:Number = 100;
var steps:Number = 360;
var speed:Number = 3.1415/steps;
var buttons:Array = new Array(this.age,this.ethnicity,this.sex,this.social,this.ability,this.orientation,this.faith);
for (i=0;i<buttons.length;i++) {
buttons[i].onEnterFrame = function() {
moveButtons(this);
controllButtons(this);
};
buttons[i]._order = (360/buttons.length) * (i+1);
}
function moveButtons(e) {
e._anglePhase = angle+e._order;
e._x = originX+Math.sin(e._anglePhase)*radiusX;
e._y = originY+Math.cos(e._anglePhase)*radiusY;
}
function controllButtons(e) {
angle += speed;
if (angle>=360) {
angle -= 360;
}
}
Please note I got the base of this script from http://www.actionscript.org/forums/showthread.php3?t=161830&page=2 converted it to AS2 and made it work from an array.

Resources