How can I keep the diagonal speed to be the same as the horizontal and vertical speed without clamping any value or using ".normaized". I tryed to normalize the values but I lost the joystick values between 1 and 0. Here is my code :
void ListenInput()
{
Vector3 rightDirection = camera.right;
Vector3 frontDirection = camera.GetForwardFromAngleY();
move = new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
);
MoveCharacter(rightDirection * move.x);
MoveCharacter(frontDirection * move.y);
}
void MoveCharacter(Vector3 velocity)
{
transform.position += velocity * Time.deltaTime * runningSpeed;
}
Here, you should clamp the magnitude of the input Vector2.
For example with Vector2.ClampMagnitude() from the Unity API.
That will keep the input non-binary and prevent the diagonal from getting larger than purely horizontal/vertical inputs.
void ListenInput()
{
Vector3 rightDirection = camera.right;
Vector3 frontDirection = camera.GetForwardFromAngleY();
move = new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
);
move = Vector2.ClampMagnitude(move, 1f);
MoveCharacter(rightDirection * move.x);
MoveCharacter(frontDirection * move.y);
}
void MoveCharacter(Vector3 velocity)
{
transform.position += velocity * Time.deltaTime * runningSpeed;
}
If you normalize a vector you will make sure it's length is 1. This is a great way to avoid quirks like "diagonal movement is faster than normal movement".
However, the fact that the length is always 1 also means that there is no "move slowly" or "move at full speed" distinction from the joystick. When you say "I lost the joystick values between 1 and 0" is due to this fact.
One way developers get around this is by using a mathematical formula to scale the speed.
You could:
Use the largest value (horizontal or vertical) to control the speed
Use the smallest value
Use a combination of the two
Another way to do this is to store how long ago the movement started, then scale the speed based on that. This method has its own challenges, but is very familiar to players.
Examples
For instance, if I have:
horizontalInput = 1
verticalInput = 0.5
This means my normalized vector looks like this:
I could:
Use the largest value
Move at full speed (1) on the direction of my vector.
Use the smallest value
Move at half speed (0.5) on the direction of my vector.
Use a Use a combination of the two values
For this instance, lets use the following formula: (x+y)/2.
Move at 3/4 speed (0.75) on the direction of my vector.
NOTE: This formula will not "feel" as nice if you have x=0 and y=1, this is just an example. You most likely want to use Min, Max, Avg and if-clauses to control how the speed works.
You can use different formulas and different techniques to make the movement in your game feel like what you want, but take the time to analyze WHY it feels like that.
Related
I'm trying to make an airplane controller, I am kind of aiming for something between arcade and realistic, so I want the plane to turn with a force proportional to the roll.
I haven't coded in any adjustments and I'm still prototyping the whole thing, but I encountered a problem with getting the signed rotation angle while using quaternions, I had a look at Determining if quarternion rotation is clockwise or counter clockwise here on SO but I am having trouble generalizing the solution to the (almost) arbitrary plane the rotation can be at.
What I made by now:
private void FixedUpdate()
{
float desiredYaw = _yaw * _rotationSpeed * Time.fixedDeltaTime;
float desiredPitch = -_pitch * _rotationSpeed * Time.fixedDeltaTime;
float rotationStepSize = _throttle * Time.fixedDeltaTime;
Quaternion toRotate = Quaternion.Euler(desiredPitch, 0, desiredYaw);
Quaternion straighRotation = Quaternion.LookRotation(_transform.forward, Vector3.up );
_rotation = _transform.rotation * toRotate;
float turningForce = Quaternion.Angle( _rotation, straighRotation );
_rigidbody.MoveRotation( _rotation );
_rigidbody.AddTorque( turningForce * _rotationForce * rotationStepSize * Vector3.up );
_rigidbody.AddRelativeForce( _speed * rotationStepSize * Vector3.forward );
}
EDIT: I realized I'm calculating the turning force using the roll rather then the yaw, that was intended just wrong wording, corrected now.
Since all you need is a factor that describes how downward the plane's right is, you can just use the y component of the plane's right for that. No need to bring in quaternions or even trigonometry. Explanation in comments:
private void FixedUpdate()
{
// ...
// Calculate how downward local right is in range [-1,1]
// The more downward, the more tilted right the plane is
// positive = tilted right
// negative = tilted left
float turnFactor = -_transform.right.y;
// Could do things to modify turnFactor to affect easing here.
// For instance, if turning rate should start slower then rapidly increase:
// turnFactor = Mathf.Sign(turnFactor) * turnFactor * turnFactor;
// Use factor and _rotationForce member to calculate torque, apply along
// global up.
// We expect to call this every fixed frame so we can just use the default
// ForceMode of ForceMode.Force which multiplies fixed delta time inside.
_rigidbody.AddTorque(_rotationForce * turnFactor * Vector3.up);
// ...
}
So I am currently creating movement for a character and all is well except diagonal movement is doubled because it is obviously combining vertical and horizontal movement. Now I did try normalizing the Vector3, but that results in a delayed stop for the character. According to other forums I read, I think it's because it should only normalize when it is greater/less than -1/1 but I don't know how to setup this constraint. Please help!
forwardInput = Input.GetAxis("Horizontal");
horizontalInput = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(horizontalInput, 0, forwardInput).normalized * speed * Time.deltaTime;
transform.Translate(movement);
Problem with always normalizing is that you move with the same speed no matter how far the input is pressed.
This is no problem on a PC with keyboard input which is either 0, -1 or 1 but will not work as expected if e.g. using a controller since it will also round up the value in case you normalize if the original input vector's magnitude is smaller then 1.
So yes you should normalize only in case the magnitude of the input vector exceeds 1 e.g. when pressing in two directions at the same time in order to maintain a certain maximum speed. In order to not type redundant code I would rather separate the vector and assignment in different variables and use Vector3.Normalize instead:
forwardInput = Input.GetAxis("Horizontal");
horizontalInput = Input.GetAxis("Vertical");
var inputVector = new Vector3(horizontalInput, 0, forwardInput);
// note that a magnitude is always positive, there is no -1
// if magnitude > 1 then implicitely also sqrMagnitude > 1 and the other way round
// sqrMagnitude is faster to access then magnitude and for this check
// here provides the same result
if(inputVector.sqrMagnitude > 1) inputVector.Normalize();
var movement = inputVector * speed * Time.deltaTime;
transform.Translate(movement);
Hi I am using this code to have objects moving on the y axis.
using UnityEngine;
using System.Collections;
public class TargetMovementVertical : MonoBehaviour
{
public int maxSpeed;
private Vector3 startPosition;
// Use this for initialization
void Start ()
{
startPosition = transform.position;
}
// Update is called once per frame
void Update ()
{
MoveVertical ();
}
void MoveVertical()
{
transform.position = new Vector3(transform.position.x, Mathf.Sin(Time.time * maxSpeed), transform.position.z);
if(transform.position.y > 1.0f)
{
transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
}
else if(transform.position.y < -1.0f)
{
transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
}
}
}
My only problem is that the object are only moving within 1 and -1 and i would like to have them move lower. is there a possible way please ?
Programming
In your code, you are setting the position using this line:
transform.position = new Vector3(transform.position.x, Mathf.Sin(Time.time * maxSpeed), transform.position.z);
Here, the only coordinate that is changing is the y coordinate. And it changes according to the function Mathf.Sin.
If you read the documentation for Mathf.Sin you will find that it returns values between -1 and +1.
That is why...
the object are only moving within 1 and -1
The simple solution is to multiply the result of Mathf.Sin by some factor.
Math
This is the sine function:
red plot: y = sin(x)
As you can see, the range of the sine function is [-1, 1]. Thus, regardless of what input value you put into the function, you will get a result in the interval [-1, 1].
If you multiply the input, you are changing the frequency of the sine wave, for example:
Red plot: y = sin(5x)
Observe that placing a factor inside the function will not affect the amplitud of the wave. Compare with the following:
Red plot: y = 5sin(x)
The above graph, at difference with the prior ones, has the range [-5, 5].
Here you can see them all for comparison:
Red plot: 5sin(x)
Blue plot: sin(x)
Purple plot: sin(5x)
These plots were created with the graphing calculator from meta-calculator. You can try the functions there yourself if you don't want to take my word for it.
To understand why the sin function has this shape, remember that the sine function takes an angle and returns the vertical component of a unit vector that has angle with the horizontal...
I mean this:
Unit circle with sine and cosine, θ=45 degrees.
Since we are taking a unit vector, (we are working on the unit circle), the maximum value that the vertical (sine) will take is 1, and the minimum is -1.
To understand how the sine plots we saw above come from this, I hope this animation makes it clearer:
Animation showing how the sine function (in red) y = sin(θ) is graphed from the y-coordinate (red dot) of a point on the unit circle (in green) at an angle of θ in radians.
Back to programming
As I said at the start of the answer, if you want to scale the movement, you can change the amplitude to the sine wave by multipliying the result by some factor, for example: Mathf.Sin(angle) * amplitude.
That amplitude value will tell how far the value will reach, that is, by multiplying by Mathf.Sin by amplitude you get a value in the range - amplitude and + amplitude.
I expect that you find that approach reasonable know that the reasoning behind it have been presented.
I hope the above explanation makes it clear that the sine function does not preserve factors. That is: sin(a*x) ≠ a*sin(x). In other words that the sine function is not transitive with scaling, the reason for that is that the sine function is NOT a linear transformation.
I am testing the accelerometer code in Unity3D 4.3. What I want to do is simple change the object angle while tilting the ipad, to fake view angle like real live. Everything works fine except for the fact that the accelerometer is a bit too sensitive and I can see the GameObject is like of flickering even I put it on table. How can I make it less sensitive so that even when you hold with your hand the angle will change according to the tilt and the object remain steady?
Here are my code:
void Update () {
Vector3 dir = Vector3.zero;
dir.x = Mathf.Round(Input.acceleration.x * 1000.0f) / 1000.0f;
dir.y = Mathf.Round(Input.acceleration.y * 1000.0f) / 1000.0f;
dir.z = Mathf.Round(Input.acceleration.z * 1000.0f) / 1000.0f;
// clamp acceleration vector to the unit sphere
if (dir.sqrMagnitude > 1)
dir.Normalize();
// Make it move 10 meters per second instead of 10 meters per frame...
dir *= Time.deltaTime;
dir *= speed;
acx = dir.x;
acy = dir.y;
acz = dir.z;
transform.rotation = Quaternion.Euler(dir.y-20, -dir.x, 0);
}
You may need to use a low pass filter (s. Exponential Moving Average for a better description regarding software) before using the signal output. I always use native code to get accelerometer and gyroscope values on iPhone so I am not 100% sure how Unity handles this. But from what you are describing the values appear unfiltered.
A low pass filter calculate a weighted average from all your previous values. Having for example a filter factor on 0.1 your weighted average is:
Vector3 aNew = Input.acceleration;
float a = 0.1f * aNew + 0.9f * a;
This way your values are smoothed at the expense of a small delay. Running the accelerometer with 50 Hz you won't notice it.
I couldn't make Kay's example work as it was not multiplying the last part, so here's my small correction:
Vector3 aNew = Input.acceleration;
a = (0.1 * aNew) + (0.9 * a);
I'm making a galaxian-like shooter, and my enemy objects have a destination Vector which they travel towards, using this bit of code:
position.X -= (Motion.X / Magnitude) * Speed;
position.Y -= (Motion.Y / Magnitude) * Speed;
Motion is worked out by:
this.Motion = InitialPosition - Destination;
This makes them travel in a straight line towards the destination.
However, I want to make them a bit more interesting, and travel on a sin or cos wave, a bit like Galaxian did.
How can I do this?
You might be better off defining a bezier curve for the movement function than simple functions like a sine wave. Galaxian certainly had more complex movements than that.
Here is a link to a primer on the maths of Bezier curves. It's quite a long document, but does a good job of covering the maths involved, with plenty of examples.
Hope that helps inspire you.
One way to do this would be to create an acceleration factor for the horizontal motion and add that factor to the horizontal speed every tick. So if your horizontal speed for a given enemy was 2 to begin, and your acceleration was -.01, then after 200 ticks the enemy would be going straight down, and after another 200 ticks it would be moving at a horizontal speed of -2. This will give a nice curve.
By determining the speed and acceleration randomly for each enemy (within certain limits determined by experimentation) you can create a nice looking variety of attack profiles without too much effort. This would give a very Galaxian-like motion.
You can do the same thing with the vertical as well, though, of course, the acceleration limits would be very different...for the horizontal acceleration you would probably want to determine a range that was equal in magnitude on either side of 0 (say -.02 to +.02), while for the vertical acceleration, you probably always want the ship to end up going down off the bottom of the screen, so you probably want that acceleration to always end up positive (or negative depending on how you're doing screen coordinates.)
You would do this by utilizing waypoint navigation, in line with your current motion code. You would calculate the waypoints by graphing the sine wave. You would do this by using something to the effect of Destination.Y = Math.Sin(Destination.X) - it's a little difficult to say for sure without seeing your code at large.
Creating an oscillator and moving the enemy (even without momentum) perpendicularly to its direction by an offset equals to the sine or cosine of the oscillator would be enough.
The following example, while working, is clearly just a guideline. I hope it can help you.
var dest = new PointF(200, 100);
var pos = new PointF(30, 140);
var oscAngle = 0d;
var dirAngle = Math.Atan2(dest.Y - pos.Y, dest.X - pos.X);
//Constants for your simulation
const int movSpeed = 2;
const int amp = 2;
const double frequency = Math.PI / 5;
//Inappropriate loop condition, change it to proper
while (true)
{
oscAngle += frequency;
//Scalar offset, you can use Cos as well
var oscDelta = Math.Sin(oscAngle);
//Linear movement
var stepVector = new SizeF((float)(Math.Cos(dirAngle) * movSpeed), (float)(Math.Sin(dirAngle) * movSpeed));
//Oscillating movement, making it transversal by adding 90° to the direction angle
var oscNormalAngle = dirAngle + Math.PI / 2;
//Vector for the oscillation
var oscVector = new SizeF((float)(Math.Cos(oscNormalAngle) * oscDelta) * amp, (float)(Math.Sin(oscNormalAngle) * oscDelta) * amp);
pos += stepVector + oscVector;
//Operate below
}