I'm trying to implement my own gravity for a character.
But for some unknown to me reason the character's landing speed is higher landing than when jumping, by quite a lot (~15 initial jump speed, ~24 final land speed). I'm ultimately trying to replicate behavior shown on the gif below.
https://imgur.com/a/q77w5kS
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Movement : MonoBehaviour {
public float speed = 3;
public float jumpSpeed = 15;
public float gravity = -1f;
private float ySpeed = 0;
private float jumpTime = 0;
private bool direction = false; //true for going up, false for falling down - gravity
private bool previousDirection = false; //to keep track of changing direction
private CharacterController _characterController;
// Start is called before the first frame update
void Start() {
_characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update() {
float dx = Input.GetAxis("Horizontal");
float dz = Input.GetAxis("Vertical");
float dy = Input.GetAxis("Jump");
// move around
Vector3 movement = new Vector3(dx, 0, dz);
// limit diagonal movement
movement = Vector3.ClampMagnitude(movement, 1);
// speed
movement *= speed;
// change of direction
if(ySpeed>0 && direction!=true)
direction = true;
else if(ySpeed<0 && direction!=false)
direction = false;
// if changed direction - peak of the jump
if(direction!=previousDirection){
Debug.Log("Jump Time = " + jumpTime);
jumpTime = Time.deltaTime;
previousDirection = direction;
}
// jump/on ground
if(_characterController.isGrounded) {
ySpeed = -0.5f;
jumpTime = 0;
if(dy > 0f){
ySpeed = jumpSpeed;
}
}
// gravity - when not on ground
else{
// dv/dt
ySpeed += gravity*jumpTime;
// add jump time
jumpTime += Time.deltaTime;
// Debug.Log(jumpTime);
Debug.Log(ySpeed);
}
movement.y = ySpeed;
// direction adjustment
movement = transform.TransformDirection(movement);
movement *= Time.deltaTime;
_characterController.Move(movement);
}
}
I think I figured out what was wrong with my idea.
ySpeed += gravity*jumpTime
So every frame I'm adding more and more acceleration downwards. This should just be: ySpeed += gravity *Time.deltaTime
(Acceleration due to gravity is then constant, not getting greater as time passes)
It is being integrated over many steps, so each Update() cycle is a slice of time that adds some velocity based on acceleration due to gravity, multiplied by the amount of time taken by that little slice.
In another words...
Gravity is a constant acceleration. I've made it a linear function of the time spent in the air. So, I start the jump with no gravity and end the jump with very high gravity.
Related
I use the First Person Character Controller Script from Brackeys. But sometimes, my character walks backwards with no button pressed. I've tried to change the values of the Character Controller and to change the values of the axis in the Project Settings, but nothing really helped.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovementScript : MonoBehaviour
{
public CharacterController controller;
public Transform groundcheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
public float speed = 12f;
public float gravity = -9.81f;
public float jumpheight = 3f;
Vector3 velocity;
bool isGrounded;
void Start()
{
}
void Update()
{
isGrounded = Physics.CheckSphere(groundcheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
if (Input.GetButtonDown("Jump") && isGrounded)
{
velocity.y += Mathf.Sqrt(jumpheight * -2f * gravity);
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
}
I really dont know what the problem is, but maybe you can help me
I see nothing wrong with the script. Something must be giving a vertical input outside the script.
If you're using a controller, your left joystick might have some drift issues. Adding some deadzone to your movement vector can prevent any involuntary movement from happening. For example, you can check if your input is inferior to a certain threshold and set it to zero if that's the case.
Here's a simple way to do it (in this example, the threshold is 0.125. If the joystick drift still gives an input, try an higher value.):
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
if(x <= 0.125f)
x = 0
if(y <= 0.125f)
y = 0
Little late here but I had the same issue.
What caused it was having another controller plugged in besides mouse and keyboard, in my case a steering wheel. So make sure all extra controllers are unplugged
So I am developing an online multiplayer FPS game for my SDD major work, and I have run into a problem. Below is the movement code for my player. I am trying to implement strafing and bunnyhopping into my game, and so I setup a test environment within my project and I managed to get everything working. The only problem was that after I tried to put this back into my main project, it stopped working.
The main problem is that the jumping only works sometimes. The player only jumps a very small amount, and sometimes stay there, meaning that there is no friction as the player is stuck off the ground. He also seems to phase beneath the ground a tiny bit, but the collider on him shouldn't let that happen. I have tried to remove the code that makes downward falls faster, but that only fixes it sometimes.
Code link: https://pastebin.com/cLkemQhX
public class PlayerMovement: MonoBehaviourPun
{
[Header("Movement Settings")]
public float maxVelocityGround = 15f;
public float maxVelocityAir = 10f;
public float groundAccelerate = 90f;
public float airAccelerate = 180f;
public float fallMultiplier = 1.2f;
public float lookSens = 8f;
public float slowDrag = 0;
public float thrusterForce = 7f;
public float friction = 10f;
private float _friction = 0f;
[Header("Rotation")]
float xRot = 0F;
float yRot = 0F;
float minY = -90f;
float maxY = 90f;
Quaternion originalRotation;
//More Movement Variables
private float distToGround;
private Vector3 _velocity;
private Vector2 xyVelocity;
[Header("References")]
//public Text grounded;
//public Text velocity;
public Camera cam;
private Move motor;
private Rigidbody rb;
private CapsuleCollider collidr;
private GameObject rayPoint;
//private PlayerSetup playerSetup;
//Booleans
private bool jumpy = false;
void Start()
{
_friction = friction;
//Locks the cursor to the middle of the screen, and hides it from view
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
//These attatch the correct objectss to their references in the script, defined above
motor = GetComponent<Move>();
rb = GetComponent<Rigidbody>();
collidr = GetComponent<CapsuleCollider>();
rayPoint = GameObject.Find("point");
//playerSetup = GetComponent<PlayerSetup>();
//Sets the distance to the ground from the center of the collider, used in determining whe nthe player is on the ground
distToGround = collidr.bounds.extents.y;
if (GetComponent<Rigidbody>())
GetComponent<Rigidbody>().freezeRotation = true;
originalRotation = transform.localRotation;
}
void FixedUpdate()
{
//Debug function that shows a visual representation of when the player is on the ground
//string a = isGrounded().ToString();
//grounded.GetComponent<Text>().text = a;
//Finds the Velocity in the x plane. Also used in debug, as it shows it on screen
//xyVelocity = new Vector2(rb.velocity.x, rb.velocity.z);
//velocity.GetComponent<Text>().text = xyVelocity.magnitude.ToString("0.000");
//This statement checks if the player both is on the ground, and has requested to jump. If so it will jump
//while also not allowing multiple jumps while in the air. If the player holds it down, he will jump as soon
//as he touches the ground, avoiding friction and allowing the player to bunnyhop
if (Input.GetKey("space") && isGrounded())
{
rb.AddForce(Vector3.up * thrusterForce, ForceMode.Impulse);
jumpy = true;
}
//rb.AddForce(Jump(), ForceMode.Impulse);
//This makes you fall faster than you rise, and make jumping feel nicer overall.
if (rb.velocity.y < 0)
{
rb.velocity += Vector3.up * Physics.gravity.y * fallMultiplier * Time.deltaTime;
}
//Gets the input from the axis'
float input_y = Input.GetAxis("Vertical");
float input_x = Input.GetAxis("Horizontal");
//Calculates the direction we want to move in, taking into account mouse movement to allow for strafing
Vector3 accelDir = (transform.forward * input_y + transform.right * input_x).normalized;
//Checks if we are not pressing any inputs, and makes us decelerate faster, to make moving feel more snappy
decelerate(accelDir);
//This code adjusts the vector to be projected onto the plane that we are currently moving on. Makes strafing better
RaycastHit hit;
Physics.Raycast(collidr.center, Vector3.down, out hit, 1000);
accelDir = Vector3.ProjectOnPlane(accelDir, hit.normal).normalized;
//Finds the current velocity of our RIgidBody
Vector3 curVel = rb.velocity;
//Determines whether or not we want to use the air movement(ignore friction) or not.
//The boolean checks if we have just jumped, so while we are still on the ground, we don't want to calculate friciton
if (isGrounded() && jumpy == false)
{
_velocity = MoveGround(accelDir, curVel);
}
else
{
_velocity = MoveAir(accelDir, curVel);
}
//Apply Movement
rb.velocity = _velocity;
//These call the rotation functions below, one rotates the camera in the y plane, the other the rigidbody in the x plane, which has the camera as a child
cameraRotate();
rbRotate();
jumpy = false;
}
private Vector3 MoveGround(Vector3 accelDir, Vector3 prevVelocity)
{
// Apply Friction
float speed = prevVelocity.magnitude;
if (speed != 0) // To avoid divide by zero errors
{
float drop = speed * _friction * Time.fixedDeltaTime;
prevVelocity *= Mathf.Max(speed - drop, 0) / speed; // Scale the velocity based on friction.
}
return Accelerate(accelDir, prevVelocity, groundAccelerate, maxVelocityGround);
}
private Vector3 MoveAir(Vector3 accelDir, Vector3 prevVelocity)
{
return Accelerate(accelDir, prevVelocity, airAccelerate, maxVelocityAir);
}
private Vector3 Accelerate(Vector3 accelDir, Vector3 prevVelocity, float accelerate, float max_velocity)
{
float projVel = Vector3.Dot(prevVelocity, accelDir); // Vector projection of Current velocity onto accelDir.
float accelVel = accelerate * Time.fixedDeltaTime; // Accelerated velocity in direction of movment
// If necessary, truncate the accelerated velocity so the vector projection does not exceed max_velocity
if (projVel + accelVel > max_velocity)
{
accelVel = max_velocity - projVel;
};
return prevVelocity + accelDir * accelVel;
}
//Function to check if we are on the ground
public bool isGrounded()
{
return Physics.Raycast(collidr.bounds.center, Vector3.down, distToGround);
//Below is a capsule cast, it would be better to implement because it has a thickness
//return Physics.CheckCapsule(collider.bounds.center, new Vector3(collider.bounds.center.x, collider.bounds.min.y, collider.bounds.center.z), collider.radius * 0.9f);
}
/// <summary>
/// decelerates us faster when not moving
/// </summary>
/// <param name="input"></param>
public void decelerate(Vector3 input)
{
if (input == Vector3.zero)
{
_friction = 10f * friction;
}
else
{
_friction = friction;
}
}
/// <summary>
/// This function is the camera rotation function
/// </summary>
public void cameraRotate()
{
//Get the input from the mouse and multiply it by sensitivity so it can be adjusted from ingame
yRot += Input.GetAxis("Mouse Y") * lookSens;
//Clamp it so that we cant just keep spinning in the Y direction
yRot = clamp(yRot, minY, maxY);
//Calculate the quaternion using the amount of rotation found before. We use the negative mouse input as the amount
//of rotation, and then specify that we want this rotation to be around the x axis. It seems counter intuitive, but this is the way to do it
Quaternion yRotAngle = Quaternion.AngleAxis(-yRot, Vector3.right);
//Apply this rotation to the cameras transform
cam.transform.localRotation = originalRotation * yRotAngle;
}
/// <summary>
/// Basically the same as above, but we dont need to clamp it because we want to be able to forever spin to the left and right
/// </summary>
public void rbRotate()
{
xRot += Input.GetAxis("Mouse X") * lookSens;
Quaternion xRotAngle = Quaternion.AngleAxis(xRot, Vector3.up);
rb.transform.localRotation = originalRotation * xRotAngle;
}
/// <summary>
/// just clamps the angle we input between the next 2 floats
/// </summary>
/// <param name="angle">the float representation of the angle</param>
/// <param name="min">the min that the angle can be</param>
/// <param name="max">you got it by now right?></param>
/// <returns></returns>
private float clamp(float angle, float min, float max)
{
return Mathf.Clamp(angle, min, max);
}
}
I'm making a simple project in Unity where there is a Ball attached to a SpringJoint2d component the ball is on an angled slope, like in the image below:
I simply want the user to be able to drag the ball backward along the edge of the slope only,in other words I don't want the user to be able to move the ball away from the slope or into it.
I'v been trying several ways I thought could do the job hers the script of the dragging with what I tried:
(This Is the updated version)
public class ball : MonoBehaviour
{
public Rigidbody2D rb;
public Transform spring;
public Transform calcpoint;
private Vector3 start;
private Vector3 end;
private bool isPressed = false;
RaycastHit2D[] hits = new RaycastHit2D[2];
RaycastHit2D[] hits2 = new RaycastHit2D[2];
float factor = 0;
private void OnMouseDown()
{
if (!isPressed)
{
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
/// <summary>
/// release the ball from the spring joint after a small amount of time
/// </summary>
/// <returns></returns>
IEnumerator release()
{
yield return new WaitForSeconds(0.1f);
rb.GetComponent<SpringJoint2D>().enabled = false;
}
// Update is called once per frame
void Update()
{
if (isPressed)
{
if (Vector3.Distance(spring.position, rb.position) > 3f || spring.position.x < (rb.position.x - 1)) return;//restrict the dragging of the ball to not go beyond the spring point and not too far back
float angle = 0;
if (checkGround() > 1)//if we hit the slope with the ray cast downward from the mouse/Tap position
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
factor = (float)(((45 - angle) * 0.02) + 1) * (angle / 45);//an inaccurate formula to offset the ball to be on top of the slope that works just fine with some glitches
rb.position = hits[1].point + new Vector2(0, factor * 1f);//position the ball at the point were the ray cast downward from the mouse hit
//(that puts the ball center on the line of the slope) so I offset it usinf the formula above
}
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
}
here are the settings on the ball:
and the slope setup:
The dragging of the ball works fine ,at one point the player could drag it in the air or into the slope, I managed to fix that with the new code so now the player could only drag it on the edge, though my calculations are still a bit flawed and when the slopes angle is changed the ball would dip a bit inside the slope and that causes some problems at release.
The method used to try to solve the problem is simple, when the player start dragging the ball I cast a ray from the mouse downward and pit the ball on the point of impact with the slope ,offsetting it to sit on top of it,right ow the problem is that the offsetting part is not accurate enough.
I hope I explained myself a bit better this time Thanks:)
After lots of trial and error I did manage to come up with a perfect solution to the problem so I thought I might as well share the answer maybe it will help someone.
here is the updated code I changed my method of restricting the movement completely now I use simple linear line equation as shown below:
private void OnMouseDown()
{
if (!isPressed)
{
//cast a ray on the slope
if (checkGround() > 1)
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
slope = Mathf.Tan(angle * Mathf.Deg2Rad);//get the slope steepiness
}
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
void Update() {
if (isPressed)
{
xMove = Camera.main.ScreenToWorldPoint(Input.mousePosition).x - spring.position.x;//get how much the mouse moved backward or forward
if (xMove < -3f ) xMove = -3f; //restrict the drag range to 3 backward
if (xMove > 0.3f) xMove = 0.3f;//restrict the drag range to 0.3 forward
xpos = spring.position.x+xMove;//since the ball and the spring start at exactly the same position the new ball's x position would be the spring x + the x movement we calculated above
ypos = (xMove * slope)- spring.position.y; //the y posistion would be y=mx+b so the the x movement * the slop steepiness - the starting y position
rb.position = new Vector2(xpos, -ypos);//set the new position of the ball
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
I want to program a bouncing ball. The actual bounce shouldn't follow real physic laws but rather build an absolute sine wave. Like in this image
That is pretty easy so far. See the attached code.
However I want to slow down the movement of my ball as soon as I press a button.
I'll reduce the movement speed which will slow down the transformation on the x axis. But the ball is still bouncing pretty fast up and down, because neither frequency nor magnitude of the sine wave has changed. But when I change one or both, this will end in a weird behavior.
Imagine the ball is on the downmove at 20% of the distance from top to bottom. When I now change any sine parameters the ball will instantly move on the y axis which looks really weird and not smooth.
So my actual question is: How can I slow down an absolute sine wave (x/y axis translation) without choppy movement?
float _movementSpeed = 10.0f;
float _sineFrequency = 5.0f;
float _sineMagnitude = 2.5f;
Vector3 _axis;
Vector3 _direction;
Vector3 _position;
void Awake()
{
_position = transform.position;
_axis = transform.up;
_direction = transform.right;
}
void Update()
{
_position += _direction * Time.deltaTime * _movementSpeed;
// Time.time is the time since the start of the game
transform.position = _position + _axis * Mathf.Abs(Mathf.Sin(Time.time * _sineFrequency)) * _sineMagnitude;
}
I guess you want this (more red - lower freq, more blue - higher):
The easiest way to achieve this is to scale deltatime, for example this was generated with:
using UnityEngine;
using System.Collections;
public class Bounce : MonoBehaviour {
// for visualisation of results
public Graph graph;
[Range(0, 10)]
public float frequency = 1;
float t;
void Update()
{
t += Time.deltaTime * frequency;
float y = Mathf.Abs(Mathf.Sin(t));
float time = Time.realtimeSinceStartup;
graph.AddPoint(time, y, Color.Lerp(Color.red, Color.blue, frequency/10));
}
}
And for the completeness, the Graph class:
using UnityEngine;
using System.Collections.Generic;
public class Graph : MonoBehaviour {
struct PosAndColor
{
public Vector3 pos;
public Color color;
}
List<PosAndColor> values=new List<PosAndColor>();
public void AddPoint(float x, float y, Color c)
{
values.Add(new PosAndColor() { pos = new Vector3(x, y, 0), color = c });
}
void OnDrawGizmos()
{
for (int i = 1; i < values.Count; i++)
{
Gizmos.color = values[i - 1].color;
Gizmos.DrawLine(values[i - 1].pos, values[i].pos);
}
}
}
Another aproach would be to change the frequency with the offset of your sine to match the current amplitude. I don't think you'd need this here, but if you want to I can post some more on the subject.
(Using unity3d 4.3 2d, it uses box2d like physics).
I have problems with predicting trajectory
I'm using:
Vector2 startPos;
float power = 10.0f;
float interval = 1/30.0f;
GameObject[] ind;
void Start (){
transform.rigidbody2D.isKinematic = true;
ind = new GameObject[dots];
for(int i = 0; i<dots; i++){
GameObject dot = (GameObject)Instantiate(Dot);
dot.renderer.enabled = false;
ind[i] = dot;
}
}
void Update (){
if(shot) return;
if(Input.GetAxis("Fire1") == 1){
if(!aiming){
aiming = true;
startPos = Input.mousePosition;
ShowPath();
}
else{
CalculatePath();
}
}
else if(aiming && !shot){
transform.rigidbody2D.isKinematic = false;
transform.rigidbody2D.AddForce(GetForce(Input.mous ePosition));
shot = true;
aiming = false;
HidePath();
}
}
Vector2 GetForce(Vector3 mouse){
return (new Vector2(startPos.x, startPos.y)- new Vector2(mouse.x, mouse.y))*power;
}
void CalculatePath(){
ind[0].transform.position = transform.position; //set frist dot to ball position
Vector2 vel = GetForce(Input.mousePosition); //get velocity
for(int i = 1; i < dots; i++){
ind[i].renderer.enabled = true; //make them visible
Vector3 point = PathPoint(transform.position, vel, i); //get position of the dot
point.z = -1.0f;
ind[i].transform.position = point;
}
}
Vector2 PathPoint(Vector2 startP, Vector2 startVel, int n){
//Standard formula for trajectory prediction
float t = interval;
Vector2 stepVelocity = t*startVel;
Vector2 StepGravity = t*t*Physics.gravity;
Vector2 whattoreturn = ((startP + (n * stepVelocity)+(n*n+n)*StepGravity) * 0.5f);
return whattoreturn;
}
Using this, I get wrong trajectory.
1. It's like gravity doesn't drag trajectory down at all, and yes i know that gravity is weak because:
t*t*Physics.gravity = 0.03^2 * vector2(0, -9.8) = vector2(0, -0.00882)
But that is the formula :S
2. Since gravity is low, velocity is too strong.
Here is the video:
http://tinypic.com/player.php?v=1z50w3m&s=5
Trajectory formula form:
http://www.iforce2d.net/b2dtut/projected-trajectory
What should I do?
I found that if I set
StepGravity to something stronger like (0, -0.1)
and devide startVel by 8
I get nearly right trajectory, but i don't want that, I need true trajectory path.
Users from answer.unity3d.com said I should ask here, because here is a bigger group of mathematical coders.
And I searched a lot about this problem (that how I found that formula).
you're only calculating the effect of gravity over 1/30th of a second for each step - you need to do it cumulatively. Step 1 should end with a velocity of 0.09G, Step 2 with .18G, step3 with .27G etc.
Here's a very simple example that draws the ballistic trajectory based on start velocity and a supplied time:
using UnityEngine;
using System.Collections;
public class grav : MonoBehaviour {
public Vector3 StartVelocity;
public float PredictionTime;
private Vector3 G;
void OnDrawGizmos()
{
if (G == Vector3.zero)
{
// a hacky way of making sure this gets initialized in editor too...
// this assumes 60 samples / sec
G = new Vector3(0,-9.8f,0) / 360f;
}
Vector3 momentum = StartVelocity;
Vector3 pos = gameObject.transform.position;
Vector3 last = gameObject.transform.position;
for (int i = 0; i < (int) (PredictionTime * 60); i++)
{
momentum += G;
pos += momentum;
Gizmos.DrawLine(last, pos);
last = pos;
}
}
}
In you version you'd want draw your dots where I'm drawing the Gizmo, but it's the same idea unless I'm misunderstanding your problem.