How to make Unity gravity and AddForce accelerate - c#

I'm making a clone of Hollow Knight, and my character is falling at a constant rate instead of accelerating. I tried changing the gravity scale and using Addforce instead of rigidbody gravity.
This is the code I tried for the gravity
public Rigidbody2D rb;
void FixedUpdate(){
rb.AddForce(-transform.up*100f * Time.fixedDeltaTime, ForceMode2D.Impulse);
}

I've done a lot of testing and it doesn't seem like what you say is true.
In a new project, I simply created a rigidbody2D gameobject and added this script to it.
public void Start()
{
StartCoroutine(PrintDistance());
}
IEnumerator PrintDistance()
{
float p = 0;
for (; ; )
{
p = transform.position.y;
yield return new WaitForSeconds(1);
print("Distance per second: " + (transform.position.y - p));
}
}
As you can see from the console, every second, the distance traveled increases, so the speed is not constant but accelerated.
How can you see that the falling speed is not constant?
Try adding this script and tell me the results.
I can speculate that some other part of your project is causing this problem, or you have changed some properties in the Physics panel in Project Settings, but that is not likely.
if you think my answer helped you, you can mark it as accepted. I would very much appreciate it :)

Related

How to convert my Character Controller into Rigibody Movement in Unity?

I'm making a 3D Side-Scroll Platformer Game,
I have trouble with my character when it steps on the moving platform it will not come along on the platform. I want my character to stay on the moving platform so I think converting my Character Controller into Rigibody will help me,
I need help to give me ideas on how I can reuse my Character Controller Script in Rigibody. This is my code, how can I reuse this in Rigibody script?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public CharacterController controller;
private Vector3 direction;
public float speed = 8;
public float jumpForce = 30;
public float gravity = -20;
public Transform groundCheck;
public LayerMask groundLayer;
public bool ableToMakeADoubleJump = true;
public Animator animator;
public Transform model;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (PlayerManager.gameOver)
{
//play death animation
animator.SetTrigger("die");
//disable the script
this.enabled = false;
}
float hInput = Input.GetAxis("Horizontal");
direction.x = hInput * speed;
animator.SetFloat("speed", Mathf.Abs(hInput));
bool isGrounded = Physics.CheckSphere(groundCheck.position, 0.15f, groundLayer);
animator.SetBool("isGrounded", isGrounded);
if (isGrounded)
{
//Jump Codes
ableToMakeADoubleJump = true;
if (Input.GetButtonDown("Jump"))
{
Jump();
}
}
else
{
direction.y += gravity * Time.deltaTime;
if (ableToMakeADoubleJump & Input.GetButtonDown("Jump"))
{
DoubleJump();
}
}
if (hInput != 0)
{
Quaternion newRoattion = Quaternion.LookRotation(new Vector3(hInput, 0, 0));
model.rotation = newRoattion;
}
//Move the player using the character controller
controller.Move(direction * Time.deltaTime);
}
private void DoubleJump()
{
//Double Jump Codes
animator.SetTrigger("doubleJump");
direction.y = jumpForce;
ableToMakeADoubleJump = false;
}
private void Jump()
{
direction.y = jumpForce;
}
}
I would not recommend switching between the two. It would get tricky, and think about it, you are alternating between two very different things. One is movement and one is physics.
However, I would reccomend adding to your current script so that the player would move with the moving platform.
There is a lot of stuff in this answer, so read the whole thing.
Btw, when I talk about velocity, in your case it is direction.
Since it seems like you know how to code pretty well, I won’t write out the script, rather tell you some physics ideas to get you going in the right direction.
The reason people can stand on a moving platform and not fall off is because of friction.
If you are standing on a gameObject with enough friction (you could add a physics material the gameObject you stand on and change friction there. Note that physics materials only work with rigidbodies, but you might want to use it to just read the value)
First of all, you are going to want to raycast down to obtain the object you are standing on. From there you can get the physics material from hit.collider.sharedMaterial (or any other hit. to obtain data about what object you are standing on.
If they friction is too low, just make the character slip off, like it was before (I assume)
If the friction is above a threshold, get the velocity from the object you are standing on. If it was a rigidbody, hit.rigidbody.velocity. If it is controlled by script, use hit.collider.gameObject.GetComponent<scriptname>().velocityvariablename This part is continued later on
This is not necessary but useful: You can think of this as grabbing on a rope. When you are grabbing on a slippery rope, and someone pulls it (Like tug of war), You won’t move because the rope will slide through your hands. If the rope had grip tape on it and someone pulled it, you would come with it because it has more friction. You can think of the platform the same way. Now on to the more complex part: When you grip a rope that is stationary, and someone pulls it, you come with it as its velocity changes. When the rope is already being pulled, so its velocity is not stationary and it is already something. You grab onto it and a similar thing happens. It is like you are becoming a part of that rope. Similar to how if you are running, the arms and legs and head is a part of you. If you lose grip, you are no longer a part of that body, like your arms falling off when running. In other words, you become part of the body when you attach yourself to it.
Bottom line:
Get the velocity of the platform and set platformVel to it, do not add that to velocity, rather do a seperate controller.Move(platformVel).
A small customization:
Vector3.Lerp the platformVel to 0, so it doesn’t change while on the platform, but gradually goes to (0,0,0) when you get off. This way, there is a little momentum maintained from standing on the platform.
Feel free to ask anything in the comments.

How to reduce 'speed' of a rigidbody without changing the distance it has to cover?

I am moving a rigidbody using rb.AddForce(force,ForceMode.Impulse) where force is the target position the rigidbody have to reach.
Now the speed it goes directly depends on the distance it has to cover.
Let's say the time taken to reach the target position is 3sec. I need the rigidbody to cover the same target pos in 5sec.
I dont want to change the timescale as it affects my gameflow
On Changing the velocity of rigidbody it fails to reach the target position
Some basic physics/math:
velocity = change-in-position / travel-time
force = mass * change-in-velocity / acceleration-time
For ease, we're going to call change-in-position as distance, and change-in-velocity/acceleration-time as acceleration
Now, since the acceleration-time component is effectively zero because you're using Impulse, we're going to remove it from the equation (in math terms, we set it at '1')
force = mass * change-in-velocity
Assuming your object starts at zero velocity, we can simplify change-in-velocity to just velocity
force = mass * velocity
force = mass * distance / travel-time
To bring that back into Unity code:
var mass = rb.mass;
var distance = destination.position - transform.position;
var travelTime = 5f; // seconds
var force = mass * distance / travelTime;
rb.AddForce(force, ForceMode.Impulse);
Note that this assumes a frictionless transfer and constant velocity.
If you ignore gravity, this code solves the problem, here I changed the drag according to weight and distance, it may be a little bit away from the destination at the end, the reason should be higher drag friction.
public void ForceToTarget(Transform target, float time = 1f)
{
var rb = GetComponent<Rigidbody>();
var vector = target.position - transform.position;
var distance = vector.magnitude;
rb.drag = distance/time;
rb.AddForce(vector*rb.mass*distance/time, ForceMode.Impulse);
}
If you want precise control over your speed, then stop using ForceMode.Impulse because other physics effects like drag will make your answers wrong. Instead, just set the speed yourself. You can do this with a Coroutine to control timing and ForceMode.VelocityChange to control the speed. Basically, just look at where you are, where the target is, how much time is left, and apply the speed directly.
private bool canMove = true;
public void MoveTo(Vector3 targetPosition, float targetTime)
{
if(canMove)
{
StartCoroutine(MoveToCoroutine(targetPosition,targetTime));
}
}
private IEnumerator MoveToCoroutine(Vector3 targetPosition, float time)
{
canMove = false;
while(time > 0)
{
var positionDelta = transform.position - targetPosition;
var targetSpeed = positionDelta / time;
var speedDelta = targetSpeed - rb.velocity;
rb.AddForce(speedDelta , ForceMode.VelocityChange);
yield return null;
time -= Time.deltaTime;
}
// Bring the object to a stop before fully releasing the coroutine
rb.AddForce(-rb.velocity, ForceMode.VelocityChange);
canMove = true;
}
I wrote this here into the text editor, no IDE and haven't tested it, but I'm pretty sure this'll do what you want.
Assuming you're using the target position as-is then larger vectors will cause larger force to be applied than smaller vectors. Similarly, if using a direction vector as-is then as the rb gets closer to the target the magnitute of the vector gets smaller and thus less force is applied.
To get a constant speed use the direction to the target and Normalise it instead. Regardless of the distance the direction vector will always have a magnitude of 1 so you can multiply it by any value to accurately control the speed of the object:
Rigidbody rb;
public Transform target;
public float dist;
public float speed = 2f; // or whatever
public float targetDistance = 40f; // or whatever
private void Start()
{
rb = GetComponent<Rigidbody>();
StartCoroutine("IMove");
}
IEnumerator IMove()
{
dist = Vector3.Distance(transform.position, target.position);
while (dist > targetDistance)
{
dist = Vector3.Distance(transform.position, target.position);
rb.AddForce(Vector3.Normalize(target.position - transform.position) * speed, ForceMode.Impulse);
yield return new WaitForFixedUpdate();
}
}
Without getting too much into the physics and maths, if you want it to travel slower but the same distance you need to reduce the gravity on it and the initial force.
Note in this example I am assuming the weight is 1 to make the calculation a bit easier for force.
public class TrevelSpeedAdjusted
{
public float speedFactor = 1;
void FixedUpdate()
{
// Reduce the gravity on the object
rb.AddForce(-Physics.gravity * rigidbody.mass * (1 - speedFactor));
}
public float AddAdjustedForce(Vector3 force, ForceMode forceMode)
{
rb.AddForce(force * speedFactor, forceMode);
}
}
So you can try DoTween package to do this pretty easily and its very convenient to use a package instead of using Unity's inbuilt system.
With doTween use this:
DOMove(Vector3 to, float duration, bool snapping) condition to tween your physics Gameobject to a given target position in the duration you require.
Here's documentation you can refer to if you want: http://dotween.demigiant.com/documentation.php
Let me give you an example:
Install the doTween Package. http://dotween.demigiant.com/download
Import it to unity.
Go to your script where you want to achieve the functionality you mentioned on your question and add this header "using DG.Tweening".
Now get access of your RigidBody.
For Example Lets say: I have a cube gameobject with rigidbidy and this script attached.
The Cube Initial Position is at 0,0,0.
And I want it to move to 5,5,5 in 3 seconds or 5 seconds as per your questions request. And lets say I want this to happen when I click SpaceBar on keyboard.
So I would simply do.
Rigidbody rb;
void Start()
{
rb= GetComponent<Rigibody>();
}
void Update()
{
if(Input.GetButtonDown(Keycode.Space))
{
MoveCube(new Vector3(5,5,5),5);
}
}
void MoveCube(Vector3 inTargetPosition , float durationToReachTheTarget)
{
//What this line does is first take in the target position you want your physics object to reach as it first parameter, Then takes in the duration in which you want it to reach there.
rb.DoMove(inTargetPosition,durationToReachTheTarget);
}
This should help you. But remember this is only if you okay with adding an extra package. Personally this package is very good and I would recommend you this.

Unity3D Player walking through and on the Stone

Hello guys my Player is walking on the Stone and through the Stone. The Player called Champ has a Box Collider and the Stone has a Mesh Collider. Also the Player has Rigidbody. I tried everthing i found but nothing helped me with my problem.
MovePlayer.cs Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovePlayer : MonoBehaviour
{
Rigidbody rb;
public float speed = 10f;
private Vector3 moveDirection;
public float rotationSpeed = 0.05f;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")).normalized;
}
void FixedUpdate()
{
rb.MovePosition(rb.position + transform.TransformDirection(moveDirection * speed * Time.deltaTime));
RotatePlayer();
}
void RotatePlayer()
{
if (moveDirection != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveDirection.normalized), rotationSpeed);
}
transform.Translate(moveDirection * speed * Time.deltaTime, Space.World);
}
}
Player Settings in Inspector
Stone Settings in Inspector
Scene Preview
Thank you for help guys! :)
So guys i found out the Solution with the help of the guys posted above.
The problem was that my player speed was too high in the Code the speed was on float 10, but i changed the velocity in the Unity Inspector of Player to float 50.
So my first step to solve the problem was to set the speed down to float 10, but i still wanted to move with a speed of 50f...
The Solution for this problem was that in Unity 2020.3.24f1 and higher (probably lower) you can go to Edit>Project Settings>Physics and set the "Default Max Depenetration Velocity" to the speed you want the objects stopping and not going through. In my case i wanted to move with speed = 50f so i needed to change Default Max Depenetration Velocity to 50.
I hope i can help someone with this Answer in future!
Best Wishes
Max G.
Tested your code and collisions seem to be working fine on my end.
Tested it by adding the script to a GameObject with box collider and creating a small level using cubes. Also made a wall that I modified to use mesh-collider instead of box collider. Player collided normally with objects in the scene.
You should double check your Layer collision matrix from Project Settings > Physics whether you've set layers player and wall to collide.
You could also try adding new cube to the scene and setting its layer to wall to see if player collides with it. If it does then the there might be issues with the mesh of the stone.
If not then I would disable animator and Gravity Body components from the player to make sure they're not interfering with the collisions
Rigidbody.MovePosition basically makes the player teleport which can cause unexpected behaviors. It's generally recommended to use Rigidbody.AddForce instead. For precise movement ForceMode.VeloictyChange can be used.
public float maxVelocityChange = 5.0f;
void moveUsingForces(){
Vector3 targetVelocity = moveDirection;
// Doesn't work with the given RotatePlayer implementation.
// targetVelocity = transform.TransformDirection(targetVelocity);
targetVelocity *= speed;
// Apply a force that attempts to reach our target velocity
Vector3 velocity = rb.velocity;
Vector3 velocityChange = (targetVelocity - velocity);
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
velocityChange.y = 0;
rb.AddForce(velocityChange, ForceMode.VelocityChange);
}
In this code you have applied motion twice and the problem is that transform.Translate is used. Remember that Rigidbody class methods are sensitive to colliders and recognize them, but transform is not the same and only applies a point-to-point shift. To solve the problem, I think you will not need a duplicate motion code with translate in the rotate section.
void RotatePlayer()
{
if (moveDirection != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveDirection.normalized), rotationSpeed);
}
// removed translate
}

Why is this transform.position reset between frames

So, my issue is as follows:
I'm developing a very simple 2D game as I'm quite new to Unity. Basically, there is a knight, and there is a troll. I'm developing my custom AI for the troll and got stumped at the very beginning. I tried to make the troll sprite correctly rotate and walk towards the player (in Update()), but then it turned out that the troll game object doesn't move as much as a millionth of a unit (although it rotates correctly)!
Now, what is special about my problem is the following: it's not about using localPosition instead of position (the troll has no parents – pun intended), it's not about referencing the transform in the script via "[SerializeField]", it's not about working with static objects or messed-up prefab coordinates or any of the other issues I've found in my desperate 10-hour search on the Internet. No, the issue is that I HAVE the troll transform in my code, I modify it successfully, but then it fails to be applied to the in-game troll object. Let that sink in – I am correctly modifying the correct transform (checked multiple times via debugging) but at the beginning of the next frame, all changes get magically reverted and the transform doesn't move the slightest.
I've made almost twenty modifications to my code in order to try and make it work (including separating stuff in different files as was suggested in a particular forum topic) but it just doesn't, and I can't think of anything else. Any help is very, very much appreciated.
public class EnemyController : MonoBehaviour
{
[SerializeField]
private Animator animator;
[SerialiseField]
private float walkingSpeed;
[SerializeField]
[Header("The aggro range of the enemy in Unity units.")]
private float aggroRange;
private float dir;
private Transform playerTransform;
void Update()
{
if (Aggro(playerTransform, aggroRange))
{
//WALKING:
WalkTowards(playerTransform, walkingSpeed);
}
}
private void WalkTowards(Transform targetTransform, float walkingSpeed)
{
animator.SetBool("IsRunning", false);
dir = 0.0f;
//Get the direction (left or right)
if (targetTransform.position.x < transform.position.x) //Left
{
dir = -1.0f;
//Flip the sprite to the left
transform.rotation = Quaternion.Euler(0.0f, 180.0f, 0.0f);
}
else //Right
{
dir = 1.0f;
//Flip the sprite to the right
transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);
}
transform.position = new Vector3(
transform.position.x + (walkingSpeed * dir * Time.deltaTime),
transform.position.y,
transform.position.z
);
Debug.Log(
string.Format("Troll 1 position:\nx: {0:#.##}", transform.position.x) +
string.Format(" y: {0:#.##}", transform.position.y) +
string.Format(" z: {0:#.##}", transform.position.z)
);
}
}
(NOTE: Aggro() is a custom method that determines when the enemy aggros; not included in the code as it is irrelevant to the issue)
So basically, transform.position gets changed correctly in the script and stays that way until the end of the method, but at the beginning of the next frame – i.e. when WalkTowards() gets called again, the transform.position remains the same as it was at the beginning of the last frame – as if no calculations or changes were ever made.
#Ruzihm,
You were right!!! Turns out the Animator component of the troll was causing the issue – "Apply root motion" was off, I turned it on and the position gets correctly updated now. Thank you ever so much for the help!
Example:

How to make enemy circle player when in range?

I am working on a propably simple script. As I am new to coding and stuff, there is a high chance that my code looks horrible.
Okay, so here's the thing:
I have an enemy triggered, and only spawning when the player get's near a certain point. Then the Enemy has to follow the player, doesn't matter where he is, and keeping a certain range of 3 units.
To this point. Everything works fine.
Now, what doesn't seem to work is, that I need my enemy to "orbit" around my player, when he is in a certain range (3) and only then.
For now, it's orbiting right from the start...what did I miss??
Thats my code so far:
public Transform mTarget;
float mSpeed = 10.0f;
const float EPSILON = 3.0f;
public float speed;
void Start()
{
OrbitAround ();
}
void Update() {
transform.LookAt (mTarget.position);
if ((transform.position - mTarget.position).magnitude > EPSILON)
transform.Translate (0.0f, 0.0f, mSpeed * Time.deltaTime);
}
void OrbitAround() {
if(Vector3.Distance(transform.position, mTarget.transform.position) < 3) {
transform.RotateAround (mTarget.transform.position, Vector3.up, speed * Time.deltaTime);
}
}
Big Thanks in advance, if anyone can help me out.
Cheers,
As #Zibelas said, the Start function is only once.
The Unity documentation states:
Start is called on the frame when a script is enabled just before any of the Update methods is called the first time.
So try putting the call to OrbitAround() in the Update function and it should work
Well, didn't really fixed this problem exactly, but I found a way to work around it.
I just enabled the script to orbit around the target, when the enemy get close to it and disabled it, when the player goes away again.
void Update (){
if (Vector3.Distance (transform.position, mTarget.transform.position) < 15) {
script.enabled = true;
}
if(Vector2.Distance(transform.position, mTarget.transform.position) >15) {
script.enabled = false;
}
Still, thanks for the comments and information, guys.

Categories

Resources