This question already has answers here:
Rotate GameObject over time
(2 answers)
Closed 5 years ago.
So I am trying to rotate an object on key press (if I press "a" rotate left by 90 degree, if I press "d" rotate right by 90 degree).
I can't make it to work, I have tried a few things but I ended up not solving the problem. As you will see in the next example I only figure out how to rotate TO 90 degree, not more or less, I actually want to rotate the object BY 90 degrees, from whichever degree was currently on. For example:
from 90 degree to 180 degree
from 23 degree to 113 degree and so on...
here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ModuleBehaviour : MonoBehaviour {
public bool rotating = false;
private void Update()
{
if (rotating)
{
RotatePlatform();
}
}
private void RotatePlatform()
{
Vector3 target = new Vector3(0,0,90);
if (transform.eulerAngles.z > 90)
{
transform.rotation = Quaternion.Euler(target);
}
transform.Rotate(target * Time.deltaTime);
}
}
(Don't bother about the rotating variable, I am changing it in another script, it get's true whenever I press the "d" key. I only need to know how to rotate in the right direction, after that I will figure it out how to apply for left rotation)
Thank you;
This will do what you want:
private IEnumerator RotatePlatform(float dir) {
Vector3 target = new Vector3(0, 0, (transform.eulerAngles.z + dir + 360) % 360);
while(Mathf.Abs(transform.eulerAngles.z - target.z) >= Mathf.Abs(dir * Time.deltaTime * 2)) {
transform.Rotate(new Vector3(0,0,dir) * Time.deltaTime);
yield return null;
}
transform.eulerAngles = target;
rotating = false;
}
The key things to take away from this is that I encapsulated the "make it animate" code into a coroutine so that the object rotates to the desired direction from any starting rotation to any rotation in either direction. The reason this works is that the coroutine's local variables hold their state between iterations (the Unity scheduled task system will halt executing the code on a yield instruction and resume at a later time: in this case, null means it will resume on the next frame). This way we can store the starting rotation and target rotation without having to do anything crazy.
It does this by getting the current rotation, adding the rotation amount, then making that value lie within 0-360: transform.eulerAngles.z + dir + 360) % 360. transform.eulerAngles.z will already be [0,360], subtracting 90 from that would leave [-90,270], then by adding 360 we insure that the value will always be greater than 0 (without affecting the resulting angle), then % 360 effectively subtracts off any excess quantities of 360.
The Mathf.Abs(transform.eulerAngles.z - target.z) >= Mathf.Abs(dir * Time.deltaTime * 2) statement checks to see if the "distance we have yet to rotate" is "greater than the amount we're going to rotate by" with some buffer: we don't want to overshoot and have our "how far do we need to go" be 358 degrees!
The other important change was that we want to use new Vector3(0,0,dir) rather than target as our value to rotate by so that the speed remains constant.
The last 2 instructions make sure the rotation actually achieves the desired value and, of course, toggle back the bool tracking whether or not pressing keys does anything (we only want 1 instance of our coroutine).
Then here's the Update() method I used to control it:
private void Update() {
if(!rotating) {
if(Input.GetKeyDown(KeyCode.D)) {
rotating = true;
StartCoroutine(RotatePlatform(90));
}
if(Input.GetKeyDown(KeyCode.A)) {
rotating = true;
StartCoroutine(RotatePlatform(-90));
}
}
}
To rotate in the other direction, subtract 90.
void Update()
{
if (Input.GetKeyDown(KeyCode.D))
transform.Rotate(0, 0, transform.rotation.z + 90);
}
Related
I have been searching and racking my brains for a while now to try and get this code working, to no avail. Hopefully you guys can help.
I have a simple set up with a cube that moves position every frame. I need the cube to go to an x position when it reaches a different location.
Example: Cube starts at position 0, moves forward in the x axis until it gets to position 15, then reverts back to 0 and stops.
Vector3 startingPosition;
void Start ()
{
startingPosition = gameObject.transform.position;
}
void Update ()
{
if (gameObject.transform.position.x == 15) {
gameObject.transform.position = startingPosition;
} else {
float translation = Time.deltaTime * 2;
transform.Translate (0, 0, translation);
transform.Translate (Vector3.forward * translation);
}
}
}
Currently the cube continuously moves (no stopping point), it's x position having no affect on the positioning.
Change your == to >= and see if that makes a difference. My guess is that position.x is never exactly equal to 15, either due to floating-point precision errors or due to your translation logic skipping over 15 from one frame to the next.
I have had an issue with a animation not working correctly and feel like I have scoured the internet for days trying to get it to work, so this was very helpful. In my situation, the animations' GameObject was at a position of y=1 and should have moved to y=9, but would not update unless I clicked the transition in the animator. I changed the code from x == 15 to y <= 9 and Vector3.forward to Vector3.up and it works perfectly now. Hope this might help someone else with the same issue. using Unity v 2017.1.2
I am trying to simulate a boat in Unity3D. What I need it to be able to do is wobble like a real boat would while in water whenever it hits something. I have the boat colliding already, and all of its axes are unlocked. However, this means the boat will rotate, and then keep driving at an odd angle (like facing to the sky).
Is there a way to make the boat try to return to its original rotaions, without snapping to the exact values, but simply "rocking" back and forth, and then slowing down to eventually stop at the correct rotations again?
Here is the code am attempting to use:
void FixedUpdate ()
{
wobble();
}
void wobble()
{
if (this.transform.eulerAngles.x < 270)
{
this.rigidbody.AddTorque((float)19, (float)0, (float)0, ForceMode.Force);
}
else if (this.transform.eulerAngles.x > 270)
{
this.rigidbody.AddTorque((float)-19, (float)0, (float)0, ForceMode.Force);
}
else{}
if (this.transform.eulerAngles.z < 0)
{
this.rigidbody.AddTorque((float)19, (float)0, (float)0, ForceMode.Force);
}
else if (this.transform.eulerAngles.z > 0)
{
this.rigidbody.AddTorque((float)-19, (float)0, (float)0, ForceMode.Force);
}
else{}
}
However, now when my object hits something, it just starts spinning out of control. Any ideas?
You can use tweening. A wonderful technique to change values smoothly be-tween two values. In this case you can tween from your awkward bouncing angles to your boats sitting rotation by tweening between the two. There are good plugins to use like iTween which is fantastic but I will show you some half pseudo - half super-cereal code to get you started on the concept for "rotation correction"
Lets say I have my boat hit a nice big wave and its pointing my boat upwards to a 20deg angle.
My euler angle on X is 20 and I can return this to 0 by increasing the decreasing the value at a constant rate. I'm just showing X here but you can repeat the process for Z-axis. I would exclude Y as you will use your direction of your boat on Y and you don't want to screw up your boats navigation.
Update()
float angleDelta;
// check if value not 0 and tease the rotation towards it using angleDelta
if(transform.rotation.X > 0 ){
angleDelta = -0.2f;
} elseif (transform.rotation.X < 0){
angleDelta = 0.2f;
}
transform.rotation.X += angleDelta;
}
This incredibly simple implementation does the job but has the wonderful perk of being incredibly "snappy" and "jittery". So we want to add some smoothing to make it a more lifelike boat.
We do this by adding in a variable to the angleDelta:
Vector3 previousAngle;
float accelerationBuffer = 0.3f;
float decelerationBuffer = 0.1f;
Update()
Vector3 angleDelta;
//assuming that x=0 is our resting rotation
if(previousAngle.X > transform.rotation.X && transform.rotation.X > 0){
//speed up rotation correction - like gravity acting on boat
angleDelta.X += (previousAngle.X - transform.rotation.X) * accelerationBuffer;
} elseif(previousAngle.X < transform.rotation.X && transform.rotation.X > 0
//angle returning to resting place: slow down the rotation due to water resistatnce
angleDelta.X -= (previousAngle.X - transform.rotation.X) * deccelerationBuffer;
}
//If boat pointing to sky - reduce X
if(transform.rotation.X > 0 ){
transform.rotation.X -= angleDelta.X * Time.deltaTime;
//If boat diving into ocean - increase X
} elseif (transform.rotation.X < 0){
transform.rotation.X += angleDelta.X * Time.deltaTime; //Must not forget to use deltaTime!
}
//record rotation for next update
previousAngle = transform.rotation;
}
This is an incredibly rough draft but it gets across the theory that I'm trying to explain - you will need to adjust your own code accordingly as you haven't included any of your own (pastebin a snippet to us and maybe we can elaborate more!)
I have the model representing the player's ship gradually leaning when the player strafes. For instance, here's the code that leans the ship right:
In Update() of the Game class:
if (ship.rightTurnProgress < 1 && (currentKeyState.IsKeyDown(Keys.D)))
{
ship.rightTurnProgress += (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyDown(Keys.D))
{
Velocity += Vector3.Right * VelocityScale * 10.0f;
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(0.4f * rightTurnProgress);
}
This is what I'm attempting to do to make it ease back out of the lean when it stops strafing:
In Update() of the Game class:
if (ship.rightTurnProgress > 0 && currentKeyState.IsKeyUp(Keys.D))
{
ship.rightTurnProgress -= (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyUp(Keys.D) && rightTurnProgress > 0)
{
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(-0.4f * rightTurnProgress);
}
Since easing into the lean works no problem, I thought easing out of the lean would be a simple matter of reversing the process. However, it tends to not go all the way back to the default position after a long strafe. If you tap the key, it snaps all the way back to the full lean of the -opposite- direction. This isn't what I expected at all. What am I missing here?
I suggest you represent the rotation of you ship as a quaternion. That way you can use an interpolation function such as slerp. Simply have a second quaternion that represents you targeted lean angle and the ship will smoothly rotate until it achieves the targeted angle.
Here's a good tutorial on quaternions. If you want to avoid quaternions use MathHelper.Lerp to smoothly transition from the current value to the target.
if (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 1, somefloat * timeDelta);
}
else if (currentKeyState.IsKeyDown(Keys.a))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, -1, somefloat * timeDelta);
}
else (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 0, somefloat * timeDelta);
}
Edit: Also there is a GameDev stack overflow so check it out if you have more questions.
Unless you know how long the turn is or you have some kind of acceleration vector you will have to wait until the turn is stopped before returning the sprite angle to neutral, then what happens when the player turns left before the sprite has reached its neutral position? I assume that when you turn right using RightTurnProgress you also have a LeftTurnProgress I suggest you combine them into one variable to keep it smooth and avoid the snapping effect you are getting.
You are creating an 'absolute' rotation matrix so you don't need to flip the sign to -0.4f. Why not just have a variable called ship.lean and calculate the rotation matrix every update. Then you just need logic to ease ship.lean between -1 (left lean) and 1 (right lean) or 0 for no lean.
I am trying to simulate gravity in my first xna 2d game. I have the following
//Used for Jumping
double elapsedAirTime = 0.0;
double maxAirTime = 8.35;
//End Jumping
So I am trying to move the sprite up by a certain amount while the elapsedAirTime < maxAirTime
However, there is some issue where my code only seems to move the sprite up once and not multiple times during this segment of time. here is the code in my "player.cs" class, or the update method of the class.
if (newState.IsKeyDown(Keys.Space))
{
if(oldState.IsKeyUp(Keys.Space))
{
//if we are standing or jumping then change the velocity
if (playerState == PlayerStates.Standing)
{
playerState = PlayerStates.Jumping;
this.position.Y -= (float)(30.0 + ((1.2)*elapsedAirTime)*elapsedAirTime);
}
}
}
//if we are jumping give it some time
if (playerState == PlayerStates.Jumping)
{
if ((elapsedAirTime < maxAirTime) && position.Y < 3)
{
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
elapsedAirTime += gameTime.ElapsedGameTime.TotalSeconds;
}
//otherwise time to fall
else
{
playerState = PlayerStates.Falling;
}
}
//add gravity to falling objects
if (playerState == PlayerStates.Falling || playerState == PlayerStates.Standing)
{
//if we are above the ground
if (this.position.Y < windowBot - 110)
{
//chnage state to falling
playerState = PlayerStates.Falling;
this.position.Y += 3.0f + ((float)(gameTime.ElapsedGameTime.TotalSeconds));
}
else
{
playerState = PlayerStates.Standing;
elapsedAirTime = 0.0f;
}
}
Any help is much appreciated, please and thank you!
To give your sprite the feel of gravity, you should add velocity and acceleration to your Sprite class. Then, create an Update method for the Sprite, and have acceleration be added to your velocity every update, and velocity added to position every update. Position should not be based on the amount of elapsed air time. You can set the acceleration to a constant gravitational value, and then add to the velocity of the Sprite whenever you jump. This will create a flowing parabolic jump that looks nice. If you want to include timing, you can pass the GameTime into the Sprite's Update method, and use it as a modifier on the velocity. Here is an example Update method:
void Update(GameTime gt)
{
int updateTime = gt.ElapsedGameTime.TotalMilliseconds - oldgt.ElapsedGameTime.TotalMilliseconds;
float timeScalar = updateTime / AVG_FRAME_TIME;
this.velocity += this.acceleration * timeScalar;
this.position += this.velocity;
oldgt = gt;
}
If you use timing, this method is a little complicated. You have to keep track of how much time the update took, then divide it by the average amount of time an update or frame should take to get the amount you should adjust your velocity by. Without timing, the method is very simple:
void Update()
{
this.velocity += this.acceleration;
this.position += this.velocity;
}
I would suggest using the simpler method until you understand exactly how timing works and why you need to implement it.
It looks like this line is at fault:
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
I think you will find that this updates the sprites position quicker than you imagine, the sprite will move 330 pixels up the screen in 10 updates (assuming Game.IsFixedTimeStep == true) that is 1 tenth of a second realtime
It is likely that this is just updating so quickly that you don't get a change to see it rise before the && position.Y < 3 condition kicks in and changes the playerState.
It looks like you are trying to say - jump at a rate of x pixels per second for upto 8.5 seconds so long as space is held.
What you need for that is to change the calculation to this.position.y -= (float) (30 * gameTime.ElapsedGameTime.TotalSeconds), this will give a very liner movement to the jump action but it will mean that the sprite jumps at exactly 30 pixels per second.
If Game.IsFixedTimeStep == true - which is the default - the update gets called 60 times per second so gameTime.ElapsedGameTime.TotalSeconds is going to be about 0.1 every update. If something happens to cause an update to skip (rendering issues for example) then update will get delayed and gameTime.ElapsedGameTime.TotalSeconds may be 0.3 (the 2 updates skipped) but the formular still works out the correct jump rate.
I think I've configured Box2d to have some sort of maximum velocity for any body, but I'm not sure. I apply an impulse like (100000000, 100000000), and the body moves just as fast as (100, 100) - which is not that fast at all.
I'm using the Box2d XNA C# port.
My game is a top-down 2d.
Here is some code that may be relevant:
private readonly Vector2 GRAVITY = new Vector2(0, 0);
public void initializePhysics(ContactReporter contactReporter)
{
world = new World(GRAVITY, true);
IContactListener contactListener = contactReporter;
world.ContactListener = contactListener;
}
public void Update(GameTime gameTime)
{
// ...
worldState.PhysicsWorld.Step((float)gameTime.ElapsedGameTime.TotalSeconds, 10, 10);
//...
}
Here is some example code that applies the impulse:
private void ApplyImpulseFromInput()
{
Vector2 movementImpulse = new Vector2();
if (inputReader.ControlActivation(ActionInputType.MOVE_LEFT) == 1f)
{
movementImpulse.X = -Constants.PLAYER_IMPULSE_CONSTANT;
} else if (inputReader.ControlActivation(ActionInputType.MOVE_RIGHT) == 1f)
{
movementImpulse.X = Constants.PLAYER_IMPULSE_CONSTANT; ;
}
if (inputReader.ControlActivation(ActionInputType.MOVE_UP) == 1f)
{
movementImpulse.Y = -Constants.PLAYER_IMPULSE_CONSTANT; ;
} else if (inputReader.ControlActivation(ActionInputType.MOVE_DOWN) == 1f)
{
movementImpulse.Y = Constants.PLAYER_IMPULSE_CONSTANT; ;
}
model.Body.ApplyImpulse(movementImpulse, model.Position);
}
If Constants.PLAYER_IMPULSE_CONSTANT is anywhere from 1000f to 1000000000f, the player can move at most (-120, -120) to (120, 120). If the constant is less, like 1f, the player will move more slowly.
This code is used to set up physics for everything in the game world:
controller.Model.BodyDef = new BodyDef();
controller.Model.BodyDef.type = controller.Model.Mobile ? BodyType.Dynamic : BodyType.Static;
controller.Model.Body = worldState.PhysicsWorld.CreateBody(controller.Model.BodyDef);
controller.Model.Body.SetLinearDamping(10.0f);
Could it possibly be the linear damping? I changed it from 10.0f to 0, with no effect.
UPDATE: Weirdness with linear damping: I have made these observations on the body that is moved with the apply impulse method above:
Linear Damping Max Speed
0f 120
10f 120
50f 120
55f 90
60f 0
70f 0
100f 0
100000f 0
Why is there a range of sensitivity in linear damping between 50f and 60f?
That's not the way to fix that problem. You are supposed to scale down your objects when creating them in the box2D world. Then you can just scale the information that box2D gives you back to the size of your world.
Let's say for example that a box in my world is 120 pixels long. If I scaled the object down by 30 times so that it can be simulated by box2d properly then the length of my box in the box2D world would be 4 "pixels" long (120 / 30 or size of your object / scale). Now, let's say that box2D calculates that my box moved 3 "pixels" in the box2d world. I could then grab that information and scale it back up to my world's size which would mean that the box just moved 90 pixels (3 * 30). I hope that didn't sound too confusing. I usually find it hard to explain myself.
I have the same problem with version 2.1a
i found out that changing line 128 in b2Settings.as can help.
Change static public const b2_maxTranslation:Number = 2.0 to a higher number.
sure that this isn't the right way, but right now dont really know how to do it correctly.