I'm very new in Unity and I'm trying to move a simple square on a classic 2D map (Super Mario like).
I use addForce to jump, it goes as planned.
But now, I'm trying to move my character horizontally.
First, I tried to use tramsform.translation(), I quickly notice that isn't the proper way, cause this method "teleports" the character, and if it moves too quickly, it can teleport behind a wall. I also try with addForce, but I want that my character has a constant speed, and it gives inertia, so it doesn't stop instantly when I release the key. I also try with .MovePosition(), but the character is shaking with this method (apparently, due to gravity).
So, whats the proper way to move a character (Rigidbody2D) horizontaly?
Here's my code with the different try:
using UnityEngine;
using System.Collections;
public class test : MonoBehaviour {
private Rigidbody2D rb;
public Vector2 velocity;
public float jumpforce;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
Debug.Log(velocity);
if (Input.GetKeyDown("space")){
rb.AddForce(new Vector2(0, jumpforce), ForceMode2D.Impulse);
}
if (Input.GetKey("a")){ // move to the left
rb.AddForce(-velocity * Time.deltaTime, ForceMode2D.Impulse);
//rb.MovePosition(rb.position - velocity * Time.fixedDeltaTime);
//transform.Translate(-velocity);
}
if (Input.GetKey("d")){ // move to the right
rb.AddForce(velocity * Time.deltaTime, ForceMode2D.Impulse);
//rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
//transform.Translate(velocity);
}
}
}
Personally, I use a setup like this:
Rigidbody2D rb;
[SerializeField] [Range(0, 1)] float LerpConstant;
//Other stuff here
FixedUpdate() {
float h = Input.GetAxisRaw("Horizontal");
Vector2 movement = new Vector2(h, rb.velocity.y);
rb.velocity = Vector2.Lerp(rb.velocity, movement, LerpConstant);
}
Lerp simply means "take a Vector2 that is 'x' amount from A to B, and return it". So the code creates a movement vector that is your horizontal movement (user input), vertical movement (the vertical movement the rigidbody already has), and then lerp the current velocity to it. By modifying velocity directly, it ensures your movement will stay smooth and constant, so long as you know how to do it right.
If you want a "Physics" based movement then you should apply forces to the Rigidbody. A benefit of acting on the rigidbody is that it will take into account colliding with objects (like walls).
Here is a great intro tutorial (it's 3D but the same concepts apply for 2D). This is the code from that tutorial with some amendments to make it 2D:
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour {
public float speed; // Here we set a float variable to hold our speed value
private Rigidbody2D rb; // This is to hold the rigidbody component
// Start is called as you start the game, we use it to initially give values to things
void Start ()
{
rb = GetComponent<Rigidbody2D>(); // Here we actually reference the rigidbody.
}
void FixedUpdate ()
{
// We assign values based on our input here:
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
// Here we assign those values to a Vector2 variable.
Vector2 movement = new Vector2 (moveHorizontal, moveVertical);
rb.AddForce (movement * speed); // Finally we apply the forces to the rigidbody
}
}
We can alter the manner in which the force acts on the rigidbody by changing the AddForce.ForceMode2D parameter. For example, ForceMode2D.Force will
Add a force to the rigidbody, using its mass.
ForceMode2D.Impulse will
Add an instant force impulse to the rigidbody2D, using its mass.
which is better for things like jumping.
Note - it is better to put physics-based method calls in FixedUpdate, not in Update, because of frame-rate dependency.
Also note - as you are applying a force to an object, it will accelerate because you are acting on a mass (the rigidbody) and decelerate based on other forces (friction etc.) If you want your player to slow to a halt instead of stop dead, think about the forces acting upon the player. Furthermore, if you apply a force to the rigidbody once per FixedUpdate, this will result in the constant speed you want if you choose ForceMode2D.Forceas other forces acting in the opposite direction will balance it out (see below image - credit to http://www.school-for-champions.com/ for the image).
EDIT: Regarding the comment by rutter above, here is an introductory tutorial on 2D Character controllers.
Related
I'm trying to use rb.velocity in unity to make my 2D character jump. I have used Debug.Log() to check if unity registers the input, and it does. But it does not affect my character in game when pressing the button. Here's the code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpForce = 7f;
public Rigidbody2D rb;
Vector2 movement;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
movement.x = Input.GetAxisRaw("Horizontal");
}
void FixedUpdate()
{
rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Space was pressed");
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
}
}
I have tried replacing the values with all sorts of things in the Vector2, but to no avail.
If your rigidbody is using gravity then in this case a small vertical velocity of 7f would be negated by gravity very quickly (less than a second). You can check this in the physics settings of your project or with Rigidbody.useGravity.
If you intend to use gravity then it is recommended to use Rigidbody.AddForce to add an upward force to the object and let gravity bring it back down - example SO answer here.
If you want to turn off gravity and not use AddForce then it would be best to either use Rigidbody.MovePosition or set Rigidbody.velocity, but not both.
For example:
void Update() {
movement = new Vector2(Input.GetAxisRaw("Horizontal") * moveSpeed, movement.y);
}
void FixedUpdate() {
if (Input.GetKeyDown(KeyCode.Space)) {
movement.y = jumpForce;
}
rb.velocity = movement;
}
But in that case you would have to manually figure out when the jump is complete and set a downwards velocity to bring it back to the ground yourself. If you want the easiest and most natural solution, then leveraging Unity's built-in physics with gravity and forces would be best.
Honestly, your code looks fine. I would move the Input.GetKeyDown check into Update instead of FixedUpdate. But other than that it looks good.
Instead, make sure your jumpForce is not 0. Just because you set the initial value in your script
public float jumpForce = 7f;
Doesn't mean that it is 7f. Check the script in the inspector and make sure it's not 0. Setting values on scripts like you did only sets the initial value. If you created the jump force variable before assigning 7 to it, it will still be 0. You have to set the variables in the inspector.
And if that doesn't work use:
rb.AddForce(jumpForce * Vector3.up, ForceMode.Impulse);
Instead of trying to manually set the velocity.
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
}
I tested and this script cause lags in physic. It seems to me that it call physic not per 0,2 seconds, but much-much more rarely and because of this object falling much more slower.
Here's a gif to demonstrate what happens: The purple one without script, the blue one with script.
So how can I fix that?
public class PlayerMovement : MonoBehaviour
{
Rigidbody2D rb;
Vector2 movement = new Vector2(0, 0);
public float movementSpeed = 10f;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
movement = new Vector2(Input.GetAxisRaw("Horizontal"), 0);
}
void FixedUpdate()
{
rb.MovePosition(rb.position + movement * movementSpeed * Time.fixedDeltaTime);
}
}
As said MovePosition overrules the movement => don't use it if you want physics (force) based movements and reaction to collisions etc.
You want to use the input only for the horizontal movement but use physics for the vertical. MovePosition affects both directions.
Try directly setting the Rigidbody.velocity instead
private void Update()
{
// Get the current velocity
var velocity = rb.velocity;
// Only overwrite the x velocity
// since the velocity is only applied in the FixedUpdate anyway
// there is no problem setting this already in Update
// And since it is the velocity which is already frame-rate independent
// there is no need for Time.deltaTime
velocity.x = Input.GetAxisRaw("Horizontal") * movementSpeed;
// Assign back the changed velocity
rb.velocity = velocity;
}
There is an issue with the logic of your code if I understand what you want to do.
You seem to be moving in a vertical fashion yet only have the x component of your vector2 from the horizontal input.
new Vector2(Input.GetAxisRaw("Horizontal"), 0);
If the GIF you attached, the object is just in freefall, so that would be a vertical drop, or the y component of the vector2, which you are setting to 0. Think of this as (x,y) or (move horizontally (left/right), move vertically (up/down)).
I would change the above line to:
movement = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
You can now move vertically using vertical input. Something else which would not cause your issue, but is mentioned on the MovePosition docs is to mark your Rigidbody2D as Interpolate. It is the bottom field in the Rigidbody2D component labeled Interpolate, set it from None to Interpolate.
The reason your object is moving slowly without any input is because you still have your Gravity Scale on the Rigidbody set to 1 instead of 0. If you are trying to simulate gravity with this script, add the vertical component to your input vector and set the Gravity Scale field on the Rigidbody to 0. The MovePosition is fighting the current gravity attempting to affect it.
My game is a topdown zombie shooter and whenever the zombies get to the player they bunch up underneath them, to the point where the player can just walk over the zombies. I noticed that when I check isKinematic on the Rigidbody the zombies cant push the player up to go underneath him, so they just run into him(which is what I want). Despite this I am then unable to move. How can i fix this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMoving1 : MonoBehaviour {
public float moveSpeed;
private Rigidbody myRigidbody;
private Vector3 moveInput;
private Vector3 moveVelocity;
private Camera mainCamera;
public GunController theGun;
void Start () {
myRigidbody = GetComponent <Rigidbody>();
mainCamera = FindObjectOfType<Camera>();
}
// Update is called once per frame
void Update () {
moveInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
moveVelocity = moveInput * moveSpeed;
Ray cameraRay = mainCamera.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
float rayLength;
if(groundPlane.Raycast(cameraRay,out rayLength))
{
Vector3 pointToLook = cameraRay.GetPoint(rayLength);
transform.LookAt(new Vector3(pointToLook.x,transform.position.y,pointToLook.z));
}
if (Input.GetMouseButtonDown(0))
theGun.isFiring = true;
if (Input.GetMouseButtonUp(0))
theGun.isFiring = false;
}
void FixedUpdate(){
myRigidbody.velocity = moveVelocity;
}
}
With isKinematic == true You can't change object position through rigidbody, You can only change transform.position.
I think it could be better, if You set isKinematic to false and add stopping distance to enemies, so they can't get too close to player.
Being that your player can no longer be effected by the physics engine, you'd have to manipulate the object's transform manually. Your script isn't ideally setup for it currently, but if I was to hack it into it and try to make it work it would look something like this:
(you can change it from fixedUpdate to update if you're no longer utilizing the physics engine)
void update(){
float x = Input.GetAxisRaw("Horizontal")* Time.Deltatime;
float z = Input.GetAxisRaw("Vertical") * Time.Deltatime;
transform.position = new Vector3(transform.position.x+x,0,transform.position.z+z);
Another way of doing this is to lock the position of Y for the player (assuming Y is the positive "up" direction). isKinimatic is best when you want to move the player or objects around yourself.
I would say upping the mass is better in this case, and you can keep isKinematic unchecked in this case then too. Also apply the lock for Y movement (again if it is the "up" direction from the plane)
Let me know what your solution is regardless, I've had some issues in the past as well with these types of events happening
I am new to Unity trying to make my first game (a Third Person Shooter).
It's been now more than a week that I've tried again and again to get my character moving using a rigidbody component and NOT the Character Controller or the simple transform.Translate.
I have had about 30 web pages opened since a week browsing topics about it but I haven't found anything (almost made me feel like I am trying to do something impossible lol...).
So, I want to move my character just like in Splinter Cell Blacklist, and to have the camera with the crosshair controlled by the mouse (if I shoot, the character would rotate if not facing the target and then shoot).
For the movement, if it is not possible with the rigidbody then I'll use one of the others, it's just that I love the real feeling that the rigidbody has.
If there's even a tutorial that break it down to really understand, that would be great or just some code with comments (I have a C# background).
float moveSpeed = 6f; // Player's speed when walking.
float rotationSpeed = 6f;
float jumpHeight = 10f; // How high Player jumps
Vector3 moveDirection;
Rigidbody rb;
// Using the Awake function to set the references
void Awake()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
Move();
}
void Move ()
{
float hAxis = Input.GetAxis("Horizontal");
float vAxis = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(hAxis, 0f, vAxis);
rb.position += movement * moveSpeed * Time.deltaTime;
}
My idea.
If you want the real feel, you need the rigidbody.addforce to your character at the proper part of the character body. Not the rigidbody.position.
Hope help.