I'm trying to get an object to rotate smoothly on the z axis when it comes into contact with a collider. However it just seems to snap really quickly and I can't find a way to smooth this out.
I've split the rotation out into 2 scenarios - one for when the player enters the collider (which needs to be smooth), and one for when the player is already in the collider (which needs to be snappy). this is so that when the player enters the objects gravity (collider) he'll rotate smoothly before falling to the ground. However, if the player is already within the objects gravitational pull, its snappy so they can run on the ground (a circle) without any wierd floating or rolling occuring.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravitationalBody : MonoBehaviour
{
[Range(0, 0.3f)] [SerializeField] private float m_OrientationSmoothing = 0.3f; // How much to smooth out the movement
void OnTriggerEnter2D(Collider2D obj) {
// Define direction of gravity
Vector3 gravityDirection = transform.position - obj.transform.position;
// apply gravity to all objects within range
obj.GetComponent<Rigidbody2D>().AddForce(gravityDirection);
orientObjects(obj, gravityDirection, true);
}
void OnTriggerStay2D(Collider2D obj)
{
// Define direction of gravity
Vector3 gravityDirection = transform.position - obj.transform.position;
// apply gravity to all objects within range
obj.GetComponent<Rigidbody2D>().AddForce(gravityDirection);
orientObjects(obj, gravityDirection, false);
}
// Control players Z axis rotation to point toward center of planets
void orientObjects(Collider2D obj, Vector3 gravityDirection, bool lerp)
{
float orientation = 90 + Mathf.Atan2(gravityDirection.y, gravityDirection.x) * Mathf.Rad2Deg;
if (lerp)
{
Quaternion q = Quaternion.AngleAxis(orientation, Vector3.forward);
obj.transform.rotation = Quaternion.Lerp(obj.transform.rotation, q, m_OrientationSmoothing * Time.deltaTime);
}
else obj.transform.rotation = Quaternion.Euler(0f, 0f, orientation);
}
}
I don't know if it will help but if you want a smooth rotation look at this code, I'm sure you can find inspiration here:
[SerializeField] float _degreesPerSecond = 30f;
[SerializeField] Vector3 _axis = Vector3.forward;
void Update ()
{
transform.Rotate( _axis.normalized * _degreesPerSecond * Time.deltaTime );
}
Related
I'm attempting to make the ability to slide in an FPS game I'm working on and I've tried rotating the player object but when tested, it does nothing. Pressing the slide key I have assigned does nothing but rotate the player object in the Y and Z axis (I'm trying to rotate it on the X axis)
Here's the code I'm using (only relevant parts have been kept in):
public class SlideMovementScript : MonoBehaviour
{
public Rigidbody rig;
public CapsuleCollider capcol;
public GameObject player;
public float originalHeight;
public float reducedHeight;
public float slideSpeed = 10f;
// Start is called before the first frame update
void Start()
{
capcol = GetComponent<CapsuleCollider>();
rig = GetComponent<Rigidbody>();
originalHeight = capcol.height;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && Input.GetKey(KeyCode.W))
Sliding();
else if (Input.GetKeyUp(KeyCode.LeftControl))
GoUp();
}
private void Sliding()
{
capcol.height = reducedHeight;
player.transform.localRotation = Quaternion.Euler(1.599f, 0f, 0f);
rig.AddForce(transform.forward * slideSpeed, ForceMode.VelocityChange);
}
private void GoUp()
{
GetComponent<CapsuleCollider>().height = originalHeight;
player.transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
}
}
I've gotten ZERO error messages from this but I haven't managed to find the problem
Another problem I've also had is when you start sliding, it just instantly rotates the Player object to X 1.599 Y 0 Z 0. I want it to rotate to where the player is facing but I haven't found a way to do it, even using transform.foward or other references won't work.
The Explanation (skip to script if you want) (read notes)
I reccomend using Quaternion's methods. You can use Quaternion.Slerp which can smooth out the rotation towards the player.
Slerp means spherical linear interpolation
Basically, Slerp is a 3 argumented method. the first argument is the current rotation, the second argument is the target rotation, and the third is at which point between those two you want to output.
More simply, Lerp is linear interpolation. Lerps arguments are the same, except it handles floats and not Quaternions. If you Mathf.Lerp(0,1,1); you will get 1, because 1 is all the ay to the second argument. If you do Mathf(0, 3, 0.5); you will get 1.5 because 1.5 is halfway (0.5) between them both.
You can imagine slerp like that, if you give rotation (90, 0, 0) and alerp it to (0, 0, 0) by 0.5 you will get (45, 0, 0).
On this example, I slerp from the current rotation, to the target rotation, by delta time. This way, as time goes on, it slightly increases the current rotation to be the target.
I can create the target as 1.599 rotated on the x.
To make this relative Make a parent game object to the player containing the mesh and rigidbody and maybe the collider (depends if you want the collider upright). I will call this "slidePlayer" slidePlayer must be a child of the player gameObject
Notes
Make sure to have slidePlayer to a gameObject that is a child of player, but must have the player mesh or sliding won't be visible.
Change rot to the target rotation.
Change speed to speed to rotate to the target rotation.
Here is your script I changed:
public class SlideMovementScript : MonoBehaviour
{
public Rigidbody rig;
public CapsuleCollider capcol;
public GameObject player;
public GameObject slidePlayer;
public float originalHeight;
public float reducedHeight;
public float slideSpeed = 10f;
// Start is called before the first frame update
void Start()
{
capcol = GetComponent<CapsuleCollider>();
rig = GetComponent<Rigidbody>();
originalHeight = capcol.height;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && Input.GetKey(KeyCode.W))
Sliding();
else if (Input.GetKeyUp(KeyCode.LeftControl))
GoUp();
}
private void Sliding()
{
capcol.height = reducedHeight;
Quaternion rot = Quaternion.Euler(1.599f, 0f, 0f);
float speed = 1f;
Quaternion curRot = slidePlayer.transform.localRotation;
slidePlayer.transform.localRotation = Quaternion.Slerp(curRot, rot, Time.deltaTime * speed);
rig.AddForce(transform.forward * slideSpeed, ForceMode.VelocityChange);
}
private void GoUp()
{
GetComponent<CapsuleCollider>().height = originalHeight;
Quaternion rot = Quaternion.Euler(0f, 0f, 0f);
float speed = 3f;
Quaternion curRot = slidePlayer.transform.localRotation;
slidePlayer.transform.localRotation = Quaternion.Slerp(curRot, rot, Time.deltaTime * speed);
}
}
Let me know if there are any problems in coments. Thanks.
An illustration of my issue/what I'm trying to achieve
I managed to have my player move around the inside surface of a cylinder using gravity but an issue comes up when trying to rotate the camera to look around.
First, there's a script that simulates gravity by pushing the player against the inside walls of the cylinder. This script also keeps the player upright by changing the "up" direction of the player to always be facing the center of the cylinder. I know this keeps my player from looking up so for now I'm just working on getting them to look left and right.
Second, when the player is on the bottom of the cylinder and parallel with the Y-axis I can look left and right without issue because the camera rotates using the x-axis (see circle on the left side of the image). However when the player moves around the side of the cylinder the camera is still trying to rotate based on the X-axis even though they are not aligned resulting in the camera not accurately rotating (see the circle on the right side of the image), and by the time the player is 90deg around the cylinder cannot rotate at all (thanks I think to the gravity keeping the player perpendicular to the sides of the cylinder), and at 180deg around the rotation is inverted.
I assume there are two possible solutions that I have not been able to successfully implement:
ignore the world xyz axis' and rotate relative to the player's xyz.
do some math to figure out the proper angles when you take into account the player's rotation around the cylinder and the angle of the current "left" direction.
The problem with the first solution is I have not been able to successfully rotate the player independent of the world xyz. I tried adding the Space.Self to the Rotate() method but no success. The problem with the second is math scares me and I've managed to avoid Quaternions & Euler angles so far so I'm not even sure how to begin figuring that out.
If anyone has had a similar issue I would greatly appreciate any insight or suggestions on how to figure it out.
Here's my code for controlling the play movement/camera direction and my code for the gravity:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
InputManager inputActions;
InputManager.PlayerMovementActions playerMovement;
public GravityAttractor GravityAttractor;
public float moveSpeed = 15;
public float jumpHeight = 10f;
public float sensitivityX = 1f;
public float sensitivityY = 1f;
float mouseX, mouseY;
Vector2 mouseInput;
private bool isJumping = false;
private Vector3 moveDir;
private Vector2 moveInput;
private Rigidbody rbody;
private void Awake()
{
inputActions = new InputManager();
playerMovement = inputActions.PlayerMovement;
playerMovement.Movement.performed += context => moveInput = context.ReadValue<Vector2>();
playerMovement.Jump.performed += ctx => Jump();
//playerMovement.MouseX.performed += context => mouseInput = context.ReadValue<Vector2>();
playerMovement.MouseX.performed += context => mouseInput.x = context.ReadValue<float>();
playerMovement.MouseY.performed += context => mouseInput.y = context.ReadValue<float>();
rbody = GetComponent<Rigidbody>();
isJumping = false;
}
private void Update()
{
moveDir = new Vector3(moveInput.x, 0, moveInput.y).normalized;
if (rbody.velocity.y == 0)
isJumping = false;
}
private void FixedUpdate()
{
rbody.MovePosition(rbody.position + transform.TransformDirection(moveDir) * moveSpeed * Time.deltaTime);
MouseLook(mouseInput);
}
void Jump()
{
//use brackeys method of checking for contact with ground
if(isJumping == false && rbody.velocity.y == 0)
{
isJumping = true;
rbody.velocity = transform.up * jumpHeight;
Debug.Log("player jumped");
}
}
void MouseLook(Vector2 mouseInput)
{
mouseX = mouseInput.x * sensitivityX;
mouseY = mouseInput.y * sensitivityY;
var upTransform = GravityAttractor.transform.position - transform.position;
Vector3 relativeLook = upTransform - transform.forward;
Vector3 qLook = transform.forward - transform.position;
transform.Rotate(transform.up * mouseX * Time.deltaTime, Space.Self);
//transform.Rotate(relativeLook.normalized);
//transform.rotation = Quaternion.LookRotation(qLook);
//transform.Rotate(relativeLook.normalized, mouseX);
}
private void OnEnable()
{
inputActions.Enable();
}
private void OnDisable()
{
inputActions.Enable();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravityAttractor : MonoBehaviour
{
public float gravityMultiplier = -10f;
private float radius;
public float gravity;
public float distance;
private void Awake()
{
radius = transform.localScale.x/2;
}
public void Attract(Transform body)
{
Vector3 centerOfGravity = new Vector3(transform.position.x, transform.position.y, body.position.z);
distance = Vector3.Distance(centerOfGravity, body.position);
//gravity will match multiplier no matter the radius of cylinder
gravity = gravityMultiplier * (distance/radius);
Vector3 gravityUp = (centerOfGravity - body.position).normalized;
Vector3 bodyUp = body.up;
body.GetComponent<Rigidbody>().AddForce(gravityUp * gravity);
Quaternion targetRotation = Quaternion.FromToRotation(bodyUp, gravityUp) * body.rotation;
body.rotation = Quaternion.Slerp(body.rotation, targetRotation, 50 * Time.deltaTime);
}
}
Problem is that you are trying to Rotate in local space around transform.up, which is in world space.
try to use just Vector3.up, instead of transform.up.
Or you can transform any vector from world to local space with transform.InverseTransformDirection()
transform.Rotate(Vector3.up * mouseX * Time.deltaTime, Space.Self);
I don't know hierarchy of your GameObjects. In order for this to work, you shuld rotate Player GameObject to face up to center of cilinder. And camera should be child of Player GameObject
I have a 3d world with a simple platform with a cube representing the player. When I rotate the platform the cube glides and perform as you expect when increase and decrease the friction in the physics material.
I want the cube to glide after the input for example forward is terminated. It does not. I tried to update the position with rigidbody.position and update it. I quickly understood that it would not work with the physics engine.
Now I have the following code. It does not work as expected anyway. I would like to have some pointers to solve this.
public class Player1 : MonoBehaviour
{
private float speed = 10f;
private Vector3 direction;
private Vector3 velocity;
private float vertical;
private float horizontal;
Rigidbody playerRigidBody;
// Start is called before the first frame update
void Start()
{
playerRigidBody = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
vertical = Input.GetAxisRaw("Vertical");
horizontal = Input.GetAxisRaw("Horizontal");
direction = new Vector3(horizontal, 0, vertical);
}
private void FixedUpdate()
{
velocity = direction.normalized * speed * Time.fixedDeltaTime;
playerRigidBody.MovePosition(transform.position + velocity);
}
}
Use playerRigidBody.AddForce(Vector3 direction, ForceMode forceMode) to move your player.
If you don't want your player to move at a demential speed use playerRigidBody.velocity = Vector3.Clamp(Vector3 vec3, float minValue, float maxValue);
Then play with different variables to get the result you want !
I have a SMALL a problem. In Unity 3D 2020 Beta, I've put a player with a sphere collider on it and some cubes (walls) with box colliders. I've added a player controller script to the player object.
I've put the camera above the plane where the player and the walls are on, and I've made that the player should rotate to face the mouse position. I used rigidbody.AddForce for movement in a FixedUpdate function.
The player controller script is attached below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Keys")]
public KeyCode forward;
public KeyCode backward;
public KeyCode left;
public KeyCode right;
public KeyCode fire;
[Header("Health")]
public int hitpoints = 3;
[Header("Movement")]
public float speed;
public float turningSpeed;
[Header("Shooting")]
public GameObject bulletPrefab;
public Transform bulletSpawner;
public float bulletSpeed;
public float reloadTime;
private float currentReload;
private Rigidbody rb;
private Quaternion targetRotation;
void Start()
{
rb = GetComponent<Rigidbody>();
currentReload = reloadTime;
}
void LateUpdate()
{
if (hitpoints == 0)
Die();
// Rotation
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
targetRotation = Quaternion.LookRotation(hit.point - transform.position);
Debug.DrawLine(transform.position, hit.point, Color.white);
}
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turningSpeed * Time.deltaTime);
transform.eulerAngles = new Vector3(0, transform.rotation.eulerAngles.y, 0);
currentReload += Time.deltaTime;
// Shooting
if (Input.GetKeyDown(fire) && currentReload >= reloadTime)
{
currentReload = 0f;
GameObject bulletGO = Instantiate(bulletPrefab, bulletSpawner.position, transform.rotation);
bulletGO.transform.position = bulletSpawner.position;
Bullet bulletScript = bulletGO.GetComponent<Bullet>();
bulletScript.speed = bulletSpeed;
Destroy(bulletGO, 5f);
}
}
void FixedUpdate()
{
// Movement
if (Input.GetKey(forward))
{
rb.AddForce(Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(backward))
{
rb.AddForce(-Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(left))
{
rb.AddForce(Vector3.left * speed, ForceMode.Force);
}
if (Input.GetKey(right))
{
rb.AddForce(Vector3.right * speed, ForceMode.Force);
}
//transform.position = new Vector3(transform.position.x, 10, transform.position.z);
// ON RIGIDBODY I HAVE CONSTRAINS:
// POSITION: Y (thats why I commented the line above)
// ROTATION: X, Z (topdown -> so I want only rotation on Y)
}
private void Die()
{
Destroy(gameObject);
}
}
But the problem is when the player hits very hard a wall, the sphere collider starts shaking and the player does not look at the mouse position exactly (it is somewhere 10 degrees away most of the times - it depends on how hard do I hit the walls).
I can record if it helps. If you want any information, feel free to ask! Any help will be appreciated! :)
The problem here seems to be you're directly altering the transform of your object despite having a Rigidbody component. Generally you should avoid altering a transform directly when you have a Rigidbody attached, especially a non-kinematic one, as by attaching one you are signalling that the object is to be controlled by the physics simulation.
Solutions I would explore:
If you don't need a rigidbody, don't use one
If you can avoid altering the transform directly, then do not do so. You can rotate objects by applying torque and the likes
Try setting your object to kinematic if you don't need collisions to affect the rigidbody's physics
Manually set the torque and velocity of your object to 0 each fixed update
Hi guys i have a problem with that code:
using UnityEngine;
using UnitySampleAssets.CrossPlatformInput;
namespace CompleteProject
{
public class PlayerMovement : MonoBehaviour
{
public float speed = 6f; // The speed that the player will move at.
Vector3 movement; // The vector to store the direction of the player's movement.
Animator anim; // Reference to the animator component.
Rigidbody playerRigidbody; // Reference to the player's rigidbody.
#if !MOBILE_INPUT
int floorMask; // A layer mask so that a ray can be cast just at gameobjects on the floor layer.
float camRayLength = 100f; // The length of the ray from the camera into the scene.
#endif
void Awake ()
{
#if !MOBILE_INPUT
// Create a layer mask for the floor layer.
floorMask = LayerMask.GetMask ("Floor");
#endif
// Set up references.
anim = GetComponent <Animator> ();
playerRigidbody = GetComponent <Rigidbody> ();
}
void FixedUpdate ()
{
// Store the input axes.
float h = CrossPlatformInputManager.GetAxisRaw("Horizontal");
float v = CrossPlatformInputManager.GetAxisRaw("Vertical");
// Move the player around the scene.
Move (h, v);
// Turn the player to face the mouse cursor.
Turning ();
// Animate the player.
Animating (h, v);
}
void Move (float h, float v)
{
// Set the movement vector based on the axis input.
movement.Set (h, 0f, v);
// Normalise the movement vector and make it proportional to the speed per second.
movement = movement.normalized * speed * Time.deltaTime;
// Move the player to it's current position plus the movement.
playerRigidbody.MovePosition (transform.position + movement);
}
void Turning ()
{
#if !MOBILE_INPUT
// Create a ray from the mouse cursor on screen in the direction of the camera.
Ray camRay = Camera.main.ScreenPointToRay (Input.mousePosition);
// Create a RaycastHit variable to store information about what was hit by the ray.
RaycastHit floorHit;
// Perform the raycast and if it hits something on the floor layer...
if(Physics.Raycast (camRay, out floorHit, camRayLength, floorMask))
{
// Create a vector from the player to the point on the floor the raycast from the mouse hit.
Vector3 playerToMouse = floorHit.point - transform.position;
// Ensure the vector is entirely along the floor plane.
playerToMouse.y = 0f;
// Create a quaternion (rotation) based on looking down the vector from the player to the mouse.
Quaternion newRotatation = Quaternion.LookRotation (playerToMouse);
// Set the player's rotation to this new rotation.
playerRigidbody.MoveRotation (newRotatation);
}
#else
Vector3 turnDir = new Vector3(CrossPlatformInputManager.GetAxisRaw("Mouse X") , 0f , CrossPlatformInputManager.GetAxisRaw("Mouse Y"));
if (turnDir != Vector3.zero)
{
// Create a vector from the player to the point on the floor the raycast from the mouse hit.
Vector3 playerToMouse = (transform.position + turnDir) - transform.position;
// Ensure the vector is entirely along the floor plane.
playerToMouse.y = 0f;
// Create a quaternion (rotation) based on looking down the vector from the player to the mouse.
Quaternion newRotatation = Quaternion.LookRotation(playerToMouse);
// Set the player's rotation to this new rotation.
playerRigidbody.MoveRotation(newRotatation);
}
#endif
}
void Animating (float h, float v)
{
// Create a boolean that is true if either of the input axes is non-zero.
bool walking = h != 0f || v != 0f;
// Tell the animator whether or not the player is walking.
anim.SetBool ("IsWalking", walking);
}
}
}
I have tried many ways , but unfortunately none of them works , I need that my player can jump , and maybe even do a double jump . I just can not figure out how to blow it up .
I say thanks to those who will help me.
You need to use RigidBody's AddForce function: http://docs.unity3d.com/ScriptReference/Rigidbody.AddForce.html
As in the example, use a Vector3.Up multiplied by the force of your jump.
You can include this code in the FidexUpdate function. If the jump key is down (http://docs.unity3d.com/ScriptReference/Input.GetKeyDown.html), then AddForce to the player.